Files
medical-mall/unpackage/cache/.app-android/sourcemap/index.kt.map

1 line
1.6 MiB
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"version":3,"sources":["../../../../../../../HBuilderX/HBuilderX/plugins/uniapp-cli-vite/node_modules/@dcloudio/uni-console/src/runtime/app/socket.ts","uni_modules/ak-req/ak-req.uts","App.uvue","../../../../../../../HBuilderX/HBuilderX/plugins/uniapp-cli-vite/node_modules/@dcloudio/uni-console/src/runtime/app/index.ts","uni_modules/ak-req/interface.uts","ak/config.uts","uni_modules/i18n/index.uts","utils/utils.uts","components/supadb/aksupa.uts","components/supadb/aksupainstance.uts","types/mall-types.uts","pages/sense/types.uts","utils/sapi.uts","utils/store.uts","main.uts","utils/supabaseService.uts","pages/main/index.uvue","pages/main/category.uvue","pages/main/cart.uvue","pages/main/profile.uvue","pages/mall/consumer/settings.uvue","pages/mall/consumer/wallet.uvue","pages/mall/consumer/withdraw.uvue","pages/mall/consumer/search.uvue","pages/mall/consumer/shop-detail.uvue","pages/mall/consumer/coupons.uvue","pages/mall/consumer/favorites.uvue","pages/mall/consumer/footprint.uvue","pages/mall/consumer/address-list.uvue","pages/mall/consumer/address-edit.uvue","pages/mall/consumer/checkout.uvue","pages/mall/consumer/payment.uvue","pages/mall/consumer/orders.uvue","pages/mall/consumer/order-detail.uvue","pages/mall/consumer/logistics.uvue","pages/mall/consumer/review.uvue","pages/mall/consumer/refund.uvue","pages/mall/consumer/chat.uvue","pages/mall/consumer/subscription/followed-shops.uvue","pages/mall/consumer/points/index.uvue","pages/mall/consumer/points/signin.uvue","pages/mall/consumer/points/exchange.uvue","pages/mall/consumer/points/exchange-records.uvue","pages/mall/consumer/product-reviews.uvue","pages/mall/consumer/my-reviews.uvue","pages/mall/consumer/balance/index.uvue","pages/mall/consumer/share/index.uvue","pages/mall/consumer/share/detail.uvue","pages/mall/consumer/member/index.uvue","pages/mall/consumer/message-detail.uvue","pages/mall/consumer/red-packets/index.uvue","pages/mall/consumer/bank-cards/index.uvue","pages/mall/consumer/bank-cards/add.uvue"],"sourcesContent":["/// <reference types=\"@dcloudio/uni-app-x/types/uni/global\" />\n// 之所以又写了一份是因为外层的socketconnectSocket的时候必须传入multiple:true\n// 但是android又不能传入目前代码里又不能写条件编译之类的。\nexport function initRuntimeSocket(\n hosts: string,\n port: string,\n id: string\n): Promise<SocketTask | null> {\n if (hosts == '' || port == '' || id == '') return Promise.resolve(null)\n return hosts\n .split(',')\n .reduce<Promise<SocketTask | null>>(\n (\n promise: Promise<SocketTask | null>,\n host: string\n ): Promise<SocketTask | null> => {\n return promise.then((socket): Promise<SocketTask | null> => {\n if (socket != null) return Promise.resolve(socket)\n return tryConnectSocket(host, port, id)\n })\n },\n Promise.resolve(null)\n )\n}\n\nconst SOCKET_TIMEOUT = 500\nfunction tryConnectSocket(\n host: string,\n port: string,\n id: string\n): Promise<SocketTask | null> {\n return new Promise((resolve, reject) => {\n const socket = uni.connectSocket({\n url: `ws://${host}:${port}/${id}`,\n fail() {\n resolve(null)\n },\n })\n const timer = setTimeout(() => {\n // @ts-expect-error\n socket.close({\n code: 1006,\n reason: 'connect timeout',\n } as CloseSocketOptions)\n resolve(null)\n }, SOCKET_TIMEOUT)\n\n socket.onOpen((e) => {\n clearTimeout(timer)\n resolve(socket)\n })\n socket.onClose((e) => {\n clearTimeout(timer)\n resolve(null)\n })\n socket.onError((e) => {\n clearTimeout(timer)\n resolve(null)\n })\n })\n}\n","import { AkReqUploadOptions, AkReqOptions, AkReqResponse, AkReqError } from './interface.uts';\r\nimport { SUPA_URL, SUPA_KEY, IS_TEST_MODE } from '@/ak/config.uts';\r\n\r\n// token 持久化 key\r\nconst ACCESS_TOKEN_KEY = 'akreq_access_token';\r\nconst REFRESH_TOKEN_KEY = 'akreq_refresh_token';\r\nconst EXPIRES_AT_KEY = 'akreq_expires_at';\r\n\r\n// 优化:用静态变量缓存 token只有 set/clear 时同步 storage\r\nlet _accessToken : string | null = null;\r\nlet _refreshToken : string | null = null;\r\nlet _expiresAt : number | null = null;\r\n\r\nexport class AkReq {\r\n\tstatic setToken(token : string, refreshToken : string, expiresAt : number) {\r\n\t\t_accessToken = token;\r\n\t\t_refreshToken = refreshToken;\r\n\t\t_expiresAt = expiresAt;\r\n\t\tuni.setStorageSync(ACCESS_TOKEN_KEY, token);\r\n\t\tuni.setStorageSync(REFRESH_TOKEN_KEY, refreshToken);\r\n\t\tuni.setStorageSync(EXPIRES_AT_KEY, expiresAt);\r\n\t}\r\n\tstatic getToken() : string | null {\r\n\t\tif (_accessToken != null) return _accessToken;\r\n\t\tconst t = uni.getStorageSync(ACCESS_TOKEN_KEY) as string | null;\r\n\t\t_accessToken = t;\r\n\t\treturn t;\r\n\t}\r\n\tstatic getRefreshToken() : string | null {\r\n\t\tif (_refreshToken != null) return _refreshToken;\r\n\t\tconst t = uni.getStorageSync(REFRESH_TOKEN_KEY) as string | null;\r\n\t\t_refreshToken = t;\r\n\t\treturn t;\r\n\t} static getExpiresAt() : number | null {\r\n\t\tconst val = _expiresAt;\r\n\t\tif (val != null) return val;\r\n\t\tconst t = uni.getStorageSync(EXPIRES_AT_KEY) as number | null;\r\n\t\t_expiresAt = t;\r\n\t\treturn t;\r\n\t}\r\n\tstatic clearToken() {\r\n\t\t_accessToken = null;\r\n\t\t_refreshToken = null;\r\n\t\t_expiresAt = null;\r\n\t\tuni.removeStorageSync(ACCESS_TOKEN_KEY);\r\n\t\tuni.removeStorageSync(REFRESH_TOKEN_KEY);\r\n\t\tuni.removeStorageSync(EXPIRES_AT_KEY);\r\n\t}\t// 判断 token 是否即将过期提前5分钟刷新\r\n\tstatic isTokenExpiring() : boolean {\r\n\t\tconst expiresAt = this.getExpiresAt();\r\n\t\tif (expiresAt === null || expiresAt == 0) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\tconst now = Math.floor(Date.now() / 1000);\r\n\t\treturn (expiresAt - now) < 300; // 提前5分钟刷新\r\n\t}\r\n\r\n\t// 自动刷新 token返回 true=已刷新false=未刷新\r\n\tstatic async refreshTokenIfNeeded(apikey ?: string) : Promise<boolean> {\r\n\t\t// 没有 access_token 直接返回,不刷新\r\n\t\tconst accessToken = this.getToken();\r\n\t\tif (accessToken === null || accessToken === \"\") {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tif (!this.isTokenExpiring()) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tconst refreshToken = this.getRefreshToken();\r\n\t\tif (refreshToken === null || refreshToken === \"\") {\r\n\t\t\tthis.clearToken();\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t// 构造 header必须带 apikey\r\n\t\tlet headers = new UTSJSONObject();\r\n\t\tif (apikey !== null && apikey !== \"\") {\r\n\t\t\theaders.set('apikey', apikey)\r\n\t\t}\r\n\t\tconst reqData = new UTSJSONObject()\r\n\t\treqData.set('refresh_token', refreshToken)\r\n\t\ttry {\r\n\t\t\tconst res = await this.request({\r\n\t\t\t\turl: SUPA_URL + '/auth/v1/token?grant_type=refresh_token',\r\n\t\t\t\tmethod: 'POST',\r\n\t\t\t\tdata: reqData,\r\n\t\t\t\theaders: headers,\r\n\t\t\t\tcontentType: 'application/json'\r\n\t\t\t}, true); // skipRefresh=true避免递归\r\n\t\t\tconst data = res.data as UTSJSONObject | null;\r\n\t\t\tlet accessToken : string | null = null;\r\n\t\t\tlet refreshTokenNew : string | null = null;\r\n\t\t\tlet expiresAt : number | null = null;\r\n\t\t\tif (data != null && typeof data.getString === 'function' && typeof data.getNumber === 'function') {\r\n\t\t\t\taccessToken = data.getString('access_token');\r\n\t\t\t\trefreshTokenNew = data.getString('refresh_token');\r\n\t\t\t\texpiresAt = data.getNumber('expires_at');\r\n\t\t\t}\r\n\t\t\tif (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {\r\n\t\t\t\tthis.setToken(accessToken, refreshTokenNew, expiresAt);\r\n\t\t\t\treturn true;\r\n\t\t\t} else {\r\n\t\t\t\tthis.clearToken();\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\tthis.clearToken();\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\t// options: AkReqOptions, skipRefresh: boolean = false\r\n\tstatic async request(options : AkReqOptions, skipRefresh ?: boolean) : Promise<AkReqResponse<any>> {\r\n\t\t// 自动刷新 token\r\n\t\tif (skipRefresh != true) {\r\n\t\t\tlet apikey : string | null = null;\r\n\t\t\tconst headersObj = options.headers;\r\n\t\t\tif (headersObj != null && typeof headersObj.getString === 'function') {\r\n\t\t\t\tapikey = headersObj.getString('apikey');\r\n\t\t\t}\r\n\t\t\tawait this.refreshTokenIfNeeded(apikey);\r\n\t\t}\r\n\r\n\t\t// 构建新的 headers 对象,确保所有字段都被正确传递\r\n\t\tconst newHeaders = new UTSJSONObject()\r\n\t\t\r\n\t\t// 首先复制原始 headers\r\n\t\tif (options.headers != null) {\r\n\t\t\tconst originalHeaders = options.headers\r\n\t\t\tif (typeof originalHeaders.getString === 'function') {\r\n\t\t\t\t// 复制 apikey\r\n\t\t\t\tconst apikeyStr = originalHeaders.getString('apikey')\r\n\t\t\t\tif (apikeyStr != null) {\r\n\t\t\t\t\tnewHeaders.set('apikey', apikeyStr)\r\n\t\t\t\t}\r\n\t\t\t\t// 复制 Content-Type\r\n\t\t\t\tconst contentType = originalHeaders.getString('Content-Type')\r\n\t\t\t\tif (contentType != null) {\r\n\t\t\t\t\tnewHeaders.set('Content-Type', contentType)\r\n\t\t\t\t}\r\n\t\t\t\t// 复制 Prefer\r\n\t\t\t\tconst prefer = originalHeaders.getString('Prefer')\r\n\t\t\t\tif (prefer != null) {\r\n\t\t\t\t\tnewHeaders.set('Prefer', prefer)\r\n\t\t\t\t}\r\n\t\t\t\t// 复制 Authorization如果存在\r\n\t\t\t\tconst auth = originalHeaders.getString('Authorization')\r\n\t\t\t\tif (auth != null) {\r\n\t\t\t\t\tnewHeaders.set('Authorization', auth)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// 补齐 apikey (如果 headers 中没有,则直接使用 SUPA_KEY 补全)\r\n\t\tif (newHeaders.getString('apikey') == null) {\r\n\t\t\tif (SUPA_KEY != null && SUPA_KEY != \"\") {\r\n\t\t\t\tnewHeaders.set('apikey', SUPA_KEY)\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// 添加/更新 Authorization\r\n\t\tconst token = this.getToken();\r\n\t\tif (token != null && token != \"\") {\r\n\t\t\tnewHeaders.set('Authorization', `Bearer ${token}`)\r\n\t\t}\r\n\t\t\r\n\t\t// 确保 Content-Type 存在\r\n\t\tif (newHeaders.getString('Content-Type') == null) {\r\n\t\t\tconst contentType = options.contentType ?? 'application/json'\r\n\t\t\tif (contentType != null && contentType != \"\") {\r\n\t\t\t\tnewHeaders.set('Content-Type', contentType)\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// 添加 Accept\r\n\t\tnewHeaders.set('Accept', 'application/json')\r\n\t\t\r\n\t\t__f__('log','at uni_modules/ak-req/ak-req.uts:175','[AkReq.request] headers:', JSON.stringify(newHeaders))\r\n\t\t\r\n\t\tconst headers = newHeaders\r\n\r\n\t\tconst timeout = options.timeout ?? 10000;\r\n\t\tconst maxRetry = Math.max(0, options.retryCount ?? 0);\r\n\t\tconst baseDelay = Math.max(0, options.retryDelayMs ?? 300);\r\n\r\n\t\tconst doOnce = (): Promise<AkReqResponse<any>> => {\r\n\t\t\treturn new Promise<AkReqResponse<any>>((resolve) => {\r\n\t\t\t\tuni.request({\r\n\t\t\t\t\turl: options.url,\r\n\t\t\t\t\tmethod: options.method ?? 'GET',\r\n\t\t\t\t\tdata: options.data,\r\n\t\t\t\t\theader: headers,\r\n\t\t\t\t\ttimeout: timeout,\r\n\t\t\t\t\tsuccess: (res) => {\r\n\t\t\t\t\t\t// HEAD 请求特殊处理:没有响应体,只有 headers\r\n\t\t\t\t\t\tif (options.method == 'HEAD') {\r\n\t\t\t\t\t\t\tconst result = AkReq.createResponse<any>(\r\n\t\t\t\t\t\t\t\tres.statusCode,\r\n\t\t\t\t\t\t\t\t[] as Array<any>,\r\n\t\t\t\t\t\t\t\tres.header as UTSJSONObject\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\tresolve(result);\r\n\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// 兼容 res.data 可能为 string 或 UTSJSONObject 或 UTSArray\r\n\t\t\t\t\t\tlet data : UTSJSONObject | Array<UTSJSONObject> | null;\r\n\t\t\t\t\t\tif (typeof res.data == 'string') {\r\n\t\t\t\t\t\t\tconst strData = res.data as string;\r\n\t\t\t\t\t\t\tif (strData.length > 0 && /[^\\s]/.test(strData)) {\r\n\t\t\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\t\t\tdata = JSON.parse(strData) as UTSJSONObject;\r\n\t\t\t\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\t\t\t\t// 非 JSON 响应(例如纯文本/空响应/数字等),保持原始字符串,避免 JSON.parse 崩溃\r\n\t\t\t\t\t\t\t\t\tdata = new UTSJSONObject({ raw: strData });\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tdata = null;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else if (Array.isArray(res.data)) {\r\n\t\t\t\t\t\t\tdata = res.data as UTSJSONObject[];\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tconst objData = res.data as UTSJSONObject | null;\r\n\t\t\t\t\t\t\tdata = objData;\r\n\t\t\t\t\t\t\tif (objData != null) {\r\n\t\t\t\t\t\t\t\tconst accessToken = objData.getString('access_token');\r\n\t\t\t\t\t\t\t\tconst refreshTokenNew = objData.getString('refresh_token');\r\n\t\t\t\t\t\t\t\tconst expiresAt = objData.getNumber('expires_at');\r\n\t\t\t\t\t\t\t\tif (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {\r\n\t\t\t\t\t\t\t\t\tAkReq.setToken(accessToken, refreshTokenNew, expiresAt);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tconst result = AkReq.createResponse<any>(\r\n\t\t\t\t\t\t\tres.statusCode,\r\n\t\t\t\t\t\t\tdata ?? {},\r\n\t\t\t\t\t\t\tres.header as UTSJSONObject\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t\tresolve(result);\r\n\t\t\t\t\t},\r\n\t\t\t\t\tfail: (err) => {\r\n\t\t\t\t\t\tconst errStatus = (err.errCode != null && typeof err.errCode === 'number') ? err.errCode : 0;\r\n\t\t\t\t\t\tconst result = AkReq.createResponse<any>(\r\n\t\t\t\t\t\t\terrStatus,\r\n\t\t\t\t\t\t\t{} as UTSJSONObject,\r\n\t\t\t\t\t\t\t{} as UTSJSONObject,\r\n\t\t\t\t\t\t\tnew UniError('uni-request', errStatus, err.errMsg ?? 'request fail')\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t\tresolve(result);\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t};\r\n\r\n\t\tlet attempt = 0;\r\n\t\tlet lastRes: AkReqResponse<any> | null = null;\r\n\t\twhile (attempt <= maxRetry) {\r\n\t\t\tconst res = await doOnce();\r\n\t\t\tlastRes = res;\r\n\t\t\t// 仅网络失败/超时errCode 非 0 且 status 非 2xx/3xx时重试\r\n\t\t\tconst status = res.status ?? 0;\r\n\t\t\tconst isOk = status >= 200 && status < 400;\r\n\t\t\tif (isOk) return res;\r\n\t\t\tif (attempt === maxRetry) break;\r\n\t\t\t// 简单退避\r\n\t\t\tconst delay = baseDelay * Math.pow(2, attempt);\r\n await new Promise<void>((r) => { setTimeout(() => { r(); }, delay); });\r\n\t\t\tattempt++;\r\n\t\t}\r\n\t\tconst finalRes = lastRes!!;\r\n\t\t// 全局处理 401 未授权:在非 refresh 场景下,清理 token。\r\n\t\t// 测试模式下不强制跳登录页,避免影响任意跳转调试。\r\n\t\tif ((finalRes.status === 401) && (skipRefresh !== true)) {\r\n\t\t\ttry {\r\n\t\t\t\tthis.clearToken();\r\n\t\t\t\tuni.showToast({ title: '未授权或登录已过期,请重新登录', icon: 'none' });\r\n\t\t\t} catch (e) {}\r\n\t\t\ttry {\r\n\t\t\t\t// 动态读取配置,避免 ak-req 模块与业务工程强耦合\r\n\t\t\t\t// const cfg = require('@/ak/config.uts') as any\r\n\t\t\t\t// const isTest = cfg != null ? (cfg.IS_TEST_MODE === true) : false\r\n const isTest = IS_TEST_MODE\r\n\t\t\t\t// if (!isTest) {\r\n\t\t\t\t// \tuni.reLaunch({ url: '/pages/user/login' });\r\n\t\t\t\t// }\r\n\t\t\t} catch (e) {\r\n\t\t\t\t// try { uni.reLaunch({ url: '/pages/user/login' }); } catch (e2) {}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn finalRes;\r\n\t}\r\n\r\n\t// 新增 upload 方法,支持 uni.uploadFile自动带 token/apikey\t\r\n\tstatic async upload(options : AkReqUploadOptions) : Promise<AkReqResponse<any>> {\r\n\t\t// 上传前尝试刷新 token若即将过期。优先从 options.headers 或 apikey 字段获取 apikey\r\n\t\tlet apikey: string | null = null;\r\n\t\tconst hdr = options.headers;\r\n\t\tif (hdr != null && typeof hdr.getString === 'function') {\r\n\t\t\tapikey = hdr.getString('apikey');\r\n\t\t}\r\n if (apikey == null && options.apikey != null) apikey = options.apikey;\r\n await this.refreshTokenIfNeeded(apikey != null ? apikey : null);\r\n\r\n\t\tlet headers = options.headers ?? ({} as UTSJSONObject);\r\n\t\tconst token = this.getToken();\r\n\t\tif (token != null && token !== \"\") {\r\n\t\t\theaders = Object.assign({}, headers, { Authorization: `Bearer ${token}` }) as UTSJSONObject;\r\n\t\t}\r\n if (apikey != null && apikey !== \"\") {\r\n\t\t\theaders = Object.assign({}, headers, { apikey: apikey }) as UTSJSONObject;\r\n\t\t}\r\n\t\t// 默认 Accept\r\n\t\theaders = Object.assign({ Accept: 'application/json' } as UTSJSONObject, headers) as UTSJSONObject;\r\n\r\n\t\tconst timeout = options.timeout ?? 10000;\r\n\t\tconst maxRetry = Math.max(0, options.retryCount ?? 0);\r\n\t\tconst baseDelay = Math.max(0, options.retryDelayMs ?? 300);\r\n\r\n\t\tconst doOnce = (): Promise<AkReqResponse<any>> => {\r\n\t\t\treturn new Promise<AkReqResponse<any>>((resolve) => {\r\n\t\tconst task = uni.uploadFile({\r\n\t\t\turl: options.url,\r\n\t\t\tfilePath: options.filePath,\r\n\t\t\tname: options.name,\r\n\t\t\tformData: options.formData ?? {},\r\n\t\t\theader: headers,\r\n\t\t\ttimeout: timeout,\r\n\t\t\tsuccess: (res : UploadFileSuccess) => {\r\n\t\t\t\tlet parsed: UTSJSONObject | null = null;\r\n\t\t\t\ttry {\r\n\t\t\t\t\tparsed = JSON.parse(res.data) as UTSJSONObject;\r\n\t\t\t\t} catch (e) {\r\n\t\t\t\t\tparsed = null;\r\n\t\t\t\t}\r\n\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\tconst accessToken = parsed.getString('access_token');\r\n\t\t\t\t\tconst refreshTokenNew = parsed.getString('refresh_token');\r\n\t\t\t\t\tconst expiresAt = parsed.getNumber('expires_at');\r\n\t\t\t\t\tif (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {\r\n\t\t\t\t\t\tAkReq.setToken(accessToken, refreshTokenNew, expiresAt);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tconst result = AkReq.createResponse<any>(\r\n\t\t\t\t\tres.statusCode,\r\n\t\t\t\t\tparsed ?? {},\r\n\t\t\t\t\theaders\r\n\t\t\t\t);\r\n\t\t\t\tresolve(result);\r\n\t\t\t},\r\n\t\t\tfail: (err) => {\r\n\t\t\t\tconst errStatus = (err.errCode != null && typeof err.errCode === 'number') ? err.errCode : 0;\r\n\t\t\t\tconst result = AkReq.createResponse<any>(\r\n\t\t\t\t\terrStatus,\r\n\t\t\t\t\t{} as UTSJSONObject,\r\n\t\t\t\t\t{} as UTSJSONObject,\r\n\t\t\t\t\tnew UniError('uni-upload', errStatus, err.errMsg ?? 'upload fail')\r\n\t\t\t\t);\r\n\t\t\t\tresolve(result);\r\n\t\t\t}\r\n\t\t});\r\n\t\tif (options.onProgress != null && task != null) {\r\n\t\t\tconst progressCallback = (res: OnProgressUpdateResult) => {\r\n\t\t\t\tconst percent = res.progress as number; // 0-100\r\n\t\t\t\tconst sent = res.totalBytesSent as number | null;\r\n\t\t\t\tconst expected = res.totalBytesExpectedToSend as number | null;\r\n\t\t\t\tif (options.onProgress != null) {\r\n\t\t\t\t\toptions.onProgress(percent, sent, expected);\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t\ttask.onProgressUpdate(progressCallback);\r\n\t\t}\r\n\t\t\t});\r\n\t\t};\r\n\r\n\t\tlet attempt = 0;\r\n\t\tlet lastRes: AkReqResponse<any> | null = null;\r\n\t\twhile (attempt <= maxRetry) {\r\n\t\t\tconst res = await doOnce();\r\n\t\t\tlastRes = res;\r\n\t\t\tconst status = res.status ?? 0;\r\n\t\t\tconst isOk = status >= 200 && status < 400;\r\n\t\t\tif (isOk) return res;\r\n\t\t\tif (attempt === maxRetry) break;\r\n\t\t\tconst delay = baseDelay * Math.pow(2, attempt);\r\n\t\t\tawait new Promise<void>((resolve) => {\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tresolve();\r\n\t\t\t\t}, delay);\r\n\t\t\t});\r\n\t\t\tattempt++;\r\n\t\t}\r\n\t\treturn lastRes!!;\r\n\t}\r\n\t// 辅助方法:创建 AkReqResponse 对象,避免类型推断问题\r\n\tstatic createResponse<T>(\r\n\t\tstatus: number,\r\n\t\tdata: T | Array<T> ,\r\n\t\theaders: UTSJSONObject,\r\n\t\terror: UniError | null = null,\r\n\t\ttotal: number | null = null,\r\n\t\tpage: number | null = null,\r\n\t\tlimit: number | null = null,\r\n\t\thasmore: boolean | null = null,\r\n\t\torigin: any | null = null\r\n\t): AkReqResponse<T> {\r\n\t\treturn {\r\n\t\t\tstatus,\r\n\t\t\tdata,\r\n\t\t\theaders,\r\n\t\t\terror,\r\n\t\t\ttotal,\r\n\t\t\tpage,\r\n\t\t\tlimit,\r\n\t\t\thasmore,\r\n\t\t\torigin\r\n\t\t};\r\n\t}\r\n\r\n}\r\n\r\nexport default AkReq;","<script lang=\"uts\">\r\n\timport { setIsLoggedIn, setUserProfile, getCurrentUser } from '@/utils/store.uts'\r\n\timport supa from '@/components/supadb/aksupainstance.uts'\r\n\r\n\texport default {\r\n\t\tonLaunch: function () {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t\t\r\n\t\t\t// 检查是否已有有效会话,有则恢复登录状态\r\n\t\t\tthis.checkExistingSession()\r\n\t\t},\r\n\t\tonShow: function () {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function () {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tcheckExistingSession: function(): void {\r\n\t\t\t\t// 检查是否已有有效会话\r\n\t\t\t\tconst session = supa.getSession()\r\n\t\t\t\tif (session.user != null) {\r\n\t\t\t\t\tconsole.log('已有有效会话,恢复登录状态')\r\n\t\t\t\t\tsetIsLoggedIn(true)\r\n\t\t\t\t\tuni.reLaunch({ url: '/pages/mall/consumer/index' })\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// 检查本地存储的登录状态\r\n\t\t\t\tconst savedUserId = uni.getStorageSync('user_id')\r\n\t\t\t\tif (savedUserId != null && savedUserId != '') {\r\n\t\t\t\t\tconsole.log('本地存储中有用户ID尝试恢复会话')\r\n\t\t\t\t\tgetCurrentUser().then((profile) => {\r\n\t\t\t\t\t\tif (profile != null) {\r\n\t\t\t\t\t\t\tconsole.log('会话恢复成功')\r\n\t\t\t\t\t\t\tsetIsLoggedIn(true)\r\n\t\t\t\t\t\t\tuni.reLaunch({ url: '/pages/mall/consumer/index' })\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}).catch(() => {\r\n\t\t\t\t\t\tconsole.log('会话恢复失败,需要重新登录')\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// 没有有效会话,显示登录页\r\n\t\t\t\tconsole.log('无有效会话,显示登录页')\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n/* ===== 全局重置样式 (App-UVUE 不支持标签选择器和通配符,已注释) ===== */\r\n/*\r\n* {\r\n margin: 0;\r\n padding: 0;\r\n box-sizing: border-box;\r\n}\r\n\r\nhtml, body {\r\n height: 100%;\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\r\n font-size: 14px;\r\n line-height: 1.5;\r\n color: #262626;\r\n background-color: #f0f2f5;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\n\r\nview, text, input, textarea, button {\r\n box-sizing: border-box;\r\n}\r\n*/\r\n\r\n/* ===== 全局主题色 (建议移动到 uni.scss 或使用 class) ===== */\r\n/* :root 选择器在 UVUE 中可能不支持,建议定义为 class */\r\n.theme-vars {\r\n /* 主色调 */\r\n --primary-color: #1890ff;\r\n --primary-hover: #40a9ff;\r\n --primary-active: #096dd9;\r\n\r\n /* 成功色 */\r\n --success-color: #52c41a;\r\n --success-hover: #73d13d;\r\n --success-active: #389e0d;\r\n\r\n /* 警告色 */\r\n --warning-color: #faad14;\r\n --warning-hover: #ffc53d;\r\n --warning-active: #d48806;\r\n\r\n\r\n /* 错误色 */\r\n --error-color: #ff4d4f;\r\n --error-hover: #ff7875;\r\n --error-active: #d4380d;\r\n\r\n /* 中性色 */\r\n --text-primary: #262626;\r\n --text-secondary: #595959;\r\n --text-disabled: #bfbfbf;\r\n --text-inverse: #ffffff;\r\n\r\n /* 边框色 */\r\n --border-color: #d9d9d9;\r\n --border-color-light: #f0f0f0;\r\n --border-color-dark: #bfbfbf;\r\n\r\n /* 背景色 */\r\n --background-color: #ffffff;\r\n --background-color-light: #fafafa;\r\n --background-color-dark: #f5f5f5;\r\n\r\n /* 阴影 */\r\n --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);\r\n --shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\r\n --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.08);\r\n --shadow-xl: 0 6px 16px rgba(0, 0, 0, 0.12);\r\n\r\n /* 圆角 */\r\n --border-radius-sm: 2px;\r\n --border-radius: 4px;\r\n --border-radius-lg: 6px;\r\n --border-radius-xl: 8px;\r\n\r\n /* 间距 */\r\n --spacing-xs: 4px;\r\n --spacing-sm: 8px;\r\n --spacing: 16px;\r\n --spacing-lg: 24px;\r\n --spacing-xl: 32px;\r\n --spacing-xxl: 48px;\r\n\r\n /* 字体大小 */\r\n --font-size-xs: 12px;\r\n --font-size-sm: 14px;\r\n --font-size: 16px;\r\n --font-size-lg: 18px;\r\n --font-size-xl: 20px;\r\n --font-size-xxl: 24px;\r\n\r\n /* 行高 */\r\n --line-height-tight: 1.25;\r\n --line-height-normal: 1.5;\r\n --line-height-relaxed: 1.75;\r\n}\r\n\r\n/* ===== 全局字体设置 ===== */\r\n.text-xs { font-size: var(--font-size-xs); }\r\n.text-sm { font-size: var(--font-size-sm); }\r\n.text-base { font-size: var(--font-size); }\r\n.text-lg { font-size: var(--font-size-lg); }\r\n.text-xl { font-size: var(--font-size-xl); }\r\n.text-2xl { font-size: var(--font-size-xxl); }\r\n\r\n.font-medium { font-weight: 400; } /* UVUE不支持500 */\r\n.font-semibold { font-weight: 700; } /* UVUE不支持600 */\r\n.font-bold { font-weight: 700; }\r\n\r\n/* ===== 全局颜色类 ===== */\r\n.text-primary { color: var(--primary-color); }\r\n.text-success { color: var(--success-color); }\r\n.text-warning { color: var(--warning-color); }\r\n.text-error { color: var(--error-color); }\r\n.text-secondary { color: var(--text-secondary); }\r\n.text-disabled { color: var(--text-disabled); }\r\n\r\n/* ===== 全局背景类 ===== */\r\n.bg-white { background-color: var(--background-color); }\r\n.bg-light { background-color: var(--background-color-light); }\r\n.bg-dark { background-color: var(--background-color-dark); }\r\n\r\n/* ===== 全局边框类 ===== */\r\n.border { border: 1px solid var(--border-color); }\r\n.border-light { border: 1px solid var(--border-color-light); }\r\n.border-primary { border: 1px solid var(--primary-color); }\r\n\r\n/* ===== 全局阴影类 ===== */\r\n.shadow-sm { box-shadow: var(--shadow-sm); }\r\n.shadow { box-shadow: var(--shadow); }\r\n.shadow-lg { box-shadow: var(--shadow-lg); }\r\n.shadow-xl { box-shadow: var(--shadow-xl); }\r\n\r\n/* ===== 全局圆角类 ===== */\r\n.rounded-sm { border-radius: var(--border-radius-sm); }\r\n.rounded { border-radius: var(--border-radius); }\r\n.rounded-lg { border-radius: var(--border-radius-lg); }\r\n.rounded-xl { border-radius: var(--border-radius-xl); }\r\n\r\n/* ===== 全局间距类 ===== */\r\n.m-1 { margin: var(--spacing-xs); }\r\n.m-2 { margin: var(--spacing-sm); }\r\n.m-3 { margin: var(--spacing); }\r\n.m-4 { margin: var(--spacing-lg); }\r\n.m-5 { margin: var(--spacing-xl); }\r\n\r\n.p-1 { padding: var(--spacing-xs); }\r\n.p-2 { padding: var(--spacing-sm); }\r\n.p-3 { padding: var(--spacing); }\r\n.p-4 { padding: var(--spacing-lg); }\r\n.p-5 { padding: var(--spacing-xl); }\r\n\r\n/* ===== 全局布局类 ===== */\r\n.flex { display: flex; }\r\n.inline-flex { display: flex; } /* UVUE仅支持flex */\r\n.block { display: flex; } /* UVUE仅支持flex */\r\n.inline-block { display: flex; } /* UVUE仅支持flex */\r\n\r\n.flex-col { flex-direction: column; }\r\n.flex-row { flex-direction: row; }\r\n.flex-wrap { flex-wrap: wrap; }\r\n.flex-nowrap { flex-wrap: nowrap; }\r\n\r\n.items-start { align-items: flex-start; }\r\n.items-center { align-items: center; }\r\n.items-end { align-items: flex-end; }\r\n.items-stretch { align-items: stretch; }\r\n\r\n.justify-start { justify-content: flex-start; }\r\n.justify-center { justify-content: center; }\r\n.justify-end { justify-content: flex-end; }\r\n.justify-between { justify-content: space-between; }\r\n.justify-around { justify-content: space-around; }\r\n\r\n.flex-1 { flex: 1; }\r\n.flex-auto { flex: auto; }\r\n.flex-none { flex: none; }\r\n\r\n/* ===== 全局工具类 ===== */\r\n.w-full { width: 100%; }\r\n.h-full { height: 100%; }\r\n.text-left { text-align: left; }\r\n.text-center { text-align: center; }\r\n.text-right { text-align: right; }\r\n\r\n/* .cursor-pointer { cursor: pointer; } */\r\n/* .cursor-default { cursor: default; } */\r\n\r\n/* ===== 响应式断点 (App-UVUE 不支持 @media :root) ===== */\r\n/*\r\n@media (min-width: 1200px) {\r\n :root {\r\n --container-width: 1200px;\r\n }\r\n}\r\n\r\n@media (max-width: 1199px) and (min-width: 768px) {\r\n :root {\r\n --container-width: 100%;\r\n }\r\n}\r\n\r\n@media (max-width: 767px) {\r\n :root {\r\n --container-width: 100%;\r\n }\r\n}\r\n*/\r\n\r\n/* ===== App根容器 ===== */\r\n.app-root {\r\n /* min-height: 100vh; */ /* UVUE不支持vh */\r\n background-color: var(--background-color);\r\n flex: 1;\r\n}\r\n\r\n/* ===== 24栅格系统 (App-UVUE暂不支持百分比max-width) ===== */\r\n/*\r\n.row {\r\n display: flex;\r\n flex-wrap: wrap;\r\n margin: 0 calc(-1 * var(--spacing-sm));\r\n}\r\n\r\n.col {\r\n padding: 0 var(--spacing-sm);\r\n}\r\n\r\n.col-1 { flex: 0 0 4.16666667%; max-width: 4.16666667%; }\r\n\r\n/* Grid system disabled */\r\n/* (Grid system md disabled) */\r\n\r\n/* ===== 按钮基础样式 ===== */\r\n.btn {\r\n display: flex; /* UVUE 不支持 inline-flex */\r\n align-items: center;\r\n justify-content: center;\r\n padding: var(--spacing-sm) var(--spacing);\r\n font-size: var(--font-size-sm);\r\n font-weight: 400;\r\n line-height: var(--line-height-tight);\r\n white-space: nowrap;\r\n border: 1px solid transparent;\r\n border-radius: var(--border-radius);\r\n /* cursor: pointer; */\r\n transition: all 0.2s ease;\r\n /* user-select: none; */\r\n}\r\n\r\n.btn:disabled {\r\n opacity: 0.6;\r\n /* cursor: not-allowed; */\r\n}\r\n\r\n.btn-primary {\r\n color: var(--text-inverse);\r\n background-color: var(--primary-color);\r\n border-color: var(--primary-color);\r\n}\r\n\r\n/* .btn-primary:hover:not(:disabled) {\r\n background-color: var(--primary-hover);\r\n border-color: var(--primary-hover);\r\n} */\r\n\r\n.btn-secondary {\r\n color: var(--text-secondary);\r\n background-color: var(--background-color);\r\n border-color: var(--border-color);\r\n}\r\n\r\n/* .btn-secondary:hover:not(:disabled) {\r\n color: var(--primary-color);\r\n border-color: var(--primary-color);\r\n} */\r\n\r\n/* ===== 卡片基础样式 ===== */\r\n.card {\r\n background-color: var(--background-color);\r\n border: 1px solid var(--border-color-light);\r\n border-radius: var(--border-radius-lg);\r\n box-shadow: var(--shadow);\r\n overflow: hidden;\r\n}\r\n\r\n/* ===== 输入框基础样式 ===== */\r\n.input {\r\n display: flex; /* UVUE 不支持 block */\r\n width: 100%;\r\n padding: var(--spacing-sm) var(--spacing);\r\n font-size: var(--font-size-sm);\r\n line-height: var(--line-height-tight);\r\n color: var(--text-primary);\r\n background-color: var(--background-color);\r\n border: 1px solid var(--border-color);\r\n border-radius: var(--border-radius);\r\n transition: border-color 0.2s ease;\r\n}\r\n\r\n.input:focus {\r\n /* outline: none; */\r\n border-color: var(--primary-color);\r\n box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);\r\n}\r\n\r\n/* ===== 滚动条样式 (App-UVUE 不支持 CSS 滚动条设置) ===== */\r\n/*\r\n::-webkit-scrollbar {\r\n width: 6px;\r\n height: 6px;\r\n}\r\n\r\n::-webkit-scrollbar-track {\r\n background: var(--background-color-light);\r\n border-radius: 3px;\r\n}\r\n\r\n::-webkit-scrollbar-thumb {\r\n background: var(--border-color);\r\n border-radius: 3px;\r\n}\r\n\r\n::-webkit-scrollbar-thumb:hover {\r\n background: var(--text-disabled);\r\n}\r\n*/\r\n</style>\r\n","import { initRuntimeSocket } from './socket'\n\nexport function initRuntimeSocketService(): Promise<boolean> {\n const hosts: string = process.env.UNI_SOCKET_HOSTS\n const port: string = process.env.UNI_SOCKET_PORT\n const id: string = process.env.UNI_SOCKET_ID\n if (hosts == '' || port == '' || id == '') return Promise.resolve(false)\n let socketTask: SocketTask | null = null\n __registerWebViewUniConsole(\n (): string => {\n return process.env.UNI_CONSOLE_WEBVIEW_EVAL_JS_CODE\n },\n (data: string) => {\n socketTask?.send({\n data,\n } as SendSocketMessageOptions)\n }\n )\n return Promise.resolve()\n .then((): Promise<boolean> => {\n return initRuntimeSocket(hosts, port, id).then((socket): boolean => {\n if (socket == null) {\n return false\n }\n socketTask = socket\n return true\n })\n })\n .catch((): boolean => {\n return false\n })\n}\n\ninitRuntimeSocketService()\n","// ak-req 类型定义\r\nexport type AkReqOptions = {\r\n url: string;\r\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' |'HEAD';\r\n data?: UTSJSONObject | Array<UTSJSONObject>;\r\n headers?: UTSJSONObject;\r\n timeout?: number;\r\n contentType?: string; // 新增,支持顶级 contentType\r\n // 可选:重试设置(仅网络错误/超时触发)。默认重试 0 次\r\n retryCount?: number; // 最大重试次数,默认 0\r\n retryDelayMs?: number; // 首次重试延迟,默认 300ms指数退避\r\n};\r\n// 上传参数类型定义\r\nexport type AkReqUploadOptions = {\r\n url: string,\r\n filePath: string,\r\n name: string,\r\n formData?: UTSJSONObject,\r\n headers?: UTSJSONObject,\r\n apikey?: string,\r\n timeout?: number,\r\n // 进度回调0-100注意H5/APP 平台支持不同)\r\n onProgress?: (progress: number, transferredBytes?: number, totalBytes?: number) => void,\r\n // 可选:重试设置(仅网络错误/超时触发)。默认 0\r\n retryCount?: number,\r\n retryDelayMs?: number\r\n};\r\n\r\nexport type AkReqResponse<T = any> = {\r\n status: number;\r\n data: T | Array<T> | null; // 支持 null\r\n headers: UTSJSONObject;\r\n error: UniError | null;\r\n total:number |null;\r\n page: number |null;\r\n limit: number |null;\r\n hasmore:boolean |null;\r\n origin: any | null;\r\n};\r\n\r\nexport class AkReqError extends Error {\r\n code: number;\r\n constructor(message: string, code: number = 0) {\r\n super(message);\r\n this.code = code;\r\n this.name = 'AkReqError';\r\n }\r\n}\r\n","// Supabase 配置\r\n// 内网环境 - 本地部署的 Supabase\r\n// IP: 192.168.1.62\r\n// Kong HTTP Port: 8000\r\n\r\n//export const SUPA_URL: string = 'http://192.168.1.61:18000'\r\n//export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'\r\nexport const SUPA_URL: string = 'http://119.146.131.237:9126'\r\nexport const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'\r\n\r\n// WebSocket 实时连接(内网使用 ws:// 而非 wss://\r\nexport const WS_URL: string = 'ws://119.146.131.237:9126/realtime/v1/websocket'\r\n//export const WS_URL: string = 'ws://localhost:18000/realtime/v1/websocket'\r\n\r\n// 备用配置(已注释,如需切换可取消注释)\r\n// 开发环境 - 其他内网地址\r\n// export const SUPA_URL: string = 'http://192.168.0.150:8080'\r\n// export const SUPA_KEY: string = 'your-anon-key'\r\n// export const WS_URL: string = 'ws://192.168.0.150:8080/realtime/v1/websocket'\r\n\r\n// 生产环境 - Supabase 云服务(已注释)\r\n// export const SUPA_URL: string = 'https://ak3.oulog.com'\r\n// export const SUPA_KEY: string = 'your-anon-key'\r\n// export const WS_URL: string = 'wss://ak3.oulog.com/realtime/v1/websocket'\r\n\r\n// 指向你的 Supabase 服务(开发/私有部署)\r\n// export const SUPA_URL: string = 'http://192.168.1.64:3000'\r\n// export const SUPA_KEY: string = 'your-anon-key'\r\n// export const WS_URL: string = 'ws://192.168.1.64:3000/realtime/v1'\r\n\r\n// 路由配置\r\nexport const HOME_REDIRECT: string = '/pages/main/index'\r\nexport const TABORPAGE: string = '/pages/main/index'\r\n\r\n// 测试模式:放开任意跳转(禁用启动页/登录/401 的强制重定向)\r\nexport const IS_TEST_MODE: boolean = true","// i18n 国际化配置\r\n// 这是一个简化的 i18n 实现,用于支持多语言切换\r\n\r\n// 语言资源\r\nconst messages: UTSJSONObject = new UTSJSONObject()\r\n\r\n// 默认语言\r\nconst defaultLocale = 'zh-CN'\r\n\r\n// 当前语言(响应式)\r\nlet currentLocale = defaultLocale\r\n\r\n// 翻译函数\r\nfunction t(key: string, values: UTSJSONObject | null = null, locale: string | null = null): string {\r\n\tconst targetLocale = locale ?? currentLocale\r\n\t// 这里应该从 messages 中获取翻译,简化实现直接返回 key\r\n\t// 实际项目中应该加载语言资源文件\r\n\treturn key\r\n}\r\n\r\n// 创建响应式 locale 对象\r\nclass LocaleWrapper {\r\n get value(): string {\r\n return currentLocale\r\n }\r\n set value(newLocale: string) {\r\n currentLocale = newLocale\r\n }\r\n}\r\nconst localeObj = new LocaleWrapper()\r\n\r\n// I18n Global Context\r\nclass I18nGlobal {\r\n\tt(key: string, values: UTSJSONObject | null = null, locale: string | null = null): string {\r\n\t\treturn t(key, values, locale)\r\n\t}\r\n\tlocale: LocaleWrapper = localeObj\r\n}\r\n\r\n// I18n Instance\r\nclass I18nInstance {\r\n\tglobal: I18nGlobal = new I18nGlobal()\r\n}\r\n\r\n// 导出 i18n 对象\r\nconst i18n = new I18nInstance()\r\nexport default i18n\r\n","// 通用 UTSJSONObject 转任意 type 的函数\r\n// UTS 2024\r\n\r\nimport i18n from '@/uni_modules/i18n/index.uts';\r\n\r\n/**\r\n * 切换应用语言设置\r\n * @param locale 语言代码,如 'zh-CN' 或 'en-US'\r\n */\r\nexport function switchLocale(locale: string) {\r\n // 设置存储\r\n uni.setStorageSync('uVueI18nLocale', locale);\r\n \r\n // 设置 i18n 语言\r\n try {\r\n if (i18n != null && i18n.global != null) {\r\n i18n.global.locale.value = locale;\r\n }\r\n } catch (err) {\r\n __f__('error','at utils/utils.uts:20','Failed to switch locale:', err);\r\n }\r\n}\r\n\r\n/**\r\n * 获取当前语言设置\r\n * @returns 当前语言代码\r\n */\r\nexport function getCurrentLocale(): string {\r\n const locale = uni.getStorageSync('uVueI18nLocale') as string;\r\n if (locale == null || locale == '') {\r\n return 'zh-CN';\r\n }\r\n return locale;\r\n}\r\n\r\n/**\r\n * 确保语言设置正确初始化\r\n */\r\nexport function ensureLocaleInitialized() {\r\n const currentLocale = getCurrentLocale();\r\n if (currentLocale == null || currentLocale == '') {\r\n switchLocale('zh-CN');\r\n }\r\n}\r\n/**\r\n * 将任意错误对象转换为标准的 UniError\r\n * @param error 任意类型的错误对象\r\n * @param defaultMessage 默认错误消息\r\n * @returns 标准化的 UniError 对象\r\n */\r\nexport function toUniError(error: any, defaultMessage: string = '操作失败'): UniError {\r\n // 如果已经是 UniError直接返回\r\n if (error instanceof UniError) {\r\n return error\r\n }\r\n let errorMessage = defaultMessage\r\n let errorCode = -1\r\n \r\n try {\r\n // 如果是普通 Error 对象\r\n if (error instanceof Error) {\r\n errorMessage = error.message != null && error.message != '' ? error.message : defaultMessage\r\n }\r\n // 如果是字符串\r\n else if (typeof error === 'string') {\r\n errorMessage = error\r\n } // 如果是对象,尝试提取错误信息\r\n else if (error != null && typeof error === 'object') {\r\n const errorObj = error as UTSJSONObject\r\n let message: string = ''\r\n \r\n // 逐个检查字段,避免使用 || 操作符\r\n if (errorObj['message'] != null) {\r\n const msgValue = errorObj['message']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n } else if (errorObj['errMsg'] != null) {\r\n const msgValue = errorObj['errMsg']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n } else if (errorObj['error'] != null) {\r\n const msgValue = errorObj['error']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n } else if (errorObj['details'] != null) {\r\n const msgValue = errorObj['details']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n } else if (errorObj['msg'] != null) {\r\n const msgValue = errorObj['msg']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n }\r\n \r\n if (message != '') {\r\n errorMessage = message\r\n }\r\n \r\n // 尝试提取错误码\r\n let code: number = 0\r\n if (errorObj['code'] != null) {\r\n const codeValue = errorObj['code']\r\n if (typeof codeValue === 'number') {\r\n code = codeValue\r\n }\r\n } else if (errorObj['errCode'] != null) {\r\n const codeValue = errorObj['errCode']\r\n if (typeof codeValue === 'number') {\r\n code = codeValue\r\n }\r\n } else if (errorObj['status'] != null) {\r\n const codeValue = errorObj['status']\r\n if (typeof codeValue === 'number') {\r\n code = codeValue\r\n }\r\n }\r\n \r\n if (code != 0) {\r\n errorCode = code\r\n }\r\n }\r\n } catch (e) {\r\n __f__('error','at utils/utils.uts:128','Error converting to UniError:', e)\r\n errorMessage = defaultMessage\r\n }\r\n // 创建标准 UniError\r\n const uniError = new UniError('AppError', errorCode, errorMessage)\r\n return uniError\r\n}\r\n\r\n/**\r\n * 响应式状态管理\r\n * @returns 响应式状态对象\r\n */\r\nexport function responsiveState() {\r\n const screenInfo = uni.getSystemInfoSync()\r\n const screenWidth = screenInfo.screenWidth\r\n \r\n return {\r\n isLargeScreen: screenWidth >= 768,\r\n isSmallScreen: screenWidth < 576,\r\n screenWidth: screenWidth,\r\n cardColumns: screenWidth >= 768 ? 3 : screenWidth >= 576 ? 2 : 1\r\n }\r\n}\r\n\r\nexport function goToLogin(redirectUrl?: string | null) {\r\n try {\r\n const target = redirectUrl != null && redirectUrl.length > 0 ? redirectUrl : ''\r\n if (target.length > 0) {\r\n const redirect = encodeURIComponent(target)\r\n uni.navigateTo({ url: `/pages/user/login?redirect=${redirect}` })\r\n } else {\r\n uni.navigateTo({ url: '/pages/user/login' })\r\n }\r\n } catch (e) {\r\n uni.navigateTo({ url: '/pages/user/login' })\r\n }\r\n}\r\n\r\n/**\r\n * 兼容 UTS Android 的剪贴板写入\r\n * @param text 要写入剪贴板的文本\r\n */\r\nexport function setClipboard(text: string): void {\r\n\r\n\r\n\r\n}\r\n\r\n/**\r\n * 格式化时间,显示为相对时间(如:刚刚,几小时前)\r\n * @param dateStr ISO 格式的日期字符串\r\n * @returns 格式化后的相对时间字符串\r\n */\r\nexport function formatTime(dateStr: string): string {\r\n if (dateStr == '') return ''\r\n try {\r\n const date = new Date(dateStr)\r\n const now = new Date()\r\n const diff = now.getTime() - date.getTime()\r\n const hours = Math.floor(diff / (1000 * 60 * 60))\r\n \r\n if (hours < 1) {\r\n return '刚刚'\r\n } else if (hours < 24) {\r\n return `${hours}小时前`\r\n } else {\r\n return `${Math.floor(hours / 24)}天前`\r\n }\r\n } catch (e) {\r\n __f__('error','at utils/utils.uts:197','formatTime error:', e)\r\n return dateStr.replace('T', ' ').split('.')[0]\r\n }\r\n}\r\n\r\n","import { AkReqResponse, AkReqUploadOptions, AkReq } from '@/uni_modules/ak-req/index.uts'\r\nimport type { AkReqOptions } from '@/uni_modules/ak-req/index.uts'\r\nimport { toUniError } from '@/utils/utils.uts'\r\n\r\nexport type AkSupaSignInResult = {\r\n\taccess_token : string;\r\n\trefresh_token : string;\r\n\texpires_at : number;\r\n\tuser : UTSJSONObject | null;\r\n\ttoken_type ?: string;\r\n\texpires_in ?: number;\r\n\traw : UTSJSONObject;\r\n}\r\n\r\n// Count 选项枚举\r\nexport type CountOption = 'exact' | 'planned' | 'estimated';\r\n\r\n// 定义查询选项类型,兼容 UTS\r\nexport type AkSupaSelectOptions = {\r\n\tlimit ?: number;\r\n\torder ?: string;\r\n\tgetcount ?: string; // 保持向后兼容\r\n\tcount ?: CountOption; // 新增:更清晰的 count 选项\r\n\thead ?: boolean; // 新增head 模式,只返回元数据\r\n\tcolumns ?: string;\r\n\tsingle ?: boolean; // 新增,支持 single-object\r\n\trangeFrom ?: number; // 新增range 分页起始位置\r\n\trangeTo ?: number; // 新增range 分页结束位置\r\n};\r\n\r\n// 新增order方法参数类型\r\nexport type OrderOptions = {\r\n\tascending ?: boolean;\r\n};\r\n\r\n// 新增类型定义,便于 getSession 返回类型复用\r\nexport type AkSupaSessionInfo = {\r\n\tsession : AkSupaSignInResult | null;\r\n\tuser : UTSJSONObject | null;\r\n};\r\n\r\n// 链式请求构建器\r\n// 强类型条件定义\r\ntype AkSupaCondition = {\r\n\tfield : string; // 已经 encodeURIComponent 过\r\n\top : string;\r\n\tvalue : any;\r\n\tlogic : string; // 'and' | 'or'\r\n};\r\n\r\nexport class AkSupaQueryBuilder {\r\n\tprivate _supa : AkSupa;\r\n\tprivate _table : string;\r\n\tprivate _filter : UTSJSONObject | null = null;\r\n\tprivate _options : AkSupaSelectOptions = {};\r\n\tprivate _values : UTSJSONObject | Array<UTSJSONObject> | null = null;\r\n\tprivate _single : boolean = false;\r\n\tprivate _conditions : Array<AkSupaCondition> = [];\r\n\tprivate _nextLogic : string = 'and';\r\n\t// 新增:记录当前操作类型\r\n\tprivate _action : 'select' | 'insert' | 'update' | 'delete' | 'rpc' | null = null;\r\n\tprivate _orString : string | null = null; // 新增:支持 or 字符串\r\n\tprivate _rpcFunction : string | null = null;\r\n\tprivate _rpcParams : UTSJSONObject | null = null;\r\n\tprivate _page : number = 1; // 新增:当前页码\r\n\r\n\tconstructor(supa : AkSupa, table : string) {\r\n\t\tthis._supa = supa;\r\n\t\tthis._table = table;\r\n\t}\r\n\r\n\t// 链式条件方法\r\n\teq(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'eq', value); }\r\n\tneq(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'neq', value); }\r\n\tgt(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'gt', value); }\r\n\tgte(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'gte', value); }\r\n\tlt(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'lt', value); }\r\n\tlte(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'lte', value); }\r\n\tlike(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'like', value); }\r\n\tilike(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'ilike', value); }\r\n\tin(field : string, value : any[]) : AkSupaQueryBuilder { return this._addCond(field, 'in', value); }\r\n\tis(field : string, value : any | null) : AkSupaQueryBuilder { return this._addCond(field, 'is', value); }\r\n\tcontains(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'cs', value); }\r\n\tcontainedBy(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'cd', value); }\r\n\tnot(field : string, opOrValue : any, value: any | null = null) : AkSupaQueryBuilder {\r\n\t\tif (value != null) {\r\n\t\t\t// 三元形式field, operator, value\r\n\t\t\t// 例如 not('badge', 'is', null) -> badge=not.is.null\r\n\t\t\tconst combinedOp = 'not.' + opOrValue;\r\n\t\t\t// 将 null 转换为字符串 'null',避免构造对象时缺少 value 属性\r\n\t\t\tlet safeValue = value;\r\n\t\t\tif (value === null) {\r\n\t\t\t\tsafeValue = 'null';\r\n\t\t\t}\r\n\t\t\treturn this._addCond(field, combinedOp, safeValue);\r\n\t\t} else {\r\n\t\t\t// 二元形式field, value\r\n\t\t\tlet safeValue = opOrValue;\r\n\t\t\tif (opOrValue === null) {\r\n\t\t\t\tsafeValue = 'null';\r\n\t\t\t}\r\n\t\t\treturn this._addCond(field, 'not', safeValue);\r\n\t\t}\r\n\t}\r\n\r\n\tand() : AkSupaQueryBuilder { this._nextLogic = 'and'; return this; }\r\n\tor(str ?: string) : AkSupaQueryBuilder {\r\n\t\tif (typeof str == 'string') {\r\n\t\t\tthis._orString = str;\r\n\t\t} else {\r\n\t\t\tthis._nextLogic = 'or';\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\tprivate _addCond(afield : string, op : string, value : any | null) : AkSupaQueryBuilder {\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:117','add cond:', op, afield, value)\r\n\t\tconst field = encodeURIComponent(afield)!!\r\n\t\t// 将值安全存储,避免安卓端类型转换问题\r\n\t\tlet safeValue: any | null = value;\r\n\t\tif (value === null) {\r\n\t\t\tsafeValue = 'null';\r\n\t\t} else if (Array.isArray(value)) {\r\n\t\t\t// 数组类型保持原样,用于 in 操作符\r\n\t\t\tsafeValue = value;\r\n\t\t} else if (typeof value === 'number') {\r\n\t\t\t// 数字类型保持原样\r\n\t\t\tsafeValue = value;\r\n\t\t} else if (typeof value === 'boolean') {\r\n\t\t\t// 布尔类型保持原样\r\n\t\t\tsafeValue = value;\r\n\t\t} else if (typeof value !== 'string') {\r\n\t\t\t// 其他类型尝试转换为字符串\r\n\t\t\ttry {\r\n\t\t\t\tsafeValue = value.toString();\r\n\t\t\t} catch (e) {\r\n\t\t\t\tsafeValue = '';\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis._conditions.push({ field, op, value: safeValue ?? '', logic: this._nextLogic });\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:141',this._conditions)\r\n\t\tthis._nextLogic = 'and';\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// 支持原有 where 方式\r\n\twhere(filter : UTSJSONObject) : AkSupaQueryBuilder {\r\n\t\tthis._filter = filter;\r\n\t\treturn this;\r\n\t}\r\n\r\n\tpage(page : number) : AkSupaQueryBuilder {\r\n\t\tthis._page = page;\r\n\t\t// 如果已设置 limit则自动设置 range\r\n\t\tlet limit = 0;\r\n\t\tif (typeof this._options.limit == 'number') {\r\n\t\t\tlimit = this._options.limit ?? 0;\r\n\t\t}\r\n\t\tif (limit > 0) {\r\n\t\t\tconst from = (page - 1) * limit;\r\n\t\t\tconst to = from + limit - 1;\r\n\t\t\tthis.range(from, to);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\tlimit(limit : number) : AkSupaQueryBuilder {\r\n\t\tthis._options.limit = limit;\r\n\t\t// 总是为 limit 设置对应的 range确保限制生效\r\n\t\tconst from = (this._page - 1) * limit;\r\n\t\tconst to = from + limit - 1;\r\n\t\tthis.range(from, to);\r\n\t\treturn this;\r\n\t}\r\n\r\n\torder(order : string, options ?: OrderOptions) : AkSupaQueryBuilder {\r\n\t\tif (options != null && options.ascending == false) {\r\n\t\t\tthis._options.order = order + '.desc';\r\n\t\t} else {\r\n\t\t\tthis._options.order = order + '.asc';\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\tcolumns(columns : string) : AkSupaQueryBuilder {\r\n\t\tthis._options.columns = columns;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// 新增:专门的 count 方法\r\n\tcount(option : CountOption = 'exact') : AkSupaQueryBuilder {\r\n\t\tthis._options.count = option;\r\n\t\tthis._options.head = true; // count 操作默认使用 head 模式\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// 新增:便捷的 count 方法\r\n\tcountExact() : AkSupaQueryBuilder {\r\n\t\treturn this.count('exact');\r\n\t}\r\n\r\n\tcountEstimated() : AkSupaQueryBuilder {\r\n\t\treturn this.count('estimated');\r\n\t}\r\n\r\n\tcountPlanned() : AkSupaQueryBuilder {\r\n\t\treturn this.count('planned');\r\n\t}\r\n\r\n\t// 新增head 模式方法\r\n\thead(enable : boolean = true) : AkSupaQueryBuilder {\r\n\t\tthis._options.head = enable;\r\n\t\treturn this;\r\n\t}\r\n\r\n\tvalues(values : UTSJSONObject) : AkSupaQueryBuilder {\r\n\t\tthis._values = values;\r\n\t\treturn this;\r\n\t}\r\n\tsingle() : AkSupaQueryBuilder {\r\n\t\tthis._single = true;\r\n\t\treturn this;\r\n\t}\r\n\trange(from : number, to : number) : AkSupaQueryBuilder {\r\n\t\tthis._options.rangeFrom = from;\r\n\t\tthis._options.rangeTo = to;\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:225','设置 range:', from, 'to', to);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// 辅助函数:安全地将值转换为字符串\r\n\tprivate _valToStr(val: any): string {\r\n\t\tif (val == null) return '';\r\n\t\ttry {\r\n\t\t\t// 尝试直接调用 toString\r\n\t\t\treturn val.toString();\r\n\t\t} catch (e) {\r\n\t\t\ttry {\r\n\t\t\t\t// 尝试 JSON 序列化\r\n\t\t\t\treturn JSON.stringify(val);\r\n\t\t\t} catch (e2) {\r\n\t\t\t\treturn '';\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// 将 _conditions 强类型直接转换为 Supabase/PostgREST 查询字符串(不再用 UTSJSONObject 做中转)\r\n\tprivate _buildFilter() : string | null {\r\n\t\tif (this._conditions.length == 0 && (this._orString==null || this._orString == \"\")) {\r\n\t\t\t// 兼容 where(filter) 方式\r\n\t\t\tif (this._filter == null) return null;\r\n\t\t\t// 兼容旧的 UTSJSONObject filter\r\n\t\t\treturn buildSupabaseFilterQuery(this._filter);\r\n\t\t}\r\n\r\n\t\t// 先分组 and/or全部用 AkSupaCondition 强类型\r\n\t\tconst ands: AkSupaCondition[] = [];\r\n\t\tconst ors: AkSupaCondition[] = [];\r\n\t\tfor (const c of this._conditions) {\r\n\t\t\tif (c.logic == \"or\") {\r\n\t\t\t\tors.push(c);\r\n\t\t\t} else {\r\n\t\t\t\tands.push(c);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst params: string[] = [];\r\n\t\t// 处理 and 条件\r\n\t\tfor (const cond of ands) {\r\n\t\t\tconst k = cond.field;\r\n\t\t\tconst op = cond.op;\r\n\t\t\tconst val = cond.value;\r\n\t\t\tif ((op == 'in' || op == 'not.in') && Array.isArray(val)) {\r\n\t\t\t\tparams.push(`${k}=${op}.(${val.map(x => this._valToStr(x)).map(x => encodeURIComponent(x)).join(',')})`);\r\n\t\t\t} else if ((op == 'is' || op == 'not.is') && (val == null || val == 'null')) {\r\n\t\t\t\tparams.push(`${k}=${op}.null`);\r\n\t\t\t} else if (op == 'like' || op == 'ilike') {\r\n\t\t\t\tparams.push(`${k}=${op}.${this._valToStr(val)}`);\r\n\t\t\t} else {\r\n\t\t\t\tparams.push(`${k}=${op}.${encodeURIComponent(this._valToStr(val))}`);\r\n\t\t\t}\r\n\t\t}\r\n\t\t// 处理 or 条件\r\n\t\tif (ors.length > 0) {\r\n\t\t\tconst orStr = ors.map(o => {\r\n\t\t\t\tconst k = o.field;\r\n\t\t\t\tconst op = o.op;\r\n\t\t\t\tconst val = o.value;\r\n\t\t\t\tif (op == \"in\" && Array.isArray(val)) {\r\n\t\t\t\t\treturn `${k}.in.(${val.map(x => encodeURIComponent(this._valToStr(x))).join(\",\")})`;\r\n\t\t\t\t}\r\n\t\t\t\tif (op == \"is\" && (val == null)) {\r\n\t\t\t\t\treturn `${k}.is.null`;\r\n\t\t\t\t}\r\n\t\t\t\tif (op == \"like\" || op == \"ilike\") {\r\n\t\t\t\t\treturn `${k}.${op}.${this._valToStr(val)}`;\r\n\t\t\t\t}\r\n\t\t\t\treturn `${k}.${op}.${encodeURIComponent(this._valToStr(val))}`;\r\n\t\t\t}).join(\",\");\r\n\t\t\tparams.push(`or=(${orStr})`);\r\n\t\t}\r\n\t\tif (this._orString!=null && this._orString !== \"\") {\r\n\t\t\t__f__('log','at components/supadb/aksupa.uts:301','[AkSupaQueryBuilder] or字符串:', this._orString)\r\n\t\t\tparams.push(`or=(${this._orString!!})`);\r\n\t\t}\r\n\t\treturn params.length > 0 ? params.join('&') : null;\r\n\t}\r\n\r\n\tselect(columns : string = \"*\", opt : UTSJSONObject | null = null) : AkSupaQueryBuilder {\r\n\t\tthis._action = 'select';\r\n\t\tif (columns != null) {\r\n\t\t\tthis._options.columns = columns;\r\n\t\t}\r\n\t\tif (opt != null) {\r\n\t\t\t// 合并 opt 到 this._options\r\n\t\t\tObject.assign(this._options, opt);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\tinsert(values : UTSJSONObject | Array<UTSJSONObject>) : AkSupaQueryBuilder {\r\n\t\tthis._action = 'insert';\r\n\t\t// 检查是否为空\r\n\t\tif (Array.isArray(values)) {\r\n\t\t\tif (values.length == 0) throw toUniError('No values set for insert', 'Insert操作缺少数据');\r\n\t\t} else {\r\n\t\t\tif (UTSJSONObject.keys(values).length == 0) throw toUniError('No values set for insert', 'Insert操作缺少数据');\r\n\t\t}\r\n\t\tthis._values = values;\r\n\t\treturn this;\r\n\t}\r\n\tupdate(values : UTSJSONObject) : AkSupaQueryBuilder {\r\n\t\tthis._action = 'update';\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:331','ak update', this._action)\r\n\t\tif (UTSJSONObject.keys(values).length == 0) throw toUniError('No values set for update', '更新操作缺少数据');\r\n\t\tthis._values = values;\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:334','ak update', values)\r\n\t\treturn this;\r\n\t}\r\n\tdelete() : AkSupaQueryBuilder {\r\n\t\tthis._action = 'delete';\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:339','delete action now')\r\n\t\tconst filter = this._buildFilter();\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:341',filter)\r\n\t\tif (filter == null) throw toUniError('No filter set for delete', '删除操作缺少筛选条件');\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:343','delete action')\r\n\t\treturn this;\r\n\t}\r\n\r\n\trpc(functionName : string, params ?: UTSJSONObject) : AkSupaQueryBuilder {\r\n\t\tthis._action = 'rpc';\r\n\t\tthis._rpcFunction = functionName;\r\n\t\tthis._rpcParams = params;\r\n\t\treturn this;\r\n\t}\r\n\t// 链式请求最终执行方法 - 返回 UTSJSONObject\r\n\tasync execute() : Promise<AkReqResponse<any>> {\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:355','execute')\r\n\t\tconst filter = this._buildFilter();\r\n\t\t__f__('log','at components/supadb/aksupa.uts:357','[AkSupaQueryBuilder] execute - 表:', this._table, 'filter:', filter)\r\n\t\tlet res : any;\r\n\t\tswitch (this._action) {\r\n\t\t\tcase 'select': {\r\n\t\t\t\t// 传递 single 状态到 options\r\n\t\t\t\tif (this._single) {\r\n\t\t\t\t\tthis._options.single = true;\r\n\t\t\t\t\t// 如果是 single 请求,自动设置 limit 为 1\r\n\t\t\t\t\tif (this._options.limit == null) {\r\n\t\t\t\t\t\tthis._options.limit = 1;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t//__f__('log','at components/supadb/aksupa.uts:368',this._options)\r\n\t\t\t\t}\t\t\t\t// 保证分页统计\r\n\t\t\t\tif (this._options.limit != null) {\r\n\t\t\t\t\tif (this._options.getcount == null && this._options.count == null) {\r\n\t\t\t\t\t\tthis._options.count = 'exact'; // 优先使用新的 count 选项\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tres = await this._supa.select(this._table, filter, this._options);\r\n\t\t\t\t// 解析 content-range header\r\n\t\t\t\tlet total = 0;\r\n\t\t\t\tlet hasmore = false;\r\n\t\t\t\tconst page = this._page;\r\n\t\t\t\tlet resdata = res.data\r\n\t\t\t\tlet limit = 0;\r\n\t\t\t\tif (typeof this._options.limit == 'number') {\r\n\t\t\t\t\tlimit = this._options.limit ?? 0;\r\n\t\t\t\t} else if (Array.isArray(resdata)) {\r\n\t\t\t\t\tlimit = resdata.length;\r\n\t\t\t\t}\r\n\t\t\t\tlet contentRange : string | null = null;\r\n\t\t\t\tif (res.headers != null) {\r\n\t\t\t\t\tlet theheader = res.headers as UTSJSONObject\r\n\t\t\t\t\tif (typeof theheader.get == 'function') {\r\n\r\n\t\t\t\t\t\tcontentRange = theheader.get('content-range') as string | null;\r\n\t\t\t\t\t} else if (typeof theheader['content-range'] == 'string') {\r\n\t\t\t\t\t\tcontentRange = theheader['content-range'] as string;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (contentRange != null) {\r\n\t\t\t\t\tconst match = /\\/(\\d+)$/.exec(contentRange);\r\n\t\t\t\t\tif (match != null) {\r\n\t\t\t\t\t\ttotal = parseInt(match[1] ?? \"0\");\r\n\t\t\t\t\t\thasmore = (page * limit) < total;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (total == 0) {\r\n\t\t\t\t\t// 使用 JSON 序列化访问 res 对象\r\n\t\t\t\t\tconst resStr = JSON.stringify(res)\r\n\t\t\t\t\tconst resParsed = JSON.parse(resStr)\r\n\t\t\t\t\tif (resParsed != null) {\r\n\t\t\t\t\t\tconst resObj = resParsed as UTSJSONObject\r\n\t\t\t\t\t\tconst countVal = resObj.getNumber('count')\r\n\t\t\t\t\t\tif (countVal != null) {\r\n\t\t\t\t\t\t\ttotal = countVal\r\n\t\t\t\t\t\t} else if (Array.isArray(resdata)) {\r\n\t\t\t\t\t\t\ttotal = resdata.length\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\ttotal = 0\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else if (Array.isArray(resdata)) {\r\n\t\t\t\t\t\ttotal = resdata.length\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\ttotal = 0\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (!hasmore) hasmore = (page * limit) < total;\t\t\t\t// 如果是 head 模式,只返回 count 信息\r\n\t\t\t\tif (this._options.head == true) {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\tdata: null, // head 模式不返回数据\r\n\t\t\t\t\t\ttotal,\r\n\t\t\t\t\t\tpage,\r\n\t\t\t\t\t\tlimit,\r\n\t\t\t\t\t\thasmore: false, // head 模式不需要分页信息\r\n\t\t\t\t\t\torigin: res,\r\n\t\t\t\t\t\tstatus: res.status,\r\n\t\t\t\t\t\theaders: res.headers,\r\n\t\t\t\t\t\terror: res.error\r\n\t\t\t\t\t} as AkReqResponse<any>;\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn {\r\n\t\t\t\t\tdata: res.data,\r\n\t\t\t\t\ttotal,\r\n\t\t\t\t\tpage,\r\n\t\t\t\t\tlimit,\r\n\t\t\t\t\thasmore,\r\n\t\t\t\t\torigin: res,\r\n\t\t\t\t\tstatus: res.status,\r\n\t\t\t\t\theaders: res.headers,\r\n\t\t\t\t\terror: res.error\r\n\t\t\t\t} as AkReqResponse<any>;\r\n\t\t\t}\r\n\t\t\tcase 'insert': {\r\n\t\t\t\tconst insertValues = this._values;\r\n\t\t\t\tif (insertValues == null) throw toUniError('No values set for insert', '插入操作缺少数据');\r\n\t\t\t\tres = await this._supa.insert(this._table, insertValues);\r\n\t\t\t\tbreak;\r\n\t\t\t} case 'update': {\r\n\t\t\t\tconst updateValues = this._values;\r\n\t\t\t\tif (updateValues == null) throw toUniError('No values set for update', '更新操作缺少数据');\r\n\t\t\t\tif (filter == null) throw toUniError('No filter set for update', '更新操作缺少筛选条件');\r\n\t\t\t\t// Update操作只支持单个对象不支持数组\r\n\t\t\t\tif (Array.isArray(updateValues)) throw toUniError('Update does not support array values', '更新操作不支持数组数据');\r\n\t\t\t\tres = await this._supa.update(this._table, filter, updateValues as UTSJSONObject);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tcase 'delete': {\r\n\t\t\t\tif (filter == null) throw toUniError('No filter set for delete', '删除操作缺少筛选条件');\r\n\t\t\t\tres = await this._supa.delete(this._table, filter);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tcase 'rpc': {\r\n\t\t\t\tif (this._rpcFunction == null) throw toUniError('No RPC function specified', 'RPC调用缺少函数名');\r\n\t\t\t\tres = await this._supa.rpc(this._rpcFunction as string, this._rpcParams);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tdefault: {\r\n\t\t\t\tres = await this._supa.select(this._table, filter, this._options);\r\n\t\t\t}\r\n\t\t}\r\n\t\t// 保证 data 字段存在不能赋null赋空对象或空字符串\r\n\t\tif (res[\"data\"] == null) res[\"data\"] = {};\r\n\t\treturn res;\r\n\t}\t// 新增:支持类型转换的执行方法\r\n\tasync executeAs<T>() : Promise<AkReqResponse<T>> {\r\n\t\tconst result = await this.execute();\r\n\r\n\t\tif (result.data == null) {\r\n\t\t\treturn result as AkReqResponse<T>;\r\n\t\t}\r\n\r\n\t\tlet convertedData : any | null = null;\r\n\r\n\t\ttry {\r\n\t\t\tif (Array.isArray(result.data)) {\r\n\t\t\t\tconst dataArray = result.data;\r\n\t\t\t\tconst convertedArray : Array<any> = [];\r\n\t\t\t\tfor (let i = 0; i < dataArray.length; i++) {\r\n\t\t\t\t\tconst item = dataArray[i];\r\n\t\t\t\t\tif (item instanceof UTSJSONObject) {\r\n\r\n\t\t\t\t\t\tconst parsed = item.parse<T>();\r\n\r\n\r\n\r\n\r\n\t\t\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\t\t\tconvertedArray.push(parsed);\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t__f__('warn','at components/supadb/aksupa.uts:508','转换失败,使用原始对象:', item);\r\n\t\t\t\t\t\t\tconvertedArray.push(item);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tconst jsonObj = new UTSJSONObject(item);\r\n\r\n\t\t\t\t\t\tconst parsed = jsonObj.parse<T>();\r\n\r\n\r\n\r\n\r\n\t\t\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\t\t\tconvertedArray.push(parsed);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse {\r\n\t\t\t\t\t\t\t__f__('warn','at components/supadb/aksupa.uts:523','转换失败,使用原始对象:', item);\r\n\t\t\t\t\t\t\tconvertedArray.push(item);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tconvertedData = convertedArray;\r\n\r\n\t\t\t} else {\r\n\t\t\t\tconst convertedArray : Array<any> = [];\r\n\t\t\t\tif (result.data instanceof UTSJSONObject) {\r\n\t\t\t\t\tconst parsed = result.data.parse<T>();\r\n\r\n\t\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\t\tconvertedArray.push(parsed);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\t//__f__('log','at components/supadb/aksupa.uts:539','转换失败:', result.data)\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst jsonObj = new UTSJSONObject(result.data);\r\n\t\t\t\t\tconst parsed = jsonObj.parse<T>();\r\n\t\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\t\tconvertedArray.push(parsed);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\t//__f__('log','at components/supadb/aksupa.uts:548','转换失败:', result.data)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tconvertedData = convertedArray;\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\t__f__('warn','at components/supadb/aksupa.uts:554','数据类型转换失败,使用原始数据:', e);\r\n\t\t\t__f__('log','at components/supadb/aksupa.uts:555',result.data)\r\n\t\t\tconvertedData = result.data as any;\r\n\t\t}\r\n\t\tresult.data = convertedData\r\n\t\treturn result as AkReqResponse<T>;\r\n\r\n\t}\r\n}\r\n\r\n// 新增:链式 Storage 上传\r\nexport class AkSupaStorageUploadBuilder {\r\n\tprivate _supa : AkSupa;\r\n\tprivate _bucket : string = '';\r\n\tprivate _path : string = '';\r\n private _file : string = '';\r\n\tprivate _options : UTSJSONObject = {};\r\n\r\n\tconstructor(supa : AkSupa, bucket : string) {\r\n\t\tthis._supa = supa;\r\n\t\tthis._bucket = bucket;\r\n\t}\r\n\r\n\tpath(path : string) : AkSupaStorageUploadBuilder {\r\n\t\tthis._path = path;\r\n\t\treturn this;\r\n\t}\r\n\r\n file(file : string) : AkSupaStorageUploadBuilder {\r\n\t\tthis._file = file;\r\n\t\treturn this;\r\n\t}\r\n\r\n\toptions(options : UTSJSONObject) : AkSupaStorageUploadBuilder {\r\n\t\tthis._options = options;\r\n\t\treturn this;\r\n\t}\r\n\tasync upload() : Promise<AkReqResponse<any>> {\r\n if (this._bucket == '' || this._path == '' || this._file == '') {\r\n\t\t\tthrow toUniError('bucket, path, file are required', '上传文件缺少必要参数');\r\n\t\t}\r\n\t\tconst url = `${this._supa.baseUrl}/storage/v1/object/${this._bucket}/${this._path}`;\r\n\t\tconst apikey = this._supa.apikey;\r\n\t\t// 适配 uni.uploadFile\r\n\t\tconst uploadOptions : AkReqUploadOptions = {\r\n\t\t\turl,\r\n\t\t\tfilePath: this._file, // 这里假设 file 是本地路径\r\n\t\t\tname: 'file', // 默认字段名\r\n\t\t\theaders: {},\r\n\t\t\tapikey,\r\n\t\t\tformData: this._options\r\n\t\t};\r\n\t\treturn await AkReq.upload(uploadOptions);\r\n\t}\r\n}\r\n\r\n// 新增:明确的 StorageBucket 类,支持链式 upload\r\nclass AkSupaStorageBucket {\r\n\tprivate supa : AkSupa;\r\n\tprivate bucket : string;\r\n\tconstructor(supa : AkSupa, bucket : string) {\r\n\t\tthis.supa = supa;\r\n\t\tthis.bucket = bucket;\r\n\t}\r\n\tasync upload(path : string, filePath : string, options ?: UTSJSONObject) : Promise<AkReqResponse<any>> {\r\n\t\tconst url = `${this.supa.baseUrl}/storage/v1/object/${this.bucket}/${path}`;\r\n\t\tlet headers : UTSJSONObject = { apikey: this.supa.apikey };\r\n\t\tconst formData : UTSJSONObject = {};\r\n\t\tif (options != null && typeof options == 'object') {\r\n\t\t\tif (typeof options.get == 'function' && options.get('x-upsert') != null) {\r\n\t\t\t\theaders['x-upsert'] = options.get('x-upsert');\r\n\t\t\t}\r\n\t\t\tconst keys = UTSJSONObject.keys(options);\r\n\t\t\tfor (let i = 0; i < keys.length; i++) {\r\n\t\t\t\tconst k = keys[i];\r\n\t\t\t\tif (k != 'x-upsert') formData[k] = options.get(k);\r\n\t\t\t}\r\n\t\t}\r\n\t\tconst token = AkReq.getToken();\r\n\t\tif (token != null && !(token == '')) {\r\n\t\t\theaders['Authorization'] = `Bearer ${token}`;\r\n\t\t}\r\n\t\treturn await AkReq.upload({\r\n\t\t\turl,\r\n\t\t\tfilePath,\r\n\t\t\tname: 'file',\r\n\t\t\tapikey: this.supa.apikey,\r\n\t\t\tformData,\r\n\t\t\theaders\r\n\t\t});\r\n\t}\r\n}\r\n\r\nexport class AkSupaStorageApi {\r\n\tprivate _supa : AkSupa;\r\n\tconstructor(supa : AkSupa) {\r\n\t\tthis._supa = supa;\r\n\t}\r\n\tfrom(bucket : string) : AkSupaStorageBucket {\r\n\t\treturn new AkSupaStorageBucket(this._supa, bucket);\r\n\t}\r\n}\r\n\r\nexport class AkSupa {\r\n\tbaseUrl : string;\r\n\tapikey : string;\r\n\tsession : AkSupaSignInResult | null = null;\r\n\tuser : UTSJSONObject | null = null;\r\n\tstorage : AkSupaStorageApi;\r\n\r\n\tconstructor(baseUrl : string, apikey : string) {\r\n\t\tthis.baseUrl = baseUrl;\r\n\t\tthis.apikey = apikey;\r\n\t\tthis.storage = new AkSupaStorageApi(this);\r\n\t\t// [CHANGE][2026-01-30] hydrate user/session from persisted token (see docs: components/supadb/docs/CHANGELOG.md)\r\n\t\ttry {\r\n\t\t\tthis.hydrateSessionFromStorage();\r\n\t\t} catch (e) {\r\n\t\t\t// ignore\r\n\t\t}\r\n\t}\r\n\r\n\t// [CHANGE][2026-01-30] hydrate user from /auth/v1/user when token exists in storage\r\n\tasync hydrateSessionFromStorage() : Promise<boolean> {\r\n\t\ttry {\r\n\t\t\tconst token = AkReq.getToken();\r\n\t\t\tif (token == null || token == '') return false;\r\n\t\t\tconst res = await AkReq.request({\r\n\t\t\t\turl: this.baseUrl + '/auth/v1/user',\r\n\t\t\t\tmethod: 'GET',\r\n\t\t\t\theaders: {\r\n\t\t\t\t\tapikey: this.apikey,\r\n\t\t\t\t\tAuthorization: `Bearer ${token}`,\r\n\t\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t\t} as UTSJSONObject\r\n\t\t\t}, false);\r\n\t\t\tconst status = res.status ?? 0;\r\n\t\t\tif (!(status >= 200 && status < 400)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tlet user: UTSJSONObject | null = null;\r\n\t\t\ttry {\r\n\t\t\t\tuser = new UTSJSONObject(res.data);\r\n\t\t\t} catch (e) {\r\n\t\t\t\tuser = null;\r\n\t\t\t}\r\n\t\t\tif (user == null) return false;\r\n\t\t\tthis.user = user;\r\n\t\t\t// 仅补齐最小 session 结构,供 getSession / UI 判断登录态使用\r\n\t\t\tif (this.session == null) {\r\n\t\t\t\tthis.session = {\r\n\t\t\t\t\taccess_token: token,\r\n\t\t\t\t\trefresh_token: AkReq.getRefreshToken() ?? '',\r\n\t\t\t\t\texpires_at: AkReq.getExpiresAt() ?? 0,\r\n\t\t\t\t\tuser: user,\r\n\t\t\t\t\ttoken_type: 'bearer',\r\n\t\t\t\t\texpires_in: 0,\r\n\t\t\t\t\traw: user\r\n\t\t\t\t} as AkSupaSignInResult;\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t} catch (e) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tasync resetPassword(email : string) : Promise<boolean> {\r\n\t\tconst res = await AkReq.request({\r\n\t\t\turl: this.baseUrl + '/auth/v1/recover',\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\tapikey: this.apikey,\r\n\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t} as UTSJSONObject,\r\n\t\t\tdata: { email } as UTSJSONObject,\r\n\t\t\tcontentType: 'application/json'\r\n\t\t}, false);\r\n\r\n\t\t// Supabase returns 200 when the reset email is sent successfully\r\n\t\treturn res.status == 200;\r\n\t}\r\n\tasync signOut() {\r\n\t\tthis.session = null\r\n\t\tthis.user = null\r\n\t}\r\n\tasync signIn(email : string, password : string) : Promise<AkSupaSignInResult> {\r\n\t\t// 提前检查 apikey 配置是否为占位符,避免发送无效请求导致 401\r\n\t\tif (this.apikey == null || this.apikey.trim() === '' || this.apikey === 'your-anon-key') {\r\n\t\t\tthrow new Error('Supabase 配置错误:请在 ak/config.uts 中设置 SUPA_KEY当前为占位符');\r\n\t\t}\r\n\t\tconst headers = new UTSJSONObject()\r\n\t\theaders.set('apikey', this.apikey)\r\n\t\theaders.set('Content-Type', 'application/json')\r\n\t\tconst reqData = new UTSJSONObject()\r\n\t\treqData.set('email', email)\r\n\t\treqData.set('password', password)\r\n\t\tconst res = await AkReq.request({\r\n\t\t\turl: this.baseUrl + '/auth/v1/token?grant_type=password',\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: headers,\r\n\t\t\tdata: reqData,\r\n\t\t\tcontentType: 'application/json'\r\n\t\t}, false);\r\n\t\t// 如果响应不是 2xx例如 401提取后端错误信息并抛出便于上层显示具体原因\r\n\t\tconst status = res.status ?? 0;\r\n\t\tif (!(status >= 200 && status < 400)) {\r\n\t\t\tlet msg = 'user.login.login_failed';\r\n\t\t\ttry {\r\n\t\t\t\tif (res.data != null) {\r\n\t\t\t\t\tconst obj = new UTSJSONObject(res.data);\r\n\t\t\t\t\tconst rawMsg = obj.getString('message') ?? obj.getString('error') ?? obj.getString('msg') ?? obj.getString('description') ?? obj.getString('error_description') ?? '';\r\n\t\t\t\t\t\r\n\t\t\t\t\t// 核心修复:在这里拦截英文错误并转换为中文\r\n\t\t\t\t\tif (rawMsg.includes('Invalid login credentials')) {\r\n\t\t\t\t\t\tmsg = '用户名或密码错误';\r\n\t\t\t\t\t} else if (rawMsg != '') {\r\n\t\t\t\t\t\tmsg = rawMsg;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} catch (e) {\r\n\t\t\t\t// ignore\r\n\t\t\t}\r\n\t\t\tthrow new Error(msg);\r\n\t\t}\r\n\t\t// 解析成功的返回体\r\n\t\tlet data: UTSJSONObject;\r\n\t\ttry {\r\n\t\t\tdata = new UTSJSONObject(res.data);\r\n\t\t} catch (e) {\r\n\t\t\tdata = new UTSJSONObject({});\r\n\t\t}\r\n\t\tconst access_token = data.getString('access_token') ?? '';\r\n\t\tconst refresh_token = data.getString('refresh_token') ?? '';\r\n\t\tconst expires_at = data.getNumber('expires_at') ?? 0;\r\n\t\tconst user = data.getJSON('user');\r\n\t\tAkReq.setToken(access_token, refresh_token, expires_at);\r\n\t\tconst session : AkSupaSignInResult = {\r\n\t\t\taccess_token: access_token,\r\n\t\t\trefresh_token: refresh_token,\r\n\t\t\texpires_at: expires_at,\r\n\t\t\tuser: user,\r\n\t\t\ttoken_type: data.getString('token_type') ?? '',\r\n\t\t\texpires_in: data.getNumber('expires_in') ?? 0,\r\n\t\t\traw: data\r\n\t\t};\r\n\t\tthis.session = session;\r\n\t\tthis.user = user;\r\n\t\treturn session;\r\n\t}\r\n\r\n\t/**\r\n\t * 获取当前 session 和 user\r\n\t */\r\n\tgetSession() : AkSupaSessionInfo {\r\n\t\treturn {\r\n\t\t\tsession: this.session,\r\n\t\t\tuser: this.user\r\n\t\t};\r\n\t}\r\n\r\n\tasync signUp(email : string, password : string, options : UTSJSONObject | null = null) : Promise<UTSJSONObject> {\r\n\t\tconst headers = new UTSJSONObject()\r\n\t\theaders.set('apikey', this.apikey)\r\n\t\theaders.set('Content-Type', 'application/json')\r\n\t\tconst data = new UTSJSONObject()\r\n\t\tdata.set('email', email)\r\n\t\tdata.set('password', password)\r\n\t\t\r\n\t\tif (options != null) {\r\n\t\t\tconst dataField = options.getJSON('data')\r\n\t\t\tif (dataField != null) {\r\n\t\t\t\tdata.set('data', dataField)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst res = await AkReq.request({\r\n\t\t\turl: this.baseUrl + '/auth/v1/signup',\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: headers,\r\n\t\t\tdata: data,\r\n\t\t\tcontentType: 'application/json'\r\n\t\t}, false);\r\n\t\treturn res.data as UTSJSONObject;\r\n\t}\r\n\r\n\r\n\t/**\r\n\t * 查询表数据GET方式支持多条件、limit等filter自动转为supabase风格query\r\n\t * filter 支持:\r\n\t * { usr_id: { lt: 800 }, name: { ilike: '%foo%' }, status: 'active', age: { gte: 18, lte: 30 } }\r\n\t * 操作符支持 eq, neq, lt, lte, gt, gte, like, ilike, in, is, not, contains, containedBy, range, fts, plfts, phfts, wfts\r\n\t */\r\nasync select(table : string, filter ?: string | null, options ?: AkSupaSelectOptions) : Promise<AkReqResponse<any>> {\r\n\tlet url = this.baseUrl + '/rest/v1/' + table;\r\n\tlet headers = new UTSJSONObject()\r\n\theaders.set('apikey', this.apikey)\r\n\theaders.set('Content-Type', 'application/json')\r\n\theaders.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)\r\n\tlet params : string[] = [];\r\n\tif (options != null) {\r\n\t\tif (options.columns != null && !(options.columns == \"\")) params.push('select=' + encodeURIComponent(options.columns ?? \"\"));\r\n\t\tif (options.limit != null) {\r\n\t\t\tparams.push('limit=' + options.limit);\r\n\t\t\t//__f__('log','at components/supadb/aksupa.uts:857','设置 limit 参数:', options.limit);\r\n\t\t}\r\n\t\tif (options.order != null && !(options.order == \"\")) params.push('order=' + encodeURIComponent(options.order ?? \"\"));\r\n\t\tif (options.rangeFrom != null && options.rangeTo != null) {\r\n\t\t\theaders['Range'] = `${options.rangeFrom}-${options.rangeTo}`;\r\n\t\t\theaders['Range-Unit'] = 'items';\r\n\t\t\t//__f__('log','at components/supadb/aksupa.uts:863','设置 Range 头部:', `${options.rangeFrom}-${options.rangeTo}`);\r\n\t\t}\r\n\r\n\t\t// 向后兼容:支持旧的 getcount 参数\r\n\t\tlet countOption = options.count ?? options.getcount;\r\n\t\tif (countOption != null) {\r\n\t\t\theaders['Prefer'] = `count=${countOption}`;\r\n\t\t}\r\n\t\t// 新增head 模式支持\r\n\t\tif (options.head == true) {\r\n\t\t\t//__f__('log','at components/supadb/aksupa.uts:873','使用 head 模式,只返回元数据');\r\n\t\t\t// HEAD 请求用于只获取 count不返回数据\r\n\t\t\tif (headers['Prefer'] != null) {\r\n\t\t\t\theaders['Prefer'] = (headers['Prefer'] as string) + ',return=minimal';\r\n\t\t\t} else {\r\n\t\t\t\theaders['Prefer'] = 'return=minimal';\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (options.single == true) {\r\n\t\t\t//__f__('log','at components/supadb/aksupa.uts:883','使用 single() 模式');\r\n\t\t\tif (headers['Prefer'] != null) {\r\n\t\t\t\theaders['Prefer'] = (headers['Prefer'] as string) + ',return=representation,single-object';\r\n\t\t\t} else {\r\n\t\t\t\theaders['Prefer'] = 'return=representation,single-object';\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// 确保有 select 参数\r\n\t\tif (options.columns == null) {\r\n\t\t\tparams.push('select=*');\r\n\t\t} else if (options.columns == \"\") {\r\n\t\t\tparams.push('select=*');\r\n\t\t}\r\n\t} else {\r\n\t\tparams.push('select=*');\r\n\t}\r\n\t// 直接用 string filter\r\n\tif (filter!=null && filter !== \"\") {\r\n\t\tparams.push(filter!!);\r\n\t}\r\n\tif (params.length > 0) {\r\n\t\turl += '?' + params.join('&');\r\n\t}\r\n\r\n\t//__f__('log','at components/supadb/aksupa.uts:908',url)\r\n\r\n\t// 确定HTTP方法如果是head模式使用HEAD方法\r\n\tlet httpMethod: 'GET' | 'HEAD' = 'GET';\r\n\tif (options != null && options.head == true) {\r\n\t\thttpMethod = 'HEAD';\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:914','使用 HEAD 方法进行 count 查询');\r\n\t}\r\n\r\n\tlet reqOptions : AkReqOptions = {\r\n\t\turl,\r\n\t\tmethod: httpMethod,\r\n\t\theaders\r\n\t};\r\n\treturn await this.requestWithAutoRefresh(reqOptions);\r\n}\r\n\r\nasync select_uts(table : string, filter ?: UTSJSONObject | null, options ?: AkSupaSelectOptions) : Promise<AkReqResponse<any>> {\r\n\tconst filter_str = buildSupabaseFilterQuery(filter)\r\n\treturn this.select(table,filter_str,options)\r\n}\r\n\t/**\r\n\t * 插入表数据\r\n\t * @param table 表名\r\n\t * @param row 插入对象\r\n\t * @returns 插入结果\r\n\t */\r\n\tasync insert(table : string, row : UTSJSONObject | Array<UTSJSONObject>) : Promise<AkReqResponse<any>> {\r\n\t\tconst url = this.baseUrl + '/rest/v1/' + table;\r\n\t\tconst headers = new UTSJSONObject()\r\n\t\theaders.set('apikey', this.apikey)\r\n\t\theaders.set('Content-Type', 'application/json')\r\n\t\theaders.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)\r\n\t\theaders.set('Prefer', 'return=representation')\r\n\r\n\t\tlet reqOptions : AkReqOptions = {\r\n\t\t\turl,\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders,\r\n\t\t\tdata: row,\r\n\t\t\tcontentType: 'application/json'\r\n\t\t};\r\n\t\treturn await this.requestWithAutoRefresh(reqOptions);\r\n\t}\r\n\r\n\t/**\r\n\t * 更新表数据\r\n\t * @param table 表名\r\n\t * @param filter 过滤条件对象\r\n\t * @param values 更新内容对象\r\n\t * @returns 更新结果\r\n\t */\r\nasync update(table : string, filter : string | null, values : UTSJSONObject) : Promise<AkReqResponse<any>> {\r\n\tlet url = this.baseUrl + '/rest/v1/' + table;\r\n\tif (filter!=null && filter !== \"\") {\r\n\t\turl += '?' + filter;\r\n\t}\r\n\tconst headers = new UTSJSONObject()\r\n\theaders.set('apikey', this.apikey)\r\n\theaders.set('Content-Type', 'application/json')\r\n\theaders.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)\r\n\theaders.set('Prefer', 'return=representation')\r\n\tlet reqOptions : AkReqOptions = {\r\n\t\turl,\r\n\t\tmethod: 'PATCH',\r\n\t\theaders,\r\n\t\tdata: values,\r\n\t\tcontentType: 'application/json'\r\n\t};\r\n\treturn await this.requestWithAutoRefresh(reqOptions);\r\n}\r\n\r\n\t/**\r\n\t * 删除表数据\r\n\t * @param table 表名\r\n\t * @param filter 过滤条件对象\r\n\t * @returns 删除结果\r\n\t */\r\nasync delete(table : string, filter : string | null) : Promise<AkReqResponse<any>> {\r\n\tlet url = this.baseUrl + '/rest/v1/' + table;\r\n\tif (filter!=null && filter !== \"\") {\r\n\t\turl += '?' + filter;\r\n\t}\r\n\tconst headers = new UTSJSONObject()\r\n\theaders.set('apikey', this.apikey)\r\n\theaders.set('Content-Type', 'application/json')\r\n\theaders.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)\r\n\theaders.set('Prefer', 'return=representation')\r\n\tlet reqOptions : AkReqOptions = {\r\n\t\turl,\r\n\t\tmethod: 'DELETE',\r\n\t\theaders,\r\n\t\tcontentType: 'application/json'\r\n\t};\r\n\treturn await this.requestWithAutoRefresh(reqOptions);\r\n}\r\n\r\n\t/**\r\n\t * 调用 Supabase/PostgREST RPC (function)\r\n\t * @param functionName 函数名\r\n\t * @param params 参数对象\r\n\t * @returns AkReqResponse<any>\r\n\t */\r\n\tasync rpc(functionName : string, params ?: UTSJSONObject) : Promise<AkReqResponse<any>> {\r\n\t\tconst url = this.baseUrl + '/rest/v1/rpc/' + functionName;\r\n\t\tconst headers = new UTSJSONObject()\r\n\t\theaders.set('apikey', this.apikey)\r\n\t\theaders.set('Content-Type', 'application/json')\r\n\t\theaders.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)\r\n\t\tlet reqOptions : AkReqOptions = {\r\n\t\t\turl,\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders,\r\n\t\t\tdata: params ?? new UTSJSONObject(),\r\n\t\t\tcontentType: 'application/json'\r\n\t\t};\r\n\t\treturn await this.requestWithAutoRefresh(reqOptions);\r\n\t}\r\n\t/**\r\n\t * 兼容 supabase-js 风格\r\n\t * @param tableName 表名\r\n\t */\r\n\tfrom(tableName : string) : AkSupaQueryBuilder {\r\n\t\treturn new AkSupaQueryBuilder(this, tableName);\r\n\t}\r\n\r\n /**\r\n * 创建实时订阅通道 (兼容 Supabase Realtime 接口,目前使用轮询模拟)\r\n * @param topic 通道名称,如 public:table\r\n */\r\n channel(topic: string): AkSupaRealtimeChannel {\r\n return new AkSupaRealtimeChannel(this, topic);\r\n }\r\n \r\n /**\r\n * 移除通道\r\n */\r\n removeChannel(channel: AkSupaRealtimeChannel): Promise<string> {\r\n channel.unsubscribe();\r\n return Promise.resolve('ok');\r\n }\r\n\t// AkSupa类内新增自动刷新session\r\n\tasync refreshSession() : Promise<boolean> {\r\n\t\tif (this.session == null || this.session?.refresh_token == null) return false;\r\n\t\ttry {\r\n\t\t\tconst headers = new UTSJSONObject()\r\n\t\t\theaders.set('apikey', this.apikey)\r\n\t\t\theaders.set('Content-Type', 'application/json')\r\n\t\t\tconst data = new UTSJSONObject()\r\n\t\t\tdata.set('refresh_token', this.session?.refresh_token)\r\n\t\t\tconst res = await AkReq.request({\r\n\t\t\t\turl: this.baseUrl + '/auth/v1/token?grant_type=refresh_token',\r\n\t\t\t\tmethod: 'POST',\r\n\t\t\t\theaders: headers,\r\n\t\t\t\tdata: data,\r\n\t\t\t\tcontentType: 'application/json'\r\n\t\t\t}, false);\r\n\t\t\tif (res.status == 200 && (res.data != null)) {\r\n\t\t\t\tconst data = res.data as UTSJSONObject;\r\n\t\t\t\tconst access_token = data.getString('access_token') ?? '';\r\n\t\t\t\tconst refresh_token = data.getString('refresh_token') ?? '';\r\n\t\t\t\tconst expires_at = data.getNumber('expires_at') ?? 0;\r\n\t\t\t\tconst user = data.getJSON('user');\r\n\t\t\t\tthis.session = {\r\n\t\t\t\t\taccess_token,\r\n\t\t\t\t\trefresh_token,\r\n\t\t\t\t\texpires_at,\r\n\t\t\t\t\tuser,\r\n\t\t\t\t\ttoken_type: data.getString('token_type') ?? '',\r\n\t\t\t\t\texpires_in: data.getNumber('expires_in') ?? 0,\r\n\t\t\t\t\traw: data\r\n\t\t\t\t};\r\n\t\t\t\tthis.user = user;\r\n\t\t\t\t// 更新本地token\r\n\t\t\t\tAkReq.setToken(access_token, refresh_token, expires_at);\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t} catch (e) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tasync updateUserMetadata(metadata: UTSJSONObject): Promise<UTSJSONObject> {\r\n\t\tconst headers = new UTSJSONObject()\r\n\t\theaders.set('apikey', this.apikey)\r\n\t\theaders.set('Content-Type', 'application/json')\r\n\t\theaders.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)\r\n\t\tconst data = new UTSJSONObject()\r\n\t\tdata.set('data', metadata)\r\n\t\tconst res = await AkReq.request({\r\n\t\t\turl: this.baseUrl + '/auth/v1/user',\r\n\t\t\tmethod: 'PUT',\r\n\t\t\theaders: headers,\r\n\t\t\tdata: data,\r\n\t\t\tcontentType: 'application/json'\r\n\t\t}, false);\r\n\t\treturn res.data as UTSJSONObject;\r\n\t}\r\n\r\n\t// AkSupa类内新增自动刷新封装\r\n\tasync requestWithAutoRefresh(reqOptions : AkReqOptions, isRetry = false) : Promise<AkReqResponse<any>> {\r\n\t\tlet res = await AkReq.request(reqOptions, false);\r\n\t\t// JWT过期Supabase风格\r\n\t\tconst isJwtExpired = (res.status == 401); //res != null && res.data != null && typeof res.data == 'object' && (res.data as UTSJSONObject)?.getString('code') == 'PGRST301';\r\n\t\t// 401未授权\r\n\t\tconst isUnauthorized = (res.status == 401);\r\n\t\tif ((isJwtExpired || isUnauthorized) && !isRetry) {\r\n\t\t\tconst ok = await this.refreshSession();\r\n\t\t\tif (ok) {\r\n\t\t\t\tlet headers = reqOptions.headers\r\n\t\t\t\tif (headers == null) {\r\n\t\t\t\t\theaders = new UTSJSONObject()\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof headers.set == 'function') {\r\n\t\t\t\t\theaders.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)\r\n\t\t\t\t\treqOptions.headers = headers\r\n\t\t\t\t}\r\n\r\n\t\t\t\tres = await AkReq.request(reqOptions, false);\r\n\t\t\t} else {\r\n\t\t\t\tuni.removeStorageSync('user_id');\r\n\t\t\t\tuni.removeStorageSync('token');\r\n\r\n\t\t\t\t//uni.reLaunch({ url: '/pages/user/login' });\r\n __f__('log','at components/supadb/aksupa.uts:1133','登录已过期,请重新登录');\r\n\t\t\t\tthrow toUniError('登录已过期,请重新登录', '用户认证失败');\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn res;\r\n\t}\r\n}\r\n\r\n// 工具函数:将 UTSJSONObject filter 转为 Supabase/PostgREST 查询字符串\r\nfunction buildSupabaseFilterQuery(filter : UTSJSONObject | null) : string {\r\n\t//__f__('log','at components/supadb/aksupa.uts:1143',filter)\r\n\tif (filter == null) return \"\";\r\n\t// 类型保护:如果不是 UTSJSONObject自动包裹\r\n\tif (typeof filter.get !== 'function') {\r\n\t\ttry {\r\n\t\t\tfilter = new UTSJSONObject(filter as any)\r\n\t\t} catch (e) {\r\n\t\t\t__f__('warn','at components/supadb/aksupa.uts:1150','filter 不是 UTSJSONObject且无法转换', filter)\r\n\t\t\treturn ''\r\n\t\t}\r\n\t}\r\n\tconst params : string[] = [];\r\n\tconst keys : string[] = UTSJSONObject.keys(filter);\r\n\tfor (let i = 0; i < keys.length; i++) {\r\n\t\tconst k = keys[i];\r\n\t\tconst v = filter.get(k);\r\n\t\tif (k == 'or' && typeof v == 'string') {\r\n\t\t\tparams.push(`or=(${v})`);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tif (v != null && typeof v == 'object' && typeof (v as UTSJSONObject).get == 'function') {\r\n\t\t\tconst vObj = v as UTSJSONObject;\r\n\t\t\tconst opKeys = UTSJSONObject.keys(vObj);\r\n\t\t\tfor (let j = 0; j < opKeys.length; j++) {\r\n\t\t\t\tconst op = opKeys[j];\r\n\t\t\t\tconst opVal = vObj.get(op);\r\n\t\t\t\tif ((op == 'in' || op == 'not.in') && Array.isArray(opVal)) {\r\n\t\t\t\t\tparams.push(`${k}=${op}.(${opVal.map(x => typeof x == 'object' ? encodeURIComponent(JSON.stringify(x)) : encodeURIComponent(x.toString())).join(',')})`);\r\n\t\t\t\t} else if (op == 'is' && (opVal == null || opVal == 'null')) {\r\n\t\t\t\t\tparams.push(`${k}=is.null`);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst opvalstr : string = (typeof opVal == 'object') ? JSON.stringify(opVal) : (opVal as string);\r\n\t\t\t\t\tparams.push(`${k}=${op}.${encodeURIComponent(opvalstr)}`);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (v != null && typeof v == 'object') {\r\n\t\t\tconst vObj = v as UTSJSONObject;\r\n\t\t\tconst opKeys = UTSJSONObject.keys(vObj);\r\n\t\t\tfor (let j = 0; j < opKeys.length; j++) {\r\n\t\t\t\tconst op = opKeys[j];\r\n\t\t\t\tconst opVal = vObj.get(op);\r\n\t\t\t\tparams.push(`${k}=${op}.${encodeURIComponent(!(opVal == null) ? (typeof opVal == 'object' ? JSON.stringify(opVal) : opVal.toString()) : '')}`);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tparams.push(`${k}=eq.${encodeURIComponent(!(v == null) ? v.toString() : '')}`);\r\n\t\t}\r\n\t}\r\n\treturn params.join('&');\r\n}\r\n\r\n/**\r\n * 创建 Supabase 客户端实例\r\n * @param url 项目 URL\r\n * @param key 项目匿名密钥 (Anon Key)\r\n */\r\nexport function createClient(url : string, key : string) : AkSupa {\r\n\treturn new AkSupa(url, key);\r\n}\r\n\r\n// 模拟 Realtime Channel 类 (Polling Fallback)\r\nexport class AkSupaRealtimeChannel {\r\n private _supa: AkSupa;\r\n private _topic: string;\r\n private _timer: number = 0;\r\n private _callback: ((payload: any) => void) | null = null;\r\n private _table: string = '';\r\n private _lastTime: string = new Date().toISOString(); \r\n private _isSubscribed: boolean = false;\r\n\r\n constructor(supa: AkSupa, topic: string) {\r\n this._supa = supa;\r\n this._topic = topic;\r\n }\r\n\r\n // 绑定事件 (仅支持 postgres_changes INSERT)\r\n on(type: string, filter: UTSJSONObject, callback: (payload: any) => void): AkSupaRealtimeChannel {\r\n // 解析 table\r\n const table = filter.getString('table');\r\n if (table != null) {\r\n this._table = table;\r\n }\r\n this._callback = callback;\r\n return this;\r\n }\r\n\r\n // 开始订阅\r\n subscribe(callback?: (status: string, err: any | null) => void): AkSupaRealtimeChannel {\r\n if (this._isSubscribed) return this;\r\n this._isSubscribed = true;\r\n \r\n // 初始回调\r\n if (callback != null) {\r\n callback('SUBSCRIBED', null);\r\n }\r\n\r\n // 如果没有指定 table无法轮询\r\n if (this._table == '') {\r\n __f__('warn','at components/supadb/aksupa.uts:1240','Realtime check: No table specified for polling.');\r\n return this;\r\n }\r\n\r\n // 开始轮询 (每3秒)\r\n this._timer = setInterval(() => {\r\n this._checkUpdates();\r\n }, 3000);\r\n\r\n return this;\r\n }\r\n\r\n // 停止订阅\r\n unsubscribe() {\r\n if (this._timer > 0) {\r\n clearInterval(this._timer);\r\n this._timer = 0;\r\n }\r\n this._isSubscribed = false;\r\n }\r\n\r\n // 检查更新\r\n private async _checkUpdates() {\r\n if (!this._isSubscribed || this._table == '') return;\r\n \r\n try {\r\n const now = new Date().toISOString();\r\n \r\n const res = await this._supa\r\n .from(this._table)\r\n .select('*')\r\n .gt('created_at', this._lastTime)\r\n .order('created_at', { ascending: true })\r\n .execute();\r\n \r\n if (res.error == null && res.data != null) {\r\n let list: any[] = [];\r\n if (Array.isArray(res.data)) {\r\n list = res.data as any[];\r\n }\r\n \r\n if (list.length > 0) {\r\n // 更新最后时间\r\n const lastItem = list[list.length - 1];\r\n let lastTimeStr: string | null = null;\r\n \r\n if (lastItem instanceof UTSJSONObject) {\r\n lastTimeStr = lastItem.getString('created_at');\r\n } else {\r\n // 尝试转 json\r\n const j = JSON.parse(JSON.stringify(lastItem)) as UTSJSONObject;\r\n lastTimeStr = j.getString('created_at');\r\n }\r\n \r\n if (lastTimeStr != null) {\r\n this._lastTime = lastTimeStr;\r\n } else {\r\n this._lastTime = now;\r\n }\r\n\r\n // 触发回调\r\n if (this._callback != null) {\r\n // 模拟 Realtime payload\r\n list.forEach(item => {\r\n const payload = {\r\n new: item,\r\n eventType: 'INSERT',\r\n old: null\r\n };\r\n this._callback?.(payload);\r\n });\r\n }\r\n }\r\n }\r\n } catch (e) {\r\n __f__('error','at components/supadb/aksupa.uts:1315','Realtime polling error:', e);\r\n }\r\n }\r\n}\r\n\r\nexport default AkSupa;\r\n","// /components/supadb/aksupainstance.uts\r\nimport { createClient } from './aksupa.uts'\r\nimport { SUPA_URL, SUPA_KEY } from '@/ak/config.uts'\r\n\r\n// 创建单一真实的 Supabase 客户端实例 (使用 config.uts 配置)\r\n// Create single source of truth client using config\r\nconst supaInstance = createClient(SUPA_URL, SUPA_KEY)\r\n\r\n// 导出默认实例 (供 login.uvue 等使用)\r\nexport default supaInstance\r\n\r\n// 导出命名实例 'supabase' (供 store.uts 使用)\r\nexport const supabase = supaInstance\r\n\r\n// 导出 isSupabaseReady 状态\r\nexport const isSupabaseReady = true\r\n\r\n// 兼容 ensureSupabaseReady\r\nexport async function ensureSupabaseReady() {\r\n return true\r\n}\r\n\r\n// 检查连接状态的函数\r\nexport function checkConnection() {\r\n return Promise.resolve(true)\r\n}\r\n\r\n// 兼容 supaReady Promise\r\nexport const supaReady = Promise.resolve(true)\r\n\r\n// 如果有其他需要导出的函数,可以这样导出:\r\nexport function initializeSupabase(url: string, key: string) {\r\n return createClient(url, key)\r\n}\r\n","// 电商商城系统类型定义 - UTS Android 兼容\r\n\r\n// 用户类型\r\nexport type UserType = {\r\n\tid: string\r\n\tphone: string\r\n\temail: string | null\r\n\tnickname: string | null\r\n\tavatar_url: string | null\r\n\tgender: number\r\n\tuser_type: number\r\n\tstatus: number\r\n\tcreated_at: string\r\n}\r\n\r\n// 商城用户扩展信息类型\r\nexport type MallUserProfileType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tuser_type: number\r\n\tstatus: number\r\n\treal_name: string | null\r\n\tid_card: string | null\r\n\tcredit_score: number\r\n\tmall_role: string\r\n\tverification_status: number\r\n\tverification_data: UTSJSONObject | null\r\n\tbusiness_license: string | null\r\n\tshop_category: string | null\r\n\tservice_areas: UTSJSONObject | null\r\n\temergency_contact: string | null\r\n\tpreferences: UTSJSONObject | null\r\n\tcreated_at: string\r\n\tupdated_at: string\r\n}\r\n\r\n// 用户地址类型\r\nexport type UserAddressType = {\r\n\tid: string\r\n\tuser_id: string\r\n\treceiver_name: string\r\n\treceiver_phone: string\r\n\tprovince: string\r\n\tcity: string\r\n\tdistrict: string\r\n\taddress_detail: string\r\n\tpostal_code: string | null\r\n\tis_default: boolean\r\n\tlabel: string | null\r\n\tcoordinates: string | null\r\n\tdelivery_instructions: string | null\r\n\tbusiness_hours: string | null\r\n\tstatus: number\r\n\tcreated_at: string\r\n\tupdated_at: string\r\n}\r\n\r\n// 商家类型\r\nexport type MerchantType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tshop_name: string\r\n\tshop_logo: string | null\r\n\tshop_banner: string | null\r\n\tshop_description: string | null\r\n\tcontact_name: string\r\n\tcontact_phone: string\r\n\tshop_status: number\r\n\trating: number\r\n\ttotal_sales: number\r\n\tcreated_at: string\r\n}\r\n\r\n// 商品类型\r\nexport type ProductType = {\r\n\tid: string\r\n\tmerchant_id: string\r\n\tcategory_id: string\r\n\tname: string\r\n\tdescription: string | null\r\n\timages: Array<string>\r\n\tprice: number\r\n\toriginal_price: number | null\r\n\tstock: number\r\n\tsales: number\r\n\tstatus: number\r\n\tcreated_at: string\r\n\t// 药品相关字段\r\n\tspecification?: string | null // 规格说明\r\n\tusage?: string | null // 用法用量\r\n\tside_effects?: string | null // 副作用\r\n\tprecautions?: string | null // 注意事项\r\n\texpiry_date?: string | null // 有效期\r\n\tstorage_conditions?: string | null // 储存条件\r\n\tapproval_number?: string | null // 批准文号\r\n\ttags?: Array<string> | null // 商品标签\r\n}\r\n\r\n// 商品SKU类型\r\nexport type ProductSkuType = {\r\n\tid: string\r\n\tproduct_id: string\r\n\tsku_code: string\r\n\tspecifications: UTSJSONObject | null\r\n\tprice: number\r\n\tstock: number\r\n\timage_url: string | null\r\n\tstatus: number\r\n}\r\n\r\n// 购物车类型\r\nexport type CartItemType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tproduct_id: string\r\n\tsku_id: string\r\n\tquantity: number\r\n\tselected: boolean\r\n\tproduct: ProductType | null\r\n\tsku: ProductSkuType | null\r\n}\r\n\r\n// 订单类型\r\nexport type OrderType = {\r\n\tid: string\r\n\torder_no: string\r\n\tuser_id: string\r\n\tmerchant_id: string\r\n\tstatus: number\r\n\ttotal_amount: number\r\n\tdiscount_amount: number\r\n\tdelivery_fee: number\r\n\tactual_amount: number\r\n\tpayment_method: number | null\r\n\tpayment_status: number\r\n\tdelivery_address: UTSJSONObject\r\n\tcreated_at: string\r\n}\r\n\r\n// 订单商品类型\r\nexport type OrderItemType = {\r\n\tid: string\r\n\torder_id: string\r\n\tproduct_id: string\r\n\tsku_id: string\r\n\tproduct_name: string\r\n\tsku_specifications: UTSJSONObject | null\r\n\tprice: number\r\n\tquantity: number\r\n\ttotal_amount: number\r\n}\r\n\r\n// 配送员类型\r\nexport type DeliveryDriverType = {\r\n\tid: string\r\n\tuser_id: string\r\n\treal_name: string\r\n\tid_card: string\r\n\tdriver_license: string | null\r\n\tvehicle_type: number\r\n\tvehicle_number: string | null\r\n\twork_status: number\r\n\tcurrent_location: UTSJSONObject | null\r\n\tservice_areas: Array<string>\r\n\trating: number\r\n\ttotal_orders: number\r\n\tauth_status: number\r\n\tcreated_at: string\r\n\tupdated_at: string\r\n}\r\n\r\n// 配送任务类型\r\nexport type DeliveryTaskType = {\r\n\tid: string\r\n\torder_id: string\r\n\tdriver_id: string | null\r\n\tpickup_address: UTSJSONObject\r\n\tdelivery_address: UTSJSONObject\r\n\tdistance: number | null\r\n\testimated_time: number | null\r\n\tdelivery_fee: number\r\n\tstatus: number\r\n\tpickup_time: string | null\r\n\tdelivered_time: string | null\r\n\tdelivery_code: string | null\r\n\tremark: string | null\r\n\tcreated_at: string\r\n\tupdated_at: string\r\n}\r\n\r\n// 优惠券模板类型\r\nexport type CouponTemplateType = {\r\n\tid: string\r\n\tname: string\r\n\tdescription: string | null\r\n\tcoupon_type: number\r\n\tdiscount_type: number\r\n\tdiscount_value: number\r\n\tmin_order_amount: number\r\n\tmax_discount_amount: number | null\r\n\ttotal_quantity: number | null\r\n\tper_user_limit: number\r\n\tusage_limit: number\r\n\tmerchant_id: string | null\r\n\tcategory_ids: Array<string>\r\n\tproduct_ids: Array<string>\r\n\tuser_type_limit: number | null\r\n\tstart_time: string\r\n\tend_time: string\r\n\tstatus: number\r\n\tcreated_at: string\r\n}\r\n\r\n// 用户优惠券类型\r\nexport type UserCouponType = {\r\n\tid: string\r\n\tuser_id: string\r\n\ttemplate_id: string\r\n\tcoupon_code: string\r\n\tstatus: number\r\n\tused_at: string | null\r\n\torder_id: string | null\r\n\treceived_at: string\r\n\texpire_at: string\r\n}\r\n\r\n// 分页数据类型\r\nexport type PageDataType<T> = {\r\n\tdata: Array<T>\r\n\ttotal: number\r\n\tpage: number\r\n\tpageSize: number\r\n\thasMore: boolean\r\n}\r\n\r\n// API响应类型\r\nexport type ApiResponseType<T> = {\r\n\tsuccess: boolean\r\n\tdata: T | null\r\n\tmessage: string\r\n\tcode: number\r\n}\r\n\r\n// 订单状态枚举\r\nexport const ORDER_STATUS = {\r\n\tPENDING_PAYMENT: 1,\r\n\tPAID: 2,\r\n\tSHIPPED: 3,\r\n\tDELIVERED: 4,\r\n\tCOMPLETED: 5,\r\n\tCANCELLED: 6,\r\n\tREFUNDING: 7,\r\n\tREFUNDED: 8\r\n}\r\n\r\n// 优惠券类型枚举\r\nexport const COUPON_TYPE = {\r\n\tDISCOUNT_AMOUNT: 1, // 满减券\r\n\tDISCOUNT_PERCENT: 2, // 折扣券\r\n\tFREE_SHIPPING: 3, // 免运费券\r\n\tNEWBIE: 4, // 新人券\r\n\tMEMBER: 5, // 会员券\r\n\tCATEGORY: 6, // 品类券\r\n\tMERCHANT: 7, // 商家券\r\n\tLIMITED_TIME: 8 // 限时券\r\n}\r\n\r\n// 支付方式枚举\r\nexport const PAYMENT_METHOD = {\r\n\tWECHAT: 1,\r\n\tALIPAY: 2,\r\n\tUNIONPAY: 3,\r\n\tBALANCE: 4\r\n}\r\n\r\n// 配送状态枚举\r\nexport const DELIVERY_STATUS = {\r\n\tPENDING: 1,\r\n\tASSIGNED: 2,\r\n\tPICKED_UP: 3,\r\n\tIN_TRANSIT: 4,\r\n\tDELIVERED: 5,\r\n\tFAILED: 6\r\n}\r\n\r\n// 用户类型枚举\r\nexport const MALL_USER_TYPE = {\r\n\tCONSUMER: 1, // 消费者\r\n\tMERCHANT: 2, // 商家\r\n\tDELIVERY: 3, // 配送员\r\n\tSERVICE: 4, // 客服\r\n\tADMIN: 5 // 管理员\r\n}\r\n\r\n// 用户状态枚举\r\nexport const USER_STATUS = {\r\n\tNORMAL: 1, // 正常\r\n\tFROZEN: 2, // 冻结\r\n\tCANCELLED: 3, // 注销\r\n\tPENDING: 4 // 待审核\r\n} as const\r\n\r\n// 认证状态枚举\r\nexport const VERIFICATION_STATUS = {\r\n\tUNVERIFIED: 0, // 未认证\r\n\tVERIFIED: 1, // 已认证\r\n\tFAILED: 2 // 认证失败\r\n}\r\n\r\n// 地址标签枚举\r\nexport const ADDRESS_LABEL = {\r\n\tHOME: 'home', // 家\r\n\tOFFICE: 'office', // 公司\r\n\tSCHOOL: 'school', // 学校\r\n\tOTHER: 'other' // 其他\r\n}\r\n\r\n// 收藏类型枚举\r\nexport const FAVORITE_TYPE = {\r\n\tPRODUCT: 'product', // 商品\r\n\tSHOP: 'shop' // 店铺\r\n}\r\n\r\n// =========================\r\n// 订阅相关类型与枚举\r\n// =========================\r\n\r\n// 订阅周期枚举\r\nexport const SUBSCRIPTION_PERIOD = {\r\n\tMONTHLY: 'monthly',\r\n\tYEARLY: 'yearly'\r\n}\r\n\r\n// 订阅状态枚举\r\nexport const SUBSCRIPTION_STATUS = {\r\n\tTRIAL: 'trial',\r\n\tACTIVE: 'active',\r\n\tPAST_DUE: 'past_due',\r\n\tCANCELED: 'canceled',\r\n\tEXPIRED: 'expired'\r\n}\r\n\r\n// 软件订阅方案类型\r\nexport type SubscriptionPlanType = {\r\n\tid: string\r\n\tplan_code: string\r\n\tname: string\r\n\tdescription: string | null\r\n\tfeatures: UTSJSONObject | null // { featureKey: description }\r\n\tprice: number // 单位:元(或分,取决于后端;前端以显示为准)\r\n\tcurrency: string | null // 'CNY' | 'USD' ...\r\n\tbilling_period: string // 'monthly' | 'yearly'\r\n\ttrial_days: number | null\r\n\tis_active: boolean\r\n\tsort_order?: number | null\r\n\tcreated_at?: string\r\n\tupdated_at?: string\r\n}\r\n\r\n// 用户订阅记录类型\r\nexport type UserSubscriptionType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tplan_id: string\r\n\tstatus: string\r\n\tstart_date: string\r\n\tend_date: string | null\r\n\tnext_billing_date: string | null\r\n\tauto_renew: boolean\r\n\tcancel_at_period_end?: boolean | null\r\n\tmetadata?: UTSJSONObject | null\r\n\tcreated_at?: string\r\n\tupdated_at?: string\r\n}\r\n\r\n// 用户基础信息类型 (兼容 pages/user/types.uts)\r\nexport type UserProfile = {\r\n id?: string;\r\n username: string;\r\n email: string;\r\n gender?: string;\r\n birthday?: string;\r\n height_cm?: number;\r\n weight_kg?: number;\r\n bio?: string;\r\n avatar_url?: string;\r\n preferred_language?: string;\r\n role?: string;\r\n school_id?: string;\r\n grade_id?: string;\r\n class_id?: string;\r\n created_at?: string;\r\n updated_at?: string;\r\n}\r\n\r\nexport type UserStats = {\r\n trainings: number;\r\n points: number;\r\n streak: number;\r\n}\r\n\r\n// 足迹项类型\r\nexport type FootprintItemType = {\r\n id: string\r\n name: string\r\n price: number\r\n original_price: number | null\r\n image: string\r\n sales: number\r\n shopId: string\r\n shopName: string\r\n viewTime: number\r\n}\r\n\r\n// =========================\r\n// 积分相关类型\r\n// =========================\r\n\r\n// 签到记录类型\r\nexport type SigninRecordType = {\r\n id: string\r\n user_id: string\r\n signin_date: string\r\n points_earned: number\r\n bonus_points: number\r\n continuous_days: number\r\n created_at: string\r\n}\r\n\r\n// 签到结果类型\r\nexport type SigninResultType = {\r\n success: boolean\r\n points: number\r\n continuous_days: number\r\n bonus_points: number\r\n total_points: number\r\n message: string\r\n}\r\n\r\n// 积分兑换商品类型\r\nexport type PointProductType = {\r\n id: string\r\n name: string\r\n description: string | null\r\n image_url: string | null\r\n product_type: string\r\n points_required: number\r\n original_price: number | null\r\n stock: number\r\n status: number\r\n sort_order: number\r\n created_at: string\r\n}\r\n\r\n// 积分兑换记录类型\r\nexport type PointExchangeType = {\r\n id: string\r\n user_id: string\r\n product_id: string\r\n quantity: number\r\n points_used: number\r\n status: number\r\n tracking_no: string | null\r\n address_snapshot: UTSJSONObject | null\r\n created_at: string\r\n product: PointProductType | null\r\n}\r\n\r\n// 积分规则类型\r\nexport type PointRuleType = {\r\n id: string\r\n rule_type: string\r\n rule_name: string\r\n points: number\r\n description: string | null\r\n config: UTSJSONObject | null\r\n status: number\r\n}\r\n\r\n// 积分概览类型\r\nexport type PointsOverviewType = {\r\n current_points: number\r\n total_earned: number\r\n total_used: number\r\n expiring_points: number\r\n expiring_date: string | null\r\n}\r\n\r\n// =========================\r\n// 评价相关类型\r\n// =========================\r\n\r\n// 商品评价类型(扩展)\r\nexport type ProductReviewType = {\r\n id: string\r\n user_id: string\r\n product_id: string\r\n order_id: string\r\n order_item_id: string | null\r\n rating: number\r\n content: string | null\r\n images: string[]\r\n videos: string[]\r\n tags: string[]\r\n is_anonymous: boolean\r\n like_count: number\r\n is_edited: boolean\r\n append_content: string | null\r\n append_at: string | null\r\n append_images: string[]\r\n reply: string | null\r\n reply_time: string | null\r\n created_at: string\r\n updated_at: string\r\n user_name: string | null\r\n user_avatar: string | null\r\n is_liked: boolean\r\n}\r\n\r\n// 评价统计类型\r\nexport type ReviewStatsType = {\r\n total_count: number\r\n avg_rating: number\r\n good_rate: number\r\n rating_distribution: Map<string, number>\r\n tags: ReviewTagType[]\r\n}\r\n\r\n// 评价标签类型\r\nexport type ReviewTagType = {\r\n name: string\r\n count: number\r\n}\r\n\r\n// 评价点赞类型\r\nexport type ReviewLikeType = {\r\n id: string\r\n review_id: string\r\n user_id: string\r\n created_at: string\r\n}\r\n\r\n// 评价举报类型\r\nexport type ReviewReportType = {\r\n id: string\r\n review_id: string\r\n user_id: string\r\n reason: string\r\n description: string | null\r\n status: number\r\n handle_result: string | null\r\n created_at: string\r\n}\r\n\r\n// 配送员评价类型\r\nexport type DeliveryRatingType = {\r\n id: string\r\n order_id: string\r\n delivery_user_id: string\r\n user_id: string\r\n rating: number\r\n content: string | null\r\n created_at: string\r\n}\r\n\r\n// 我的评价列表项类型\r\nexport type MyReviewItemType = {\r\n id: string\r\n product_id: string\r\n product_name: string\r\n product_image: string\r\n rating: number\r\n content: string | null\r\n images: string[]\r\n created_at: string\r\n can_append: boolean\r\n can_edit: boolean\r\n}\r\n\r\n// =========================\r\n// 推销模式相关类型\r\n// =========================\r\n\r\n// 用户余额类型\r\nexport type UserBalanceType = {\r\n id: string\r\n user_id: string\r\n balance: number\r\n frozen_balance: number\r\n total_earned: number\r\n total_withdrawn: number\r\n updated_at: string\r\n}\r\n\r\n// 余额变动记录类型\r\nexport type BalanceRecordType = {\r\n id: string\r\n user_id: string\r\n type: string\r\n amount: number\r\n balance_before: number\r\n balance_after: number\r\n related_id: string | null\r\n description: string | null\r\n operator_id: string | null\r\n created_at: string\r\n}\r\n\r\n// 分享记录类型\r\nexport type ShareRecordType = {\r\n id: string\r\n user_id: string\r\n product_id: string\r\n order_id: string\r\n order_item_id: string | null\r\n share_code: string\r\n product_name: string\r\n product_image: string | null\r\n product_price: number\r\n required_count: number\r\n current_count: number\r\n status: number\r\n reward_amount: number | null\r\n created_at: string\r\n completed_at: string | null\r\n expired_at: string | null\r\n}\r\n\r\n// 二级购买记录类型\r\nexport type SecondaryPurchaseType = {\r\n id: string\r\n share_record_id: string\r\n buyer_id: string\r\n order_id: string\r\n quantity: number\r\n unit_price: number\r\n created_at: string\r\n}\r\n\r\n// 免单奖励记录类型\r\nexport type FreeOrderRewardType = {\r\n id: string\r\n user_id: string\r\n share_record_id: string\r\n amount: number\r\n status: number\r\n balance_record_id: string | null\r\n cleared_at: string | null\r\n cleared_by: string | null\r\n created_at: string\r\n}\r\n\r\n// 会员等级类型\r\nexport type MemberLevelType = {\r\n id: number\r\n name: string\r\n min_amount: number\r\n discount: number\r\n icon: string | null\r\n description: string | null\r\n sort_order: number\r\n status: number\r\n}\r\n\r\n// 用户会员信息类型\r\nexport type UserMemberInfoType = {\r\n member_level: number\r\n level_name: string\r\n discount: number\r\n total_spent: number\r\n next_level: MemberLevelType | null\r\n progress_percent: number\r\n manual_level: boolean\r\n}\r\n\r\n// 会员等级变更记录类型\r\nexport type MemberLevelLogType = {\r\n id: string\r\n user_id: string\r\n old_level: number\r\n new_level: number\r\n reason: string | null\r\n operator_id: string | null\r\n created_at: string\r\n}\r\n","// 设备信息类型\r\nexport type DeviceInfo = {\r\n\tid: string\r\n\tdevice_name?: string\r\n\tstatus?: string // 'online' | 'offline' | 其他状态\r\n\tuser_id?: string\r\n\t// 可根据实际需求添加更多字段\r\n}\r\n\r\n// 设备查询参数类型\r\nexport type DeviceParams = {\r\n\tuser_id: string\r\n\t// 可根据实际需求添加更多查询参数\r\n}\r\n","import supabase, { supaReady } from '@/components/supadb/aksupainstance.uts'\r\nimport type { UserProfile } from '@/types/mall-types.uts'\r\n\r\n/**\r\n * 确保用户资料存在,如果不存在则创建基础资料\r\n * @param sessionUser 会话用户对象 (UTSJSONObject)\r\n * @returns 创建的用户资料,如果创建失败则返回 null\r\n */\r\nexport async function ensureUserProfile(sessionUser: UTSJSONObject): Promise<UserProfile | null> {\r\n\ttry {\r\n\t\tawait supaReady\r\n \r\n\t\t// 从 sessionUser 中获取用户ID和邮箱\r\n\t\tconst userId = sessionUser.getString('id')\r\n\t\tconst email = sessionUser.getString('email') ?? ''\r\n\t\t\r\n\t\tif (userId == null || userId === '') {\r\n\t\t\t__f__('error','at utils/sapi.uts:18','无法获取用户ID')\r\n\t\t\treturn null\r\n\t\t}\r\n\t\t\r\n\t\t// 检查用户是否已存在ak_users 通过 id 或 auth_id 关联 auth.users.id\r\n\t\tconst checkRes = await supabase.from('ak_users')\r\n\t\t\t.select('*', {})\r\n\t\t\t.or('id.eq.' + userId + ',auth_id.eq.' + userId)\r\n\t\t\t.execute()\r\n\t\t\r\n\t\t__f__('log','at utils/sapi.uts:28','ensureUserProfile check ak_users:', {\r\n\t\t\tstatus: checkRes.status,\r\n\t\t\thasData: checkRes.data != null\r\n\t\t})\r\n\t\t\r\n\t\tconst dataList = checkRes.data\r\n\t\tif (checkRes.status >= 200 && checkRes.status < 300 && dataList != null && (dataList as any[]).length > 0) {\r\n\t\t\t// 用户已存在返回现有资料H5 下 checkRes.data 可能是 plain object不一定是 UTSJSONObject\r\n\t\t\tlet existingUser: UTSJSONObject\r\n\t\t\tconst firstItem = (dataList as any[])[0]\r\n\t\t\tif (firstItem instanceof UTSJSONObject) {\r\n\t\t\t\texistingUser = firstItem\r\n\t\t\t} else {\r\n\t\t\t\texistingUser = new UTSJSONObject(firstItem)\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tconst currentRole = existingUser.getString('role')\r\n\t\t\tconst currentAuthId = existingUser.getString('auth_id')\r\n\t\t\t\r\n\t\t\t// 【强力修复逻辑】如果 role 是 student 或者 auth_id 为空,进行 upsert 修复\r\n\t\t\tif (currentRole == 'student' || currentAuthId == null || currentAuthId == '') {\r\n\t\t\t\t__f__('log','at utils/sapi.uts:49','检测到旧数据异常正在修复角色或关联ID...')\r\n\t\t\t\tconst updateData = new UTSJSONObject()\r\n\t\t\t\t// updateData.set('id', userId) // update 场景不需要传 ID 在 body 里,通常作为 filter\r\n\t\t\t\tupdateData.set('auth_id', userId)\r\n\t\t\t\tupdateData.set('role', 'consumer')\r\n\t\t\t\t\r\n\t\t\t\tawait supabase.from('ak_users')\r\n\t\t\t\t.update(updateData)\r\n\t\t\t\t.eq('id', userId)\r\n\t\t\t\t.execute()\r\n\t\t\t\t\r\n\t\t\t\t// 同步 Auth 元数据\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst meta = new UTSJSONObject()\r\n\t\t\t\t\tmeta.set('user_role', 'consumer')\r\n\t\t\t\t\tawait supabase.updateUserMetadata(meta)\r\n\t\t\t\t} catch (e) {\r\n\t\t\t\t\t__f__('warn','at utils/sapi.uts:66','同步 Auth 元数据失败:', e)\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// 返回修复后的对象\r\n\t\t\t\treturn {\r\n\t\t\t\t\tid: userId,\r\n\t\t\t\t\tusername: existingUser.getString('username') ?? email.split('@')[0],\r\n\t\t\t\t\temail: existingUser.getString('email') ?? email,\r\n\t\t\t\t\trole: 'consumer'\r\n\t\t\t\t} as UserProfile\r\n\t\t\t}\r\n\r\n\t\t\treturn {\r\n\t\t\t\tid: userId, // 始终返回 auth.users.id 作为 Profile ID\r\n\t\t\t\tusername: existingUser.getString('username') ?? '',\r\n\t\t\t\temail: existingUser.getString('email') ?? email,\r\n\t\t\t\tgender: existingUser.getString('gender'),\r\n\t\t\t\tbirthday: existingUser.getString('birthday'),\r\n\t\t\t\theight_cm: existingUser.getNumber('height_cm'),\r\n\t\t\t\tweight_kg: existingUser.getNumber('weight_kg'),\r\n\t\t\t\tbio: existingUser.getString('bio'),\r\n\t\t\t\tavatar_url: existingUser.getString('avatar_url'),\r\n\t\t\t\tpreferred_language: existingUser.getString('preferred_language'),\r\n\t\t\t\trole: existingUser.getString('role') ?? 'consumer',\r\n\t\t\t\tcreated_at: existingUser.getString('created_at'),\r\n\t\t\t\tupdated_at: existingUser.getString('updated_at')\r\n\t\t\t} as UserProfile\r\n\t\t}\r\n\t\t\r\n\t\t// 用户不存在,创建新用户资料\r\n\t\tconst newUserData = new UTSJSONObject()\r\n\t\tnewUserData.set('id', userId)\r\n\t\tnewUserData.set('auth_id', userId) // 确保 auth_id 被存储,解决登录查不到 Profile 的问题\r\n\t\tnewUserData.set('email', email)\r\n\t\tnewUserData.set('username', email.split('@')[0] ?? 'user') // 默认用户名为邮箱前缀\r\n\t\tnewUserData.set('role', 'consumer') // 强制设置为消费者角色\r\n\t\tnewUserData.set('created_at', new Date().toISOString()) // 手动设置创建时间\r\n\t\t\r\n\t\t// 尝试同步更新 Auth 元数据,确保以后登录生成的 JWT 中角色也为 consumer\r\n\t\ttry {\r\n\t\t\tconst meta = new UTSJSONObject()\r\n\t\t\tmeta.set('user_role', 'consumer')\r\n\t\t\tawait supabase.updateUserMetadata(meta)\r\n\t\t} catch (e) {\r\n\t\t\t__f__('warn','at utils/sapi.uts:110','同步 Auth 元数据失败 (非致命):', e)\r\n\t\t}\r\n\r\n\t\t__f__('log','at utils/sapi.uts:113','准备插入 ak_users, 目标 ID:', userId)\r\n\t\t__f__('log','at utils/sapi.uts:114','正在执行 insert 资料:', JSON.stringify(newUserData))\r\n\t\tconst insertRes = await supabase.from('ak_users')\r\n\t\t\t.insert(newUserData)\r\n\t\t\t.select('*', {})\r\n\t\t\t.execute()\r\n\t\t\r\n\t\t__f__('log','at utils/sapi.uts:120','ensureUserProfile insert ak_users 完整结果:', JSON.stringify(insertRes))\r\n\t\t\r\n\t\tif (insertRes.status >= 200 && insertRes.status < 300) {\r\n\t\t\tconst dataList = (insertRes.data != null) ? (insertRes.data as any[]) : []\r\n\t\t\tconst rawData = dataList.length > 0 ? dataList[0] : null\r\n\t\t\t\r\n\t\t\tif (rawData == null) {\r\n\t\t\t\t__f__('log','at utils/sapi.uts:127','ensureUserProfile: 资料插入操作已完成(200),但未返回数据(可能是 RLS 限制)');\r\n\t\t\t\treturn {\r\n\t\t\t\t\tid: userId,\r\n\t\t\t\t\tusername: email.split('@')[0] ?? 'user',\r\n\t\t\t\t\temail: email,\r\n\t\t\t\t\trole: 'consumer',\r\n\t\t\t\t\tcreated_at: newUserData.getString('created_at')\r\n\t\t\t\t} as UserProfile\r\n\t\t\t}\r\n\r\n\t\t\tconst newUser = (rawData instanceof UTSJSONObject)\r\n\t\t\t\t? (rawData as UTSJSONObject)\r\n\t\t\t\t: new UTSJSONObject(rawData)\r\n\t\t\treturn {\r\n\t\t\t\tid: newUser.getString('id') ?? userId,\r\n\t\t\t\tusername: newUser.getString('username') ?? email.split('@')[0],\r\n\t\t\t\temail: newUser.getString('email') ?? email,\r\n\t\t\t\tgender: newUser.getString('gender'),\r\n\t\t\t\tbirthday: newUser.getString('birthday'),\r\n\t\t\t\theight_cm: newUser.getNumber('height_cm'),\r\n\t\t\t\tweight_kg: newUser.getNumber('weight_kg'),\r\n\t\t\t\tbio: newUser.getString('bio'),\r\n\t\t\t\tavatar_url: newUser.getString('avatar_url'),\r\n\t\t\t\tpreferred_language: newUser.getString('preferred_language'),\r\n\t\t\t\trole: newUser.getString('role') ?? 'consumer',\r\n\t\t\t\tcreated_at: newUser.getString('created_at'),\r\n\t\t\t\tupdated_at: newUser.getString('updated_at')\r\n\t\t\t} as UserProfile\r\n\t\t} else {\r\n\t\t\t__f__('error','at utils/sapi.uts:156','创建用户资料失败:', insertRes.status)\r\n\t\t\treturn null\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('error','at utils/sapi.uts:160','ensureUserProfile 异常:', error)\r\n\t\treturn null\r\n\t}\r\n}\r\n","import supa, { supaReady } from '@/components/supadb/aksupainstance.uts'\r\nimport type { UserProfile, UserStats } from '@/types/mall-types.uts'\r\nimport type { DeviceInfo } from '@/pages/sense/types.uts'\r\nimport { SenseDataService, type DeviceParams } from '@/pages/sense/senseDataService.uts'\r\nimport { reactive } from 'vue'\r\nimport { ensureUserProfile } from './sapi.uts'\r\n\r\n// 设备状态类型\r\nexport type DeviceState = {\r\n\tdevices : Array<DeviceInfo>\r\n\tcurrentDevice : DeviceInfo | null\r\n\tisLoading : boolean\r\n\tlastUpdated : number | null\r\n}\r\n\r\n//定义一个大写的State类型\r\nexport type State = {\r\n\tglobalNum : number\r\n\tuserProfile ?: UserProfile\r\n\tisLoggedIn : boolean // 新增字段\r\n\tdeviceState : DeviceState // 新增设备状态\r\n\t// 如有需要,可增加更多属性\r\n}\r\n\r\n// 实例化为state\r\nexport const state = reactive({\r\n\tglobalNum: 0,\r\n\tuserProfile: { username: '', email: '' },\r\n\tisLoggedIn: false,\r\n\tdeviceState: {\r\n\t\tdevices: [],\r\n\t\tcurrentDevice: null,\r\n\t\tisLoading: false,\r\n\t\tlastUpdated: null\r\n\t} as DeviceState\r\n} as State)\r\n// 定义修改属性值的方法\r\nexport const setGlobalNum = (num : number) => {\r\n\tstate.globalNum = num\r\n}\r\n// 新增:设置登录状态的方法\r\nexport const setIsLoggedIn = (val : boolean) => {\r\n\tstate.isLoggedIn = val\r\n}\r\n// 定义全局设置用户信息的方法\r\nexport const setUserProfile = (profile : UserProfile) => {\r\n\tstate.userProfile = profile\r\n}\r\n\r\n// 获取当前用户信息(含补全 profile\r\nexport async function getCurrentUser() : Promise<UserProfile | null> {\r\n\ttry {\r\n\t\tawait supaReady\r\n\t} catch (_) {}\r\n\r\n\tconst sessionInfo = supa.getSession()\r\n\tif (sessionInfo.user == null) {\r\n\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\tstate.isLoggedIn = false // 未登录\r\n\t\treturn null\r\n\t}\r\n\tconst userId = sessionInfo.user?.getString(\"id\")\r\n\tif (userId == null) {\r\n\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\tstate.isLoggedIn = false // 未登录\r\n\t\treturn null\r\n\t}\t// 查询 ak_users 表补全 profile\r\n\tconst res = await supa.from('ak_users').select('*', {}).eq('id', userId).execute()\r\n\t__f__('log','at utils/store.uts:69',res)\r\n\tif (res.status >= 200 && res.status < 300 && (res.data != null)) {\r\n\t\tlet user : UTSJSONObject | null = null;\r\n\t\tconst data = res.data as any;\r\n\t\tif (Array.isArray(data)) {\r\n\t\t\tif (data.length > 0) {\r\n\t\t\t\tuser = data[0] as UTSJSONObject;\r\n\t\t\t}\r\n\t\t} else if (data != null) {\r\n\t\t\tuser = data as UTSJSONObject;\r\n\t\t} __f__('log','at utils/store.uts:79',user)\r\n\t\tif (user == null) {\r\n\t\t\t__f__('log','at utils/store.uts:81','用户资料为空,尝试创建基础资料...')\t\t\t// 如果用户资料为空,尝试创建基础用户资料\r\n\t\t\tconst sessionUser = sessionInfo.user\r\n\t\t\tif (sessionUser != null) {\r\n\t\t\t\tconst createdProfile = await ensureUserProfile(sessionUser)\r\n\t\t\t\tif (createdProfile != null) {\r\n\t\t\t\t\tstate.userProfile = createdProfile\r\n\t\t\t\t\tstate.isLoggedIn = true\r\n\t\t\t\t\treturn createdProfile\r\n\t\t\t\t} else {\r\n\t\t\t\t\t__f__('error','at utils/store.uts:90','创建用户资料失败')\r\n\t\t\t\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\t\t\t\tstate.isLoggedIn = false\r\n\t\t\t\t\treturn null\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t__f__('error','at utils/store.uts:96','会话用户信息为空')\r\n\t\t\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\t\t\tstate.isLoggedIn = false\r\n\t\t\t\treturn null\r\n\t\t\t}\r\n\t\t}\r\n\t\t__f__('log','at utils/store.uts:102',user)\r\n\t\t// 直接用 getString/getNumber无需兜底属性\t\t\r\n\t\tconst profile : UserProfile = {\r\n\t\t\tid: user.getString('id'),\r\n\t\t\tusername: user.getString('username') ?? \"\",\r\n\t\t\temail: user.getString('email') ?? \"\",\r\n\t\t\tgender: user.getString('gender'),\r\n\t\t\tbirthday: user.getString('birthday'),\r\n\t\t\theight_cm: user.getNumber('height_cm'),\r\n\t\t\tweight_kg: user.getNumber('weight_kg'),\r\n\t\t\tbio: user.getString('bio'),\r\n\t\t\tavatar_url: user.getString('avatar_url'),\r\n\t\t\tpreferred_language: user.getString('preferred_language'),\r\n\t\t\trole: user.getString('role'),\r\n\t\t\tschool_id: user.getString('school_id'),\r\n\t\t\tgrade_id: user.getString('grade_id'),\r\n\t\t\tclass_id: user.getString('class_id')\r\n\t\t}\r\n\t\tstate.userProfile = profile\r\n\t\tstate.isLoggedIn = true // 登录成功\r\n\t\treturn profile\r\n\t} else {\r\n\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\tstate.isLoggedIn = false // 未登录\r\n\t\treturn null\r\n\t}\r\n}\r\n\r\n// 登出并清空用户信息\r\nexport function logout() {\r\n\tsupa.signOut()\r\n\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\tstate.isLoggedIn = false // 登出\r\n}\r\n\r\n// 获取当前用户ID优先级state.userProfile.id > session > localStorage\r\nexport function getCurrentUserId() : string {\r\n\ttry {\r\n\t\tconst profile = state.userProfile\r\n\t\tif (profile != null && profile.id != null) {\r\n\t\t\tconst profileId = profile.id\r\n\t\t\tif (profileId != null) {\r\n\t\t\t\treturn profileId\r\n\t\t\t}\r\n\t\t}\r\n\t} catch (e) { }\r\n\ttry {\r\n\t\tconst session = supa.getSession()\r\n\t\tif (session != null) {\r\n\t\t\tconst curuser = session.user\r\n\t\t\tconst userId = curuser?.getString('id')\r\n\t\t\tif (userId != null) return userId\r\n\t\t}\r\n\t} catch (e) { }\r\n\treturn ''\r\n}\r\n\r\n// 获取当前用户的class_id\r\nexport function getCurrentUserClassId() : string | null {\r\n\ttry {\r\n\t\tconst profile = state.userProfile\r\n\t\tif (profile != null && profile.class_id != null) {\r\n\t\t\treturn profile.class_id\r\n\t\t}\r\n\t} catch (e) {\r\n\t\t__f__('error','at utils/store.uts:167','获取用户class_id失败:', e)\r\n\t}\r\n\treturn null\r\n}\r\n\r\n// User store API for component compatibility\r\nexport function getUserStore() {\r\n\treturn {\r\n\t\tgetUserId() : string | null {\r\n\t\t\tconst sessionInfo = supa.getSession()\r\n\t\t\treturn sessionInfo.user?.getString(\"id\") ?? null\r\n\t\t},\r\n\r\n\t\tgetUserName() : string | null {\r\n\t\t\treturn state.userProfile?.username ?? null\r\n\t\t},\r\n\r\n\t\tgetUserRole() : string | null {\r\n\t\t\t// Default role logic - can be enhanced based on your needs\r\n\t\t\tconst sessionInfo = supa.getSession()\r\n\t\t\tif (sessionInfo.user == null) return null\r\n\r\n\t\t\t// You can add role detection logic here\r\n\t\t\t// For now, return a default role\r\n\t\t\treturn 'teacher' // or determine from user profile/database\r\n\t\t},\r\n\r\n\t\tgetProfile() : UserProfile | null {\r\n\t\t\treturn state.userProfile\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// ========== 设备状态管理方法 ==========\r\n\r\n/**\r\n * 设置设备加载状态\r\n */\r\nexport const setDeviceLoading = (loading : boolean) => {\r\n\tstate.deviceState.isLoading = loading\r\n}\r\n\r\n/**\r\n * 设置设备列表\r\n */\r\nexport const setDevices = (devices : Array<DeviceInfo>) => {\r\n\tstate.deviceState.devices = devices\r\n\tstate.deviceState.lastUpdated = Date.now()\r\n}\r\n\r\n/**\r\n * 添加设备到列表\r\n */\r\nexport const addDevice = (device : DeviceInfo) => {\r\n\tconst existingIndex = state.deviceState.devices.findIndex(d => d.id === device.id)\r\n\tif (existingIndex >= 0) {\r\n\t\t// 更新现有设备\r\n\t\tstate.deviceState.devices[existingIndex] = device\r\n\t} else {\r\n\t\t// 添加新设备\r\n\t\tstate.deviceState.devices.push(device)\r\n\t}\r\n\tstate.deviceState.lastUpdated = Date.now()\r\n}\r\n\r\n/**\r\n * 从列表中移除设备\r\n */\r\nexport const removeDevice = (deviceId : string) => {\r\n\tconst index = state.deviceState.devices.findIndex(d => d.id === deviceId)\r\n\tif (index >= 0) {\r\n\t\tstate.deviceState.devices.splice(index, 1)\r\n\t\t// 如果移除的是当前设备,清空当前设备\r\n\t\tif (state.deviceState.currentDevice?.id === deviceId) {\r\n\t\t\tstate.deviceState.currentDevice = null\r\n\t\t}\r\n\t\tstate.deviceState.lastUpdated = Date.now()\r\n\t}\r\n}\r\n\r\n/**\r\n * 更新设备信息\r\n */\r\nexport const updateDevice = (device : DeviceInfo) => {\r\n\tconst index = state.deviceState.devices.findIndex(d => d.id === device.id)\r\n\tif (index >= 0) {\r\n\t\tstate.deviceState.devices[index] = device\r\n\t\t// 如果更新的是当前设备,也更新当前设备\r\n\t\tif (state.deviceState.currentDevice?.id === device.id) {\r\n\t\t\tstate.deviceState.currentDevice = device\r\n\t\t}\r\n\t\tstate.deviceState.lastUpdated = Date.now()\r\n\t}\r\n}\r\n\r\n/**\r\n * 设置当前选中的设备\r\n */\r\nexport const setCurrentDevice = (device : DeviceInfo | null) => {\r\n\tstate.deviceState.currentDevice = device\r\n}\r\n\r\n/**\r\n * 根据设备ID获取设备信息\r\n */\r\nexport const getDeviceById = (deviceId : string) : DeviceInfo | null => {\r\n\treturn state.deviceState.devices.find(d => d.id === deviceId) ?? null\r\n}\r\n\r\n/**\r\n * 获取在线设备列表\r\n */\r\nexport const getOnlineDevices = () : Array<DeviceInfo> => {\r\n\treturn state.deviceState.devices.filter(d => d.status === 'online')\r\n}\r\n\r\n/**\r\n * 从服务器加载设备列表\r\n */\r\nexport const loadDevices = async (forceRefresh : boolean) : Promise<boolean> => {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId == null || userId === '') {\r\n\t\t__f__('log','at utils/store.uts:289','用户未登录,无法加载设备列表')\r\n\t\treturn false\r\n\t}\r\n\r\n\t// 如果不是强制刷新且数据较新5分钟内直接返回\r\n\tconst now = Date.now()\r\n\tconst lastUpdated = state.deviceState.lastUpdated\r\n\tif (forceRefresh == false && lastUpdated != null && (now - lastUpdated < 5 * 60 * 1000)) {\r\n\t\t__f__('log','at utils/store.uts:297','设备数据较新,跳过刷新')\r\n\t\treturn true\r\n\t}\r\n\tsetDeviceLoading(true)\r\n\ttry {\r\n\t\tconst result = await SenseDataService.getDevices({ user_id: userId })\r\n\t\tif (result.error === null && result.data != null) {\r\n\t\t\tconst devices = result.data as Array<DeviceInfo>\r\n\t\t\tsetDevices(devices)\r\n\t\t\t__f__('log','at utils/store.uts:306',`加载设备列表成功,共${devices.length}个设备`)\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\t__f__('log','at utils/store.uts:309','加载设备列表失败:', result.error?.message ?? '未知错误')\r\n\t\t\treturn false\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('log','at utils/store.uts:313','加载设备列表异常:', error)\r\n\t\treturn false\r\n\t} finally {\r\n\t\tsetDeviceLoading(false)\r\n\t}\r\n}\r\n\r\n/**\r\n * 从服务器加载设备列表 - 带默认参数的重载版本\r\n */\r\nexport const loadDevicesWithDefault = async () : Promise<boolean> => {\r\n\treturn await loadDevices(false)\r\n}\r\n\r\n/**\r\n * 绑定新设备\r\n */\r\nexport const bindNewDevice = async (deviceData : UTSJSONObject) : Promise<boolean> => {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId == null) {\r\n\t\t__f__('log','at utils/store.uts:333','用户未登录,无法绑定设备')\r\n\t\treturn false\r\n\t}\r\n\r\n\t// 确保设备数据中包含用户ID\r\n\tdeviceData.set('user_id', userId)\r\n\ttry {\r\n\t\tconst result = await SenseDataService.bindDevice(deviceData)\r\n\t\tif (result.error === null && result.data != null) {\r\n\t\t\t// 添加到本地状态\r\n\t\t\taddDevice(result.data as DeviceInfo)\r\n\t\t\tconst deviceName = (result.data as DeviceInfo).device_name ?? '未知设备'\r\n\t\t\t__f__('log','at utils/store.uts:345','设备绑定成功:', deviceName)\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\t__f__('log','at utils/store.uts:348','设备绑定失败:', result.error?.message ?? '未知错误')\r\n\t\t\treturn false\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('log','at utils/store.uts:352','设备绑定异常:', error)\r\n\t\treturn false\r\n\t}\r\n}\r\n\r\n/**\r\n * 解绑设备\r\n */\r\nexport const unbindDevice = async (deviceId : string) : Promise<boolean> => {\r\n\ttry {\r\n\t\tconst result = await SenseDataService.unbindDevice(deviceId)\r\n\t\tif (result.error === null) {\r\n\t\t\t// 从本地状态中移除\r\n\t\t\tremoveDevice(deviceId)\r\n\t\t\t__f__('log','at utils/store.uts:366','设备解绑成功')\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\t__f__('log','at utils/store.uts:369','设备解绑失败:', result.error?.message ?? '未知错误')\r\n\t\t\treturn false\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('log','at utils/store.uts:373','设备解绑异常:', error)\r\n\t\treturn false\r\n\t}\r\n}\r\n\r\n/**\r\n * 更新设备配置\r\n */\r\nexport const updateDeviceConfig = async (deviceId : string, configData : UTSJSONObject) : Promise<boolean> => {\r\n\ttry {\r\n\t\tconst result = await SenseDataService.updateDevice(deviceId, configData)\r\n\t\tif (result.error === null && result.data != null) {\r\n\t\t\t// 更新本地状态\r\n\t\t\tupdateDevice(result.data as DeviceInfo)\r\n\t\t\t__f__('log','at utils/store.uts:387','设备配置更新成功')\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\t__f__('log','at utils/store.uts:390','设备配置更新失败:', result.error?.message ?? '未知错误')\r\n\t\t\treturn false\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('log','at utils/store.uts:394','设备配置更新异常:', error)\r\n\t\treturn false\r\n\t}\r\n}\r\n\r\n// ========== 设备管理 API ==========\r\n\r\n/**\r\n * 获取设备管理相关的API\r\n */\r\nexport function getDeviceStore() {\r\n\treturn {\r\n\t\t// 获取设备状态\r\n\t\tgetDevices() : Array<DeviceInfo> {\r\n\t\t\treturn state.deviceState.devices\r\n\t\t},\r\n\r\n\t\tgetCurrentDevice() : DeviceInfo | null {\r\n\t\t\treturn state.deviceState.currentDevice\r\n\t\t},\r\n\r\n\t\tisLoading() : boolean {\r\n\t\t\treturn state.deviceState.isLoading\r\n\t\t},\r\n\t\tgetLastUpdated() : number | null {\r\n\t\t\treturn state.deviceState.lastUpdated\r\n\t\t},\r\n\r\n\t\t// 设备操作方法\r\n\t\tasync loadDevices(forceRefresh : boolean) : Promise<boolean> {\r\n\t\t\treturn await loadDevices(forceRefresh)\r\n\t\t},\r\n\r\n\t\tasync refreshDevices() : Promise<boolean> {\r\n\t\t\treturn await loadDevicesWithDefault()\r\n\t\t},\r\n\r\n\t\tasync bindDevice(deviceData : UTSJSONObject) : Promise<boolean> {\r\n\t\t\treturn await bindNewDevice(deviceData)\r\n\t\t},\r\n\r\n\t\tasync unbindDevice(deviceId : string) : Promise<boolean> {\r\n\t\t\treturn await unbindDevice(deviceId)\r\n\t\t},\r\n\r\n\t\tasync updateDevice(deviceId : string, configData : UTSJSONObject) : Promise<boolean> {\r\n\t\t\treturn await updateDeviceConfig(deviceId, configData)\r\n\t\t},\r\n\r\n\t\t// 设备查询方法\r\n\t\tgetDeviceById(deviceId : string) : DeviceInfo | null {\r\n\t\t\treturn getDeviceById(deviceId)\r\n\t\t},\r\n\r\n\t\tgetOnlineDevices() : Array<DeviceInfo> {\r\n\t\t\treturn getOnlineDevices()\r\n\t\t},\r\n\r\n\t\t// 设备选择\r\n\t\tsetCurrentDevice(device : DeviceInfo | null) {\r\n\t\t\tsetCurrentDevice(device)\r\n\t\t}\r\n\t}\r\n}","import 'D:/HBuilderX/HBuilderX/plugins/uniapp-cli-vite/node_modules/@dcloudio/uni-console/src/runtime/app/index.ts';// 简化的main.uts移除i18n依赖\r\nimport { createSSRApp } from 'vue'\r\nimport App from './App.uvue'\r\nimport i18n from '@/uni_modules/i18n/index.uts'\r\n\r\nexport function createApp() {\r\n const app = createSSRApp(App)\r\n \r\n // 注册 i18n 全局属性,使组件可以使用 $t 方法\r\n\tapp.config.globalProperties.$t = (key: string, values?: any, locale?: string): string => {\r\n\t\tif (i18n.global == null) {\r\n\t\t\t__f__('error','at main.uts:12','i18n is not initialized')\r\n\t\t\treturn key\r\n\t\t}\r\n const params = values as UTSJSONObject | null\r\n\t\tconst res = i18n.global.t(key, params, locale)\r\n if (res.length > 0) {\r\n return res\r\n }\r\n return key\r\n\t}\r\n \r\n return { app }\r\n}\r\n\nexport function main(app: IApp) {\n definePageRoutes();\n defineAppConfig();\n (createApp()['app'] as VueApp).mount(app, GenUniApp());\n}\n\nexport class UniAppConfig extends io.dcloud.uniapp.appframe.AppConfig {\n override name: string = \"商城消费者端\"\n override appid: string = \"__UNI__CONSUMER\"\n override versionName: string = \"1.0.0\"\n override versionCode: string = \"100\"\n override uniCompilerVersion: string = \"4.87\"\n \n constructor() { super() }\n}\n\nimport GenPagesUserLoginClass from './pages/user/login.uvue'\nimport GenPagesMainIndexClass from './pages/main/index.uvue'\nimport GenPagesMainCategoryClass from './pages/main/category.uvue'\nimport GenPagesMainCartClass from './pages/main/cart.uvue'\nimport GenPagesMainProfileClass from './pages/main/profile.uvue'\nimport GenPagesMallConsumerSettingsClass from './pages/mall/consumer/settings.uvue'\nimport GenPagesMallConsumerWalletClass from './pages/mall/consumer/wallet.uvue'\nimport GenPagesMallConsumerWithdrawClass from './pages/mall/consumer/withdraw.uvue'\nimport GenPagesMallConsumerSearchClass from './pages/mall/consumer/search.uvue'\nimport GenPagesMallConsumerProductDetailClass from './pages/mall/consumer/product-detail.uvue'\nimport GenPagesMallConsumerShopDetailClass from './pages/mall/consumer/shop-detail.uvue'\nimport GenPagesMallConsumerCouponsClass from './pages/mall/consumer/coupons.uvue'\nimport GenPagesMallConsumerFavoritesClass from './pages/mall/consumer/favorites.uvue'\nimport GenPagesMallConsumerFootprintClass from './pages/mall/consumer/footprint.uvue'\nimport GenPagesMallConsumerAddressListClass from './pages/mall/consumer/address-list.uvue'\nimport GenPagesMallConsumerAddressEditClass from './pages/mall/consumer/address-edit.uvue'\nimport GenPagesMallConsumerCheckoutClass from './pages/mall/consumer/checkout.uvue'\nimport GenPagesMallConsumerPaymentClass from './pages/mall/consumer/payment.uvue'\nimport GenPagesMallConsumerPaymentSuccessClass from './pages/mall/consumer/payment-success.uvue'\nimport GenPagesMallConsumerOrdersClass from './pages/mall/consumer/orders.uvue'\nimport GenPagesMallConsumerOrderDetailClass from './pages/mall/consumer/order-detail.uvue'\nimport GenPagesMallConsumerLogisticsClass from './pages/mall/consumer/logistics.uvue'\nimport GenPagesMallConsumerReviewClass from './pages/mall/consumer/review.uvue'\nimport GenPagesMallConsumerRefundClass from './pages/mall/consumer/refund.uvue'\nimport GenPagesMallConsumerApplyRefundClass from './pages/mall/consumer/apply-refund.uvue'\nimport GenPagesMallConsumerRefundReviewClass from './pages/mall/consumer/refund-review.uvue'\nimport GenPagesMallConsumerChatClass from './pages/mall/consumer/chat.uvue'\nimport GenPagesMallConsumerSubscriptionFollowedShopsClass from './pages/mall/consumer/subscription/followed-shops.uvue'\nimport GenPagesMallConsumerPointsIndexClass from './pages/mall/consumer/points/index.uvue'\nimport GenPagesMallConsumerPointsSigninClass from './pages/mall/consumer/points/signin.uvue'\nimport GenPagesMallConsumerPointsExchangeClass from './pages/mall/consumer/points/exchange.uvue'\nimport GenPagesMallConsumerPointsExchangeRecordsClass from './pages/mall/consumer/points/exchange-records.uvue'\nimport GenPagesMallConsumerProductReviewsClass from './pages/mall/consumer/product-reviews.uvue'\nimport GenPagesMallConsumerMyReviewsClass from './pages/mall/consumer/my-reviews.uvue'\nimport GenPagesMallConsumerBalanceIndexClass from './pages/mall/consumer/balance/index.uvue'\nimport GenPagesMallConsumerShareIndexClass from './pages/mall/consumer/share/index.uvue'\nimport GenPagesMallConsumerShareDetailClass from './pages/mall/consumer/share/detail.uvue'\nimport GenPagesMallConsumerMemberIndexClass from './pages/mall/consumer/member/index.uvue'\nimport GenPagesMallConsumerMessageDetailClass from './pages/mall/consumer/message-detail.uvue'\nimport GenPagesMallConsumerRedPacketsIndexClass from './pages/mall/consumer/red-packets/index.uvue'\nimport GenPagesMallConsumerBankCardsIndexClass from './pages/mall/consumer/bank-cards/index.uvue'\nimport GenPagesMallConsumerBankCardsAddClass from './pages/mall/consumer/bank-cards/add.uvue'\nfunction definePageRoutes() {\n__uniRoutes.push({ path: \"pages/user/login\", component: GenPagesUserLoginClass, meta: { isQuit: true } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"用户登录\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/main/index\", component: GenPagesMainIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"首页\"],[\"navigationStyle\",\"custom\"],[\"enablePullDownRefresh\",false]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/main/category\", component: GenPagesMainCategoryClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"分类\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/main/cart\", component: GenPagesMainCartClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"购物车\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/main/profile\", component: GenPagesMainProfileClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/settings\", component: GenPagesMallConsumerSettingsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"设置\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/wallet\", component: GenPagesMallConsumerWalletClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的钱包\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/withdraw\", component: GenPagesMallConsumerWithdrawClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"余额提现\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/search\", component: GenPagesMallConsumerSearchClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"搜索\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/product-detail\", component: GenPagesMallConsumerProductDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"商品详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/shop-detail\", component: GenPagesMallConsumerShopDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"店铺详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/coupons\", component: GenPagesMallConsumerCouponsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的优惠券\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/favorites\", component: GenPagesMallConsumerFavoritesClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的收藏\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/footprint\", component: GenPagesMallConsumerFootprintClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的足迹\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/address-list\", component: GenPagesMallConsumerAddressListClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"收货地址\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/address-edit\", component: GenPagesMallConsumerAddressEditClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"编辑地址\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/checkout\", component: GenPagesMallConsumerCheckoutClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"确认订单\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/payment\", component: GenPagesMallConsumerPaymentClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"收银台\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/payment-success\", component: GenPagesMallConsumerPaymentSuccessClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"支付成功\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/orders\", component: GenPagesMallConsumerOrdersClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的订单\"],[\"enablePullDownRefresh\",true]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/order-detail\", component: GenPagesMallConsumerOrderDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"订单详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/logistics\", component: GenPagesMallConsumerLogisticsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"物流详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/review\", component: GenPagesMallConsumerReviewClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"评价晒单\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/refund\", component: GenPagesMallConsumerRefundClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"退款/售后\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/apply-refund\", component: GenPagesMallConsumerApplyRefundClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"申请售后\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/refund-review\", component: GenPagesMallConsumerRefundReviewClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"服务评价\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/chat\", component: GenPagesMallConsumerChatClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"客服聊天\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/subscription/followed-shops\", component: GenPagesMallConsumerSubscriptionFollowedShopsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"关注店铺\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/points/index\", component: GenPagesMallConsumerPointsIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"积分管理\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/points/signin\", component: GenPagesMallConsumerPointsSigninClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"每日签到\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/points/exchange\", component: GenPagesMallConsumerPointsExchangeClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"积分兑换\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/points/exchange-records\", component: GenPagesMallConsumerPointsExchangeRecordsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"兑换记录\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/product-reviews\", component: GenPagesMallConsumerProductReviewsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"商品评价\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/my-reviews\", component: GenPagesMallConsumerMyReviewsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的评价\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/balance/index\", component: GenPagesMallConsumerBalanceIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的余额\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/share/index\", component: GenPagesMallConsumerShareIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的分享\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/share/detail\", component: GenPagesMallConsumerShareDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"分享详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/member/index\", component: GenPagesMallConsumerMemberIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"会员中心\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/message-detail\", component: GenPagesMallConsumerMessageDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"消息详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/red-packets/index\", component: GenPagesMallConsumerRedPacketsIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的红包\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/bank-cards/index\", component: GenPagesMallConsumerBankCardsIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"银行卡管理\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/bank-cards/add\", component: GenPagesMallConsumerBankCardsAddClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"添加银行卡\"]]) } as UniPageRoute)\n}\nconst __uniTabBar: Map<string, any | null> | null = _uM([[\"color\",\"#999999\"],[\"selectedColor\",\"#ff5000\"],[\"borderStyle\",\"black\"],[\"backgroundColor\",\"#ffffff\"],[\"list\",[_uM([[\"pagePath\",\"pages/main/index\"],[\"text\",\"首页\"],[\"iconPath\",\"static/tabbar/home.png\"],[\"selectedIconPath\",\"static/tabbar/home-active.png\"]]),_uM([[\"pagePath\",\"pages/main/category\"],[\"text\",\"分类\"],[\"iconPath\",\"static/tabbar/category.png\"],[\"selectedIconPath\",\"static/tabbar/category.png\"]]),_uM([[\"pagePath\",\"pages/main/cart\"],[\"text\",\"购物车\"],[\"iconPath\",\"static/tabbar/cart.png\"],[\"selectedIconPath\",\"static/tabbar/cart.png\"]]),_uM([[\"pagePath\",\"pages/main/profile\"],[\"text\",\"我的\"],[\"iconPath\",\"static/tabbar/user.png\"],[\"selectedIconPath\",\"static/tabbar/user.png\"]])]]])\nconst __uniLaunchPage: Map<string, any | null> = _uM([[\"url\",\"pages/user/login\"],[\"style\",_uM([[\"navigationBarTitleText\",\"用户登录\"],[\"navigationStyle\",\"custom\"]])]])\nfunction defineAppConfig(){\n __uniConfig.entryPagePath = '/pages/user/login'\n __uniConfig.globalStyle = _uM([[\"navigationBarTextStyle\",\"black\"],[\"navigationBarTitleText\",\"商城\"],[\"navigationBarBackgroundColor\",\"#ffffff\"],[\"backgroundColor\",\"#f5f5f5\"]])\n __uniConfig.getTabBarConfig = ():Map<string, any> | null => _uM([[\"color\",\"#999999\"],[\"selectedColor\",\"#ff5000\"],[\"borderStyle\",\"black\"],[\"backgroundColor\",\"#ffffff\"],[\"list\",[_uM([[\"pagePath\",\"pages/main/index\"],[\"text\",\"首页\"],[\"iconPath\",\"static/tabbar/home.png\"],[\"selectedIconPath\",\"static/tabbar/home-active.png\"]]),_uM([[\"pagePath\",\"pages/main/category\"],[\"text\",\"分类\"],[\"iconPath\",\"static/tabbar/category.png\"],[\"selectedIconPath\",\"static/tabbar/category.png\"]]),_uM([[\"pagePath\",\"pages/main/cart\"],[\"text\",\"购物车\"],[\"iconPath\",\"static/tabbar/cart.png\"],[\"selectedIconPath\",\"static/tabbar/cart.png\"]]),_uM([[\"pagePath\",\"pages/main/profile\"],[\"text\",\"我的\"],[\"iconPath\",\"static/tabbar/user.png\"],[\"selectedIconPath\",\"static/tabbar/user.png\"]])]]])\n __uniConfig.tabBar = __uniConfig.getTabBarConfig()\n __uniConfig.conditionUrl = ''\n __uniConfig.uniIdRouter = new Map()\n \n __uniConfig.ready = true\n}\n","import supa from '@/components/supadb/aksupainstance.uts'\r\nimport type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'\r\nimport type { OrderOptions } from '@/components/supadb/aksupa.uts'\r\n\r\nconst OLD_URL = '192.168.1.61:18000'\r\nconst NEW_URL = '119.146.131.237:9126'\r\n\r\nfunction fixImageUrl(url: string | null): string {\r\n if (url == null) return ''\r\n if (url.indexOf(OLD_URL) >= 0) {\r\n return url.replace(OLD_URL, NEW_URL)\r\n }\r\n return url\r\n}\r\n\r\nfunction fixImageUrls(urls: any): string[] {\r\n if (urls == null) return []\r\n if (Array.isArray(urls)) {\r\n const result: string[] = []\r\n const arr = urls as any[]\r\n for (let i = 0; i < arr.length; i++) {\r\n try {\r\n const urlStr = JSON.stringify(arr[i])\r\n if (urlStr != null && urlStr.startsWith('\"') && urlStr.endsWith('\"')) {\r\n const fixed = fixImageUrl(urlStr.substring(1, urlStr.length - 1))\r\n if (fixed !== '') result.push(fixed)\r\n }\r\n } catch (e) {}\r\n }\r\n return result\r\n }\r\n return []\r\n}\r\n\r\n// 使用单例 Supabase 客户端\r\n// const supa = createClient(SUPA_URL, SUPA_KEY)\r\n\r\n// 辅助函数:安全获取字符串值\r\nfunction safeGetString(obj: UTSJSONObject, key: string): string {\r\n try {\r\n const rawVal = obj.get(key)\r\n if (rawVal == null) return ''\r\n const strVal = JSON.stringify(rawVal)\r\n if (strVal == null) return ''\r\n if (strVal.startsWith('\"') && strVal.endsWith('\"')) {\r\n return strVal.substring(1, strVal.length - 1)\r\n }\r\n return strVal\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:50','safeGetString error for key:', key, e)\r\n return ''\r\n }\r\n}\r\n\r\n// 辅助函数:安全获取数值\r\nfunction safeGetNumber(obj: UTSJSONObject, key: string): number {\r\n try {\r\n const rawVal = obj.get(key)\r\n if (rawVal == null) return 0\r\n try {\r\n const numVal = rawVal as number\r\n if (!isNaN(numVal)) return numVal\r\n } catch (e) {}\r\n return 0\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:66','safeGetNumber error for key:', key, e)\r\n return 0\r\n }\r\n}\r\n\r\n// 辅助函数:安全获取布尔值\r\nfunction safeGetBoolean(obj: UTSJSONObject, key: string): boolean {\r\n try {\r\n const rawVal = obj.get(key)\r\n if (rawVal == null) return false\r\n try {\r\n const boolVal = rawVal as boolean\r\n return boolVal\r\n } catch (e) {}\r\n return false\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:82','safeGetBoolean error for key:', key, e)\r\n return false\r\n }\r\n}\r\n\r\n// 辅助函数:安全获取字符串数组\r\nfunction safeGetStringArray(obj: UTSJSONObject, key: string): string[] {\r\n try {\r\n const rawVal = obj.get(key)\r\n if (rawVal != null && Array.isArray(rawVal)) {\r\n return rawVal as string[]\r\n }\r\n return [] as string[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:96','safeGetStringArray error for key:', key, e)\r\n return [] as string[]\r\n }\r\n}\r\n\r\n// 辅助函数:从原始数据解析商品\r\nfunction parseProductFromRaw(item: any): Product {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:104','[parseProductFromRaw] 开始解析商品')\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n __f__('log','at utils/supabaseService.uts:106','[parseProductFromRaw] JSON转换成功')\r\n \r\n const mainImageUrl = fixImageUrl(safeGetString(itemObj, 'main_image_url'))\r\n const imageUrls = fixImageUrls(safeGetStringArray(itemObj, 'image_urls'))\r\n __f__('log','at utils/supabaseService.uts:110','[parseProductFromRaw] 图片处理完成')\r\n \r\n const result: Product = {\r\n id: safeGetString(itemObj, 'id'),\r\n name: safeGetString(itemObj, 'name'),\r\n description: safeGetString(itemObj, 'description'),\r\n base_price: safeGetNumber(itemObj, 'base_price'),\r\n price: safeGetNumber(itemObj, 'base_price'),\r\n original_price: safeGetNumber(itemObj, 'market_price'),\r\n market_price: safeGetNumber(itemObj, 'market_price'),\r\n main_image_url: mainImageUrl,\r\n image_url: mainImageUrl,\r\n images: imageUrls,\r\n category_id: safeGetString(itemObj, 'category_id'),\r\n brand_id: safeGetString(itemObj, 'brand_id'),\r\n merchant_id: safeGetString(itemObj, 'merchant_id'),\r\n total_stock: safeGetNumber(itemObj, 'total_stock'),\r\n stock: safeGetNumber(itemObj, 'total_stock'),\r\n sale_count: safeGetNumber(itemObj, 'sale_count'),\r\n status: safeGetNumber(itemObj, 'status'),\r\n is_featured: safeGetBoolean(itemObj, 'is_featured'),\r\n is_new: safeGetBoolean(itemObj, 'is_new'),\r\n is_hot: safeGetBoolean(itemObj, 'is_hot'),\r\n specification: safeGetString(itemObj, 'specification'),\r\n usage: safeGetString(itemObj, 'usage'),\r\n side_effects: safeGetString(itemObj, 'side_effects'),\r\n precautions: safeGetString(itemObj, 'precautions'),\r\n expiry_date: safeGetString(itemObj, 'expiry_date'),\r\n storage_conditions: safeGetString(itemObj, 'storage_conditions'),\r\n approval_number: safeGetString(itemObj, 'approval_number'),\r\n created_at: safeGetString(itemObj, 'created_at')\r\n }\r\n __f__('log','at utils/supabaseService.uts:142','[parseProductFromRaw] 商品解析成功:', result.name)\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:145','parseProductFromRaw error:', e)\r\n return {\r\n id: '',\r\n name: '',\r\n description: '',\r\n base_price: 0,\r\n price: 0,\r\n original_price: 0,\r\n market_price: 0,\r\n main_image_url: '',\r\n image_url: '',\r\n images: [] as string[],\r\n category_id: '',\r\n brand_id: '',\r\n merchant_id: '',\r\n total_stock: 0,\r\n stock: 0,\r\n sale_count: 0,\r\n status: 0,\r\n is_featured: false,\r\n is_new: false,\r\n is_hot: false,\r\n specification: '',\r\n usage: '',\r\n side_effects: '',\r\n precautions: '',\r\n expiry_date: '',\r\n storage_conditions: '',\r\n approval_number: '',\r\n created_at: ''\r\n } as Product\r\n }\r\n}\r\n\r\n// 类型定义\r\nexport type Brand = {\r\n id: string\r\n name: string\r\n logo_url: string\r\n description: string\r\n}\r\n\r\nexport type Category = {\r\n id: string\r\n name: string\r\n icon: string\r\n description: string\r\n color: string\r\n parent_id?: string\r\n level?: number\r\n slug?: string\r\n created_at?: string\r\n}\r\n\r\nexport type Product = {\r\n id: string\r\n category_id: string\r\n merchant_id: string\r\n name: string\r\n subtitle?: string\r\n description?: string\r\n base_price?: number\r\n market_price?: number\r\n cost_price?: number\r\n main_image_url?: string\r\n image_url?: string\r\n image_urls?: string\r\n video_urls?: string\r\n images?: string[]\r\n sale_count?: number\r\n view_count?: number\r\n total_stock?: number\r\n available_stock?: number\r\n is_hot?: boolean\r\n is_new?: boolean\r\n is_featured?: boolean\r\n status?: number\r\n rating_avg?: number\r\n rating_count?: number\r\n rating?: number\r\n review_count?: number\r\n brand_id?: string\r\n shop_id?: string\r\n tags?: string\r\n attributes?: string\r\n specification?: string\r\n usage?: string\r\n side_effects?: string\r\n precautions?: string\r\n expiry_date?: string\r\n storage_conditions?: string\r\n approval_number?: string\r\n created_at?: string\r\n updated_at?: string\r\n price?: number\r\n original_price?: number\r\n stock?: number\r\n sales?: number\r\n cover?: string\r\n brand_name?: string\r\n category_name?: string\r\n shop_name?: string\r\n merchant_name?: string\r\n}\r\n\r\nexport type Shop = {\r\n id: string\r\n merchant_id: string\r\n shop_name: string\r\n shop_logo?: string\r\n shop_banner?: string\r\n description?: string\r\n contact_name?: string\r\n contact_phone?: string\r\n rating_avg?: number\r\n total_sales?: number\r\n product_count?: number\r\n total_sales_count?: number\r\n created_at?: string\r\n}\r\n\r\nexport type CartItem = {\r\n id: string\r\n user_id: string\r\n product_id: string\r\n sku_id?: string\r\n merchant_id?: string\r\n quantity: number\r\n selected: boolean\r\n product_name?: string\r\n product_image?: string\r\n product_price?: number\r\n product_specification?: string\r\n shop_id?: string\r\n shop_name?: string\r\n created_at?: string\r\n updated_at?: string\r\n}\r\n\r\nexport type UserAddress = {\r\n id: string\r\n user_id: string\r\n recipient_name: string\r\n phone: string\r\n province: string\r\n city: string\r\n district: string\r\n detail_address: string\r\n postal_code?: string\r\n is_default: boolean\r\n label?: string\r\n created_at?: string\r\n updated_at?: string\r\n}\r\n\r\nexport type UserCoupon = {\r\n id: string\r\n user_id: string\r\n template_id: string\r\n coupon_code: string\r\n status: number // 1: unused, 2: used, 3: expired\r\n received_at: string\r\n expire_at: string\r\n used_at?: string\r\n // join fields from template or view\r\n template_name?: string\r\n amount?: number\r\n min_spend?: number\r\n name?: string\r\n title?: string\r\n}\r\n\r\nexport type ChatRoom = {\r\n id: string\r\n user_id: string\r\n merchant_id: string\r\n shop_name: string\r\n shop_logo?: string\r\n last_message?: string\r\n last_message_at?: string\r\n unread_count: number\r\n is_top: boolean\r\n created_at?: string\r\n updated_at?: string\r\n}\r\n\r\nexport type Notification = {\r\n id: string\r\n user_id: string\r\n type: string\r\n title: string\r\n content: string\r\n icon_url?: string\r\n link_url?: string\r\n is_read: boolean\r\n extra_data?: string\r\n created_at?: string\r\n}\r\n\r\nexport type ChatMessage = {\r\n id: string\r\n session_id?: string\r\n sender_id?: string\r\n receiver_id?: string\r\n content: string\r\n msg_type: string\r\n is_read: boolean\r\n is_from_user: boolean\r\n extra_data?: string\r\n created_at?: string\r\n}\r\n\r\nexport type PaginatedResponse<T> = {\r\n data: T[]\r\n total: number\r\n page: number\r\n limit: number\r\n hasmore: boolean\r\n}\r\n\r\nexport type ProductSku = {\r\n id: string\r\n product_id: string\r\n sku_code: string\r\n specifications: string // JSON string\r\n price: number\r\n market_price?: number\r\n cost_price?: number\r\n stock?: number\r\n warning_stock?: number\r\n image_url?: string\r\n weight?: number\r\n status?: number\r\n created_at?: string\r\n}\r\n\r\nexport type AddAddressParams = {\r\n recipient_name: string\r\n phone: string\r\n province: string\r\n city: string\r\n district: string\r\n detail_address: string\r\n postal_code?: string\r\n is_default?: boolean\r\n label?: string\r\n}\r\n\r\nexport type UpdateAddressParams = {\r\n recipient_name?: string\r\n phone?: string\r\n province?: string\r\n city?: string\r\n district?: string\r\n detail_address?: string\r\n postal_code?: string\r\n is_default?: boolean\r\n label?: string\r\n}\r\n\r\nexport type CreateOrderParams = {\r\n merchant_id: string\r\n product_amount: number\r\n shipping_fee: number\r\n total_amount: number\r\n shipping_address: any\r\n items: any[]\r\n}\r\n\r\nexport type ShopOrderParams = {\r\n shipping_address: any\r\n shopGroups: any[]\r\n deliveryFee: number\r\n discountAmount: number\r\n}\r\n\r\nexport type ShopOrderResponse = {\r\n success: boolean\r\n orderIds: string[]\r\n error?: string\r\n}\r\n\r\nexport type RefundResponse = {\r\n success: boolean\r\n message: string\r\n}\r\n\r\nexport type ConfirmReceiptResponse = {\r\n success: boolean\r\n error?: string\r\n}\r\n\r\nclass SupabaseService {\r\n // 获取当前用户ID\r\n public getCurrentUserId(): string | null {\r\n try {\r\n // 1. 优先从 Supabase 会话获取\r\n const session = supa.getSession()\r\n if (session != null && session.user != null) {\r\n return session.user.getString('id')\r\n }\r\n \r\n // 2. 尝试从 Storage 恢复 Session (针对 App 重启后内存丢失的情况)\r\n // 注意:这里无法异步调用 hydrate所以只能依赖 UI 层或 init 层的预加载\r\n // 但我们可以返回本地存储 ID 作为 fallback前提是 Token 有效\r\n \r\n // 后备:尝试从本地存储获取\r\n const userId = uni.getStorageSync('user_id')\r\n return userId != null ? userId as string : null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:455','获取用户ID失败:', e)\r\n return null\r\n }\r\n }\r\n\r\n // 确保会话有效 (异步)\r\n async ensureSession(): Promise<string | null> {\r\n let session = supa.getSession()\r\n if (session.user == null) {\r\n __f__('log','at utils/supabaseService.uts:464','Session user is null, attempting to hydrate from storage...')\r\n await supa.hydrateSessionFromStorage()\r\n session = supa.getSession()\r\n }\r\n \r\n if (session.user != null) {\r\n // 同步 user_id 到 storage 保持一致\r\n const uid = session.user!!.getString('id')\r\n if (uid != null) {\r\n uni.setStorageSync('user_id', uid)\r\n return uid\r\n }\r\n }\r\n return this.getCurrentUserId()\r\n }\r\n\r\n // 获取所有分类\r\n async getCategories(): Promise<Category[]> {\r\n try {\r\n const response = await supa\r\n .from('ml_categories')\r\n .select('*')\r\n .order('name', { ascending: true })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:490','获取分类失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n \r\n const categories: Category[] = []\r\n const rawList = rawData as any[]\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const catObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n const idVal = catObj.get('id')\r\n const nameVal = catObj.get('name')\r\n const iconVal = catObj.get('icon')\r\n const iconUrlVal = catObj.get('icon_url')\r\n const descVal = catObj.get('description')\r\n const colorVal = catObj.get('color')\r\n const parentIdVal = catObj.get('parent_id')\r\n const levelVal = catObj.get('level')\r\n \r\n const cat: Category = {\r\n id: (typeof idVal == 'string') ? (idVal as string) : '',\r\n name: (typeof nameVal == 'string') ? (nameVal as string) : '',\r\n icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''),\r\n description: (typeof descVal == 'string') ? (descVal as string) : '',\r\n color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',\r\n parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null,\r\n level: (typeof levelVal == 'number') ? (levelVal as number) : 0\r\n } as Category\r\n categories.push(cat)\r\n }\r\n return categories\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:526','获取分类异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 根据ID获取单个分类\r\n async getCategoryById(categoryId: string): Promise<Category | null> {\r\n try {\r\n const response = await supa\r\n .from('ml_categories')\r\n .select('*')\r\n .eq('id', categoryId)\r\n .limit(1)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:542','获取分类失败:', response.error)\r\n return null\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return null\r\n }\r\n \r\n // 处理数组返回值\r\n const rawList = rawData as any[]\r\n if (rawList.length == 0) {\r\n return null\r\n }\r\n \r\n const item = rawList[0]\r\n const catObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n const idVal = catObj.get('id')\r\n const nameVal = catObj.get('name')\r\n const iconVal = catObj.get('icon')\r\n const iconUrlVal = catObj.get('icon_url')\r\n const descVal = catObj.get('description')\r\n const colorVal = catObj.get('color')\r\n const parentIdVal = catObj.get('parent_id')\r\n const levelVal = catObj.get('level')\r\n \r\n const cat: Category = {\r\n id: (typeof idVal == 'string') ? (idVal as string) : '',\r\n name: (typeof nameVal == 'string') ? (nameVal as string) : '',\r\n icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''),\r\n description: (typeof descVal == 'string') ? (descVal as string) : '',\r\n color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',\r\n parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null,\r\n level: (typeof levelVal == 'number') ? (levelVal as number) : 0\r\n } as Category\r\n return cat\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:579','获取分类异常:', error)\r\n return null\r\n }\r\n }\r\n\r\n // 获取一级分类\r\n async getParentCategories(): Promise<Category[]> {\r\n try {\r\n const response = await supa\r\n .from('ml_categories')\r\n .select('*')\r\n .is('parent_id', null)\r\n .order('sort_order', { ascending: true })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:595','获取一级分类失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n\r\n const categories: Category[] = []\r\n const rawList = rawData as Array<UTSJSONObject>\r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const icon = this.getCategoryIcon(item)\r\n \r\n // 安全获取属性\r\n const idVal = item['id']\r\n const nameVal = item['name']\r\n const descVal = item['description']\r\n const colorVal = item['color']\r\n const slugVal = item['slug']\r\n \r\n const cat: Category = {\r\n id: (typeof idVal == 'string') ? (idVal as string) : '',\r\n name: (typeof nameVal == 'string') ? (nameVal as string) : '',\r\n icon: icon,\r\n description: (typeof descVal == 'string') ? (descVal as string) : '',\r\n color: (typeof colorVal == 'string') ? (colorVal as string) : '#ff5000',\r\n level: 1,\r\n slug: (typeof slugVal == 'string') ? (slugVal as string) : ''\r\n }\r\n categories.push(cat)\r\n }\r\n return categories\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:630','获取一级分类异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取子分类\r\n async getSubCategories(parentId: string): Promise<Category[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:638','[getSubCategories] 开始获取子分类, parentId:', parentId)\r\n const response = await supa\r\n .from('ml_categories')\r\n .select('*')\r\n .order('sort_order', { ascending: true })\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:645','[getSubCategories] 查询完成')\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:648','获取子分类失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n __f__('log','at utils/supabaseService.uts:654','[getSubCategories] 数据为空')\r\n return []\r\n }\r\n\r\n const categories: Category[] = []\r\n const rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:660','[getSubCategories] 原始数据条数:', rawList.length)\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n // 手动过滤 parent_id\r\n const itemParentId = safeGetString(itemObj, 'parent_id')\r\n const isMatch = (itemParentId.length > 0 && itemParentId == parentId)\r\n if (!isMatch) {\r\n continue\r\n }\r\n \r\n const icon = this.getCategoryIcon(itemObj)\r\n const cat: Category = {\r\n id: safeGetString(itemObj, 'id'),\r\n name: safeGetString(itemObj, 'name'),\r\n icon: icon,\r\n description: safeGetString(itemObj, 'description'),\r\n color: safeGetString(itemObj, 'color').length > 0 ? safeGetString(itemObj, 'color') : '#ff5000',\r\n level: 2,\r\n parent_id: safeGetString(itemObj, 'parent_id'),\r\n slug: safeGetString(itemObj, 'slug')\r\n }\r\n categories.push(cat)\r\n }\r\n __f__('log','at utils/supabaseService.uts:686','[getSubCategories] 返回分类数量:', categories.length)\r\n return categories\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:689','获取子分类异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取分类图标的辅助方法\r\n getCategoryIcon(item: UTSJSONObject): string {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n const icon = safeGetString(itemObj, 'icon')\r\n if (icon.length > 0) {\r\n return icon\r\n }\r\n const iconUrl = safeGetString(itemObj, 'icon_url')\r\n if (iconUrl.length > 0) {\r\n return iconUrl\r\n }\r\n const name = safeGetString(itemObj, 'name')\r\n if (name.includes('数码') || name.includes('电器') || name.includes('手机')) return '📱'\r\n if (name.includes('服装') || name.includes('衣服') || name.includes('鞋')) return '👕'\r\n if (name.includes('食品') || name.includes('水果') || name.includes('零食')) return '🍎'\r\n if (name.includes('美妆') || name.includes('护肤') || name.includes('化妆')) return '💄'\r\n if (name.includes('母婴') || name.includes('婴儿') || name.includes('儿童')) return '👶'\r\n if (name.includes('家居') || name.includes('家具') || name.includes('装饰')) return '🏠'\r\n if (name.includes('图书') || name.includes('文具')) return '📚'\r\n if (name.includes('运动') || name.includes('户外') || name.includes('健身')) return '⚽'\r\n if (name.includes('医药') || name.includes('保健') || name.includes('健康')) return '💊'\r\n return '📦'\r\n }\r\n\r\n // 获取所有品牌\r\n async getBrands(): Promise<Brand[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:721','[getBrands] 开始获取品牌数据...')\r\n const response = await supa\r\n .from('ml_brands')\r\n .select('id, name, logo_url, description, is_active')\r\n .order('name', { ascending: true })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:729','获取品牌失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n __f__('log','at utils/supabaseService.uts:735','[getBrands] 数据为空')\r\n return []\r\n }\r\n \r\n const brands: Brand[] = []\r\n const rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:741','[getBrands] 数据条数:', rawList.length)\r\n \r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const brandObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n const idVal = brandObj.get('id')\r\n const nameVal = brandObj.get('name')\r\n const logoVal = brandObj.get('logo_url')\r\n const descVal = brandObj.get('description')\r\n const isActiveVal = brandObj.get('is_active')\r\n \r\n let isActiveBool: boolean = true\r\n if (isActiveVal != null) {\r\n if (typeof isActiveVal == 'boolean') {\r\n isActiveBool = isActiveVal as boolean\r\n } else if (typeof isActiveVal == 'number') {\r\n isActiveBool = (isActiveVal as number) === 1\r\n }\r\n }\r\n if (!isActiveBool) {\r\n continue\r\n }\r\n \r\n const brand: Brand = {\r\n id: (typeof idVal == 'string') ? (idVal as string) : '',\r\n name: (typeof nameVal == 'string') ? (nameVal as string) : '',\r\n logo_url: (typeof logoVal == 'string') ? (logoVal as string) : '',\r\n description: (typeof descVal == 'string') ? (descVal as string) : ''\r\n } as Brand\r\n brands.push(brand)\r\n }\r\n __f__('log','at utils/supabaseService.uts:772','[getBrands] 返回品牌数量:', brands.length)\r\n return brands\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:775','获取品牌异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取指定分类的商品\r\n async getProductsByCategory(\r\n categoryId: string, \r\n page: number = 1, \r\n limit: number = 20\r\n ): Promise<PaginatedResponse<Product>> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:787','[getProductsByCategory] 开始查询分类ID:', categoryId, '页码:', page)\r\n \r\n // 在数据库层面进行分类过滤\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*', { count: 'exact' })\r\n .eq('category_id', categoryId)\r\n .eq('status', '1') // 使用字符串 '1' 而不是整数 1\r\n .order('sale_count', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:800','[getProductsByCategory] 查询完成total:', response.total)\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:803','获取商品失败:', response.error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:826','[getProductsByCategory] 返回数据条数:', rawList.length)\r\n \r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n products.push(parseProductFromRaw(item))\r\n }\r\n \r\n return {\r\n data: products,\r\n total: response.total ?? products.length,\r\n page,\r\n limit,\r\n hasmore: response.hasmore ?? false\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:841','获取商品异常:', error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n }\r\n\r\n // 根据商品ID获取SKU列表\r\n async getProductSkus(productId: string): Promise<ProductSku[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:855','[getProductSkus] 开始获取SKU商品ID:', productId)\r\n const response = await supa\r\n .from('ml_product_skus')\r\n .select('*')\r\n .eq('product_id', productId)\r\n .eq('status', '1')\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:864','获取商品SKU失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) return []\r\n \r\n const skus: ProductSku[] = []\r\n const rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:873','[getProductSkus] 获取到SKU数量:', rawList.length)\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const skuObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n const rawId = skuObj.get('id')\r\n const rawSkuCode = skuObj.get('sku_code')\r\n const rawProdId = skuObj.get('product_id')\r\n const rawPrice = skuObj.get('price')\r\n const rawStock = skuObj.get('stock')\r\n const rawImageUrl = skuObj.get('image_url')\r\n const rawSpecs = skuObj.get('specifications')\r\n \r\n let specsStr = ''\r\n if (rawSpecs != null) {\r\n try {\r\n if (typeof rawSpecs == 'string') {\r\n specsStr = rawSpecs as string\r\n } else {\r\n specsStr = JSON.stringify(rawSpecs)\r\n }\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:896','解析SKU规格失败', e)\r\n }\r\n }\r\n \r\n const sku: ProductSku = {\r\n id: (typeof rawId == 'string') ? (rawId as string) : '',\r\n product_id: (typeof rawProdId == 'string') ? (rawProdId as string) : '',\r\n sku_code: (typeof rawSkuCode == 'string') ? (rawSkuCode as string) : '',\r\n specifications: specsStr,\r\n price: (typeof rawPrice == 'number') ? (rawPrice as number) : 0,\r\n stock: (typeof rawStock == 'number') ? (rawStock as number) : 0,\r\n image_url: (typeof rawImageUrl == 'string') ? (rawImageUrl as string) : '',\r\n status: 1\r\n }\r\n skus.push(sku)\r\n }\r\n return skus\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:914','获取商品SKU异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 搜索商品\r\n async searchProducts(\r\n keyword: string, \r\n page: number = 1, \r\n limit: number = 20,\r\n sortBy: string = 'sales',\r\n ascending: boolean = false\r\n ): Promise<PaginatedResponse<Product>> {\r\n try {\r\n const keywordLower = keyword.toLowerCase()\r\n const encodedKeyword = encodeURIComponent(keywordLower)\r\n const orString = `name.ilike.%${encodedKeyword}%,description.ilike.%${encodedKeyword}%,subtitle.ilike.%${encodedKeyword}%,brand_name.ilike.%${encodedKeyword}%`\r\n __f__('log','at utils/supabaseService.uts:931','[searchProducts] 搜索关键词:', keyword, '编码后:', encodedKeyword)\r\n __f__('log','at utils/supabaseService.uts:932','[searchProducts] or条件:', orString)\r\n \r\n let query = supa\r\n .from('ml_products_detail_view')\r\n .select('*', { count: 'exact' })\r\n .eq('status', 1)\r\n .or(orString)\r\n \r\n if (sortBy === 'price') {\r\n query = query.order('base_price', { ascending })\r\n } else if (sortBy === 'sales' || sortBy === 'sale_count') {\r\n query = query.order('sale_count', { ascending: false })\r\n } else {\r\n query = query.order('sale_count', { ascending: false })\r\n }\r\n \r\n const response = await query\r\n .page(page)\r\n .limit(limit)\r\n .execute()\r\n \r\n let dataLength = 0\r\n try {\r\n const respData = response.data\r\n if (respData != null && Array.isArray(respData)) {\r\n dataLength = (respData as any[]).length\r\n }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:960','[searchProducts] 获取数据长度失败:', e)\r\n }\r\n let statusNum = 0\r\n try {\r\n statusNum = response.status as number\r\n } catch (e) {}\r\n __f__('log','at utils/supabaseService.uts:966','[searchProducts] 响应状态:', statusNum, '数据条数:', dataLength)\r\n \r\n let hasError = false\r\n try {\r\n hasError = response.error != null\r\n } catch (e) {}\r\n if (hasError) {\r\n __f__('error','at utils/supabaseService.uts:973','[searchProducts] 搜索商品失败:', response.error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n \r\n const rawData = response.data\r\n __f__('log','at utils/supabaseService.uts:984','[searchProducts] rawData:', rawData != null ? 'not null' : 'null')\r\n if (rawData == null) {\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n \r\n const products: Product[] = []\r\n let rawList: any[] = []\r\n try {\r\n rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:999','[searchProducts] rawList长度:', rawList.length)\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1001','[searchProducts] 转换rawList失败:', e)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n __f__('log','at utils/supabaseService.uts:1013','[searchProducts] 处理第', i + 1, '个商品')\r\n products.push(parseProductFromRaw(item))\r\n }\r\n \r\n let totalNum = 0\r\n try {\r\n totalNum = response.total as number\r\n } catch (e) {}\r\n let hasmoreVal = false\r\n try {\r\n hasmoreVal = response.hasmore as boolean\r\n } catch (e) {}\r\n \r\n return {\r\n data: products,\r\n total: totalNum > 0 ? totalNum : products.length,\r\n page,\r\n limit,\r\n hasmore: hasmoreVal\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1034','搜索商品异常:', error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n }\r\n\r\n // 搜索店铺\r\n async searchShops(\r\n keyword: string,\r\n page: number = 1,\r\n limit: number = 20\r\n ): Promise<PaginatedResponse<Shop>> {\r\n try {\r\n const encodedKeyword = encodeURIComponent(keyword)\r\n const response = await supa\r\n .from('ml_shops')\r\n .select('*', { count: 'exact' })\r\n .ilike('shop_name', `%${encodedKeyword}%`)\r\n .order('product_count', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1063','搜索店铺失败:', response.error)\r\n return { data: [] as Shop[], total: 0, page, limit, hasmore: false }\r\n }\r\n\r\n const rawData = response.data\r\n if (rawData == null) {\r\n return { data: [] as Shop[], total: 0, page, limit, hasmore: false }\r\n }\r\n\r\n const shops: Shop[] = []\r\n const dataList = rawData as any[]\r\n for (let i = 0; i < dataList.length; i++) {\r\n const item = dataList[i]\r\n const shopObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n // 手动过滤 status\r\n const rawStatus = shopObj.get('status')\r\n let statusNum: number = 0\r\n if (typeof rawStatus == 'number') {\r\n statusNum = rawStatus as number\r\n }\r\n if (statusNum !== 1) continue\r\n \r\n // 手动创建 Shop 对象,避免安卓端类型转换错误\r\n const shop: Shop = {\r\n id: shopObj.getString('id') ?? '',\r\n merchant_id: shopObj.getString('merchant_id') ?? '',\r\n shop_name: shopObj.getString('shop_name') ?? '',\r\n shop_logo: shopObj.getString('shop_logo'),\r\n shop_banner: shopObj.getString('shop_banner'),\r\n description: shopObj.getString('description'),\r\n contact_name: shopObj.getString('contact_name'),\r\n contact_phone: shopObj.getString('contact_phone'),\r\n rating_avg: shopObj.getNumber('rating_avg'),\r\n total_sales: shopObj.getNumber('total_sales'),\r\n product_count: shopObj.getNumber('product_count'),\r\n total_sales_count: shopObj.getNumber('total_sales_count'),\r\n created_at: shopObj.getString('created_at')\r\n }\r\n shops.push(shop)\r\n }\r\n\r\n return {\r\n data: shops,\r\n total: response.total ?? shops.length,\r\n page,\r\n limit,\r\n hasmore: response.hasmore ?? false\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1113','搜索店铺异常:', error)\r\n return { data: [] as Shop[], total: 0, page, limit, hasmore: false }\r\n }\r\n }\r\n\r\n // 获取单个商品详情\r\n async getProductById(productId: string): Promise<Product | null> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1121','[getProductById] 开始获取商品详情ID:', productId)\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('id', productId)\r\n .limit(1)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1130','获取商品详情失败:', response.error)\r\n return null\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n __f__('log','at utils/supabaseService.uts:1136','[getProductById] 数据为空')\r\n return null\r\n }\r\n \r\n const rawList = rawData as any[]\r\n if (rawList.length == 0) {\r\n __f__('log','at utils/supabaseService.uts:1142','[getProductById] 未找到商品')\r\n return null\r\n }\r\n \r\n const item = rawList[0]\r\n const product = parseProductFromRaw(item)\r\n __f__('log','at utils/supabaseService.uts:1148','[getProductById] 成功获取商品:', product.name)\r\n return product\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1151','获取商品详情异常:', error)\r\n return null\r\n }\r\n }\r\n\r\n // --- 关注店铺相关 ---\r\n\r\n // 检查是否已关注店铺\r\n async isShopFollowed(shopId: string, userId: string): Promise<boolean> {\r\n try {\r\n const res = await supa\r\n .from('ml_shop_follows')\r\n .select('id', { count: 'exact' })\r\n .eq('shop_id', shopId)\r\n .eq('user_id', userId)\r\n .limit(1)\r\n .execute()\r\n \r\n return (res.total != null && res.total! > 0)\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1171','Check follow error:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 关注店铺\r\n async followShop(shopId: string, userId: string): Promise<boolean> {\r\n try {\r\n const res = await supa\r\n .from('ml_shop_follows')\r\n .insert({\r\n user_id: userId,\r\n shop_id: shopId\r\n })\r\n .execute()\r\n \r\n return res.error == null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1189','Follow shop error:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 取消关注\r\n async unfollowShop(shopId: string, userId: string): Promise<boolean> {\r\n try {\r\n const res = await supa\r\n .from('ml_shop_follows')\r\n .eq('shop_id', shopId)\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n \r\n return res.error == null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1206','Unfollow shop error:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 获取我关注的店铺列表\r\n async getFollowedShops(userId: string): Promise<any[]> {\r\n try {\r\n // 关联查询店铺信息\r\n const res = await supa\r\n .from('ml_shop_follows')\r\n .select('*, ml_shops(*)') \r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1223','getFollowedShops error:', res.error)\r\n return []\r\n }\r\n \r\n return res.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1229','getFollowedShops exception:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 根据商户ID获取店铺信息\r\n async getShopByMerchantId(merchantId: string): Promise<Shop | null> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1237','[getShopByMerchantId] 开始获取店铺信息merchantId:', merchantId)\r\n // 1. Try querying by merchant_id\r\n let response = await supa\r\n .from('ml_shops')\r\n .select('*')\r\n .eq('merchant_id', merchantId)\r\n .limit(1)\r\n .execute()\r\n \r\n if (response.error == null && response.data != null && (response.data as any[]).length > 0) {\r\n const shopData = (response.data as any[])[0]\r\n const shop = this.parseShopFromRaw(shopData)\r\n __f__('log','at utils/supabaseService.uts:1249','[getShopByMerchantId] 通过 merchant_id 找到店铺:', shop.shop_name)\r\n return shop\r\n }\r\n\r\n // 2. Fallback: Try querying by id (Maybe the passed ID is the Shop ID?)\r\n __f__('log','at utils/supabaseService.uts:1254','getShopByMerchantId: merchant_id not found, trying id...', merchantId)\r\n response = await supa\r\n .from('ml_shops')\r\n .select('*')\r\n .eq('id', merchantId)\r\n .limit(1)\r\n .execute()\r\n\r\n if (response.error == null && response.data != null && (response.data as any[]).length > 0) {\r\n __f__('log','at utils/supabaseService.uts:1263','Found shop by ID instead of MerchantID')\r\n const shopData = (response.data as any[])[0]\r\n const shop = this.parseShopFromRaw(shopData)\r\n return shop\r\n }\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1270','获取店铺信息失败:', response.error)\r\n }\r\n return null\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1274','获取店铺信息异常:', error)\r\n return null\r\n }\r\n }\r\n \r\n // 解析店铺数据\r\n parseShopFromRaw(item: any): Shop {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n const getSafeString = (key: string): string => {\r\n const val = itemObj.get(key)\r\n if (val == null) return ''\r\n if (typeof val == 'string') return val\r\n return ''\r\n }\r\n \r\n const getSafeNumber = (key: string): number => {\r\n const val = itemObj.get(key)\r\n if (val == null) return 0\r\n if (typeof val == 'number') return val\r\n return 0\r\n }\r\n \r\n return {\r\n id: getSafeString('id'),\r\n merchant_id: getSafeString('merchant_id'),\r\n shop_name: getSafeString('shop_name'),\r\n shop_logo: getSafeString('shop_logo'),\r\n shop_banner: getSafeString('shop_banner'),\r\n description: getSafeString('description'),\r\n contact_name: getSafeString('contact_name'),\r\n contact_phone: getSafeString('contact_phone'),\r\n rating_avg: getSafeNumber('rating_avg'),\r\n total_sales: getSafeNumber('total_sales'),\r\n product_count: getSafeNumber('product_count'),\r\n total_sales_count: getSafeNumber('total_sales_count'),\r\n created_at: getSafeString('created_at')\r\n } as Shop\r\n }\r\n\r\n // 根据商户ID获取商品列表\r\n async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1317','getProductsByMerchantId querying for:', merchantId)\r\n \r\n // 1. Try fetching from view strictly first\r\n let query = supa\r\n .from('ml_products_detail_view')\r\n .select('*', { count: 'exact' })\r\n .eq('merchant_id', merchantId)\r\n // .eq('status', 1) // Temporarily disabled status check to see if products exist at all\r\n .order('created_at', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n \r\n const response = await query.execute()\r\n \r\n // 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表\r\n if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1334','获取商户商品失败 (View):', response.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:1336','View returned 0 products, trying raw table fallback...')\r\n }\r\n \r\n // Fallback: Try raw table\r\n __f__('log','at utils/supabaseService.uts:1340','Falling back to raw ml_products table...')\r\n const query2 = supa\r\n .from('ml_products')\r\n .select('*', { count: 'exact' })\r\n .eq('merchant_id', merchantId)\r\n // .eq('status', 1) // Also disabled here\r\n .order('created_at', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n \r\n const res2 = await query2.execute()\r\n if (res2.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1352','获取商户商品失败 (Raw):', res2.error)\r\n return {data:[] as Product[], total:0, page, limit, hasmore:false}\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1356',`Fallback (Raw) found: ${(res2.data as any[]).length} products`)\r\n \r\n const mappedData: Product[] = []\r\n const rawData = res2.data as any[]\r\n for(let i = 0; i < rawData.length; i++) {\r\n const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject\r\n const images: string[] = []\r\n \r\n const mainImageUrl = fixImageUrl(item.getString('main_image_url'))\r\n if (mainImageUrl != null && mainImageUrl !== '') {\r\n images.push(mainImageUrl)\r\n }\r\n\r\n const imageUrlsRaw = item.get('image_urls')\r\n if (imageUrlsRaw != null) {\r\n try {\r\n if (Array.isArray(imageUrlsRaw)) {\r\n const arr = imageUrlsRaw as string[]\r\n if (arr.length > 0 && images.length === 0) {\r\n for (let j = 0; j < arr.length; j++) {\r\n const fixedUrl = fixImageUrl(arr[j])\r\n if (fixedUrl !== '') images.push(fixedUrl)\r\n }\r\n }\r\n } else {\r\n const rawUrlStr = imageUrlsRaw as string\r\n if (rawUrlStr.startsWith('[')) {\r\n const parsed = JSON.parse(rawUrlStr)\r\n if (Array.isArray(parsed) && images.length === 0) {\r\n for (let j = 0; j < parsed.length; j++) {\r\n const fixedUrl = fixImageUrl(parsed[j] as string)\r\n if (fixedUrl !== '') images.push(fixedUrl)\r\n }\r\n }\r\n } else {\r\n const fixedUrl = fixImageUrl(rawUrlStr)\r\n if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl)\r\n }\r\n }\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:1396','解析图片数组失败:', e)\r\n }\r\n }\r\n \r\n if (images.length === 0) {\r\n images.push('/static/default-product.png')\r\n }\r\n \r\n let safePrice = item.getNumber('base_price')\r\n if (safePrice == null) {\r\n const p = item.getNumber('price')\r\n safePrice = p != null ? p : 0\r\n }\r\n \r\n let safeOriginalPrice = item.getNumber('market_price')\r\n if (safeOriginalPrice == null) {\r\n const op = item.getNumber('original_price')\r\n safeOriginalPrice = op != null ? op : safePrice\r\n }\r\n \r\n let safeStock = item.getNumber('total_stock')\r\n if (safeStock == null) {\r\n let as_ = item.getNumber('available_stock')\r\n if (as_ == null) {\r\n const s = item.getNumber('stock')\r\n safeStock = s != null ? s : 0\r\n } else {\r\n safeStock = as_\r\n }\r\n }\r\n \r\n let safeSales = item.getNumber('sale_count')\r\n if (safeSales == null) {\r\n const s = item.getNumber('sales')\r\n safeSales = s != null ? s : 0\r\n }\r\n \r\n const product: Product = {\r\n id: item.getString('id') ?? '',\r\n category_id: item.getString('category_id') ?? '',\r\n merchant_id: item.getString('merchant_id') ?? '',\r\n name: item.getString('name') ?? '',\r\n description: item.getString('description') ?? '',\r\n images: images,\r\n price: safePrice,\r\n original_price: safeOriginalPrice,\r\n stock: safeStock,\r\n sales: safeSales,\r\n status: item.getNumber('status') ?? 1,\r\n created_at: item.getString('created_at') ?? '',\r\n base_price: safePrice,\r\n market_price: safeOriginalPrice,\r\n main_image_url: images.length > 0 ? images[0] : '',\r\n sale_count: safeSales,\r\n total_stock: safeStock\r\n } as Product\r\n mappedData.push(product)\r\n }\r\n\r\n return {\r\n data: mappedData,\r\n total: res2.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: res2.hasmore ?? false\r\n }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1464',`Merchant products found: ${(response.data as any[]).length}`)\r\n \r\n const viewData = response.data as any[]\r\n const parsedProducts: Product[] = []\r\n for (let i = 0; i < viewData.length; i++) {\r\n parsedProducts.push(parseProductFromRaw(viewData[i]))\r\n }\r\n \r\n return {\r\n data: parsedProducts,\r\n total: response.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: response.hasmore ?? false\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1480','获取商户商品异常:', error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n }\r\n\r\n // 根据店铺ID获取商品列表新增\r\n async getProductsByShopId(shopId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1494','getProductsByShopId querying for:', shopId)\r\n \r\n // 1. Try fetching from view with shop_id\r\n let query = supa\r\n .from('ml_products_detail_view')\r\n .select('*', { count: 'exact' })\r\n .eq('shop_id', shopId)\r\n .order('created_at', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n \r\n const response = await query.execute()\r\n \r\n // 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表\r\n if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1510','获取店铺商品失败 (View):', response.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:1512','View returned 0 products, trying raw table fallback...')\r\n }\r\n \r\n // Fallback: Try raw table with shop_id\r\n __f__('log','at utils/supabaseService.uts:1516','Falling back to raw ml_products table with shop_id...')\r\n const query2 = supa\r\n .from('ml_products')\r\n .select('*', { count: 'exact' })\r\n .eq('shop_id', shopId)\r\n .order('created_at', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n \r\n const res2 = await query2.execute()\r\n if (res2.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1527','获取店铺商品失败 (Raw):', res2.error)\r\n return {data:[] as Product[], total:0, page, limit, hasmore:false}\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1531',`Fallback (Raw) found: ${(res2.data as any[]).length} products`)\r\n \r\n const mappedData: Product[] = []\r\n const rawData = res2.data as any[]\r\n for(let i = 0; i < rawData.length; i++) {\r\n const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject\r\n const images: string[] = []\r\n \r\n const mainImageUrl = fixImageUrl(item.getString('main_image_url'))\r\n if (mainImageUrl != null && mainImageUrl !== '') {\r\n images.push(mainImageUrl)\r\n }\r\n\r\n const imageUrlsRaw = item.get('image_urls')\r\n if (imageUrlsRaw != null) {\r\n try {\r\n if (Array.isArray(imageUrlsRaw)) {\r\n const arr = imageUrlsRaw as string[]\r\n if (arr.length > 0 && images.length === 0) {\r\n for (let j = 0; j < arr.length; j++) {\r\n const fixedUrl = fixImageUrl(arr[j])\r\n if (fixedUrl !== '') images.push(fixedUrl)\r\n }\r\n }\r\n } else {\r\n const rawUrlStr = imageUrlsRaw as string\r\n if (rawUrlStr.startsWith('[')) {\r\n const parsed = JSON.parse(rawUrlStr)\r\n if (Array.isArray(parsed) && images.length === 0) {\r\n for (let j = 0; j < parsed.length; j++) {\r\n const fixedUrl = fixImageUrl(parsed[j] as string)\r\n if (fixedUrl !== '') images.push(fixedUrl)\r\n }\r\n }\r\n } else {\r\n const fixedUrl = fixImageUrl(rawUrlStr)\r\n if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl)\r\n }\r\n }\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:1571','解析图片数组失败:', e)\r\n }\r\n }\r\n \r\n if (images.length === 0) {\r\n images.push('/static/default-product.png')\r\n }\r\n \r\n let safePrice = item.getNumber('base_price')\r\n if (safePrice == null) {\r\n const p = item.getNumber('price')\r\n safePrice = p != null ? p : 0\r\n }\r\n \r\n let safeOriginalPrice = item.getNumber('market_price')\r\n if (safeOriginalPrice == null) {\r\n const op = item.getNumber('original_price')\r\n safeOriginalPrice = op != null ? op : safePrice\r\n }\r\n \r\n let safeStock = item.getNumber('total_stock')\r\n if (safeStock == null) {\r\n let as_ = item.getNumber('available_stock')\r\n if (as_ == null) {\r\n const s = item.getNumber('stock')\r\n safeStock = s != null ? s : 0\r\n } else {\r\n safeStock = as_\r\n }\r\n }\r\n \r\n let safeSales = item.getNumber('sale_count')\r\n if (safeSales == null) {\r\n const s = item.getNumber('sales')\r\n safeSales = s != null ? s : 0\r\n }\r\n \r\n const product: Product = {\r\n id: item.getString('id') ?? '',\r\n category_id: item.getString('category_id') ?? '',\r\n merchant_id: item.getString('merchant_id') ?? '',\r\n name: item.getString('name') ?? '',\r\n description: item.getString('description') ?? '',\r\n images: images,\r\n price: safePrice,\r\n original_price: safeOriginalPrice,\r\n stock: safeStock,\r\n sales: safeSales,\r\n status: item.getNumber('status') ?? 1,\r\n created_at: item.getString('created_at') ?? '',\r\n base_price: safePrice,\r\n market_price: safeOriginalPrice,\r\n main_image_url: images.length > 0 ? images[0] : '',\r\n sale_count: safeSales,\r\n total_stock: safeStock\r\n } as Product\r\n mappedData.push(product)\r\n }\r\n\r\n return {\r\n data: mappedData,\r\n total: res2.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: res2.hasmore ?? false\r\n }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1639',`Shop products found: ${(response.data as any[]).length}`)\r\n \r\n const viewData = response.data as any[]\r\n const parsedProducts: Product[] = []\r\n for (let i = 0; i < viewData.length; i++) {\r\n parsedProducts.push(parseProductFromRaw(viewData[i]))\r\n }\r\n \r\n return {\r\n data: parsedProducts,\r\n total: response.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: response.hasmore ?? false\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1655','获取店铺商品异常:', error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n }\r\n\r\n // 获取热销商品(按销量排序)\r\n async getHotProducts(limit: number = 10): Promise<Product[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1669','[getHotProducts] 开始获取热销商品...')\r\n \r\n // 在数据库层面过滤 status获取更多数据以便手动过滤 is_hot\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')\r\n .eq('status', '1') // 使用字符串 '1'\r\n .order('sale_count', { ascending: false })\r\n .limit(limit * 5) // 获取更多数据以便过滤\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1681','获取热销商品失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n __f__('log','at utils/supabaseService.uts:1686','[getHotProducts] 原始数据条数:', rawData != null ? (rawData as any[]).length : 0)\r\n if (rawData == null) {\r\n __f__('log','at utils/supabaseService.uts:1688','[getHotProducts] 数据为空')\r\n return []\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = rawData as any[]\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n // 手动过滤 is_hot\r\n const rawIsHot = prodObj.get('is_hot')\r\n let isHotBool: boolean = false\r\n if (typeof rawIsHot == 'boolean') {\r\n isHotBool = rawIsHot as boolean\r\n } else if (typeof rawIsHot == 'number') {\r\n isHotBool = (rawIsHot as number) == 1\r\n }\r\n if (!isHotBool) continue\r\n \r\n products.push(parseProductFromRaw(item))\r\n \r\n // 达到目标数量就停止\r\n if (products.length >= limit) break\r\n }\r\n __f__('log','at utils/supabaseService.uts:1713','[getHotProducts] 最终返回商品数:', products.length)\r\n return products\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1716','获取热销商品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取按销量排序的商品(所有商品,不限制 is_hot\r\n async getProductsBySales(limit: number = 10): Promise<Product[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1724','[getProductsBySales] 开始获取销量排序商品...')\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')\r\n .eq('status', '1')\r\n .order('sale_count', { ascending: false })\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1734','获取销量排序商品失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = rawData as any[]\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n products.push(parseProductFromRaw(item))\r\n }\r\n __f__('log','at utils/supabaseService.uts:1749','[getProductsBySales] 返回商品数:', products.length)\r\n return products\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1752','获取销量排序商品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取按价格排序的商品(升序:从低到高)\r\n async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise<Product[]> {\r\n try {\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')\r\n .eq('status', '1') // 在数据库层面过滤\r\n .order('base_price', { ascending })\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1769','获取价格排序商品失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = rawData as any[]\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n products.push(parseProductFromRaw(item))\r\n }\r\n return products\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1786','获取价格排序商品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取新品(按创建时间排序,最新的在前)\r\n async getProductsByNewest(limit: number = 10): Promise<Product[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1794','[getProductsByNewest] 开始获取新品...')\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')\r\n .eq('status', '1')\r\n .order('created_at', { ascending: false })\r\n .limit(limit * 5)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1804','获取新品失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = rawData as any[]\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n // 手动过滤 is_new\r\n const rawIsNew = prodObj.get('is_new')\r\n let isNewBool: boolean = false\r\n if (typeof rawIsNew == 'boolean') {\r\n isNewBool = rawIsNew as boolean\r\n } else if (typeof rawIsNew == 'number') {\r\n isNewBool = (rawIsNew as number) == 1\r\n }\r\n if (!isNewBool) continue\r\n \r\n products.push(parseProductFromRaw(item))\r\n if (products.length >= limit) break\r\n }\r\n \r\n // 如果 is_new 商品不足,补充普通商品\r\n if (products.length < limit) {\r\n __f__('log','at utils/supabaseService.uts:1835','[getProductsByNewest] is_new商品不足补充普通商品')\r\n const addedIds = new Set<string>()\r\n for (let i = 0; i < products.length; i++) {\r\n addedIds.add(products[i].id)\r\n }\r\n \r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n const rawId = prodObj.get('id')\r\n const itemId = (typeof rawId == 'string') ? (rawId as string) : ''\r\n \r\n if (!addedIds.has(itemId)) {\r\n products.push(parseProductFromRaw(item))\r\n addedIds.add(itemId)\r\n if (products.length >= limit) break\r\n }\r\n }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1855','[getProductsByNewest] 返回商品数:', products.length)\r\n return products\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1858','获取新品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取推荐商品is_featured=true\r\n async getRecommendedProducts(limit: number = 10): Promise<Product[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1866','[getRecommendedProducts] 开始获取推荐商品...')\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')\r\n .eq('status', '1') // 在数据库层面过滤\r\n .order('sale_count', { ascending: false })\r\n .limit(limit * 5) // 获取更多数据以便过滤 is_featured\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:1875','[getRecommendedProducts] 查询完成')\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1878','获取推荐商品失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n __f__('log','at utils/supabaseService.uts:1884','[getRecommendedProducts] 数据为空')\r\n return []\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:1890','[getRecommendedProducts] 数据条数:', rawList.length)\r\n \r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n const rawFeatured = prodObj.get('is_featured')\r\n \r\n let isFeaturedBool: boolean = false\r\n if (typeof rawFeatured == 'boolean') {\r\n isFeaturedBool = rawFeatured as boolean\r\n } else if (typeof rawFeatured == 'number') {\r\n isFeaturedBool = (rawFeatured as number) == 1\r\n }\r\n if (!isFeaturedBool) {\r\n continue\r\n }\r\n \r\n products.push(parseProductFromRaw(item))\r\n if (products.length >= limit) break\r\n }\r\n return products\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1912','获取推荐商品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取特价商品这里假设没有specific flag, just use logic or tag if exists, defaulting to hot for now or just skip\r\n // Modify to use compatible logic if badge column doesn't exist\r\n async getDiscountProducts(limit: number = 10): Promise<Product[]> {\r\n return [] as Product[] // 暂无特价字段\r\n }\r\n\r\n // 获取当前用户的购物车商品(关联商品和店铺信息)\r\n async getCartItems(): Promise<CartItem[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('warn','at utils/supabaseService.uts:1928','用户未登录,无法获取购物车')\r\n return []\r\n }\r\n\r\n // 查询购物车表,并关联商品表(使用内联关联)\r\n // 注意:使用 !inner 进行内连接,或者 left join (默认)\r\n // 修改查询语法以符合 PostgREST 规范\r\n // 尝试简化查询,先只查购物车,再查商品,避免复杂的嵌套查询导致 400 错误\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1944','获取购物车失败:', response.error)\r\n return []\r\n }\r\n \r\n const cartData = response.data as any[]\r\n // __f__('log','at utils/supabaseService.uts:1949','Raw Cart Data:', JSON.stringify(cartData))\r\n \r\n if (cartData == null || cartData.length === 0) {\r\n return []\r\n }\r\n\r\n // 收集所有 product_id 和 sku_id\r\n const productIds: string[] = []\r\n const skuIds: string[] = []\r\n for (let i = 0; i < cartData.length; i++) {\r\n let item = cartData[i]\r\n let pid: string = ''\r\n let sid: string = ''\r\n if (item instanceof UTSJSONObject) {\r\n pid = item.getString('product_id') ?? ''\r\n sid = item.getString('sku_id') ?? ''\r\n } else {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n pid = itemObj.getString('product_id') ?? ''\r\n sid = itemObj.getString('sku_id') ?? ''\r\n }\r\n if (pid !== '' && !productIds.includes(pid)) {\r\n productIds.push(pid)\r\n }\r\n if (sid !== '' && !skuIds.includes(sid)) {\r\n skuIds.push(sid)\r\n }\r\n }\r\n\r\n // 批量查询商品详情 (使用视图关联店铺信息,修复字段名 specification -> attributes)\r\n const productMap = new Map<string, any>()\r\n \r\n if (productIds.length > 0) {\r\n // Convert string array to any array for .in()\r\n const productIdsAny: any[] = []\r\n for(let i=0; i<productIds.length; i++) {\r\n productIdsAny.push(productIds[i])\r\n }\r\n\r\n const productRes = await supa\r\n .from('ml_products')\r\n .select('*')\r\n .in('id', productIdsAny)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:1994','[getCartItems] 商品查询结果:', productRes.error != null ? 'error' : 'success', productRes.error)\r\n if (productRes.error == null && productRes.data != null) {\r\n const products = productRes.data as any[]\r\n __f__('log','at utils/supabaseService.uts:1997','[getCartItems] 商品数量:', products.length)\r\n for (let i = 0; i < products.length; i++) {\r\n let p = products[i]\r\n let pid: string = ''\r\n let pname: string = ''\r\n \r\n if (p instanceof UTSJSONObject) {\r\n pid = p.getString('id') ?? ''\r\n pname = p.getString('name') ?? ''\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject\r\n pid = pObj.getString('id') ?? ''\r\n pname = pObj.getString('name') ?? ''\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2012','[getCartItems] 商品:', pid, pname)\r\n \r\n if (pid !== '') {\r\n productMap.set(pid, p)\r\n }\r\n }\r\n }\r\n }\r\n\r\n // 批量查询店铺信息\r\n const shopMap = new Map<string, string>()\r\n const merchantIds: string[] = []\r\n // 遍历 productMap 获取 merchant_id\r\n productMap.forEach((p: any, pid: string) => {\r\n let mid: string = ''\r\n if (p instanceof UTSJSONObject) {\r\n mid = p.getString('merchant_id') ?? ''\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject\r\n mid = pObj.getString('merchant_id') ?? ''\r\n }\r\n if (mid !== '' && !merchantIds.includes(mid)) {\r\n merchantIds.push(mid)\r\n }\r\n })\r\n \r\n if (merchantIds.length > 0) {\r\n const merchantIdsAny: any[] = []\r\n for(let i=0; i<merchantIds.length; i++) {\r\n merchantIdsAny.push(merchantIds[i])\r\n }\r\n const shopRes = await supa\r\n .from('ml_shops')\r\n .select('merchant_id,shop_name')\r\n .in('merchant_id', merchantIdsAny)\r\n .execute()\r\n \r\n if (shopRes.error == null && shopRes.data != null) {\r\n const shops = shopRes.data as any[]\r\n for (let i = 0; i < shops.length; i++) {\r\n let s = shops[i]\r\n let mid: string = ''\r\n let sname: string = ''\r\n if (s instanceof UTSJSONObject) {\r\n mid = s.getString('merchant_id') ?? ''\r\n sname = s.getString('shop_name') ?? ''\r\n } else {\r\n const sObj = JSON.parse(JSON.stringify(s)) as UTSJSONObject\r\n mid = sObj.getString('merchant_id') ?? ''\r\n sname = sObj.getString('shop_name') ?? ''\r\n }\r\n if (mid !== '') {\r\n shopMap.set(mid, sname)\r\n }\r\n }\r\n }\r\n }\r\n\r\n // 批量查询 SKU 详情\r\n const skuMap = new Map<string, any>()\r\n if (skuIds.length > 0) {\r\n const skuIdsAny: any[] = []\r\n for(let i=0; i<skuIds.length; i++) {\r\n skuIdsAny.push(skuIds[i])\r\n }\r\n const skuRes = await supa\r\n .from('ml_product_skus')\r\n .select('*')\r\n .in('id', skuIdsAny)\r\n .execute()\r\n\r\n if (skuRes.error == null && skuRes.data != null) {\r\n const skus = skuRes.data as any[]\r\n for (let i = 0; i < skus.length; i++) {\r\n let s = skus[i]\r\n let sid: string = ''\r\n if (s instanceof UTSJSONObject) {\r\n sid = s.getString('id') ?? ''\r\n } else {\r\n const sObj = JSON.parse(JSON.stringify(s)) as UTSJSONObject\r\n sid = sObj.getString('id') ?? ''\r\n }\r\n\r\n if (sid !== '') {\r\n skuMap.set(sid, s)\r\n }\r\n }\r\n }\r\n }\r\n \r\n // 处理返回数据构建CartItem数组\r\n const cartItems: CartItem[] = []\r\n if ((cartData as any[]) != null) { // Simplify: cartData is already any[]\r\n const cartArray = cartData as any[]\r\n for (let i = 0; i < cartArray.length; i++) {\r\n let item = cartArray[i]\r\n let itemId: string = ''\r\n let userIdVal: string = ''\r\n let productId: string = ''\r\n let skuId: string = ''\r\n let quantity: number = 0\r\n let selected: boolean = false\r\n let createdAt: string = ''\r\n let updatedAt: string = ''\r\n let cartMerchantId: string = ''\r\n \r\n if (item instanceof UTSJSONObject) {\r\n itemId = item.getString('id') ?? ''\r\n userIdVal = item.getString('user_id') ?? ''\r\n productId = item.getString('product_id') ?? ''\r\n skuId = item.getString('sku_id') ?? ''\r\n quantity = item.getNumber('quantity') ?? 0\r\n selected = item.getBoolean('selected') ?? false\r\n createdAt = item.getString('created_at') ?? ''\r\n updatedAt = item.getString('updated_at') ?? ''\r\n cartMerchantId = item.getString('merchant_id') ?? ''\r\n } else {\r\n const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n itemId = iObj.getString('id') ?? ''\r\n userIdVal = iObj.getString('user_id') ?? ''\r\n productId = iObj.getString('product_id') ?? ''\r\n skuId = iObj.getString('sku_id') ?? ''\r\n quantity = iObj.getNumber('quantity') ?? 0\r\n selected = iObj.getBoolean('selected') ?? false\r\n createdAt = iObj.getString('created_at') ?? ''\r\n updatedAt = iObj.getString('updated_at') ?? ''\r\n cartMerchantId = iObj.getString('merchant_id') ?? ''\r\n }\r\n \r\n const product = productMap.get(productId)\r\n const sku = (skuId !== '' && skuMap.has(skuId)) ? skuMap.get(skuId) : null\r\n \r\n __f__('log','at utils/supabaseService.uts:2144','[getCartItems] 处理购物车项:', itemId, 'productId:', productId, 'product存在:', product != null, 'sku存在:', sku != null)\r\n \r\n let merchantId: string = cartMerchantId\r\n let productName: string = ''\r\n let productImage: string = ''\r\n let productPrice: number = 0\r\n let productSpec: string = ''\r\n let shopNameStr: string = '未知店铺'\r\n\r\n if (product != null) {\r\n __f__('log','at utils/supabaseService.uts:2154','[getCartItems] product类型:', typeof product, 'instanceof UTSJSONObject:', product instanceof UTSJSONObject)\r\n if (product instanceof UTSJSONObject) {\r\n // 优先使用购物车中的 merchant_id如果没有则使用商品中的\r\n if (merchantId == '') {\r\n merchantId = product.getString('merchant_id') ?? ''\r\n }\r\n productName = product.getString('name') ?? ''\r\n productImage = product.getString('main_image_url') ?? ''\r\n productPrice = product.getNumber('base_price') ?? 0\r\n __f__('log','at utils/supabaseService.uts:2163','[getCartItems] 从UTSJSONObject获取: name=', productName, 'price=', productPrice)\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject\r\n if (merchantId == '') {\r\n merchantId = pObj.getString('merchant_id') ?? ''\r\n }\r\n productName = pObj.getString('name') ?? ''\r\n productImage = pObj.getString('main_image_url') ?? ''\r\n productPrice = pObj.getNumber('base_price') ?? 0\r\n __f__('log','at utils/supabaseService.uts:2172','[getCartItems] 从普通对象获取: name=', productName, 'price=', productPrice)\r\n }\r\n // 从 shopMap 获取店铺名称\r\n if (merchantId !== '' && shopMap.has(merchantId)) {\r\n shopNameStr = shopMap.get(merchantId) ?? '未知店铺'\r\n }\r\n }\r\n\r\n // 如果有SKU信息覆盖价格、图片和规格\r\n if (sku != null) {\r\n if (sku instanceof UTSJSONObject) {\r\n const skuPrice = sku.getNumber('price')\r\n if (skuPrice != null && skuPrice > 0) {\r\n productPrice = skuPrice\r\n }\r\n const skuImg = sku.getString('image_url')\r\n if (skuImg != null && skuImg !== '') {\r\n productImage = skuImg\r\n }\r\n \r\n const specRaw = sku.get('specifications')\r\n if (specRaw != null) {\r\n if (typeof specRaw === 'string') {\r\n productSpec = specRaw\r\n } else if (specRaw instanceof UTSJSONObject) {\r\n const keys = ['规格', '颜色', '尺码', '容量', '版本', '型号']\r\n const result: string[] = []\r\n for (let k = 0; k < keys.length; k++) {\r\n const key = keys[k]\r\n const val = specRaw.get(key)\r\n if (val != null && val !== '') {\r\n result.push(`${val}`)\r\n }\r\n }\r\n if (result.length > 0) {\r\n productSpec = result.join(' ')\r\n } else {\r\n const allKeys = UTSJSONObject.keys(specRaw)\r\n const parts: string[] = []\r\n for(let k = 0; k < allKeys.length; k++) {\r\n let val = specRaw.get(allKeys[k])\r\n if (val != null) {\r\n parts.push(`${val}`)\r\n }\r\n }\r\n productSpec = parts.join(' ')\r\n }\r\n } else {\r\n try {\r\n let jsonStr = JSON.stringify(specRaw)\r\n productSpec = jsonStr.replace(/[\"{}]/g, '').replace(/,/g, ' ').replace(/:/g, ' ')\r\n } catch (e) {}\r\n }\r\n }\r\n } else {\r\n const sObj = JSON.parse(JSON.stringify(sku)) as UTSJSONObject\r\n const skuPrice = sObj.getNumber('price') ?? 0\r\n if (skuPrice > 0) productPrice = skuPrice\r\n\r\n const skuImg = sObj.getString('image_url') ?? ''\r\n if (skuImg !== '') productImage = skuImg\r\n\r\n const specRaw = sObj.get('specifications')\r\n if (specRaw != null) {\r\n if (typeof specRaw === 'string') {\r\n productSpec = specRaw\r\n } else if (specRaw instanceof UTSJSONObject) {\r\n const keys = ['规格', '颜色', '尺码', '容量', '版本', '型号']\r\n const result: string[] = []\r\n for (let k = 0; k < keys.length; k++) {\r\n const key = keys[k]\r\n const val = specRaw.get(key)\r\n if (val != null && val !== '') {\r\n result.push(`${val}`)\r\n }\r\n }\r\n if (result.length > 0) {\r\n productSpec = result.join(' ')\r\n } else {\r\n const allKeys = UTSJSONObject.keys(specRaw)\r\n const parts: string[] = []\r\n for(let k = 0; k < allKeys.length; k++) {\r\n let val = specRaw.get(allKeys[k])\r\n if (val != null) {\r\n parts.push(`${val}`)\r\n }\r\n }\r\n productSpec = parts.join(' ')\r\n }\r\n } else {\r\n try {\r\n let jsonStr = JSON.stringify(specRaw)\r\n productSpec = jsonStr.replace(/[\"{}]/g, '').replace(/,/g, ' ').replace(/:/g, ' ')\r\n } catch (e) {}\r\n }\r\n }\r\n }\r\n }\r\n\r\n let shopIdStr = merchantId != '' ? merchantId : 'unknown_shop'\r\n\r\n \r\n cartItems.push({\r\n id: itemId,\r\n user_id: userIdVal,\r\n product_id: productId,\r\n sku_id: skuId,\r\n merchant_id: merchantId,\r\n quantity: quantity,\r\n selected: selected,\r\n product_name: productName,\r\n product_image: productImage,\r\n product_price: productPrice,\r\n product_specification: productSpec,\r\n shop_id: shopIdStr,\r\n shop_name: shopNameStr,\r\n created_at: createdAt,\r\n updated_at: updatedAt\r\n } as CartItem)\r\n }\r\n }\r\n \r\n return cartItems\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2296','获取购物车异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取用户通知 (系统、活动、订单)\r\n async getUserNotifications(type: string | null = null): Promise<Notification[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:2304','[getUserNotifications] 开始获取通知')\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return []\r\n\r\n let query = supa\r\n .from('ml_notifications')\r\n .select('*')\r\n .eq('user_id', userId)\r\n \r\n if (type != null) {\r\n query = query.eq('type', type)\r\n }\r\n \r\n const response = await query.order('created_at', { ascending: false }).execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2320','获取通知失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) return []\r\n \r\n const notifications: Notification[] = []\r\n const rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:2329','[getUserNotifications] 获取到通知数量:', rawList.length)\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const noteObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n const getSafeString = (key: string): string => {\r\n const val = noteObj.get(key)\r\n if (val == null) return ''\r\n if (typeof val == 'string') return val\r\n return ''\r\n }\r\n \r\n const getSafeNumber = (key: string): number => {\r\n const val = noteObj.get(key)\r\n if (val == null) return 0\r\n if (typeof val == 'number') return val\r\n return 0\r\n }\r\n \r\n const getSafeBoolean = (key: string): boolean => {\r\n const val = noteObj.get(key)\r\n if (val == null) return false\r\n if (typeof val == 'boolean') return val\r\n if (typeof val == 'number') return (val as number) == 1\r\n return false\r\n }\r\n \r\n const note: Notification = {\r\n id: getSafeString('id'),\r\n user_id: getSafeString('user_id'),\r\n type: getSafeString('type'),\r\n title: getSafeString('title'),\r\n content: getSafeString('content'),\r\n is_read: getSafeBoolean('is_read'),\r\n icon_url: getSafeString('icon_url'),\r\n link_url: getSafeString('link_url'),\r\n extra_data: getSafeString('extra_data'),\r\n created_at: getSafeString('created_at')\r\n }\r\n notifications.push(note)\r\n }\r\n return notifications\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2373','获取通知异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取聊天会话列表\r\n async getChatRooms(): Promise<ChatRoom[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:2381','[getChatRooms] 开始获取聊天会话')\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return []\r\n\r\n const response = await supa\r\n .from('ml_chat_rooms')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .order('updated_at', { ascending: false })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2393','获取聊天会话失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) return []\r\n \r\n const rooms: ChatRoom[] = []\r\n const rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:2402','[getChatRooms] 获取到会话数量:', rawList.length)\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const roomObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n const getSafeString = (key: string): string => {\r\n const val = roomObj.get(key)\r\n if (val == null) return ''\r\n if (typeof val == 'string') return val\r\n return ''\r\n }\r\n \r\n const getSafeNumber = (key: string): number => {\r\n const val = roomObj.get(key)\r\n if (val == null) return 0\r\n if (typeof val == 'number') return val\r\n return 0\r\n }\r\n \r\n const getSafeBoolean = (key: string): boolean => {\r\n const val = roomObj.get(key)\r\n if (val == null) return false\r\n if (typeof val == 'boolean') return val\r\n if (typeof val == 'number') return (val as number) == 1\r\n return false\r\n }\r\n \r\n const room: ChatRoom = {\r\n id: getSafeString('id'),\r\n user_id: getSafeString('user_id'),\r\n merchant_id: getSafeString('merchant_id'),\r\n shop_name: getSafeString('shop_name'),\r\n shop_logo: getSafeString('shop_logo'),\r\n last_message: getSafeString('last_message'),\r\n last_message_at: getSafeString('last_message_at'),\r\n unread_count: getSafeNumber('unread_count'),\r\n is_top: getSafeBoolean('is_top'),\r\n created_at: getSafeString('created_at'),\r\n updated_at: getSafeString('updated_at')\r\n }\r\n rooms.push(room)\r\n }\r\n return rooms\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2447','获取聊天会话异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取用户聊天消息\r\n async getUserChatMessages(): Promise<ChatMessage[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return []\r\n\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .select('*')\r\n .or(`sender_id.eq.${userId},receiver_id.eq.${userId}`)\r\n .order('created_at', { ascending: false })\r\n .limit(50)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2467','获取聊天记录失败:', response.error)\r\n return []\r\n }\r\n return response.data as ChatMessage[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2472','获取聊天记录异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取与特定商家的聊天记录 (合并版本)\r\n async getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise<ChatMessage[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:2480','[getChatMessages] 开始获取聊天记录merchantId:', merchantId, 'page:', page)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return []\r\n\r\n const fromIndex = (page - 1) * pageSize\r\n const toIndex = fromIndex + pageSize - 1\r\n\r\n // 使用 or 组合精确条件查询:(我发给商家) OR (商家发给我)\r\n const queryStr = `and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`\r\n \r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .select('*')\r\n .or(queryStr)\r\n .order('created_at', { ascending: false }) // 最新在前\r\n .range(fromIndex, toIndex)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2499','getChatMessages error:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) return []\r\n \r\n const messages: ChatMessage[] = []\r\n const rawList = rawData as any[]\r\n __f__('log','at utils/supabaseService.uts:2508','[getChatMessages] 获取到消息数量:', rawList.length)\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const msgObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n const getSafeString = (key: string): string => {\r\n const val = msgObj.get(key)\r\n if (val == null) return ''\r\n return val.toString()\r\n }\r\n \r\n const getSafeBoolean = (key: string): boolean => {\r\n const val = msgObj.get(key)\r\n if (val == null) return false\r\n if (typeof val == 'boolean') return val as boolean\r\n return (val.toString() == '1' || val.toString() == 'true')\r\n }\r\n \r\n const msg: ChatMessage = {\r\n id: getSafeString('id'),\r\n session_id: getSafeString('session_id'),\r\n sender_id: getSafeString('sender_id'),\r\n receiver_id: getSafeString('receiver_id'),\r\n content: getSafeString('content'),\r\n msg_type: getSafeString('msg_type'),\r\n is_read: getSafeBoolean('is_read'),\r\n is_from_user: getSafeBoolean('is_from_user'),\r\n extra_data: getSafeString('extra_data'),\r\n created_at: getSafeString('created_at')\r\n }\r\n messages.push(msg)\r\n }\r\n return messages\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2543','获取聊天记录异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 发送聊天消息\r\n async sendChatMessage(content: string, toId: string | null = null, type: string = 'text'): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n const payload = {\r\n sender_id: userId,\r\n content: content,\r\n msg_type: type,\r\n is_from_user: true,\r\n created_at: new Date().toISOString()\r\n } as UTSJSONObject\r\n if (toId != null) {\r\n payload.set('receiver_id', toId)\r\n }\r\n\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .insert(payload)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2571','发送消息失败:', response.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2576','发送消息异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 模拟客服回复\r\n async simulateServiceReply(content: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .insert({\r\n receiver_id: userId,\r\n content: content,\r\n msg_type: 'text',\r\n is_from_user: false,\r\n created_at: new Date().toISOString()\r\n })\r\n .execute()\r\n return response.error == null\r\n } catch (e) {\r\n return false\r\n }\r\n }\r\n\r\n // 添加商品到购物车\r\n async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2608','用户未登录,无法添加商品到购物车')\r\n return false\r\n }\r\n \r\n const realSkuId = (skuId != null && skuId.length > 0) ? skuId : null\r\n const realMerchantId = (merchantId != null && merchantId.length > 0) ? merchantId : null\r\n\r\n // 检查商品是否已在购物车中\r\n // 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器\r\n let query = supa\r\n .from('ml_shopping_cart')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .eq('product_id', productId)\r\n \r\n if (realSkuId != null) {\r\n query = query.eq('sku_id', realSkuId)\r\n } else {\r\n query = query.is('sku_id', null)\r\n }\r\n\r\n const existingResponse = await query.single().execute()\r\n\r\n let existingItem: any | null = null\r\n \r\n if (existingResponse.data != null) {\r\n const rawData = existingResponse.data as any\r\n if (Array.isArray(rawData)) {\r\n if (rawData.length > 0) {\r\n existingItem = rawData[0]\r\n }\r\n } else {\r\n existingItem = rawData\r\n }\r\n }\r\n\r\n let response: AkReqResponse<any>\r\n if (existingItem != null) {\r\n // 商品已存在,更新数量\r\n __f__('log','at utils/supabaseService.uts:2647','Found existing cart item:', JSON.stringify(existingItem))\r\n\r\n // 确保 existingItem.id 存在\r\n let itemId: string | null = null\r\n let itemQty: any | null = null\r\n\r\n if (existingItem instanceof UTSJSONObject) {\r\n itemId = existingItem.getString('id')\r\n itemQty = existingItem.getNumber('quantity')\r\n } else {\r\n const obj = JSON.parse(JSON.stringify(existingItem)) as UTSJSONObject\r\n itemId = obj.getString('id')\r\n itemQty = obj.getNumber('quantity')\r\n }\r\n\r\n if (itemId != null) {\r\n let currentQty = 0\r\n if (typeof itemQty === 'number') {\r\n currentQty = itemQty as number\r\n } else {\r\n const qStr = `${itemQty ?? 0}`\r\n currentQty = parseInt(qStr)\r\n }\r\n const newQty = currentQty + quantity\r\n\r\n response = await supa\r\n .from('ml_shopping_cart')\r\n .update({\r\n quantity: newQty,\r\n merchant_id: realMerchantId,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', itemId)\r\n .execute()\r\n } else {\r\n __f__('error','at utils/supabaseService.uts:2682','购物车已有商品但缺少ID无法更新. Data:', JSON.stringify(existingItem))\r\n return false\r\n }\r\n } else {\r\n // 商品不存在,添加新记录\r\n const cartPayload = new UTSJSONObject()\r\n cartPayload.set('user_id', userId)\r\n cartPayload.set('product_id', productId)\r\n cartPayload.set('sku_id', realSkuId)\r\n cartPayload.set('quantity', quantity)\r\n cartPayload.set('selected', true)\r\n cartPayload.set('created_at', new Date().toISOString())\r\n cartPayload.set('updated_at', new Date().toISOString())\r\n if (realMerchantId != null) {\r\n cartPayload.set('merchant_id', realMerchantId)\r\n }\r\n \r\n response = await supa\r\n .from('ml_shopping_cart')\r\n .insert(cartPayload)\r\n .execute()\r\n }\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2706','添加商品到购物车失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2712','添加商品到购物车异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 更新购物车商品数量\r\n async updateCartItemQuantity(cartItemId: string, quantity: number): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2722','用户未登录,无法更新购物车')\r\n return false\r\n }\r\n\r\n if (quantity < 1) {\r\n // 数量小于1时删除商品\r\n return await this.deleteCartItem(cartItemId)\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .update({\r\n quantity: quantity,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', cartItemId)\r\n .eq('user_id', userId)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2742','更新购物车商品数量失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2748','更新购物车商品数量异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 更新购物车商品选中状态\r\n async updateCartItemSelection(cartItemId: string, selected: boolean): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2758','用户未登录,无法更新购物车')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .update({\r\n selected: selected,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', cartItemId)\r\n .eq('user_id', userId)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2773','更新购物车商品选中状态失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2779','更新购物车商品选中状态异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 批量更新购物车商品选中状态\r\n async batchUpdateCartItemSelection(cartItemIds: string[], selected: boolean): Promise<boolean> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:2787','[batchUpdateCartItemSelection] 开始批量更新')\r\n __f__('log','at utils/supabaseService.uts:2788','[batchUpdateCartItemSelection] cartItemIds:', JSON.stringify(cartItemIds))\r\n __f__('log','at utils/supabaseService.uts:2789','[batchUpdateCartItemSelection] cartItemIds length:', cartItemIds.length)\r\n __f__('log','at utils/supabaseService.uts:2790','[batchUpdateCartItemSelection] selected:', selected)\r\n \r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2794','用户未登录,无法更新购物车')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .update({\r\n selected: selected,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('user_id', userId)\r\n .in('id', cartItemIds as any[])\r\n .execute()\r\n\r\n __f__('log','at utils/supabaseService.uts:2808','[batchUpdateCartItemSelection] response.error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:2809','[batchUpdateCartItemSelection] response.data:', JSON.stringify(response.data))\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2812','批量更新购物车商品选中状态失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2818','批量更新购物车商品选中状态异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 删除购物车商品\r\n async deleteCartItem(cartItemId: string): Promise<boolean> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:2826','正在执行删除购物车商品ID:', cartItemId)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2829','用户未登录,无法删除购物车商品')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .eq('id', cartItemId)\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2841','删除购物车商品失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2847','删除购物车商品异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 批量删除购物车商品\r\n async batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:2855','[batchDeleteCartItems] 开始删除, ids:', cartItemIds.length)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2858','用户未登录,无法删除购物车商品')\r\n return false\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:2862','[batchDeleteCartItems] userId:', userId)\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .eq('user_id', userId)\r\n .in('id', cartItemIds as any[])\r\n .delete()\r\n .execute()\r\n\r\n __f__('log','at utils/supabaseService.uts:2870','[batchDeleteCartItems] response.error:', response.error)\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2872','批量删除购物车商品失败:', response.error)\r\n return false\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:2876','[batchDeleteCartItems] 删除成功')\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2879','批量删除购物车商品异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 清空购物车\r\n async clearCart(): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2889','用户未登录,无法清空购物车')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2900','清空购物车失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2906','清空购物车异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 获取当前用户的所有地址\r\n async getAddresses(): Promise<UserAddress[]> {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('warn','at utils/supabaseService.uts:2915','[getAddresses] 用户未登录,无法获取地址')\r\n return []\r\n }\r\n\r\n try {\r\n __f__('log','at utils/supabaseService.uts:2920','[getAddresses] 查询地址, userId:', userId)\r\n \r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .order('is_default', { ascending: false })\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:2930','[getAddresses] response.error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:2931','[getAddresses] response.data:', JSON.stringify(response.data))\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2934','[getAddresses] 获取地址失败:', response.error)\r\n return []\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n return []\r\n }\r\n \r\n const result: UserAddress[] = []\r\n const rawData = data as any[]\r\n for (let i = 0; i < rawData.length; i++) {\r\n const item = rawData[i]\r\n let itemObj: UTSJSONObject\r\n if (item instanceof UTSJSONObject) {\r\n itemObj = item as UTSJSONObject\r\n } else {\r\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n }\r\n \r\n const addr: UserAddress = {\r\n id: itemObj.getString('id') ?? '',\r\n user_id: itemObj.getString('user_id') ?? '',\r\n recipient_name: itemObj.getString('receiver_name') ?? itemObj.getString('recipient_name') ?? '',\r\n phone: itemObj.getString('receiver_phone') ?? itemObj.getString('phone') ?? '',\r\n province: itemObj.getString('province') ?? '',\r\n city: itemObj.getString('city') ?? '',\r\n district: itemObj.getString('district') ?? '',\r\n detail_address: itemObj.getString('address_detail') ?? itemObj.getString('detail_address') ?? '',\r\n is_default: itemObj.getBoolean('is_default') ?? false,\r\n label: itemObj.getString('label') ?? '',\r\n created_at: itemObj.getString('created_at') ?? '',\r\n updated_at: itemObj.getString('updated_at') ?? ''\r\n }\r\n result.push(addr)\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2971','[getAddresses] 返回地址数量:', result.length)\r\n return result\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2974','[getAddresses] 获取地址异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 根据ID获取地址详情\r\n async getAddressById(addressId: string): Promise<UserAddress | null> {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('warn','at utils/supabaseService.uts:2983','用户未登录,无法获取地址')\r\n return null\r\n }\r\n\r\n try {\r\n const query = supa\r\n .from('ml_user_addresses')\r\n .select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')\r\n .eq('id', addressId)\r\n .eq('user_id', userId)\r\n .single()\r\n \r\n const response = await query.execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2998','获取地址详情失败:', response.error)\r\n return null\r\n }\r\n \r\n return response.data as UserAddress\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:3004','获取地址详情异常:', error)\r\n return null\r\n }\r\n }\r\n\r\n // 添加新地址\r\n async addAddress(address: AddAddressParams): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:3014','用户未登录,无法添加地址')\r\n return false\r\n }\r\n\r\n // 如果设置为默认地址,需要先取消其他默认地址\r\n if (address.is_default == true) {\r\n await this.clearDefaultAddress(userId)\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .insert({\r\n user_id: userId,\r\n receiver_name: address.recipient_name,\r\n receiver_phone: address.phone,\r\n province: address.province,\r\n city: address.city,\r\n district: address.district,\r\n address_detail: address.detail_address,\r\n postal_code: address.postal_code ?? null,\r\n is_default: address.is_default ?? false,\r\n created_at: new Date().toISOString(),\r\n updated_at: new Date().toISOString()\r\n })\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3041','添加地址失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:3047','添加地址异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 更新地址\r\n async updateAddress(addressId: string, address: UpdateAddressParams): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:3057','用户未登录,无法更新地址')\r\n return false\r\n }\r\n\r\n // 如果设置为默认地址,需要先取消其他默认地址\r\n if (address.is_default == true) {\r\n await this.clearDefaultAddress(userId)\r\n }\r\n \r\n // 构造更新数据,映射字段名到数据库列名\r\n const updateData = {}\r\n if (address.recipient_name != null) updateData['receiver_name'] = address.recipient_name\r\n if (address.phone != null) updateData['receiver_phone'] = address.phone\r\n if (address.province != null) updateData['province'] = address.province\r\n if (address.city != null) updateData['city'] = address.city\r\n if (address.district != null) updateData['district'] = address.district\r\n if (address.detail_address != null) updateData['address_detail'] = address.detail_address\r\n if (address.postal_code != null) updateData['postal_code'] = address.postal_code\r\n if (address.is_default != null) updateData['is_default'] = address.is_default\r\n if (address.label != null) updateData['label'] = address.label\r\n updateData['updated_at'] = new Date().toISOString()\r\n\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .update(updateData)\r\n .eq('id', addressId)\r\n .eq('user_id', userId)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3087','更新地址失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:3093','更新地址异常:', error)\r\n return false\r\n }\r\n }\r\n \r\n // 确认收货\r\n async confirmReceipt(orderId: string): Promise<ConfirmReceiptResponse> {\r\n __f__('log','at utils/supabaseService.uts:3100','[confirmReceipt] 开始确认收货, orderId:', orderId)\r\n try {\r\n const userId = this.getCurrentUserId()\r\n __f__('log','at utils/supabaseService.uts:3103','[confirmReceipt] userId:', userId)\r\n if (userId == null) {\r\n return { success: false, error: '用户未登录' }\r\n }\r\n\r\n const updateData = new UTSJSONObject()\r\n updateData.set('order_status', 4)\r\n updateData.set('delivered_at', new Date().toISOString())\r\n updateData.set('completed_at', new Date().toISOString())\r\n updateData.set('updated_at', new Date().toISOString())\r\n \r\n __f__('log','at utils/supabaseService.uts:3114','[confirmReceipt] 准备更新订单状态, updateData:', JSON.stringify(updateData))\r\n\r\n const response = await supa\r\n .from('ml_orders')\r\n .update(updateData)\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3123','[confirmReceipt] response.status:', response.status)\r\n __f__('log','at utils/supabaseService.uts:3124','[confirmReceipt] response.error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:3125','[confirmReceipt] response.data:', JSON.stringify(response.data))\r\n \r\n // 检查 HTTP 状态码\r\n if (response.status != null && response.status >= 400) {\r\n // 尝试从 response.data 中提取错误信息\r\n let errorMsg = '请求失败'\r\n if (response.data != null) {\r\n try {\r\n const errorData = response.data as UTSJSONObject\r\n const msg = errorData.getString('message')\r\n if (msg != null) {\r\n errorMsg = msg\r\n }\r\n } catch (e) {\r\n // ignore\r\n }\r\n }\r\n __f__('log','at utils/supabaseService.uts:3142','[confirmReceipt] HTTP错误:', response.status, errorMsg)\r\n return { success: false, error: errorMsg }\r\n }\r\n \r\n if (response.error != null) {\r\n return { success: false, error: response.error.message }\r\n }\r\n \r\n // 检查 response.data 是否包含错误代码\r\n if (response.data != null) {\r\n try {\r\n const respData = response.data as UTSJSONObject\r\n const errorCode = respData.getString('code')\r\n if (errorCode != null) {\r\n const errorMsg = respData.getString('message') ?? '数据库错误'\r\n __f__('log','at utils/supabaseService.uts:3157','[confirmReceipt] 数据库错误:', errorCode, errorMsg)\r\n return { success: false, error: errorMsg }\r\n }\r\n } catch (e) {\r\n // ignore\r\n }\r\n }\r\n \r\n // 检查是否有数据被更新\r\n if (response.data == null || (Array.isArray(response.data) && response.data.length === 0)) {\r\n __f__('log','at utils/supabaseService.uts:3167','[confirmReceipt] 没有数据被更新,可能订单不存在或无权限')\r\n return { success: false, error: '订单不存在或无权限更新' }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3171','[confirmReceipt] 确认收货成功')\r\n return { success: true }\r\n } catch (e: any) {\r\n __f__('error','at utils/supabaseService.uts:3174','[confirmReceipt] 异常:', e)\r\n return { success: false, error: e.message }\r\n }\r\n }\r\n\r\n // 取消订单\r\n async cancelOrder(orderId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_orders')\r\n .update({\r\n order_status: 5,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3198','取消订单失败:', response.error)\r\n return false\r\n }\r\n \r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3204','取消订单异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 删除订单\r\n async deleteOrder(orderId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_orders')\r\n .delete()\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3225','删除订单失败:', response.error)\r\n return false\r\n }\r\n \r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3231','删除订单异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 确认收货\r\n async confirmOrderReceived(orderId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_orders')\r\n .update({\r\n order_status: 4,\r\n shipping_status: 3,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3256','确认收货失败:', response.error)\r\n return false\r\n }\r\n \r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3262','确认收货异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 删除地址\r\n async deleteAddress(addressId: string): Promise<boolean> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:3270','正在执行删除地址ID:', addressId)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:3273','用户未登录,无法删除地址')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .eq('id', addressId)\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3285','删除地址失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:3291','删除地址异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 清除默认地址(内部使用)\r\n private async clearDefaultAddress(userId: string): Promise<void> {\r\n try {\r\n await supa\r\n .from('ml_user_addresses')\r\n .update({\r\n is_default: false,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('user_id', userId)\r\n .eq('is_default', true)\r\n .execute()\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:3309','清除默认地址异常:', error)\r\n }\r\n }\r\n\r\n // 获取用户资料\r\n async getUserProfile(): Promise<any | null> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return null\r\n \r\n // 联合查询 auth user 和 profile\r\n // 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles\r\n // 注意:使用 limit(1) 替代 single() 以避免 Android 端类型转换错误\r\n const response = await supa\r\n .from('ml_user_profiles')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .limit(1)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n // 如果不存在 profile可能只有 auth user这里暂时返回空或创建默认\r\n return null\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) return null\r\n \r\n // 作为数组处理\r\n const rawList = rawData as any[]\r\n if (rawList.length == 0) return null\r\n \r\n return rawList[0]\r\n } catch (e) {\r\n return null\r\n }\r\n }\r\n \r\n // 创建订单\r\n async createOrder(orderData: CreateOrderParams): Promise<string | null> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:3352','CreateOrder: User not logged in')\r\n return null\r\n }\r\n \r\n const orderNo = 'ML' + Date.now() + Math.floor(Math.random() * 1000)\r\n \r\n let merchantId = orderData.merchant_id\r\n __f__('log','at utils/supabaseService.uts:3359','[CreateOrder] 原始 merchant_id:', merchantId)\r\n if (merchantId == null || merchantId == '' || merchantId == 'unknown') {\r\n __f__('warn','at utils/supabaseService.uts:3361','[CreateOrder] merchant_id 为空或无效,将使用 userId 作为 fallback')\r\n merchantId = userId\r\n }\r\n __f__('log','at utils/supabaseService.uts:3364','[CreateOrder] 最终使用的 merchant_id:', merchantId)\r\n \r\n let shippingAddrStr = '{}'\r\n if (orderData.shipping_address != null) {\r\n if (typeof orderData.shipping_address === 'string') {\r\n shippingAddrStr = orderData.shipping_address\r\n } else {\r\n shippingAddrStr = JSON.stringify(orderData.shipping_address)\r\n }\r\n }\r\n \r\n const orderPayload = new UTSJSONObject()\r\n orderPayload.set('user_id', userId)\r\n orderPayload.set('merchant_id', merchantId)\r\n orderPayload.set('order_no', orderNo)\r\n orderPayload.set('product_amount', orderData.product_amount)\r\n orderPayload.set('shipping_fee', orderData.shipping_fee)\r\n orderPayload.set('total_amount', orderData.total_amount)\r\n orderPayload.set('paid_amount', 0)\r\n orderPayload.set('shipping_address', shippingAddrStr)\r\n orderPayload.set('order_status', 1)\r\n orderPayload.set('payment_status', 1)\r\n orderPayload.set('shipping_status', 1)\r\n orderPayload.set('created_at', new Date().toISOString())\r\n orderPayload.set('updated_at', new Date().toISOString())\r\n \r\n __f__('log','at utils/supabaseService.uts:3390','[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload))\r\n __f__('log','at utils/supabaseService.uts:3391','[CreateOrder] 期望的订单号:', orderNo)\r\n \r\n const orderResponse = await supa\r\n .from('ml_orders')\r\n .insert(orderPayload)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3398','[CreateOrder] insert 完成')\r\n __f__('log','at utils/supabaseService.uts:3399','[CreateOrder] orderResponse.error:', orderResponse.error)\r\n \r\n if (orderResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3402','[CreateOrder] 创建订单失败:', orderResponse.error)\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3406','[CreateOrder] 开始查询新创建的订单, order_no:', orderNo)\r\n \r\n const queryResponse = await supa\r\n .from('ml_orders')\r\n .select('id, order_no')\r\n .eq('order_no', orderNo)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3414','[CreateOrder] queryResponse.error:', queryResponse.error)\r\n __f__('log','at utils/supabaseService.uts:3415','[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data))\r\n \r\n if (queryResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3418','[CreateOrder] 查询订单失败:', queryResponse.error)\r\n return null\r\n }\r\n \r\n const queryData = queryResponse.data as any\r\n let orderId = ''\r\n \r\n __f__('log','at utils/supabaseService.uts:3425','[CreateOrder] queryData 类型:', typeof queryData, '是否数组:', Array.isArray(queryData))\r\n \r\n if (Array.isArray(queryData) && queryData.length > 0) {\r\n __f__('log','at utils/supabaseService.uts:3428','[CreateOrder] queryData 长度:', queryData.length)\r\n const firstItemRaw = queryData[0]\r\n __f__('log','at utils/supabaseService.uts:3430','[CreateOrder] firstItemRaw 类型:', typeof firstItemRaw)\r\n \r\n // 将 firstItemRaw 转换为可访问的对象\r\n const firstItemStr = JSON.stringify(firstItemRaw)\r\n const firstItemParsed = JSON.parse(firstItemStr)\r\n if (firstItemParsed == null) {\r\n __f__('error','at utils/supabaseService.uts:3436','[CreateOrder] 解析订单数据失败')\r\n return null\r\n }\r\n const firstItem = firstItemParsed as UTSJSONObject\r\n orderId = (firstItem.getString('id') ?? '') as string\r\n __f__('log','at utils/supabaseService.uts:3441','[CreateOrder] 找到新创建的订单, id:', orderId)\r\n } else {\r\n __f__('error','at utils/supabaseService.uts:3443','[CreateOrder] 未找到新创建的订单,插入可能失败')\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3447','[CreateOrder] 订单创建成功, orderId:', orderId)\r\n \r\n const orderItems: UTSJSONObject[] = []\r\n __f__('log','at utils/supabaseService.uts:3450','[CreateOrder] orderData.items 类型:', typeof orderData.items, '是否数组:', Array.isArray(orderData.items))\r\n \r\n if (orderData.items == null) {\r\n __f__('error','at utils/supabaseService.uts:3453','[CreateOrder] orderData.items 为 null!')\r\n return orderId\r\n }\r\n \r\n const rawItems = orderData.items as any[]\r\n __f__('log','at utils/supabaseService.uts:3458','[CreateOrder] rawItems 长度:', rawItems.length)\r\n \r\n if (rawItems.length === 0) {\r\n __f__('warn','at utils/supabaseService.uts:3461','[CreateOrder] rawItems 为空数组,没有商品项需要插入')\r\n return orderId\r\n }\r\n \r\n for(let i = 0; i < rawItems.length; i++) {\r\n const rawItem = rawItems[i]\r\n const itemStr = JSON.stringify(rawItem)\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed == null) {\r\n __f__('error','at utils/supabaseService.uts:3470','[CreateOrder] 商品项解析失败')\r\n continue\r\n }\r\n const item = itemParsed as UTSJSONObject\r\n\r\n const itemJson = new UTSJSONObject()\r\n \r\n let pId = item.get('product_id')\r\n if (pId == null) {\r\n pId = item.get('id')\r\n }\r\n const productId = (pId ?? '') as string\r\n \r\n itemJson.set('order_id', orderId)\r\n itemJson.set('product_id', productId)\r\n \r\n const skuIdVal = item.get('sku_id')\r\n if (skuIdVal != null && skuIdVal !== '') {\r\n itemJson.set('sku_id', skuIdVal as string)\r\n }\r\n \r\n const productName = (item.get('product_name') ?? '') as string\r\n itemJson.set('product_name', productName)\r\n \r\n const sName = item.get('sku_name')\r\n itemJson.set('sku_name', (sName ?? '') as string)\r\n \r\n const specVal = item.get('specifications')\r\n let skuSnapshot = '{}'\r\n if (specVal != null) {\r\n if (typeof specVal === 'string') {\r\n skuSnapshot = specVal as string\r\n } else {\r\n skuSnapshot = JSON.stringify(specVal)\r\n }\r\n }\r\n itemJson.set('sku_snapshot', skuSnapshot)\r\n itemJson.set('specifications', skuSnapshot)\r\n \r\n const img1 = item.get('product_image')\r\n const img2 = item.get('image_url')\r\n let imgUrl = ((img1 ?? img2 ?? '') as string)\r\n while (imgUrl.indexOf('`') >= 0) {\r\n imgUrl = imgUrl.replace('`', '')\r\n }\r\n itemJson.set('image_url', imgUrl)\r\n\r\n const iPriceRaw = item.getNumber('price') ?? 0\r\n const iMemberPrice = item.getNumber('member_price') ?? 0\r\n // 优先使用会员价\r\n const iPrice = (iMemberPrice > 0 && iMemberPrice < iPriceRaw) ? iMemberPrice : iPriceRaw\r\n const iQty = item.getNumber('quantity') ?? 1\r\n itemJson.set('price', iPrice)\r\n itemJson.set('quantity', iQty)\r\n itemJson.set('total_amount', iPrice * iQty)\r\n itemJson.set('created_at', new Date().toISOString())\r\n \r\n orderItems.push(itemJson)\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3530','[CreateOrder] 插入订单项数量:', orderItems.length)\r\n \r\n for (let j: number = 0; j < orderItems.length; j++) {\r\n __f__('log','at utils/supabaseService.uts:3533','[CreateOrder] 开始插入订单项', j)\r\n const itemJson = orderItems[j]\r\n // 将 UTSJSONObject 转换为普通对象\r\n __f__('log','at utils/supabaseService.uts:3536','[CreateOrder] 序列化订单项...')\r\n const itemObjStr = JSON.stringify(itemJson)\r\n __f__('log','at utils/supabaseService.uts:3538','[CreateOrder] 订单项 JSON:', itemObjStr)\r\n const itemObjParsed = JSON.parse(itemObjStr)\r\n __f__('log','at utils/supabaseService.uts:3540','[CreateOrder] itemObjParsed 类型:', typeof itemObjParsed)\r\n if (itemObjParsed == null) {\r\n __f__('error','at utils/supabaseService.uts:3542','[CreateOrder] 订单项转换失败')\r\n continue\r\n }\r\n \r\n // 使用 UTSJSONObject 而不是 Record<string, any>\r\n const itemObj = itemObjParsed as UTSJSONObject\r\n __f__('log','at utils/supabaseService.uts:3548','[CreateOrder] 执行 insert...')\r\n \r\n const itemsResponse = await supa\r\n .from('ml_order_items')\r\n .insert(itemObj)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3555','[CreateOrder] insert 完成, error:', itemsResponse.error) \r\n if (itemsResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3557','[CreateOrder] 创建订单项失败:', itemsResponse.error)\r\n }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3561','[CreateOrder] 订单项创建成功')\r\n \r\n const cartItemIds: string[] = []\r\n for(let i = 0; i < rawItems.length; i++) {\r\n const rawItem = rawItems[i]\r\n const itemParsed = JSON.parse(JSON.stringify(rawItem))\r\n if (itemParsed == null) continue\r\n const item = itemParsed as UTSJSONObject\r\n const iid = item.getString('id')\r\n if (iid != null && iid.length > 10) {\r\n cartItemIds.push(iid)\r\n }\r\n }\r\n \r\n if (cartItemIds.length > 0) {\r\n await this.batchDeleteCartItems(cartItemIds)\r\n }\r\n \r\n return orderId\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:3581','[CreateOrder] 创建订单异常:', error)\r\n return null\r\n }\r\n }\r\n\r\n // 批量通过店铺创建订单\r\n async createOrdersByShop(params: ShopOrderParams): Promise<ShopOrderResponse> {\r\n try {\r\n const orderIds: string[] = []\r\n const groups = params.shopGroups as any[]\r\n \r\n let grandTotal = 0.0\r\n for(let k = 0; k < groups.length; k++) {\r\n const g = JSON.parse(JSON.stringify(groups[k])) as UTSJSONObject\r\n const gItemsRaw = g.get('items')\r\n if (gItemsRaw == null) continue\r\n const gItems = gItemsRaw as any[]\r\n \r\n for(let gi = 0; gi < gItems.length; gi++) {\r\n const it = JSON.parse(JSON.stringify(gItems[gi])) as UTSJSONObject\r\n // 优先使用会员价\r\n let itPrice = it.getNumber('price') ?? 0\r\n const itMemberPrice = it.getNumber('member_price') ?? 0\r\n if (itMemberPrice > 0 && itMemberPrice < itPrice) {\r\n itPrice = itMemberPrice\r\n }\r\n const itQty = it.getNumber('quantity') ?? 1\r\n grandTotal += itPrice * itQty\r\n }\r\n }\r\n \r\n // 为每个店铺创建一个订单\r\n for (let i = 0; i < groups.length; i++) {\r\n const group = JSON.parse(JSON.stringify(groups[i])) as UTSJSONObject\r\n const shopItemsRaw = group.get('items')\r\n if (shopItemsRaw == null) continue\r\n const shopItems = shopItemsRaw as any[]\r\n \r\n let productAmount = 0.0\r\n for(let j = 0; j < shopItems.length; j++) {\r\n const sItem = JSON.parse(JSON.stringify(shopItems[j])) as UTSJSONObject\r\n // 优先使用会员价\r\n let siPrice = sItem.getNumber('price') ?? 0\r\n const siMemberPrice = sItem.getNumber('member_price') ?? 0\r\n if (siMemberPrice > 0 && siMemberPrice < siPrice) {\r\n siPrice = siMemberPrice\r\n }\r\n const siQty = sItem.getNumber('quantity') ?? 1\r\n productAmount += siPrice * siQty\r\n }\r\n \r\n // 简单平摊运费和优惠 (实际逻辑可能更复杂)\r\n const ratio = grandTotal > 0 ? (productAmount / grandTotal) : 0\r\n const shopShippingFee = params.deliveryFee * ratio\r\n const shopDiscount = params.discountAmount * ratio\r\n const shopTotal = productAmount + shopShippingFee - shopDiscount\r\n \r\n const mId = group.getString('merchant_id')\r\n const sId = group.getString('shopId')\r\n const shopName = group.getString('shopName')\r\n\r\n __f__('log','at utils/supabaseService.uts:3642','[createOrdersByShop] 店铺组信息:', {\r\n merchant_id: mId,\r\n shopId: sId,\r\n shopName: shopName\r\n })\r\n\r\n const finalMerchantId = (mId != null && mId != '') ? mId : (sId ?? '')\r\n __f__('log','at utils/supabaseService.uts:3649','[createOrdersByShop] 最终使用的 merchant_id:', finalMerchantId)\r\n \r\n // 将 shopItems 转换为普通对象数组\r\n const plainItems: any[] = []\r\n for(let k = 0; k < shopItems.length; k++) {\r\n const plainItemRaw = JSON.parse(JSON.stringify(shopItems[k]))\r\n if (plainItemRaw != null) {\r\n plainItems.push(plainItemRaw as any)\r\n }\r\n }\r\n __f__('log','at utils/supabaseService.uts:3659','[createOrdersByShop] plainItems 数量:', plainItems.length)\r\n\r\n const orderId = await this.createOrder({\r\n merchant_id: finalMerchantId,\r\n product_amount: productAmount,\r\n shipping_fee: shopShippingFee,\r\n total_amount: shopTotal,\r\n shipping_address: params.shipping_address,\r\n items: plainItems\r\n })\r\n \r\n if (orderId != null) {\r\n orderIds.push(orderId)\r\n } else {\r\n return { success: false, orderIds, error: `店铺 ${shopName} 订单创建失败` }\r\n }\r\n }\r\n \r\n return { success: true, orderIds }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3679','批量创建订单异常:', e)\r\n return { success: false, orderIds: [], error: '系统异常' }\r\n }\r\n }\r\n\r\n // 获取订单列表\r\n async getOrders(status: number = 0): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n // 关联查询店铺表获取店铺名称\r\n let query = supa\r\n .from('ml_orders')\r\n .select('*, ml_order_items(*), ml_shops(shop_name)')\r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n \r\n if (status > 0) {\r\n query = query.eq('order_status', status)\r\n }\r\n \r\n const response = await query.execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3706','[getOrders] response.error:', response.error)\r\n if (response.data != null && Array.isArray(response.data)) {\r\n __f__('log','at utils/supabaseService.uts:3708','[getOrders] 订单数量:', response.data.length)\r\n }\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3712','获取订单列表失败:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n // 修复订单项中的图片URL\r\n const orders = data as any[]\r\n for (let i = 0; i < orders.length; i++) {\r\n const order = orders[i]\r\n const orderStr = JSON.stringify(order)\r\n const orderObj = JSON.parse(orderStr) as UTSJSONObject\r\n const itemsRaw = orderObj.get('ml_order_items')\r\n if (itemsRaw != null && Array.isArray(itemsRaw)) {\r\n const items = itemsRaw as any[]\r\n for (let j = 0; j < items.length; j++) {\r\n const item = items[j]\r\n const itemStr = JSON.stringify(item)\r\n const itemObj = JSON.parse(itemStr) as UTSJSONObject\r\n const imgUrl = itemObj.getString('image_url')\r\n if (imgUrl != null) {\r\n itemObj['image_url'] = fixImageUrl(imgUrl)\r\n }\r\n const prodImg = itemObj.getString('product_image')\r\n if (prodImg != null) {\r\n itemObj['product_image'] = fixImageUrl(prodImg)\r\n }\r\n items[j] = itemObj\r\n }\r\n orderObj['ml_order_items'] = items\r\n orders[i] = orderObj\r\n }\r\n }\r\n \r\n return orders\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:3753','获取订单列表异常:', error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n \r\n // 获取订单详情\r\n async getOrderDetail(orderId: string): Promise<any | null> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:3762','[getOrderDetail] 开始获取订单详情orderId:', orderId)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return null\r\n \r\n const response = await supa\r\n .from('ml_orders')\r\n .select('*, ml_order_items(*)')\r\n .eq('id', orderId)\r\n .eq('user_id', userId!)\r\n .limit(1)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3774','[getOrderDetail] response.error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:3775','[getOrderDetail] response.data:', JSON.stringify(response.data))\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3778','[getOrderDetail] 获取订单详情失败:', response.error)\r\n return null\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n __f__('log','at utils/supabaseService.uts:3784','[getOrderDetail] 数据为空')\r\n return null\r\n }\r\n \r\n const rawList = rawData as any[]\r\n if (rawList.length == 0) {\r\n __f__('log','at utils/supabaseService.uts:3790','[getOrderDetail] 未找到订单')\r\n return null\r\n }\r\n \r\n const orderData = rawList[0]\r\n __f__('log','at utils/supabaseService.uts:3795','[getOrderDetail] 成功获取订单')\r\n \r\n const orderObj = JSON.parse(JSON.stringify(orderData)) as UTSJSONObject\r\n \r\n const result = new UTSJSONObject()\r\n result.set('id', orderObj.get('id') ?? '')\r\n result.set('order_no', orderObj.get('order_no') ?? '')\r\n result.set('order_status', orderObj.get('order_status') ?? 1)\r\n result.set('user_id', orderObj.get('user_id') ?? '')\r\n result.set('merchant_id', orderObj.get('merchant_id') ?? '')\r\n result.set('product_amount', orderObj.get('product_amount') ?? 0)\r\n result.set('shipping_fee', orderObj.get('shipping_fee') ?? 0)\r\n result.set('total_amount', orderObj.get('total_amount') ?? 0)\r\n result.set('paid_amount', orderObj.get('paid_amount') ?? 0)\r\n result.set('discount_amount', orderObj.get('discount_amount') ?? 0)\r\n result.set('payment_method', orderObj.get('payment_method') ?? '')\r\n result.set('payment_status', orderObj.get('payment_status') ?? 1)\r\n result.set('shipping_status', orderObj.get('shipping_status') ?? 1)\r\n result.set('created_at', orderObj.get('created_at') ?? '')\r\n result.set('paid_at', orderObj.get('paid_at') ?? '')\r\n result.set('shipped_at', orderObj.get('shipped_at') ?? '')\r\n result.set('completed_at', orderObj.get('completed_at') ?? '')\r\n result.set('shipping_address', orderObj.get('shipping_address'))\r\n result.set('ml_order_items', orderObj.get('ml_order_items'))\r\n // 添加物流信息\r\n result.set('tracking_no', orderObj.get('tracking_no') ?? '')\r\n result.set('carrier_name', orderObj.get('carrier_name') ?? '')\r\n result.set('delivered_at', orderObj.get('delivered_at') ?? '')\r\n \r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3826','[getOrderDetail] 获取订单详情异常:', e)\r\n return null\r\n }\r\n }\r\n\r\n // 支付订单\r\n async payOrder(orderId: string, paymentMethod: string, amount: number): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:3836','[payOrder] 用户未登录')\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3840','[payOrder] 开始更新订单状态, orderId:', orderId, 'userId:', userId)\r\n \r\n const updatePayload = new UTSJSONObject()\r\n updatePayload.set('order_status', 2)\r\n updatePayload.set('payment_status', 1)\r\n updatePayload.set('payment_method', paymentMethod)\r\n updatePayload.set('payment_time', new Date().toISOString())\r\n updatePayload.set('paid_amount', amount)\r\n updatePayload.set('updated_at', new Date().toISOString())\r\n \r\n __f__('log','at utils/supabaseService.uts:3850','[payOrder] 更新数据:', JSON.stringify(updatePayload))\r\n \r\n const response = await supa\r\n .from('ml_orders')\r\n .update(updatePayload)\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3860','[payOrder] 更新订单失败:', response.error)\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3864','[payOrder] 订单状态更新成功')\r\n\r\n if (paymentMethod === 'balance') {\r\n __f__('log','at utils/supabaseService.uts:3867','[payOrder] 余额支付,暂不扣减余额')\r\n }\r\n\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3872','[payOrder] 支付异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 根据ID获取订单信息\r\n async getOrderById(orderId: string): Promise<UTSJSONObject | null> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:3882','[getOrderById] 用户未登录')\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3886','[getOrderById] 查询订单, orderId:', orderId)\r\n \r\n const response = await supa\r\n .from('ml_orders')\r\n .select('*')\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3896','[getOrderById] 查询订单失败:', response.error)\r\n return null\r\n }\r\n \r\n const data = response.data as any[]\r\n if (data == null || data.length === 0) {\r\n __f__('log','at utils/supabaseService.uts:3902','[getOrderById] 未找到订单')\r\n return null\r\n }\r\n \r\n const orderRaw = data[0]\r\n let orderObj: UTSJSONObject\r\n if (orderRaw instanceof UTSJSONObject) {\r\n orderObj = orderRaw as UTSJSONObject\r\n } else {\r\n orderObj = JSON.parse(JSON.stringify(orderRaw)) as UTSJSONObject\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3914','[getOrderById] 订单数据:', JSON.stringify(orderObj))\r\n return orderObj\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3917','[getOrderById] 查询异常:', e)\r\n return null\r\n }\r\n }\r\n\r\n // 提交售后申请\r\n async createRefund(data: any): Promise<RefundResponse> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:3925','[createRefund] 开始处理退款申请')\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('log','at utils/supabaseService.uts:3928','[createRefund] 用户未登录')\r\n return { success: false, message: '请先登录' }\r\n }\r\n \r\n const d = JSON.parse(JSON.stringify(data)) as UTSJSONObject\r\n const orderId = d.getString('order_id') ?? ''\r\n const refundType = d.getNumber('refund_type')\r\n const refundReason = d.getString('refund_reason')\r\n const refundAmount = d.getNumber('refund_amount')\r\n const description = d.getString('description')\r\n const images = d.getArray('images')\r\n \r\n __f__('log','at utils/supabaseService.uts:3940','[createRefund] orderId:', orderId)\r\n __f__('log','at utils/supabaseService.uts:3941','[createRefund] refundType:', refundType)\r\n __f__('log','at utils/supabaseService.uts:3942','[createRefund] refundReason:', refundReason)\r\n __f__('log','at utils/supabaseService.uts:3943','[createRefund] refundAmount:', refundAmount)\r\n\r\n const payload = {\r\n user_id: userId,\r\n order_id: orderId,\r\n refund_no: 'REF' + Date.now() + Math.floor(Math.random() * 1000),\r\n refund_type: refundType,\r\n refund_reason: refundReason,\r\n refund_amount: refundAmount,\r\n description: description ?? '',\r\n images: images ?? ([] as any[]),\r\n status: 1 // Pending\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3957','[createRefund] 准备插入 ml_refunds')\r\n const response = await supa\r\n .from('ml_refunds')\r\n .insert(payload)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3963','[createRefund] insert response.error:', response.error)\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3966','提交售后失败:', response.error)\r\n return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3970','[createRefund] 插入成功,更新订单状态')\r\n // 更新订单状态为退款中\r\n const updateResponse = await supa\r\n .from('ml_orders')\r\n .update({\r\n order_status: 6, // 退款中\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', orderId)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3981','[createRefund] update response.error:', updateResponse.error)\r\n \r\n if (updateResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3984','更新订单状态失败:', updateResponse.error)\r\n // 不影响退款申请结果,只记录错误\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3988','[createRefund] 完成,返回成功')\r\n return { success: true, message: '申请提交成功' }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3991','提交售后异常:', e)\r\n return { success: false, message: '系统异常' }\r\n }\r\n }\r\n\r\n // 取消退款申请\r\n async cancelRefund(orderId: string): Promise<RefundResponse> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:3999','[cancelRefund] 开始取消退款申请, orderId:', orderId)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return { success: false, message: '请先登录' }\r\n }\r\n \r\n // 更新退款记录状态为已取消\r\n const refundUpdateResponse = await supa\r\n .from('ml_refunds')\r\n .update({\r\n status: 4, // 已取消\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('order_id', orderId)\r\n .eq('user_id', userId)\r\n .eq('status', 1) // 只能取消待处理的退款\r\n .execute()\r\n \r\n if (refundUpdateResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4018','取消退款记录失败:', refundUpdateResponse.error)\r\n return { success: false, message: '取消失败: ' + (refundUpdateResponse.error.message ?? '未知错误') }\r\n }\r\n \r\n // 恢复订单状态为已完成(假设之前是已完成状态)\r\n const orderUpdateResponse = await supa\r\n .from('ml_orders')\r\n .update({\r\n order_status: 4, // 已完成\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', orderId)\r\n .execute()\r\n \r\n if (orderUpdateResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4033','恢复订单状态失败:', orderUpdateResponse.error)\r\n // 不影响取消退款结果,只记录错误\r\n }\r\n \r\n return { success: true, message: '已取消退款申请' }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4039','取消退款异常:', e)\r\n return { success: false, message: '系统异常' }\r\n }\r\n }\r\n\r\n // 再次购买\r\n async rePurchase(order: any): Promise<boolean> {\r\n try {\r\n // 将 order 转换为 UTSJSONObject 以安全访问属性\r\n const orderObj = JSON.parse(JSON.stringify(order)) as UTSJSONObject\r\n // 尝试获取 ml_order_items 或 items\r\n let itemsKey = 'ml_order_items'\r\n let itemsRaw = orderObj.get(itemsKey)\r\n \r\n if (itemsRaw == null) {\r\n itemsKey = 'items'\r\n itemsRaw = orderObj.get(itemsKey)\r\n }\r\n \r\n if (itemsRaw == null) return false\r\n \r\n // 断言为数组\r\n const items = itemsRaw as any[]\r\n if (items.length === 0) return false\r\n\r\n // 简单的循环添加,实际项目中可以优化为批量插入\r\n for (let i = 0; i < items.length; i++) {\r\n // 同样item 也是 UTSJSONObject 或支持访问的对象\r\n const item = JSON.parse(JSON.stringify(items[i])) as UTSJSONObject\r\n const productId = item.getString('product_id') \r\n const skuId = item.getString('sku_id')\r\n // 数量可能是数字或字符串\r\n const quantity = item.getNumber('quantity') ?? 1\r\n \r\n if (productId != null) {\r\n await this.addToCart(productId, quantity, skuId ?? '', '')\r\n }\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4079','rePurchase error', e)\r\n return false\r\n }\r\n }\r\n\r\n // 申请售后 (Legacy/Simple update)\r\n async applyRefund(orderId: string, reason: string): Promise<boolean> {\r\n try {\r\n // 更新订单状态为 退款中 (6)\r\n const response = await supa\r\n .from('ml_orders')\r\n .update({\r\n order_status: 6,\r\n cancel_reason: reason,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', orderId)\r\n .execute()\r\n \r\n return response.error === null\r\n } catch (e) {\r\n return false\r\n }\r\n }\r\n\r\n // 获取售后记录列表\r\n async getRefunds(statusList: number[] = [], page: number = 1, pageSize: number = 10): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n let query = supa\r\n .from('ml_refunds')\r\n .select(`\r\n *,\r\n order:ml_orders!inner (\r\n order_no,\r\n created_at,\r\n ml_order_items (\r\n product_id,\r\n product_name,\r\n image_url\r\n )\r\n )\r\n `)\r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n\r\n if (statusList.length > 0) {\r\n // 显式转换为 any[] 以匹配 .in 方法的参数要求\r\n const anyList = statusList as any[]\r\n query = query.in('status', anyList)\r\n }\r\n\r\n query = query.range((page - 1) * pageSize, page * pageSize - 1)\r\n\r\n const response = await query.execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4141','获取售后列表失败:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const data = response.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return data\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4154','获取售后列表异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n async deleteRefund(refundId: string): Promise<boolean> {\r\n try {\r\n const response = await supa\r\n .from('ml_refunds')\r\n .delete()\r\n .eq('id', refundId)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4169','删除退款记录失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4175','删除退款记录异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n async getUserBalanceNumber(): Promise<number> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n __f__('log','at utils/supabaseService.uts:4183','[Supabase] getUserBalance userId:', userId)\r\n if (userId == null) return 0\r\n \r\n // 优先查 ml_user_wallets\r\n const walletRes = await supa\r\n .from('ml_user_wallets')\r\n .select('balance')\r\n .eq('user_id', userId!)\r\n .single()\r\n .execute()\r\n \r\n if (walletRes.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4195','[Supabase] getUserBalance error:', walletRes.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:4197','[Supabase] getUserBalance data:', walletRes.data)\r\n }\r\n\r\n if (walletRes.error == null && walletRes.data != null) {\r\n let data = walletRes.data\r\n // 如果是数组,取第一项\r\n if (Array.isArray(data)) {\r\n const arr = data as any[]\r\n if (arr.length > 0) {\r\n data = arr[0]\r\n }\r\n }\r\n\r\n let val:number = 0\r\n if (data instanceof UTSJSONObject) {\r\n val = data.getNumber('balance') ?? 0\r\n // 尝试字符串转换防止精度丢失导致转为string\r\n if (val === 0 && data.getString('balance') != null) {\r\n val = parseFloat(data.getString('balance')!)\r\n }\r\n return val\r\n } else {\r\n // 对于 Map 或 loose object\r\n const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject\r\n val = jsonObj.getNumber('balance') ?? 0\r\n if (val === 0 && jsonObj.getString('balance') != null) {\r\n val = parseFloat(jsonObj.getString('balance')!)\r\n }\r\n return val\r\n }\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:4229','[Supabase] Wallet table empty, checking profile...')\r\n\r\n // Fallback to profile\r\n const profile = await this.getUserProfile()\r\n if (profile != null) {\r\n if (profile instanceof UTSJSONObject) {\r\n return profile.getNumber('balance') ?? 0\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject\r\n return pObj.getNumber('balance') ?? 0\r\n }\r\n }\r\n return 0\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:4243','[Supabase] getUserBalance exception:', e)\r\n return 0\r\n }\r\n }\r\n \r\n // 获取用户积分\r\n async getUserPoints(): Promise<number> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n __f__('log','at utils/supabaseService.uts:4252','[Supabase] getUserPoints userId:', userId)\r\n if (userId == null) return 0\r\n \r\n // 查 ml_user_points\r\n const res = await supa\r\n .from('ml_user_points')\r\n .select('points')\r\n .eq('user_id', userId!)\r\n .single()\r\n .execute()\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4264','[Supabase] getUserPoints error:', res.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:4266','[Supabase] getUserPoints data:', res.data)\r\n }\r\n\r\n if (res.error == null && res.data != null) {\r\n let data = res.data\r\n // 如果是数组,取第一项\r\n if (Array.isArray(data)) {\r\n const arr = data as any[]\r\n if (arr.length > 0) {\r\n data = arr[0]\r\n }\r\n }\r\n\r\n if (data instanceof UTSJSONObject) {\r\n return data.getNumber('points') ?? 0\r\n } else {\r\n // 尝试转为 UTSJSONObject\r\n const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject\r\n const val = jsonObj.getNumber('points')\r\n if (val != null) return val\r\n\r\n return 0\r\n }\r\n }\r\n \r\n // Fallback check profile if needed\r\n const profile = await this.getUserProfile()\r\n if (profile != null) {\r\n if (profile instanceof UTSJSONObject) {\r\n return profile.getNumber('points') ?? 0\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject\r\n return pObj.getNumber('points') ?? 0\r\n }\r\n }\r\n \r\n return 0\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4304','[Supabase] getUserPoints exception:', e)\r\n return 0\r\n }\r\n }\r\n\r\n // 获取钱包交易记录\r\n async getTransactions(page: number = 1, limit: number = 20): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const from = (page - 1) * limit\r\n const to = from + limit - 1\r\n\r\n const response = await supa\r\n .from('ml_wallet_transactions')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .range(from, to)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4330','获取交易记录失败:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4343','获取交易记录异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n \r\n // 获取积分记录\r\n async getPointRecords(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n const res = await supa\r\n .from('ml_point_records')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n if (res.error != null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const data = res.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n return data as any[]\r\n } catch (e) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取用户红包\r\n async getUserRedPackets(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const res = await supa\r\n .from('ml_user_red_packets')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4398','获取红包失败:', res.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const data = res.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4409','获取红包异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取用户银行卡\r\n async getUserBankCards(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const res = await supa\r\n .from('ml_user_bank_cards')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4432','获取银行卡失败:', res.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const data = res.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4443','获取银行卡异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 余额充值 (调用 RPC)\r\n async rechargeBalance(amount: number): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n const res = await supa.rpc('recharge_wallet', { \r\n p_user_id: userId,\r\n p_amount: amount \r\n })\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4461','充值失败RPC:', res.error)\r\n return false\r\n }\r\n \r\n // 简单判断: 如果没有error且data里success为true\r\n const data = res.data\r\n if (data instanceof UTSJSONObject) {\r\n return data.getBoolean('success') ?? false\r\n }\r\n // 如果返回不是对象,作为失败处理\r\n return false\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4473','充值异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 余额提现 (调用 RPC)\r\n async withdrawBalance(amount: number): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n const res = await supa.rpc('withdraw_wallet', { \r\n p_user_id: userId,\r\n p_amount: amount \r\n })\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4490','提现失败RPC:', res.error)\r\n return false\r\n }\r\n \r\n const data = res.data\r\n if (data instanceof UTSJSONObject) {\r\n return data.getBoolean('success') ?? false\r\n }\r\n return false\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4500','提现异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 添加银行卡\r\n async addBankCard(card: UTSJSONObject): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n // 补全 user_id\r\n card.set('user_id', userId)\r\n \r\n const res = await supa\r\n .from('ml_user_bank_cards')\r\n .insert(card)\r\n .execute()\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4520','添加银行卡失败:', res.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4525','添加银行卡异常:', e)\r\n return false\r\n }\r\n }\r\n \r\n // 删除银行卡\r\n async deleteBankCard(cardId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n const res = await supa\r\n .from('ml_user_bank_cards')\r\n .eq('id', cardId)\r\n .eq('user_id', userId!)\r\n .delete()\r\n .execute()\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4544','删除银行卡失败:', res.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4549','删除银行卡异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 收藏相关\r\n async checkFavorite(productId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n __f__('log','at utils/supabaseService.uts:4558',`[CheckFav] Checking for User: ${userId}, Product: ${productId}`)\r\n \r\n if (userId == null) return false\r\n \r\n const response = await supa\r\n .from('ml_user_favorites')\r\n .select('*') // Select all to verify data\r\n .eq('user_id', userId!)\r\n .eq('target_id', productId)\r\n .eq('target_type', '1') // 使用字符串 '1'\r\n .limit(1)\r\n .execute()\r\n \r\n // __f__('log','at utils/supabaseService.uts:4571',`[CheckFav] Response: ${JSON.stringify(response)}`)\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4574',`[CheckFav] Error: ${JSON.stringify(response.error)}`)\r\n return false\r\n }\r\n \r\n const data = response.data\r\n if (Array.isArray(data)) {\r\n if ((data as any[]).length > 0) {\r\n // Double check: ensure the returned item actually matches the product ID\r\n // This guards against potential query filter failures\r\n const item = data[0]\r\n let targetId = ''\r\n if (item instanceof UTSJSONObject) {\r\n targetId = item.getString('target_id') ?? ''\r\n } else {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n targetId = itemObj.getString('target_id') ?? ''\r\n }\r\n \r\n if (targetId != '' && targetId != productId) {\r\n __f__('error','at utils/supabaseService.uts:4593',`[CheckFav] ID Mismatch! Query ${productId}, Got ${targetId}`)\r\n return false\r\n }\r\n \r\n return true\r\n }\r\n } else if (data instanceof UTSJSONObject) {\r\n // Handle single object return case (though limit(1) usually returns array)\r\n let targetId = data.getString('target_id') ?? ''\r\n if (targetId !== '' && targetId !== productId) {\r\n return false\r\n }\r\n return true\r\n }\r\n \r\n return false\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:4610',`[CheckFav] Exception: ${e}`)\r\n return false\r\n }\r\n }\r\n \r\n async toggleFavorite(productId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n __f__('log','at utils/supabaseService.uts:4620',`[ToggleFav] Toggling for ${productId}`)\r\n \r\n // Check if exists\r\n const exists = await this.checkFavorite(productId)\r\n __f__('log','at utils/supabaseService.uts:4624',`[ToggleFav] Current status: ${exists}`)\r\n \r\n if (exists) {\r\n const response = await supa\r\n .from('ml_user_favorites')\r\n .eq('user_id', userId!)\r\n .eq('target_id', productId)\r\n .eq('target_type', '1')\r\n .delete()\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4636','取消收藏失败:', response.error)\r\n return true // 仍然是收藏状态\r\n }\r\n return false // 已取消收藏\r\n } else {\r\n const response = await supa\r\n .from('ml_user_favorites')\r\n .insert({\r\n user_id: userId,\r\n target_id: productId,\r\n target_type: '1',\r\n created_at: new Date().toISOString()\r\n })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4652','添加收藏失败:', response.error)\r\n return false // 添加失败,仍未收藏\r\n }\r\n return true // 已收藏\r\n }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4658','切换收藏状态异常:', e)\r\n // 发生异常时,尝试查询当前状态返回\r\n return await this.checkFavorite(productId)\r\n }\r\n }\r\n \r\n async getFavorites(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n // 第一步:查询收藏列表\r\n const response = await supa\r\n .from('ml_user_favorites')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .eq('target_type', '1')\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const favorites = response.data as any[]\r\n if (favorites == null || favorites.length === 0) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n // 第二步收集商品ID\r\n const productIds: string[] = []\r\n for (let i = 0; i < favorites.length; i++) {\r\n let item: any = favorites[i]\r\n let itemObj: UTSJSONObject\r\n if (item instanceof UTSJSONObject) {\r\n itemObj = item as UTSJSONObject\r\n } else {\r\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n }\r\n \r\n // target_id 可能是 Integer 或 String 类型,需要安全转换\r\n const targetIdRaw = itemObj.get('target_id')\r\n let pid = ''\r\n if (targetIdRaw != null) {\r\n if (typeof targetIdRaw === 'string') {\r\n pid = targetIdRaw as string\r\n } else if (typeof targetIdRaw === 'number') {\r\n pid = (targetIdRaw as number).toString()\r\n }\r\n }\r\n if (pid !== '') productIds.push(pid)\r\n }\r\n \r\n if (productIds.length === 0) return []\r\n \r\n // 第三步:批量查询商品详情\r\n const anyProductIds = productIds as any[]\r\n const productRes = await supa\r\n .from('ml_products')\r\n .select('id, name, main_image_url, base_price, sale_count')\r\n .in('id', anyProductIds)\r\n .execute()\r\n \r\n if (productRes.error != null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const products = productRes.data as any[]\r\n const productMap = new Map<string, any>()\r\n \r\n for (let i = 0; i < products.length; i++) {\r\n // 显式声明类型为 any\r\n let p: any = products[i]\r\n let pid = ''\r\n if (p instanceof UTSJSONObject) {\r\n pid = p.getString('id') ?? ''\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject\r\n pid = pObj.getString('id') ?? ''\r\n }\r\n if (pid !== '') productMap.set(pid, p)\r\n }\r\n \r\n // 第四步:组合数据\r\n const result: any[] = []\r\n for (let i = 0; i < favorites.length; i++) {\r\n let item: any = favorites[i]\r\n let newItem: UTSJSONObject\r\n \r\n if (item instanceof UTSJSONObject) {\r\n newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n } else {\r\n newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n }\r\n \r\n // target_id 可能是 Integer 或 String 类型,需要安全转换\r\n const targetIdRaw = newItem.get('target_id')\r\n let targetId = ''\r\n if (targetIdRaw != null) {\r\n if (typeof targetIdRaw === 'string') {\r\n targetId = targetIdRaw as string\r\n } else if (typeof targetIdRaw === 'number') {\r\n targetId = (targetIdRaw as number).toString()\r\n }\r\n }\r\n \r\n if (targetId !== '') {\r\n const product = productMap.get(targetId)\r\n if (product != null) {\r\n newItem.set('ml_products', product)\r\n result.push(newItem)\r\n }\r\n }\r\n }\r\n \r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:4779','获取收藏列表异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取足迹列表\r\n async getFootprints(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('log','at utils/supabaseService.uts:4789','[getFootprints] 用户未登录')\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:4794','[getFootprints] 查询足迹, userId:', userId)\r\n\r\n // 1. 获取足迹记录\r\n const response = await supa\r\n .from('ml_user_footprints')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('updated_at', { ascending: false })\r\n .limit(50)\r\n .execute()\r\n\r\n __f__('log','at utils/supabaseService.uts:4805','[getFootprints] 足迹查询 error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:4806','[getFootprints] 足迹查询 data:', JSON.stringify(response.data))\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:4809','[getFootprints] 获取足迹失败:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const footprints = response.data as any[]\r\n if (footprints == null || footprints.length === 0) {\r\n __f__('log','at utils/supabaseService.uts:4816','[getFootprints] 没有足迹记录')\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:4821','[getFootprints] 足迹记录数量:', footprints.length)\r\n\r\n // 2. 收集商品ID\r\n const productIds: string[] = []\r\n for (let i = 0; i < footprints.length; i++) {\r\n let item = footprints[i]\r\n let pid = ''\r\n if (item instanceof UTSJSONObject) {\r\n pid = item.getString('product_id') ?? ''\r\n } else {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n pid = itemObj.getString('product_id') ?? ''\r\n }\r\n if (pid !== '' && !productIds.includes(pid)) productIds.push(pid)\r\n }\r\n\r\n if (productIds.length === 0) return []\r\n \r\n const productIdsAny: any[] = []\r\n for(let i=0; i<productIds.length; i++) {\r\n productIdsAny.push(productIds[i])\r\n }\r\n\r\n // 3. 批量查询商品详情\r\n const productRes = await supa\r\n .from('ml_products_detail_view')\r\n .select('id, name, main_image_url, base_price, market_price, sale_count, merchant_id, shop_name')\r\n .in('id', productIdsAny)\r\n .execute()\r\n\r\n // 如果视图失败,回退查基础表\r\n let products: any[] = []\r\n if (productRes.error == null && productRes.data != null) {\r\n products = productRes.data as any[]\r\n } else {\r\n __f__('warn','at utils/supabaseService.uts:4856','View查询失败尝试查询基础表')\r\n const baseRes = await supa\r\n .from('ml_products')\r\n .select('id, name, main_image_url, base_price, market_price, sale_count, merchant_id')\r\n .in('id', productIdsAny)\r\n .execute()\r\n if (baseRes.error == null) {\r\n products = baseRes.data as any[]\r\n }\r\n }\r\n\r\n const productMap = new Map<string, any>()\r\n for(let i=0; i<products.length; i++) {\r\n let p = products[i]\r\n let pid = ''\r\n if (p instanceof UTSJSONObject) {\r\n pid = p.getString('id') ?? ''\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject\r\n pid = pObj.getString('id') ?? ''\r\n }\r\n if (pid !== '') productMap.set(pid, p)\r\n }\r\n\r\n // 4. 组合结果\r\n const result: any[] = []\r\n for (let i = 0; i < footprints.length; i++) {\r\n let fp = footprints[i]\r\n let pid = ''\r\n let viewTime = 0\r\n \r\n if (fp instanceof UTSJSONObject) {\r\n pid = fp.getString('product_id') ?? ''\r\n const dateStr = fp.getString('updated_at')\r\n if (dateStr != null) viewTime = new Date(dateStr).getTime()\r\n } else {\r\n const fpObj = JSON.parse(JSON.stringify(fp)) as UTSJSONObject\r\n pid = fpObj.getString('product_id') ?? ''\r\n const dateStr = fpObj.getString('updated_at')\r\n if (dateStr != null) viewTime = new Date(dateStr).getTime()\r\n }\r\n\r\n const product = productMap.get(pid)\r\n if (product != null) {\r\n let pName = ''\r\n let pImage = ''\r\n let pPrice = 0\r\n let pOriginalPrice = 0\r\n let pSales = 0\r\n let pShopId = ''\r\n let pShopName = ''\r\n \r\n if (product instanceof UTSJSONObject) {\r\n pName = product.getString('name') ?? ''\r\n pImage = product.getString('main_image_url') ?? ''\r\n pPrice = product.getNumber('base_price') ?? 0\r\n pOriginalPrice = product.getNumber('market_price') ?? 0\r\n pSales = product.getNumber('sale_count') ?? 0\r\n pShopId = product.getString('merchant_id') ?? ''\r\n pShopName = product.getString('shop_name') ?? ''\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject\r\n pName = pObj.getString('name') ?? ''\r\n pImage = pObj.getString('main_image_url') ?? ''\r\n pPrice = pObj.getNumber('base_price') ?? 0\r\n pOriginalPrice = pObj.getNumber('market_price') ?? 0\r\n pSales = pObj.getNumber('sale_count') ?? 0\r\n pShopId = pObj.getString('merchant_id') ?? ''\r\n pShopName = pObj.getString('shop_name') ?? ''\r\n }\r\n\r\n const fpObj = new UTSJSONObject()\r\n fpObj.set('id', pid)\r\n fpObj.set('name', pName)\r\n fpObj.set('price', pPrice)\r\n fpObj.set('original_price', pOriginalPrice)\r\n fpObj.set('image', pImage)\r\n fpObj.set('sales', pSales)\r\n fpObj.set('shopId', pShopId)\r\n fpObj.set('shopName', pShopName)\r\n fpObj.set('merchant_id', pShopId)\r\n fpObj.set('viewTime', viewTime)\r\n result.push(fpObj)\r\n }\r\n }\r\n \r\n return result\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:4944','获取足迹异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 添加/更新足迹\r\n async addFootprint(productId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('log','at utils/supabaseService.uts:4954','[addFootprint] 用户未登录')\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:4958','[addFootprint] 添加足迹, userId:', userId, 'productId:', productId)\r\n \r\n // 检查是否已存在\r\n const checkRes = await supa\r\n .from('ml_user_footprints')\r\n .select('id')\r\n .eq('user_id', userId!)\r\n .eq('product_id', productId)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:4968','[addFootprint] 检查结果 error:', checkRes.error)\r\n __f__('log','at utils/supabaseService.uts:4969','[addFootprint] 检查结果 data:', JSON.stringify(checkRes.data))\r\n\r\n const checkData = checkRes.data as any[]\r\n const exists = checkData != null && Array.isArray(checkData) && checkData.length > 0\r\n \r\n if (checkRes.error == null && exists) {\r\n __f__('log','at utils/supabaseService.uts:4975','[addFootprint] 足迹已存在,更新时间')\r\n // 更新时间\r\n const updateRes = await supa\r\n .from('ml_user_footprints')\r\n .update({ updated_at: new Date().toISOString() })\r\n .eq('user_id', userId!)\r\n .eq('product_id', productId)\r\n .execute()\r\n __f__('log','at utils/supabaseService.uts:4983','[addFootprint] 更新结果 error:', updateRes.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:4985','[addFootprint] 足迹不存在,插入新记录')\r\n // 插入新记录\r\n const insertPayload = new UTSJSONObject()\r\n insertPayload.set('user_id', userId!)\r\n insertPayload.set('product_id', productId)\r\n insertPayload.set('created_at', new Date().toISOString())\r\n insertPayload.set('updated_at', new Date().toISOString())\r\n \r\n const insertRes = await supa\r\n .from('ml_user_footprints')\r\n .insert(insertPayload)\r\n .execute()\r\n __f__('log','at utils/supabaseService.uts:4997','[addFootprint] 插入结果 error:', insertRes.error)\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5001','[addFootprint] 添加足迹异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 删除单个足迹\r\n async deleteFootprint(productId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('log','at utils/supabaseService.uts:5011','[deleteFootprint] 用户未登录')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_footprints')\r\n .eq('user_id', userId)\r\n .eq('product_id', productId)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5023','[deleteFootprint] 删除足迹失败:', response.error)\r\n return false\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:5027','[deleteFootprint] 删除足迹成功')\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5030','[deleteFootprint] 删除足迹异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 批量删除足迹\r\n async deleteFootprints(productIds: string[]): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('log','at utils/supabaseService.uts:5040','[deleteFootprints] 用户未登录')\r\n return false\r\n }\r\n\r\n const idsAny: any[] = []\r\n for (let i = 0; i < productIds.length; i++) {\r\n idsAny.push(productIds[i])\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_footprints')\r\n .eq('user_id', userId)\r\n .in('product_id', idsAny)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5057','[deleteFootprints] 批量删除足迹失败:', response.error)\r\n return false\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:5061','[deleteFootprints] 批量删除足迹成功')\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5064','[deleteFootprints] 批量删除足迹异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 清空所有足迹\r\n async clearFootprints(): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('log','at utils/supabaseService.uts:5074','[clearFootprints] 用户未登录')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_footprints')\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5085','[clearFootprints] 清空足迹失败:', response.error)\r\n return false\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:5089','[clearFootprints] 清空足迹成功')\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5092','[clearFootprints] 清空足迹异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n async getAddressList(): Promise<UserAddress[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: UserAddress[] = []\r\n return empty\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')\r\n .eq('user_id', userId!)\r\n .order('is_default', { ascending: false })\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5114','获取地址列表失败:', response.error)\r\n const empty: UserAddress[] = []\r\n return empty\r\n }\r\n return response.data as UserAddress[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5120','获取地址列表异常:', e)\r\n const empty: UserAddress[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 设置默认地址\r\n async setDefaultAddress(addressId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:5131','用户未登录,无法设置默认地址')\r\n return false\r\n }\r\n\r\n // 先取消所有默认地址\r\n await this.clearDefaultAddress(userId!)\r\n\r\n // 设置新的默认地址\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .update({\r\n is_default: true,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', addressId)\r\n .eq('user_id', userId!)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5150','设置默认地址失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:5156','设置默认地址异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 获取用户优惠券列表\r\n async getUserCoupons(status: number = 1): Promise<UserCoupon[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: UserCoupon[] = []\r\n return empty\r\n }\r\n\r\n // 假设有一个视图或者直接关联 ml_user_coupons 和 ml_coupon_templates\r\n // 这里简化处理,尝试直接从 ml_user_coupons 读取,并且加入 template 信息\r\n // 如果没有 view可能需要改为两个查询或者使用 left join\r\n const response = await supa\r\n .from('ml_user_coupons')\r\n .select('*, template:ml_coupon_templates(name, amount, min_spend)')\r\n .eq('user_id', userId!)\r\n .eq('status', status.toString())\r\n .order('expire_at', { ascending: true })\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5182','获取优惠券失败:', response.error)\r\n const empty: UserCoupon[] = []\r\n return empty\r\n }\r\n\r\n // 安全处理返回数据 - 安卓端可能是 UTSJSONObject 或 UTSArray\r\n const rawData: any[] = []\r\n const respData = response.data\r\n __f__('log','at utils/supabaseService.uts:5190','[getUserCoupons] 原始数据类型:', typeof respData, '是否数组:', Array.isArray(respData))\r\n if (respData != null) {\r\n if (Array.isArray(respData)) {\r\n const arr = respData as any[]\r\n __f__('log','at utils/supabaseService.uts:5194','[getUserCoupons] 数组长度:', arr.length)\r\n for (let i = 0; i < arr.length; i++) {\r\n rawData.push(arr[i])\r\n }\r\n } else if (respData instanceof UTSJSONObject) {\r\n // 单个对象情况,包装成数组\r\n __f__('log','at utils/supabaseService.uts:5200','[getUserCoupons] 单个对象,包装成数组')\r\n rawData.push(respData)\r\n } else {\r\n // 尝试 JSON 转换\r\n try {\r\n const parsed = JSON.parse(JSON.stringify(respData))\r\n __f__('log','at utils/supabaseService.uts:5206','[getUserCoupons] JSON转换后是否数组:', Array.isArray(parsed))\r\n if (Array.isArray(parsed)) {\r\n __f__('log','at utils/supabaseService.uts:5208','[getUserCoupons] 转换后数组长度:', parsed.length)\r\n for (let i = 0; i < parsed.length; i++) {\r\n rawData.push(parsed[i])\r\n }\r\n }\r\n } catch (parseErr) {\r\n __f__('error','at utils/supabaseService.uts:5214','解析优惠券数据异常:', parseErr)\r\n }\r\n }\r\n }\r\n __f__('log','at utils/supabaseService.uts:5218','[getUserCoupons] 最终rawData长度:', rawData.length)\r\n\r\n // 映射数据,将 template 的字段展平\r\n const coupons: UserCoupon[] = []\r\n for (let i = 0; i < rawData.length; i++) {\r\n const item = rawData[i]\r\n let template: any | null = null\r\n let itemId = ''\r\n let itemUserId = ''\r\n let itemTmplId = ''\r\n let itemCode = ''\r\n let itemStatus = 0\r\n let itemRecv = ''\r\n let itemExpire = ''\r\n \r\n if (item instanceof UTSJSONObject) {\r\n template = item.get('template') as any | null\r\n itemId = item.getString('id') ?? ''\r\n itemUserId = item.getString('user_id') ?? ''\r\n itemTmplId = item.getString('template_id') ?? ''\r\n itemCode = item.getString('coupon_code') ?? ''\r\n itemStatus = item.getNumber('status') ?? 0\r\n itemRecv = item.getString('received_at') ?? ''\r\n itemExpire = item.getString('expire_at') ?? ''\r\n } else {\r\n const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n template = iObj.get('template') as any | null\r\n itemId = iObj.getString('id') ?? ''\r\n itemUserId = iObj.getString('user_id') ?? ''\r\n itemTmplId = iObj.getString('template_id') ?? ''\r\n itemCode = iObj.getString('coupon_code') ?? ''\r\n itemStatus = iObj.getNumber('status') ?? 0\r\n itemRecv = iObj.getString('received_at') ?? ''\r\n itemExpire = iObj.getString('expire_at') ?? ''\r\n }\r\n \r\n if (template == null) template = new UTSJSONObject()\r\n \r\n let tName = ''\r\n let tAmount = 0\r\n let tMin = 0\r\n \r\n if (template instanceof UTSJSONObject) {\r\n tName = template.getString('name') ?? '优惠券'\r\n tAmount = template.getNumber('amount') ?? 0\r\n tMin = template.getNumber('min_spend') ?? 0\r\n } else {\r\n const tObj = JSON.parse(JSON.stringify(template)) as UTSJSONObject\r\n tName = tObj.getString('name') ?? '优惠券'\r\n tAmount = tObj.getNumber('amount') ?? 0\r\n tMin = tObj.getNumber('min_spend') ?? 0\r\n }\r\n\r\n // 创建真正的 UserCoupon 对象,而不是 UTSJSONObject\r\n const couponItem: UserCoupon = {\r\n id: itemId,\r\n user_id: itemUserId,\r\n template_id: itemTmplId,\r\n coupon_code: itemCode,\r\n status: itemStatus,\r\n received_at: itemRecv,\r\n expire_at: itemExpire,\r\n template_name: tName,\r\n amount: tAmount,\r\n min_spend: tMin\r\n }\r\n \r\n coupons.push(couponItem)\r\n }\r\n\r\n return coupons\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5290','获取优惠券异常:', e)\r\n const empty: UserCoupon[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取可用优惠券数量\r\n async getUserCouponCount(): Promise<number> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return 0\r\n\r\n const response = await supa\r\n .from('ml_user_coupons')\r\n .select('id', { count: 'exact' })\r\n .eq('user_id', userId!)\r\n .eq('status', '1')\r\n .gt('expire_at', new Date().toISOString())\r\n .limit(1)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n return 0\r\n }\r\n return response.total ?? 0\r\n } catch (e) {\r\n return 0\r\n }\r\n }\r\n\r\n // 获取店铺/商品可用优惠券\r\n async getAvailableCoupons(merchantId: string): Promise<any[]> {\r\n return this.fetchShopCoupons(merchantId)\r\n }\r\n\r\n // ALIAS for Cache busting: 获取店铺优惠券\r\n async fetchShopCoupons(merchantId: string): Promise<any[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:5328','[fetchShopCoupons] 开始获取优惠券merchantId:', merchantId)\r\n // 查询该商家的优惠券 + 平台通用券 (merchant_id is null)\r\n // 注意:这里简化逻辑,实际可能需要联合查询用户是否已领取\r\n const response = await supa\r\n .from('ml_coupon_templates')\r\n .select('*')\r\n .or(`merchant_id.eq.${merchantId},merchant_id.is.null`)\r\n .eq('status', '1') // 使用字符串 '1'\r\n .gt('end_time', new Date().toISOString())\r\n .order('discount_value', { ascending: false })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5341','Fetch coupons failed:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n __f__('log','at utils/supabaseService.uts:5351','[fetchShopCoupons] 获取到优惠券数量:', (data as any[]).length)\r\n return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5354','Fetch coupons error:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 领取优惠券\r\n async claimCoupon(templateId: string, userId: string): Promise<boolean> {\r\n return this.claimShopCoupon(templateId, userId)\r\n }\r\n\r\n // ALIAS for Cache busting\r\n async claimShopCoupon(templateId: string, userId: string): Promise<boolean> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:5368','Claiming coupon templateId:', templateId, 'userId:', userId)\r\n\r\n // 1. Fetch template details to get merchant_id and validity\r\n const tmplRes = await supa\r\n .from('ml_coupon_templates')\r\n .select('*')\r\n .eq('id', templateId)\r\n .limit(1)\r\n .execute()\r\n \r\n if (tmplRes.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5379','Claim Coupon: Template query error', tmplRes.error)\r\n return false\r\n }\r\n\r\n // Null check for data\r\n if (tmplRes.data == null) {\r\n __f__('error','at utils/supabaseService.uts:5385','Claim Coupon: Template data response is null')\r\n return false\r\n }\r\n \r\n const dataList = tmplRes.data as any[]\r\n if (dataList.length === 0) {\r\n __f__('error','at utils/supabaseService.uts:5391','Claim Coupon: Template not found (empty list)')\r\n return false\r\n }\r\n\r\n const template = dataList[0]\r\n \r\n // Safe property access\r\n let validDays = 0\r\n let endTimeStr: string | null = null\r\n let merchantId: string | null = null\r\n \r\n if (template instanceof UTSJSONObject) {\r\n validDays = template.getNumber('valid_days') ?? 0\r\n endTimeStr = template.getString('end_time')\r\n merchantId = template.getString('merchant_id')\r\n } else {\r\n const tJson = JSON.parse(JSON.stringify(template)) as UTSJSONObject\r\n validDays = tJson.getNumber('valid_days') ?? 0\r\n endTimeStr = tJson.getString('end_time')\r\n merchantId = tJson.getString('merchant_id')\r\n }\r\n \r\n // Calculate expire_at\r\n let expireAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()\r\n if (validDays > 0) {\r\n expireAt = new Date(Date.now() + (validDays * 24 * 60 * 60 * 1000)).toISOString()\r\n } else if (endTimeStr != null && endTimeStr !== '') {\r\n expireAt = endTimeStr\r\n }\r\n \r\n // Handle UUID fields: Empty string is not valid UUID, must be null\r\n if (merchantId != null && merchantId.length === 0) {\r\n merchantId = null\r\n }\r\n\r\n // 2. Insert into user coupons with merchant_id\r\n const insertData = {\r\n user_id: userId,\r\n template_id: templateId,\r\n merchant_id: merchantId, // Important for shop filtering: null for platform coupons\r\n coupon_code: 'C' + Date.now() + Math.floor(Math.random() * 1000), \r\n status: 1, \r\n expire_at: expireAt,\r\n received_at: new Date().toISOString()\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:5437','Claim Coupon Insert Payload:', JSON.stringify(insertData))\r\n\r\n const response = await supa\r\n .from('ml_user_coupons')\r\n .insert(insertData)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5445','Claim Coupon: Insert failed:', JSON.stringify(response.error))\r\n // 尝试降级:如果 merchant_id 报错,尝试不带 merchant_id (仅调试用,或兼容旧表结构)\r\n if (JSON.stringify(response.error).includes('merchant_id')) {\r\n __f__('log','at utils/supabaseService.uts:5448','Retrying without merchant_id...')\r\n const fallbackData = {\r\n user_id: userId,\r\n template_id: templateId,\r\n coupon_code: 'C' + Date.now() + Math.random().toString().substring(2,6),\r\n status: 1,\r\n expire_at: expireAt,\r\n received_at: new Date().toISOString()\r\n }\r\n const res2 = await supa.from('ml_user_coupons').insert(fallbackData).execute()\r\n if (res2.error == null) return true\r\n }\r\n return false\r\n }\r\n return true\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:5464','Claim coupon error:', e)\r\n return false\r\n }\r\n }\r\n\r\n // ==========================================\r\n // 聊天相关方法\r\n // ==========================================\r\n\r\n // 发送消息\r\n async sendMessage(merchantId: string, content: string, msgType: string = 'text'): Promise<boolean> {\r\n // 确保 session 有效\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:5478',\"sendMessage failed: user not logged in or session lost\")\r\n return false\r\n }\r\n\r\n try {\r\n // Debug check\r\n // const session = supa.getSession()\r\n // __f__('log','at utils/supabaseService.uts:5485',\"Sending check: UserID\", userId, \"AuthID:\", session.user?.getString('id'))\r\n \r\n const msg = {\r\n sender_id: userId!,\r\n receiver_id: merchantId,\r\n content: content,\r\n msg_type: msgType,\r\n is_read: false,\r\n is_from_user: true\r\n }\r\n \r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .insert(msg)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5502','sendMessage error:', response.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5507','sendMessage exception:', e)\r\n return false\r\n }\r\n }\r\n \r\n // 上传聊天图片\r\n async uploadChatImage(filePath: string): Promise<string> {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:5516',\"uploadChatImage failed: user not logged in\")\r\n return ''\r\n }\r\n \r\n try {\r\n // 生成唯一文件名\r\n const timestamp = Date.now()\r\n const randomStr = Math.random().toString(36).substring(2, 8)\r\n const fileName = `chat_${userId}_${timestamp}_${randomStr}.jpg`\r\n const storagePath = `chat-images/${fileName}`\r\n \r\n __f__('log','at utils/supabaseService.uts:5527','[uploadChatImage] 开始上传:', filePath, '->', storagePath)\r\n \r\n const response = await supa.storage\r\n .from('chat')\r\n .upload(storagePath, filePath, {})\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5534','[uploadChatImage] 上传失败:', response.error)\r\n return ''\r\n }\r\n \r\n // 构建公开访问URL\r\n const publicUrl = `${supa.baseUrl}/storage/v1/object/public/chat/${storagePath}`\r\n __f__('log','at utils/supabaseService.uts:5540','[uploadChatImage] 上传成功:', publicUrl)\r\n return publicUrl\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5543','[uploadChatImage] 上传异常:', e)\r\n return ''\r\n }\r\n }\r\n \r\n // 标记会话已读\r\n async markRead(merchantId: string): Promise<boolean> {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n try {\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .update({ is_read: true })\r\n .eq('sender_id', merchantId)\r\n .eq('receiver_id', userId)\r\n .eq('is_read', false)\r\n .execute() \r\n\r\n if (response.error != null) return false\r\n } catch (e) { return false }\r\n return true\r\n }\r\n\r\n // 提交商品评价\r\n async submitProductReviews(reviews: Array<UTSJSONObject>): Promise<boolean> {\r\n try {\r\n for (let i: number = 0; i < reviews.length; i++) {\r\n const review = reviews[i]\r\n const response = await supa\r\n .from('ml_product_reviews')\r\n .insert(review)\r\n .execute() \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:5576','提交商品评价失败:', response.error)\r\n return false\r\n }\r\n }\r\n return true\r\n } catch (e) { \r\n __f__('error','at utils/supabaseService.uts:5582','提交商品评价失败:', e)\r\n return false \r\n }\r\n }\r\n\r\n // 提交店铺评价\r\n async submitShopReview(review: UTSJSONObject): Promise<boolean> {\r\n try {\r\n const response = await supa\r\n .from('ml_shop_reviews')\r\n .insert(review)\r\n .execute() \r\n return response.error == null\r\n } catch (e) { \r\n __f__('error','at utils/supabaseService.uts:5596','提交店铺评价失败:', e)\r\n return false \r\n }\r\n }\r\n\r\n // 更新订单状态\r\n async updateOrderStatus(orderId: string, status: number): Promise<boolean> {\r\n try {\r\n const updateData = new UTSJSONObject()\r\n updateData.set('order_status', status)\r\n const response = await supa\r\n .from('ml_orders')\r\n .update(updateData) \r\n .eq('id', orderId)\r\n .execute() \r\n return response.error == null\r\n } catch (e) { \r\n __f__('error','at utils/supabaseService.uts:5613','更新订单状态失败:', e)\r\n return false \r\n }\r\n }\r\n\r\n // ==================== 智能推荐相关API ====================\r\n\r\n // 获取热搜词(全站搜索频率最高的关键词)\r\n async getHotKeywords(limit: number = 10): Promise<string[]> {\r\n try {\r\n const response = await supa\r\n .from('ml_search_history')\r\n .select('keyword')\r\n .order('created_at', { ascending: false })\r\n .limit(100)\r\n .execute()\r\n \r\n if (response.error != null || response.data == null) {\r\n return [] as string[]\r\n }\r\n \r\n // 统计关键词频率\r\n const keywordCount = new Map<string, number>()\r\n const rawList = response.data as any[]\r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n const keyword = safeGetString(itemObj, 'keyword').toLowerCase().trim()\r\n if (keyword.length > 0) {\r\n const count = keywordCount.get(keyword) ?? 0\r\n keywordCount.set(keyword, count + 1)\r\n }\r\n }\r\n \r\n // 按频率排序并返回前N个 - UTS兼容方式\r\n // 将Map转换为数组进行排序\r\n type KeywordEntry = {\r\n keyword: string\r\n count: number\r\n }\r\n const entryArray: KeywordEntry[] = []\r\n \r\n // 使用forEach遍历MapUTS支持\r\n keywordCount.forEach((value: number, key: string) => {\r\n entryArray.push({\r\n keyword: key,\r\n count: value\r\n })\r\n })\r\n \r\n // 按count降序排序\r\n entryArray.sort((a: KeywordEntry, b: KeywordEntry): number => {\r\n return b.count - a.count\r\n })\r\n \r\n // 取前limit个并提取关键词\r\n const sortedKeywords: string[] = []\r\n const maxCount = Math.min(entryArray.length, limit)\r\n for (let i = 0; i < maxCount; i++) {\r\n sortedKeywords.push(entryArray[i].keyword)\r\n }\r\n \r\n return sortedKeywords\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5677','获取热搜词失败:', e)\r\n return [] as string[]\r\n }\r\n }\r\n\r\n // 获取用户搜索历史\r\n async getUserSearchHistory(limit: number = 10): Promise<string[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return [] as string[]\r\n }\r\n \r\n const response = await supa\r\n .from('ml_search_history')\r\n .select('keyword')\r\n .order('created_at', { ascending: false })\r\n .limit(limit * 2)\r\n .execute()\r\n \r\n if (response.error != null || response.data == null) {\r\n return [] as string[]\r\n }\r\n \r\n const keywords: string[] = []\r\n const rawList = response.data as any[]\r\n const seen = new Set<string>()\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n const rawUserId = itemObj.get('user_id')\r\n const itemUserId = (typeof rawUserId == 'string') ? (rawUserId as string) : ''\r\n \r\n // 只获取当前用户的搜索历史\r\n if (itemUserId !== userId) continue\r\n \r\n const keyword = safeGetString(itemObj, 'keyword').trim()\r\n if (keyword.length > 0 && !seen.has(keyword)) {\r\n keywords.push(keyword)\r\n seen.add(keyword)\r\n if (keywords.length >= limit) break\r\n }\r\n }\r\n \r\n return keywords\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5724','获取用户搜索历史失败:', e)\r\n return [] as string[]\r\n }\r\n }\r\n\r\n // 获取用户浏览历史中的商品分类\r\n async getUserBrowseCategories(limit: number = 5): Promise<string[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return [] as string[]\r\n }\r\n \r\n const response = await supa\r\n .from('ml_browse_history')\r\n .select('product_id')\r\n .order('created_at', { ascending: false })\r\n .limit(20)\r\n .execute()\r\n \r\n if (response.error != null || response.data == null) {\r\n return [] as string[]\r\n }\r\n \r\n // 获取浏览过的商品ID\r\n const productIds: string[] = []\r\n const rawList = response.data as any[]\r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n // 手动过滤 user_id\r\n const rawUserId = itemObj.get('user_id')\r\n const itemUserId = (typeof rawUserId == 'string') ? (rawUserId as string) : ''\r\n if (itemUserId !== userId) continue\r\n \r\n const productId = safeGetString(itemObj, 'product_id')\r\n if (productId.length > 0) {\r\n productIds.push(productId)\r\n }\r\n }\r\n \r\n if (productIds.length === 0) {\r\n return [] as string[]\r\n }\r\n \r\n // 查询这些商品的分类\r\n const prodResponse = await supa\r\n .from('ml_products')\r\n .select('category_id')\r\n .limit(50)\r\n .execute()\r\n \r\n if (prodResponse.error != null || prodResponse.data == null) {\r\n return [] as string[]\r\n }\r\n \r\n const categoryIds: string[] = []\r\n const prodList = prodResponse.data as any[]\r\n for (let i = 0; i < prodList.length; i++) {\r\n const prodItem = prodList[i]\r\n const prodObj = JSON.parse(JSON.stringify(prodItem)) as UTSJSONObject\r\n const prodId = safeGetString(prodObj, 'id')\r\n \r\n // 只统计浏览过的商品\r\n let found = false\r\n for (let j = 0; j < productIds.length; j++) {\r\n if (productIds[j] == prodId) {\r\n found = true\r\n break\r\n }\r\n }\r\n if (!found) continue\r\n \r\n const catId = safeGetString(prodObj, 'category_id')\r\n if (catId.length > 0 && categoryIds.indexOf(catId) < 0) {\r\n categoryIds.push(catId)\r\n if (categoryIds.length >= limit) break\r\n }\r\n }\r\n \r\n return categoryIds\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5807','获取用户浏览分类失败:', e)\r\n return [] as string[]\r\n }\r\n }\r\n\r\n // 智能推荐:综合用户搜索历史、浏览历史、热销商品\r\n async getSmartRecommendations(limit: number = 10): Promise<Product[]> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:5815','[getSmartRecommendations] 开始获取智能推荐...')\r\n \r\n const products: Product[] = []\r\n const addedIds = new Set<string>()\r\n \r\n // 1. 根据用户搜索历史推荐商品(权重最高)\r\n const searchHistory = await this.getUserSearchHistory(5)\r\n __f__('log','at utils/supabaseService.uts:5822','[getSmartRecommendations] 用户搜索历史:', searchHistory)\r\n \r\n if (searchHistory.length > 0) {\r\n // 根据搜索关键词查找商品\r\n const keywordProducts = await this.searchProductsByKeywords(searchHistory, limit)\r\n for (let i = 0; i < keywordProducts.length; i++) {\r\n const prod = keywordProducts[i]\r\n if (!addedIds.has(prod.id)) {\r\n products.push(prod)\r\n addedIds.add(prod.id)\r\n }\r\n }\r\n }\r\n \r\n // 2. 根据用户浏览历史推荐相似分类商品\r\n if (products.length < limit) {\r\n const browseCategories = await this.getUserBrowseCategories(3)\r\n __f__('log','at utils/supabaseService.uts:5839','[getSmartRecommendations] 用户浏览分类:', browseCategories)\r\n \r\n if (browseCategories.length > 0) {\r\n const categoryProducts = await this.getProductsByCategories(browseCategories, limit - products.length)\r\n for (let i = 0; i < categoryProducts.length; i++) {\r\n const prod = categoryProducts[i]\r\n if (!addedIds.has(prod.id)) {\r\n products.push(prod)\r\n addedIds.add(prod.id)\r\n }\r\n }\r\n }\r\n }\r\n \r\n // 3. 补充热销商品\r\n if (products.length < limit) {\r\n const hotProducts = await this.getHotProducts(limit - products.length + 5)\r\n for (let i = 0; i < hotProducts.length; i++) {\r\n const prod = hotProducts[i]\r\n if (!addedIds.has(prod.id)) {\r\n products.push(prod)\r\n addedIds.add(prod.id)\r\n if (products.length >= limit) break\r\n }\r\n }\r\n }\r\n \r\n // 4. 如果还不够,用普通商品补充\r\n if (products.length < limit) {\r\n const moreProducts = await this.getProductsByPrice(limit - products.length + 5, false)\r\n for (let i = 0; i < moreProducts.length; i++) {\r\n const prod = moreProducts[i]\r\n if (!addedIds.has(prod.id)) {\r\n products.push(prod)\r\n addedIds.add(prod.id)\r\n if (products.length >= limit) break\r\n }\r\n }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:5879','[getSmartRecommendations] 返回商品数量:', products.length)\r\n return products.slice(0, limit)\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5882','获取智能推荐失败:', e)\r\n return [] as Product[]\r\n }\r\n }\r\n\r\n // 根据关键词列表搜索商品\r\n async searchProductsByKeywords(keywords: string[], limit: number): Promise<Product[]> {\r\n try {\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')\r\n .order('sale_count', { ascending: false })\r\n .limit(limit * 2)\r\n .execute()\r\n \r\n if (response.error != null || response.data == null) {\r\n return [] as Product[]\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = response.data as any[]\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n // 手动过滤 status\r\n const rawStatus = prodObj.get('status')\r\n let statusNum: number = 0\r\n if (typeof rawStatus == 'number') {\r\n statusNum = rawStatus as number\r\n }\r\n if (statusNum !== 1) continue\r\n \r\n // 检查是否匹配任何关键词\r\n const name = safeGetString(prodObj, 'name').toLowerCase()\r\n const desc = safeGetString(prodObj, 'description').toLowerCase()\r\n \r\n let matched = false\r\n for (let j = 0; j < keywords.length; j++) {\r\n const keyword = keywords[j].toLowerCase()\r\n if (name.indexOf(keyword) >= 0 || desc.indexOf(keyword) >= 0) {\r\n matched = true\r\n break\r\n }\r\n }\r\n \r\n if (!matched) continue\r\n \r\n products.push(parseProductFromRaw(item))\r\n if (products.length >= limit) break\r\n }\r\n \r\n return products\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5937','根据关键词搜索商品失败:', e)\r\n return [] as Product[]\r\n }\r\n }\r\n\r\n // 根据分类列表获取商品\r\n async getProductsByCategories(categoryIds: string[], limit: number): Promise<Product[]> {\r\n try {\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')\r\n .order('sale_count', { ascending: false })\r\n .limit(limit * 2)\r\n .execute()\r\n \r\n if (response.error != null || response.data == null) {\r\n return [] as Product[]\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = response.data as any[]\r\n \r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n // 手动过滤 status\r\n const rawStatus = prodObj.get('status')\r\n let statusNum: number = 0\r\n if (typeof rawStatus == 'number') {\r\n statusNum = rawStatus as number\r\n }\r\n if (statusNum !== 1) continue\r\n \r\n // 手动过滤 category_id\r\n const rawCatId = prodObj.get('category_id')\r\n const itemCatId = (typeof rawCatId == 'string') ? (rawCatId as string) : ''\r\n \r\n let matched = false\r\n for (let j = 0; j < categoryIds.length; j++) {\r\n if (itemCatId == categoryIds[j]) {\r\n matched = true\r\n break\r\n }\r\n }\r\n \r\n if (!matched) continue\r\n \r\n products.push(parseProductFromRaw(item))\r\n if (products.length >= limit) break\r\n }\r\n \r\n return products\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:5991','根据分类获取商品失败:', e)\r\n return [] as Product[]\r\n }\r\n }\r\n\r\n // 记录用户搜索行为\r\n async recordSearch(keyword: string, resultCount: number): Promise<void> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n const searchRecord = new UTSJSONObject()\r\n searchRecord.set('keyword', keyword)\r\n searchRecord.set('result_count', resultCount)\r\n if (userId != null) {\r\n searchRecord.set('user_id', userId)\r\n }\r\n \r\n await supa\r\n .from('ml_search_history')\r\n .insert(searchRecord)\r\n .execute()\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6012','记录搜索失败:', e)\r\n }\r\n }\r\n\r\n // 记录用户浏览行为\r\n async recordBrowse(productId: string, duration: number = 0): Promise<void> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return\r\n \r\n const browseRecord = new UTSJSONObject()\r\n browseRecord.set('user_id', userId)\r\n browseRecord.set('product_id', productId)\r\n browseRecord.set('browse_duration', duration)\r\n browseRecord.set('created_at', new Date().toISOString())\r\n \r\n // UTS Android不支持upsert使用insert\r\n await supa\r\n .from('ml_browse_history')\r\n .insert(browseRecord)\r\n .execute()\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6034','记录浏览失败:', e)\r\n }\r\n }\r\n\r\n // ==================== 签到相关API ====================\r\n\r\n // 用户签到\r\n async signin(): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('success', false)\r\n result.set('points', 0)\r\n result.set('continuous_days', 0)\r\n result.set('bonus_points', 0)\r\n result.set('total_points', 0)\r\n result.set('message', '')\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n result.set('message', '请先登录')\r\n return result\r\n }\r\n\r\n const today = new Date()\r\n const todayStr = today.toISOString().split('T')[0]\r\n const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)\r\n const yesterdayStr = yesterday.toISOString().split('T')[0]\r\n\r\n // 检查今天是否已签到\r\n const checkRes = await supa\r\n .from('ml_signin_records')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .eq('signin_date', todayStr)\r\n .execute()\r\n\r\n if (checkRes.error != null) {\r\n result.set('message', '查询签到状态失败')\r\n return result\r\n }\r\n\r\n const checkData = checkRes.data as any[]\r\n if (checkData != null && checkData.length > 0) {\r\n result.set('message', '今天已签到')\r\n return result\r\n }\r\n\r\n // 查询昨天是否签到,计算连续天数\r\n const yesterdayRes = await supa\r\n .from('ml_signin_records')\r\n .select('continuous_days')\r\n .eq('user_id', userId!)\r\n .eq('signin_date', yesterdayStr)\r\n .execute()\r\n\r\n let continuousDays = 1\r\n if (yesterdayRes.error == null && yesterdayRes.data != null) {\r\n const yData = yesterdayRes.data as any[]\r\n if (yData.length > 0) {\r\n const yItem = yData[0]\r\n let yDays = 0\r\n if (yItem instanceof UTSJSONObject) {\r\n yDays = yItem.getNumber('continuous_days') ?? 0\r\n } else {\r\n const yObj = JSON.parse(JSON.stringify(yItem)) as UTSJSONObject\r\n yDays = yObj.getNumber('continuous_days') ?? 0\r\n }\r\n continuousDays = yDays + 1\r\n }\r\n }\r\n\r\n // 计算积分\r\n let pointsEarned = 5 // 每日签到基础积分\r\n let bonusPoints = 0\r\n\r\n if (continuousDays >= 30) {\r\n bonusPoints = 100\r\n } else if (continuousDays >= 7) {\r\n bonusPoints = 20\r\n }\r\n\r\n const totalPointsEarned = pointsEarned + bonusPoints\r\n\r\n // 插入签到记录\r\n const signinRecord = new UTSJSONObject()\r\n signinRecord.set('user_id', userId!)\r\n signinRecord.set('signin_date', todayStr)\r\n signinRecord.set('points_earned', pointsEarned)\r\n signinRecord.set('bonus_points', bonusPoints)\r\n signinRecord.set('continuous_days', continuousDays)\r\n\r\n const insertRes = await supa\r\n .from('ml_signin_records')\r\n .insert(signinRecord)\r\n .execute()\r\n\r\n if (insertRes.error != null) {\r\n result.set('message', '签到失败')\r\n return result\r\n }\r\n\r\n // 更新用户积分\r\n await this.addPoints(userId!, totalPointsEarned, 'signin', '每日签到')\r\n\r\n // 获取最新积分\r\n const newPoints = await this.getUserPoints()\r\n\r\n result.set('success', true)\r\n result.set('points', pointsEarned)\r\n result.set('continuous_days', continuousDays)\r\n result.set('bonus_points', bonusPoints)\r\n result.set('total_points', newPoints)\r\n result.set('message', '签到成功')\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6150','签到异常:', e)\r\n result.set('message', '签到异常')\r\n return result\r\n }\r\n }\r\n\r\n // 获取签到记录(当月)\r\n async getSigninRecords(year: number, month: number): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const startDate = `${year}-${month.toString().padStart(2, '0')}-01`\r\n const endDate = month === 12 \r\n ? `${year + 1}-01-01` \r\n : `${year}-${(month + 1).toString().padStart(2, '0')}-01`\r\n\r\n const response = await supa\r\n .from('ml_signin_records')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .gte('signin_date', startDate)\r\n .lt('signin_date', endDate)\r\n .order('signin_date', { ascending: true })\r\n .execute()\r\n\r\n if (response.error != null || response.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return response.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6186','获取签到记录失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取今日签到状态\r\n async getTodaySigninStatus(): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('signed', false)\r\n result.set('continuous_days', 0)\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return result\r\n\r\n const today = new Date().toISOString().split('T')[0]\r\n\r\n // 检查今天是否签到\r\n const todayRes = await supa\r\n .from('ml_signin_records')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .eq('signin_date', today)\r\n .execute()\r\n\r\n if (todayRes.error == null && todayRes.data != null) {\r\n const tData = todayRes.data as any[]\r\n if (tData.length > 0) {\r\n const tItem = tData[0]\r\n let cDays = 0\r\n if (tItem instanceof UTSJSONObject) {\r\n cDays = tItem.getNumber('continuous_days') ?? 0\r\n } else {\r\n const tObj = JSON.parse(JSON.stringify(tItem)) as UTSJSONObject\r\n cDays = tObj.getNumber('continuous_days') ?? 0\r\n }\r\n result.set('signed', true)\r\n result.set('continuous_days', cDays)\r\n return result\r\n }\r\n }\r\n\r\n // 今天未签到,获取最近的连续签到天数\r\n const lastRes = await supa\r\n .from('ml_signin_records')\r\n .select('continuous_days, signin_date')\r\n .eq('user_id', userId!)\r\n .order('signin_date', { ascending: false })\r\n .limit(1)\r\n .execute()\r\n\r\n if (lastRes.error == null && lastRes.data != null) {\r\n const lData = lastRes.data as any[]\r\n if (lData.length > 0) {\r\n const lItem = lData[0]\r\n let lastDate = ''\r\n let lastDays = 0\r\n if (lItem instanceof UTSJSONObject) {\r\n lastDate = lItem.getString('signin_date') ?? ''\r\n lastDays = lItem.getNumber('continuous_days') ?? 0\r\n } else {\r\n const lObj = JSON.parse(JSON.stringify(lItem)) as UTSJSONObject\r\n lastDate = lObj.getString('signin_date') ?? ''\r\n lastDays = lObj.getNumber('continuous_days') ?? 0\r\n }\r\n\r\n const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0]\r\n if (lastDate === yesterday) {\r\n result.set('continuous_days', lastDays)\r\n }\r\n }\r\n }\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6262','获取签到状态失败:', e)\r\n return result\r\n }\r\n }\r\n\r\n // ==================== 积分兑换相关API ====================\r\n\r\n // 获取积分兑换商品列表\r\n async getPointProducts(): Promise<any[]> {\r\n try {\r\n const response = await supa\r\n .from('ml_point_products')\r\n .select('*')\r\n .eq('status', 1)\r\n .gt('stock', 0)\r\n .order('sort_order', { ascending: true })\r\n .execute()\r\n\r\n if (response.error != null || response.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return response.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6287','获取积分商品失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 积分兑换\r\n async exchangeProduct(productId: string, quantity: number, addressSnapshot: UTSJSONObject | null): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('success', false)\r\n result.set('message', '')\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n result.set('message', '请先登录')\r\n return result\r\n }\r\n\r\n // 获取商品信息\r\n const productRes = await supa\r\n .from('ml_point_products')\r\n .select('*')\r\n .eq('id', productId)\r\n .single()\r\n .execute()\r\n\r\n if (productRes.error != null || productRes.data == null) {\r\n result.set('message', '商品不存在')\r\n return result\r\n }\r\n\r\n const productRaw = productRes.data\r\n let pointsRequired = 0\r\n let stock = 0\r\n let productType = ''\r\n \r\n // 检查是否是数组,如果是则取第一个元素\r\n let productObj: UTSJSONObject | null = null\r\n if (Array.isArray(productRaw)) {\r\n const arr = productRaw as any[]\r\n if (arr.length > 0) {\r\n const firstItem = arr[0]\r\n if (firstItem instanceof UTSJSONObject) {\r\n productObj = firstItem\r\n } else {\r\n productObj = JSON.parse(JSON.stringify(firstItem)) as UTSJSONObject\r\n }\r\n }\r\n } else {\r\n if (productRaw instanceof UTSJSONObject) {\r\n productObj = productRaw\r\n } else {\r\n productObj = JSON.parse(JSON.stringify(productRaw)) as UTSJSONObject\r\n }\r\n }\r\n \r\n // 使用 UTSJSONObject 方法访问属性\r\n if (productObj != null) {\r\n pointsRequired = productObj.getNumber('points_required') ?? 0\r\n stock = productObj.getNumber('stock') ?? 0\r\n productType = productObj.getString('product_type') ?? ''\r\n }\r\n\r\n const totalPoints = pointsRequired * quantity\r\n\r\n // 检查库存\r\n if (stock < quantity) {\r\n result.set('message', '库存不足')\r\n return result\r\n }\r\n\r\n // 检查积分\r\n const userPoints = await this.getUserPoints()\r\n if (userPoints < totalPoints) {\r\n result.set('message', '积分不足')\r\n return result\r\n }\r\n\r\n // 创建兑换记录\r\n const exchangeRecord = new UTSJSONObject()\r\n exchangeRecord.set('user_id', userId!)\r\n exchangeRecord.set('product_id', productId)\r\n exchangeRecord.set('quantity', quantity)\r\n exchangeRecord.set('points_used', totalPoints)\r\n exchangeRecord.set('status', 0)\r\n if (addressSnapshot != null && productType === 'physical') {\r\n exchangeRecord.set('address_snapshot', JSON.stringify(addressSnapshot))\r\n }\r\n\r\n const insertRes = await supa\r\n .from('ml_point_exchanges')\r\n .insert(exchangeRecord)\r\n .execute()\r\n\r\n if (insertRes.error != null) {\r\n __f__('error','at utils/supabaseService.uts:6383','[exchangeProduct] 创建兑换记录失败:', insertRes.error)\r\n result.set('message', '兑换失败')\r\n return result\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:6388','[exchangeProduct] 兑换记录创建成功')\r\n\r\n // 扣减库存\r\n __f__('log','at utils/supabaseService.uts:6391','[exchangeProduct] 准备扣减库存')\r\n __f__('log','at utils/supabaseService.uts:6392','[exchangeProduct] productId 类型:', typeof productId)\r\n __f__('log','at utils/supabaseService.uts:6393','[exchangeProduct] productId 值:', productId)\r\n __f__('log','at utils/supabaseService.uts:6394','[exchangeProduct] 当前库存:', stock, ', 扣减数量:', quantity)\r\n \r\n // 使用 UTSJSONObject 替代 Record<string, any>\r\n const stockUpdateData = new UTSJSONObject()\r\n stockUpdateData.set('stock', stock - quantity)\r\n \r\n __f__('log','at utils/supabaseService.uts:6400','[exchangeProduct] stockUpdateData:', stockUpdateData)\r\n __f__('log','at utils/supabaseService.uts:6401','[exchangeProduct] stockUpdateData 类型:', typeof stockUpdateData)\r\n \r\n // 先查询确认商品存在\r\n const checkProduct = await supa\r\n .from('ml_point_products')\r\n .select('id, stock')\r\n .eq('id', productId)\r\n .execute()\r\n __f__('log','at utils/supabaseService.uts:6409','[exchangeProduct] 查询商品结果:', checkProduct.data, 'error:', checkProduct.error)\r\n \r\n const stockUpdateRes = await supa\r\n .from('ml_point_products')\r\n .update(stockUpdateData)\r\n .eq('id', productId)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:6417','[exchangeProduct] 库存更新结果 error:', stockUpdateRes.error)\r\n __f__('log','at utils/supabaseService.uts:6418','[exchangeProduct] 库存更新结果 data:', stockUpdateRes.data)\r\n \r\n if (stockUpdateRes.error != null) {\r\n __f__('error','at utils/supabaseService.uts:6421','[exchangeProduct] 扣减库存失败:', stockUpdateRes.error)\r\n }\r\n\r\n // 扣减积分\r\n __f__('log','at utils/supabaseService.uts:6425','[exchangeProduct] 准备扣减积分, userId:', userId, ', 积分:', totalPoints)\r\n const deductResult = await this.deductPoints(userId!, totalPoints, 'redeem', '积分兑换商品')\r\n __f__('log','at utils/supabaseService.uts:6427','[exchangeProduct] 积分扣减结果:', deductResult)\r\n \r\n if (!deductResult) {\r\n __f__('error','at utils/supabaseService.uts:6430','[exchangeProduct] 扣减积分失败')\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:6433','[exchangeProduct] 兑换流程完成')\r\n result.set('success', true)\r\n result.set('message', '兑换成功')\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6438','积分兑换异常:', e)\r\n result.set('message', '兑换异常')\r\n return result\r\n }\r\n }\r\n\r\n // 获取兑换记录\r\n async getExchangeRecords(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const response = await supa\r\n .from('ml_point_exchanges')\r\n .select('*, product:ml_point_products(name, image_url, product_type)')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (response.error != null || response.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return response.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6467','获取兑换记录失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // ==================== 评价相关API ====================\r\n\r\n // 获取商品评价列表\r\n async getProductReviews(productId: string, page: number = 1, limit: number = 10, rating: number = 0, hasImage: boolean = false): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('total', 0)\r\n result.set('page', page)\r\n result.set('limit', limit)\r\n result.set('data', [] as any[])\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n \r\n let query = supa\r\n .from('ml_product_reviews')\r\n .select('*, user:auth.users!ml_product_reviews_user_id_fkey(raw_user_meta_data)', { count: 'exact' })\r\n .eq('product_id', productId)\r\n\r\n if (rating > 0) {\r\n query = query.eq('rating', rating)\r\n }\r\n\r\n if (hasImage) {\r\n query = query.neq('images', '[]')\r\n }\r\n\r\n const offset = (page - 1) * limit\r\n const response = await query\r\n .order('created_at', { ascending: false })\r\n .range(offset, offset + limit - 1)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:6506','获取评价列表失败:', response.error)\r\n return result\r\n }\r\n\r\n const total = response.total ?? 0\r\n const reviews = response.data as any[]\r\n\r\n // 处理评价数据\r\n const processedReviews: any[] = []\r\n for (let i = 0; i < reviews.length; i++) {\r\n const review = reviews[i]\r\n const processed = JSON.parse(JSON.stringify(review)) as UTSJSONObject\r\n\r\n // 处理用户信息\r\n const userRaw = processed.get('user')\r\n let userName = '匿名用户'\r\n let userAvatar = ''\r\n\r\n if (userRaw != null) {\r\n let userData: UTSJSONObject\r\n if (userRaw instanceof UTSJSONObject) {\r\n userData = userRaw as UTSJSONObject\r\n } else {\r\n userData = JSON.parse(JSON.stringify(userRaw)) as UTSJSONObject\r\n }\r\n const metaData = userData.get('raw_user_meta_data')\r\n if (metaData != null) {\r\n let metaObj: UTSJSONObject\r\n if (metaData instanceof UTSJSONObject) {\r\n metaObj = metaData as UTSJSONObject\r\n } else {\r\n metaObj = JSON.parse(JSON.stringify(metaData)) as UTSJSONObject\r\n }\r\n userName = metaObj.getString('nickname') ?? metaObj.getString('name') ?? '匿名用户'\r\n userAvatar = metaObj.getString('avatar_url') ?? ''\r\n }\r\n }\r\n\r\n // 检查是否匿名\r\n const isAnonymous = processed.getBoolean('is_anonymous') ?? false\r\n if (isAnonymous) {\r\n userName = '匿名用户'\r\n userAvatar = ''\r\n }\r\n\r\n processed.set('user_name', userName)\r\n processed.set('user_avatar', userAvatar)\r\n\r\n // 检查当前用户是否点赞\r\n let isLiked = false\r\n if (userId != null) {\r\n const likeRes = await supa\r\n .from('ml_review_likes')\r\n .select('id')\r\n .eq('review_id', processed.getString('id') ?? '')\r\n .eq('user_id', userId!)\r\n .limit(1)\r\n .execute()\r\n if (likeRes.error == null && likeRes.data != null) {\r\n const likeData = likeRes.data as any[]\r\n isLiked = likeData.length > 0\r\n }\r\n }\r\n processed.set('is_liked', isLiked)\r\n\r\n processedReviews.push(processed)\r\n }\r\n\r\n result.set('total', total)\r\n result.set('data', processedReviews)\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6578','获取评价列表异常:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 获取商品评价统计\r\n async getReviewStats(productId: string): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('total_count', 0)\r\n result.set('avg_rating', 0)\r\n result.set('good_rate', 0)\r\n result.set('rating_distribution', new UTSJSONObject())\r\n result.set('tags', [] as any[])\r\n\r\n try {\r\n const response = await supa\r\n .from('ml_product_reviews')\r\n .select('rating')\r\n .eq('product_id', productId)\r\n .execute()\r\n\r\n if (response.error != null || response.data == null) {\r\n return result\r\n }\r\n\r\n const reviews = response.data as any[]\r\n const totalCount = reviews.length\r\n\r\n if (totalCount === 0) return result\r\n\r\n let totalRating = 0\r\n let goodCount = 0\r\n const distribution: Map<number, number> = new Map()\r\n distribution.set(1, 0)\r\n distribution.set(2, 0)\r\n distribution.set(3, 0)\r\n distribution.set(4, 0)\r\n distribution.set(5, 0)\r\n\r\n for (let i = 0; i < reviews.length; i++) {\r\n const review = reviews[i]\r\n let rating = 0\r\n if (review instanceof UTSJSONObject) {\r\n rating = review.getNumber('rating') ?? 0\r\n } else {\r\n const rObj = JSON.parse(JSON.stringify(review)) as UTSJSONObject\r\n rating = rObj.getNumber('rating') ?? 0\r\n }\r\n\r\n totalRating += rating\r\n if (rating >= 4) goodCount++\r\n\r\n const currentCount = distribution.get(rating) ?? 0\r\n distribution.set(rating, currentCount + 1)\r\n }\r\n\r\n const avgRating = Math.round((totalRating / totalCount) * 10) / 10\r\n const goodRate = Math.round((goodCount / totalCount) * 100)\r\n\r\n const distObj = new UTSJSONObject()\r\n distribution.forEach((value: number, key: number) => {\r\n distObj.set(key.toString(), value)\r\n })\r\n\r\n result.set('total_count', totalCount)\r\n result.set('avg_rating', avgRating)\r\n result.set('good_rate', goodRate)\r\n result.set('rating_distribution', distObj)\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6649','获取评价统计异常:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 评价点赞\r\n async toggleReviewLike(reviewId: string): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('success', false)\r\n result.set('is_liked', false)\r\n result.set('like_count', 0)\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return result\r\n }\r\n\r\n // 检查是否已点赞\r\n const checkRes = await supa\r\n .from('ml_review_likes')\r\n .select('id')\r\n .eq('review_id', reviewId)\r\n .eq('user_id', userId!)\r\n .limit(1)\r\n .execute()\r\n\r\n let isLiked = false\r\n if (checkRes.error == null && checkRes.data != null) {\r\n const checkData = checkRes.data as any[]\r\n isLiked = checkData.length > 0\r\n }\r\n\r\n if (isLiked) {\r\n // 取消点赞\r\n await supa\r\n .from('ml_review_likes')\r\n .eq('review_id', reviewId)\r\n .eq('user_id', userId!)\r\n .delete()\r\n .execute()\r\n\r\n // 更新点赞数 - 直接查询并更新\r\n const currentCountRes = await supa\r\n .from('ml_product_reviews')\r\n .select('like_count')\r\n .eq('id', reviewId)\r\n .single()\r\n .execute()\r\n \r\n if (currentCountRes.error == null && currentCountRes.data != null) {\r\n let currentCount = 0\r\n if (currentCountRes.data instanceof UTSJSONObject) {\r\n currentCount = currentCountRes.data.getNumber('like_count') ?? 0\r\n } else {\r\n const countObj = JSON.parse(JSON.stringify(currentCountRes.data)) as UTSJSONObject\r\n currentCount = countObj.getNumber('like_count') ?? 0\r\n }\r\n \r\n const updateData = new UTSJSONObject()\r\n updateData.set('like_count', Math.max(0, currentCount - 1))\r\n await supa\r\n .from('ml_product_reviews')\r\n .update(updateData)\r\n .eq('id', reviewId)\r\n .execute()\r\n }\r\n\r\n result.set('is_liked', false)\r\n } else {\r\n // 添加点赞\r\n const likeRecord = new UTSJSONObject()\r\n likeRecord.set('review_id', reviewId)\r\n likeRecord.set('user_id', userId!)\r\n\r\n await supa\r\n .from('ml_review_likes')\r\n .insert(likeRecord)\r\n .execute()\r\n\r\n // 更新点赞数 - 直接查询并更新\r\n const currentCountRes = await supa\r\n .from('ml_product_reviews')\r\n .select('like_count')\r\n .eq('id', reviewId)\r\n .single()\r\n .execute()\r\n \r\n if (currentCountRes.error == null && currentCountRes.data != null) {\r\n let currentCount = 0\r\n if (currentCountRes.data instanceof UTSJSONObject) {\r\n currentCount = currentCountRes.data.getNumber('like_count') ?? 0\r\n } else {\r\n const countObj = JSON.parse(JSON.stringify(currentCountRes.data)) as UTSJSONObject\r\n currentCount = countObj.getNumber('like_count') ?? 0\r\n }\r\n \r\n const updateData = new UTSJSONObject()\r\n updateData.set('like_count', currentCount + 1)\r\n await supa\r\n .from('ml_product_reviews')\r\n .update(updateData)\r\n .eq('id', reviewId)\r\n .execute()\r\n }\r\n\r\n result.set('is_liked', true)\r\n }\r\n\r\n // 获取最新点赞数\r\n const reviewRes = await supa\r\n .from('ml_product_reviews')\r\n .select('like_count')\r\n .eq('id', reviewId)\r\n .single()\r\n .execute()\r\n\r\n if (reviewRes.error == null && reviewRes.data != null) {\r\n let likeCount = 0\r\n if (reviewRes.data instanceof UTSJSONObject) {\r\n likeCount = reviewRes.data.getNumber('like_count') ?? 0\r\n } else {\r\n const rObj = JSON.parse(JSON.stringify(reviewRes.data)) as UTSJSONObject\r\n likeCount = rObj.getNumber('like_count') ?? 0\r\n }\r\n result.set('like_count', likeCount)\r\n }\r\n\r\n result.set('success', true)\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6780','评价点赞异常:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 获取我的评价列表\r\n async getMyReviews(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const response = await supa\r\n .from('ml_product_reviews')\r\n .select(`\r\n *,\r\n product:ml_products!ml_product_reviews_product_id_fkey(name, main_image_url)\r\n `)\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (response.error != null || response.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const reviews = response.data as any[]\r\n const result: any[] = []\r\n\r\n for (let i = 0; i < reviews.length; i++) {\r\n const review = reviews[i]\r\n const processed = JSON.parse(JSON.stringify(review)) as UTSJSONObject\r\n\r\n // 处理商品信息\r\n const productRaw = processed.get('product')\r\n let productName = ''\r\n let productImage = ''\r\n if (productRaw != null) {\r\n let productObj: UTSJSONObject\r\n if (productRaw instanceof UTSJSONObject) {\r\n productObj = productRaw as UTSJSONObject\r\n } else {\r\n productObj = JSON.parse(JSON.stringify(productRaw)) as UTSJSONObject\r\n }\r\n productName = productObj.getString('name') ?? ''\r\n productImage = productObj.getString('main_image_url') ?? ''\r\n }\r\n processed.set('product_name', productName)\r\n processed.set('product_image', productImage)\r\n\r\n // 计算是否可追加评价7天内\r\n const createdAt = processed.getString('created_at') ?? ''\r\n const createdTime = new Date(createdAt).getTime()\r\n const now = Date.now()\r\n const sevenDays = 7 * 24 * 60 * 60 * 1000\r\n const canAppend = (now - createdTime) < sevenDays && (processed.getString('append_content') ?? '') === ''\r\n processed.set('can_append', canAppend)\r\n\r\n // 计算是否可编辑24小时内\r\n const oneDay = 24 * 60 * 60 * 1000\r\n const canEdit = (now - createdTime) < oneDay\r\n processed.set('can_edit', canEdit)\r\n\r\n result.push(processed)\r\n }\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6851','获取我的评价失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 追加评价\r\n async appendReview(reviewId: string, content: string, images: string[]): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n\r\n const updateData = new UTSJSONObject()\r\n updateData.set('append_content', content)\r\n updateData.set('append_images', JSON.stringify(images))\r\n updateData.set('append_at', new Date().toISOString())\r\n\r\n const response = await supa\r\n .from('ml_product_reviews')\r\n .update(updateData)\r\n .eq('id', reviewId)\r\n .eq('user_id', userId!)\r\n .execute()\r\n\r\n return response.error == null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6877','追加评价失败:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 删除评价\r\n async deleteReview(reviewId: string): Promise<boolean> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n\r\n const response = await supa\r\n .from('ml_product_reviews')\r\n .delete()\r\n .eq('id', reviewId)\r\n .eq('user_id', userId!)\r\n .execute()\r\n\r\n return response.error == null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6897','删除评价失败:', e)\r\n return false\r\n }\r\n }\r\n\r\n // ==================== 积分辅助方法 ====================\r\n\r\n // 增加积分\r\n private async addPoints(userId: string, points: number, type: string, description: string): Promise<boolean> {\r\n try {\r\n // 获取当前积分\r\n const currentPoints = await this.getUserPoints()\r\n const newPoints = currentPoints + points\r\n const totalEarned = await this.getTotalEarned()\r\n\r\n // 检查用户积分记录是否存在\r\n const checkRes = await supa\r\n .from('ml_user_points')\r\n .select('user_id')\r\n .eq('user_id', userId)\r\n .limit(1)\r\n .execute()\r\n\r\n const exists = checkRes.error == null && checkRes.data != null && (checkRes.data as any[]).length > 0\r\n\r\n if (exists) {\r\n // 更新现有记录\r\n const updateData = new UTSJSONObject()\r\n updateData.set('points', newPoints)\r\n updateData.set('total_earned', totalEarned + points)\r\n updateData.set('updated_at', new Date().toISOString())\r\n\r\n await supa\r\n .from('ml_user_points')\r\n .update(updateData)\r\n .eq('user_id', userId)\r\n .execute()\r\n } else {\r\n // 插入新记录\r\n const insertData = new UTSJSONObject()\r\n insertData.set('user_id', userId)\r\n insertData.set('points', newPoints)\r\n insertData.set('total_earned', points)\r\n insertData.set('updated_at', new Date().toISOString())\r\n\r\n await supa\r\n .from('ml_user_points')\r\n .insert(insertData)\r\n .execute()\r\n }\r\n\r\n // 记录积分变动\r\n const record = new UTSJSONObject()\r\n record.set('user_id', userId)\r\n record.set('points', points)\r\n record.set('type', type)\r\n record.set('description', description)\r\n\r\n await supa\r\n .from('ml_point_records')\r\n .insert(record)\r\n .execute()\r\n\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6962','增加积分失败:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 扣减积分\r\n private async deductPoints(userId: string, points: number, type: string, description: string): Promise<boolean> {\r\n try {\r\n const currentPoints = await this.getUserPoints()\r\n const newPoints = currentPoints - points\r\n\r\n if (newPoints < 0) return false\r\n\r\n const updateData = new UTSJSONObject()\r\n updateData.set('points', newPoints)\r\n updateData.set('updated_at', new Date().toISOString())\r\n\r\n await supa\r\n .from('ml_user_points')\r\n .update(updateData)\r\n .eq('user_id', userId)\r\n .execute()\r\n\r\n const record = new UTSJSONObject()\r\n record.set('user_id', userId)\r\n record.set('points', -points)\r\n record.set('type', type)\r\n record.set('description', description)\r\n\r\n await supa\r\n .from('ml_point_records')\r\n .insert(record)\r\n .execute()\r\n\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:6998','扣减积分失败:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 获取历史累计积分\r\n private async getTotalEarned(): Promise<number> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return 0\r\n\r\n const res = await supa\r\n .from('ml_user_points')\r\n .select('total_earned')\r\n .eq('user_id', userId!)\r\n .single()\r\n .execute()\r\n\r\n if (res.error == null && res.data != null) {\r\n if (res.data instanceof UTSJSONObject) {\r\n return res.data.getNumber('total_earned') ?? 0\r\n } else {\r\n const obj = JSON.parse(JSON.stringify(res.data)) as UTSJSONObject\r\n return obj.getNumber('total_earned') ?? 0\r\n }\r\n }\r\n return 0\r\n } catch (e) {\r\n return 0\r\n }\r\n }\r\n\r\n // ==================== 积分过期相关API ====================\r\n\r\n // 获取即将过期积分\r\n async getExpiringPoints(): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('expiring_points', 0)\r\n result.set('expiring_date', null)\r\n result.set('details', [] as any[])\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return result\r\n\r\n // 查询30天内即将过期的积分记录\r\n const now = new Date()\r\n const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)\r\n const nowStr = now.toISOString()\r\n const laterStr = thirtyDaysLater.toISOString()\r\n\r\n const res = await supa\r\n .from('ml_point_records')\r\n .select('points, description, expires_at, created_at')\r\n .eq('user_id', userId!)\r\n .gt('points', 0)\r\n .eq('is_expired', false)\r\n .not('expires_at', 'is', null)\r\n .gte('expires_at', nowStr)\r\n .lte('expires_at', laterStr)\r\n .order('expires_at', { ascending: true })\r\n .execute()\r\n\r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:7062','获取即将过期积分失败:', res.error)\r\n return result\r\n }\r\n\r\n if (res.data != null && Array.isArray(res.data)) {\r\n const records = res.data as any[]\r\n let totalExpiring = 0\r\n let earliestDate: string | null = null\r\n const details: any[] = []\r\n\r\n for (let i = 0; i < records.length; i++) {\r\n const record = records[i]\r\n let recordObj: UTSJSONObject\r\n if (record instanceof UTSJSONObject) {\r\n recordObj = record\r\n } else {\r\n recordObj = JSON.parse(JSON.stringify(record)) as UTSJSONObject\r\n }\r\n\r\n const points = recordObj.getNumber('points') ?? 0\r\n const expiresAt = recordObj.getString('expires_at') ?? ''\r\n\r\n totalExpiring += points\r\n\r\n if (earliestDate == null || expiresAt < earliestDate) {\r\n earliestDate = expiresAt\r\n }\r\n\r\n details.push({\r\n points: points,\r\n description: recordObj.getString('description'),\r\n expires_at: expiresAt,\r\n created_at: recordObj.getString('created_at') ?? ''\r\n })\r\n }\r\n\r\n result.set('expiring_points', totalExpiring)\r\n result.set('expiring_date', earliestDate != null ? earliestDate.split('T')[0] : null)\r\n result.set('details', details)\r\n }\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7105','获取即将过期积分异常:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 获取积分概览(包含即将过期积分)\r\n async getPointsOverview(): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('current_points', 0)\r\n result.set('total_earned', 0)\r\n result.set('expiring_points', 0)\r\n result.set('expiring_date', null)\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return result\r\n\r\n const res = await supa\r\n .from('ml_user_points')\r\n .select('points, total_earned, expiring_points, expiring_date')\r\n .eq('user_id', userId!)\r\n .single()\r\n .execute()\r\n\r\n if (res.error == null && res.data != null) {\r\n let data: UTSJSONObject\r\n if (res.data instanceof UTSJSONObject) {\r\n data = res.data as UTSJSONObject\r\n } else {\r\n data = JSON.parse(JSON.stringify(res.data)) as UTSJSONObject\r\n }\r\n\r\n result.set('current_points', data.getNumber('points') ?? 0)\r\n result.set('total_earned', data.getNumber('total_earned') ?? 0)\r\n result.set('expiring_points', data.getNumber('expiring_points') ?? 0)\r\n result.set('expiring_date', data.getString('expiring_date'))\r\n }\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7145','获取积分概览异常:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 获取过期提醒通知列表\r\n async getExpiryNotifications(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const res = await supa\r\n .from('ml_point_expiry_notifications')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .eq('is_sent', false)\r\n .order('expiry_date', { ascending: true })\r\n .execute()\r\n\r\n if (res.error != null || res.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return res.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7174','获取过期提醒失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 标记过期提醒为已读\r\n async markNotificationRead(notificationId: string): Promise<boolean> {\r\n try {\r\n const res = await supa\r\n .from('ml_point_expiry_notifications')\r\n .update({ is_sent: true, sent_at: new Date().toISOString() })\r\n .eq('id', notificationId)\r\n .execute()\r\n\r\n return res.error == null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7191','标记通知失败:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 手动触发积分维护任务(管理员功能)\r\n // 注意UTS不支持rpc此功能需要在Supabase后台手动执行或通过其他方式触发\r\n async triggerPointsMaintenance(): Promise<boolean> {\r\n __f__('warn','at utils/supabaseService.uts:7199','triggerPointsMaintenance: UTS不支持rpc调用请在Supabase后台手动执行 daily_points_maintenance()')\r\n return false\r\n }\r\n\r\n // ==================== 推销模式 - 商家配置API ====================\r\n\r\n // 获取商家推销配置\r\n async getMerchantPromotionConfig(merchantId: string): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('promotion_enabled', false)\r\n result.set('share_free_enabled', false)\r\n result.set('distribution_enabled', false)\r\n result.set('required_count', 4)\r\n result.set('reward_type', 'product_price')\r\n result.set('fixed_reward_amount', 0)\r\n\r\n try {\r\n const res = await supa\r\n .from('ml_merchant_promotion_config')\r\n .select('*')\r\n .eq('merchant_id', merchantId)\r\n .limit(1)\r\n .execute()\r\n\r\n if (res.error == null && res.data != null && Array.isArray(res.data)) {\r\n const arr = res.data as any[]\r\n if (arr.length > 0) {\r\n const item = arr[0]\r\n const itemAny = item as any\r\n \r\n if (itemAny instanceof UTSJSONObject) {\r\n result.set('promotion_enabled', itemAny.getBoolean('promotion_enabled') ?? false)\r\n result.set('share_free_enabled', itemAny.getBoolean('share_free_enabled') ?? false)\r\n result.set('distribution_enabled', itemAny.getBoolean('distribution_enabled') ?? false)\r\n result.set('required_count', itemAny.getNumber('required_count') ?? 4)\r\n result.set('reward_type', itemAny.getString('reward_type') ?? 'product_price')\r\n result.set('fixed_reward_amount', itemAny.getNumber('fixed_reward_amount') ?? 0)\r\n }\r\n }\r\n }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7240','获取商家推销配置失败:', e)\r\n }\r\n\r\n return result\r\n }\r\n\r\n // 检查商家是否开启分享免单\r\n async isShareFreeEnabled(merchantId: string): Promise<boolean> {\r\n try {\r\n const config = await this.getMerchantPromotionConfig(merchantId)\r\n const promotionEnabled = config.get('promotion_enabled')\r\n const shareFreeEnabled = config.get('share_free_enabled')\r\n return (promotionEnabled === true || promotionEnabled === 'true') && \r\n (shareFreeEnabled === true || shareFreeEnabled === 'true')\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7255','检查分享免单状态失败:', e)\r\n return false\r\n }\r\n }\r\n\r\n // ==================== 推销模式 - 余额相关API ====================\r\n\r\n // 获取用户余额\r\n async getUserBalance(): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('balance', 0)\r\n result.set('frozen_balance', 0)\r\n result.set('total_earned', 0)\r\n result.set('total_withdrawn', 0)\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return result\r\n\r\n const res = await supa\r\n .from('ml_user_balance')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .limit(1)\r\n .execute()\r\n\r\n if (res.error == null && res.data != null && Array.isArray(res.data)) {\r\n const arr = res.data as any[]\r\n if (arr.length > 0) {\r\n const item = arr[0]\r\n const itemAny = item as any\r\n if (itemAny instanceof UTSJSONObject) {\r\n result.set('balance', itemAny.getNumber('balance') ?? 0)\r\n result.set('frozen_balance', itemAny.getNumber('frozen_balance') ?? 0)\r\n result.set('total_earned', itemAny.getNumber('total_earned') ?? 0)\r\n result.set('total_withdrawn', itemAny.getNumber('total_withdrawn') ?? 0)\r\n }\r\n }\r\n }\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7297','获取用户余额失败:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 获取余额变动记录\r\n async getBalanceRecords(page: number = 1, limit: number = 20): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const offset = (page - 1) * limit\r\n const res = await supa\r\n .from('ml_balance_records')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .range(offset, offset + limit - 1)\r\n .execute()\r\n\r\n if (res.error != null || res.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return res.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7327','获取余额记录失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // ==================== 推销模式 - 分享免单相关API ====================\r\n\r\n // 创建分享记录\r\n async createShareRecord(productId: string, orderId: string, orderItemId: string | null, productName: string, productImage: string | null, productPrice: number): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('success', false)\r\n result.set('share_code', '')\r\n result.set('message', '')\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n result.set('message', '请先登录')\r\n return result\r\n }\r\n\r\n // 生成分享码\r\n const shareCode = this.generateShareCode()\r\n\r\n const insertData = new UTSJSONObject()\r\n insertData.set('user_id', userId)\r\n insertData.set('product_id', productId)\r\n insertData.set('order_id', orderId)\r\n insertData.set('order_item_id', orderItemId)\r\n insertData.set('share_code', shareCode)\r\n insertData.set('product_name', productName)\r\n insertData.set('product_image', productImage)\r\n insertData.set('product_price', productPrice)\r\n insertData.set('required_count', 4)\r\n insertData.set('current_count', 0)\r\n insertData.set('status', 0)\r\n\r\n const res = await supa\r\n .from('ml_share_records')\r\n .insert(insertData)\r\n .execute()\r\n\r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:7371','[createShareRecord] 插入失败:', res.error)\r\n __f__('error','at utils/supabaseService.uts:7372','[createShareRecord] 插入数据:', JSON.stringify(insertData))\r\n result.set('message', '创建分享记录失败: ' + (res.error.message ?? '未知错误'))\r\n return result\r\n }\r\n\r\n // 获取插入记录的id\r\n let insertedId = ''\r\n if (res.data != null && Array.isArray(res.data) && res.data.length > 0) {\r\n const inserted = res.data[0]\r\n let insertedObj: UTSJSONObject | null = null\r\n if (inserted instanceof UTSJSONObject) {\r\n insertedObj = inserted\r\n } else {\r\n insertedObj = JSON.parse(JSON.stringify(inserted)) as UTSJSONObject\r\n }\r\n insertedId = insertedObj.getString('id') ?? ''\r\n }\r\n\r\n result.set('success', true)\r\n result.set('id', insertedId)\r\n result.set('share_code', shareCode)\r\n result.set('message', '分享创建成功')\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7396','创建分享记录失败:', e)\r\n result.set('message', '创建分享记录异常')\r\n return result\r\n }\r\n }\r\n\r\n // 生成分享码\r\n private generateShareCode(): string {\r\n const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'\r\n let result = ''\r\n for (let i = 0; i < 8; i++) {\r\n const randomIndex = Math.floor(Math.random() * chars.length)\r\n result += chars.charAt(randomIndex)\r\n }\r\n return result\r\n }\r\n\r\n // 验证分享码\r\n async validateShareCode(shareCode: string): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('valid', false)\r\n result.set('share_record', null)\r\n\r\n try {\r\n const res = await supa\r\n .from('ml_share_records')\r\n .select('*')\r\n .eq('share_code', shareCode)\r\n .eq('status', 0)\r\n .limit(1)\r\n .execute()\r\n\r\n if (res.error == null && res.data != null && Array.isArray(res.data)) {\r\n const arr = res.data as any[]\r\n if (arr.length > 0) {\r\n result.set('valid', true)\r\n result.set('share_record', arr[0])\r\n }\r\n }\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7438','验证分享码失败:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 获取我的分享记录\r\n async getMyShareRecords(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const res = await supa\r\n .from('ml_share_records')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (res.error != null || res.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return res.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7466','获取分享记录失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取分享详情\r\n async getShareDetail(shareId: string): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('share_record', null)\r\n result.set('secondary_purchases', [] as any[])\r\n\r\n try {\r\n const res = await supa\r\n .from('ml_share_records')\r\n .select('*')\r\n .eq('id', shareId)\r\n .limit(1)\r\n .execute()\r\n\r\n if (res.error == null && res.data != null && Array.isArray(res.data)) {\r\n const arr = res.data as any[]\r\n if (arr.length > 0) {\r\n result.set('share_record', arr[0])\r\n }\r\n }\r\n\r\n // 获取二级购买记录\r\n const purchasesRes = await supa\r\n .from('ml_secondary_purchases')\r\n .select('*')\r\n .eq('share_record_id', shareId)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (purchasesRes.error == null && purchasesRes.data != null) {\r\n result.set('secondary_purchases', purchasesRes.data)\r\n }\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7507','获取分享详情失败:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 获取免单奖励记录\r\n async getFreeOrderRewards(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const res = await supa\r\n .from('ml_free_order_rewards')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (res.error != null || res.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return res.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7535','获取免单奖励记录失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // ==================== 推销模式 - 会员等级相关API ====================\r\n\r\n // 获取会员等级列表\r\n async getMemberLevels(): Promise<any[]> {\r\n try {\r\n const res = await supa\r\n .from('ml_member_levels')\r\n .select('*')\r\n .eq('is_active', true)\r\n .order('level_rank', { ascending: true } as OrderOptions)\r\n .execute()\r\n\r\n if (res.error != null || res.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return res.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7560','获取会员等级列表失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取用户会员信息\r\n async getUserMemberInfo(): Promise<UTSJSONObject> {\r\n const result = new UTSJSONObject()\r\n result.set('member_level', 0)\r\n result.set('level_name', '普通会员')\r\n result.set('discount', 1.0)\r\n result.set('total_spent', 0)\r\n result.set('next_level', null)\r\n result.set('progress_percent', 0)\r\n result.set('manual_level', false)\r\n\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return result\r\n\r\n // 获取用户信息(包括 tier_id\r\n const userRes = await supa\r\n .from('ml_user_profiles')\r\n .select('tier_id, total_spent, manual_level')\r\n .eq('user_id', userId!)\r\n .limit(1)\r\n .execute()\r\n\r\n let tierId: string = ''\r\n let totalSpent = 0\r\n let manualLevel = false\r\n\r\n if (userRes.error == null && userRes.data != null && Array.isArray(userRes.data)) {\r\n const arr = userRes.data as any[]\r\n if (arr.length > 0) {\r\n const item = arr[0]\r\n const itemAny = item as any\r\n if (itemAny instanceof UTSJSONObject) {\r\n tierId = itemAny.getString('tier_id') ?? ''\r\n totalSpent = itemAny.getNumber('total_spent') ?? 0\r\n manualLevel = itemAny.getBoolean('manual_level') ?? false\r\n }\r\n }\r\n }\r\n\r\n // 获取等级信息\r\n const levels = await this.getMemberLevels()\r\n let levelName = '普通会员'\r\n let discount = 1.0\r\n let nextLevel: UTSJSONObject | null = null\r\n let progressPercent = 0\r\n let currentLevelRank = 0\r\n\r\n // 通过 tier_id 匹配等级\r\n for (let i = 0; i < levels.length; i++) {\r\n const level = levels[i]\r\n const levelAny = level as any\r\n let levelId = ''\r\n let levelNameStr = ''\r\n let levelRank = 0\r\n let levelDiscount = 1.0\r\n\r\n if (levelAny instanceof UTSJSONObject) {\r\n levelId = levelAny.getString('id') ?? ''\r\n levelNameStr = levelAny.getString('name') ?? ''\r\n levelRank = levelAny.getNumber('level_rank') ?? 0\r\n levelDiscount = levelAny.getNumber('discount_rate') ?? 1.0\r\n }\r\n\r\n // 通过 tier_id 匹配当前等级\r\n if (levelId == tierId) {\r\n levelName = levelNameStr\r\n discount = levelDiscount\r\n currentLevelRank = levelRank\r\n }\r\n }\r\n\r\n // 找下一等级level_rank 更大的第一个等级)\r\n for (let i = 0; i < levels.length; i++) {\r\n const level = levels[i]\r\n const levelAny = level as any\r\n let levelRank = 0\r\n let levelNameStr = ''\r\n let levelMinAmount = 0\r\n\r\n if (levelAny instanceof UTSJSONObject) {\r\n levelRank = levelAny.getNumber('level_rank') ?? 0\r\n levelNameStr = levelAny.getString('name') ?? ''\r\n levelMinAmount = levelAny.getNumber('min_amount') ?? 0\r\n }\r\n\r\n if (levelRank > currentLevelRank && nextLevel == null) {\r\n const nextLevelObj = new UTSJSONObject()\r\n const levelObj = level as UTSJSONObject\r\n nextLevelObj.set('id', levelObj.getString('id') ?? '')\r\n nextLevelObj.set('name', levelNameStr)\r\n nextLevelObj.set('min_amount', levelMinAmount)\r\n nextLevel = nextLevelObj\r\n }\r\n }\r\n\r\n result.set('member_level', currentLevelRank)\r\n result.set('level_name', levelName)\r\n result.set('discount', discount)\r\n result.set('total_spent', totalSpent)\r\n result.set('next_level', nextLevel)\r\n result.set('progress_percent', progressPercent)\r\n result.set('manual_level', manualLevel)\r\n\r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7672','获取用户会员信息失败:', e)\r\n return result\r\n }\r\n }\r\n\r\n // 获取当前等级的最低消费金额\r\n private getCurrentLevelMinAmount(levels: any[], currentLevel: number): number {\r\n for (let i = 0; i < levels.length; i++) {\r\n const level = levels[i]\r\n const levelAny = level as any\r\n if (levelAny instanceof UTSJSONObject) {\r\n const levelId = levelAny.getNumber('id') ?? 0\r\n if (levelId === currentLevel) {\r\n return levelAny.getNumber('min_amount') ?? 0\r\n }\r\n }\r\n }\r\n return 0\r\n }\r\n\r\n // 获取会员等级变更记录\r\n async getMemberLevelLogs(): Promise<any[]> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const res = await supa\r\n .from('ml_member_level_logs')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (res.error != null || res.data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return res.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:7715','获取会员等级变更记录失败:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n}\r\n\r\n// 导出单例实例\r\nexport const supabaseService = new SupabaseService()\r\n\r\n// 默认导出\r\nexport default supabaseService\r\n","<!-- pages/main/index.uvue -->\r\n<template>\r\n\t<view class=\"medic-home\">\r\n\t\t<!-- 智能顶部导航栏 - 添加滚动隐藏效果 -->\r\n\t\t<view \r\n\t\t\tclass=\"smart-navbar\" \r\n\t\t\t:style=\"{ \r\n\t\t\t\tpaddingTop: statusBarHeight + 'px',\r\n\t\t\t\ttransform: showNavbar ? 'translateY(0)' : 'translateY(-100%)'\r\n\t\t\t}\"\r\n\t\t>\r\n\t\t\t<view class=\"search-container\" :style=\"{ paddingRight: (navBarRight > 0 ? navBarRight : 16) + 'px' }\">\r\n\t\t\t\t<view class=\"search-box\" @click=\"navigateToSearch\" :style=\"{ height: '30px' }\">\r\n\t\t\t\t\t<!-- 模拟输入框 -->\r\n\t\t\t\t\t<text class=\"search-placeholder\">请输入商品名称、店铺</text>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 扫码图标 -->\r\n\t\t\t\t\t<view class=\"nav-icon-btn\" @click.stop=\"onScan\">\r\n\t\t\t\t\t\t<text class=\"nav-icon\">🔳</text>\r\n\t\t\t\t\t</view>\r\n\r\n\t\t\t\t\t<!-- 相机图标 -->\r\n\t\t\t\t\t<view class=\"nav-camera-btn\" @click.stop=\"onCamera\">\r\n\t\t\t\t\t\t<text class=\"nav-camera-icon\">📷</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 搜索按钮 -->\r\n\t\t\t\t\t<view class=\"nav-inner-search-btn\" :style=\"{ height: '22px' }\">\r\n\t\t\t\t\t\t<text class=\"nav-inner-search-text\">搜索</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<!-- 导航栏占位符 - 移除,改为使用 margin-top -->\r\n\t\t<!-- <view class=\"navbar-placeholder\" :style=\"{ height: (statusBarHeight + 44) + 'px' }\"></view> -->\r\n\t\t\r\n\t\t<!-- 主内容区 -->\r\n\t\t<scroll-view \r\n\t\t\tdirection=\"vertical\" \r\n\t\t\tclass=\"main-scroll\"\r\n\t\t\trefresher-enabled\r\n\t\t\t:refresher-triggered=\"refreshing\"\r\n\t\t\t:lower-threshold=\"50\"\r\n\t\t\t@refresherrefresh=\"onRefresh\"\r\n\t\t\t@scrolltolower=\"loadMore\"\r\n\t\t\t@scroll=\"handleScroll\"\r\n\t\t>\r\n\t\t\t<!-- 健康资讯轮播 (Moved Up) -->\r\n\t\t\t<!-- 健康资讯轮播 (Hidden) -->\r\n <!-- \r\n\t\t\t<view class=\"health-news\">\r\n\t\t\t\t...\r\n\t\t\t</view>\r\n -->\r\n\r\n\t\t\t<!-- 智能健康卡片 (Hidden) -->\r\n\t\t\t<!-- <view class=\"smart-health-card\" :style=\"{ marginTop: (statusBarHeight + 44 + 10) + 'px' }\">\r\n\t\t\t\t<view class=\"health-content\">\r\n\t\t\t\t\t<view class=\"health-header\">\r\n\t\t\t\t\t\t<text class=\"health-title\">智能健康助手</text>\r\n\t\t\t\t\t\t<text class=\"health-subtitle\">根据您的健康数据推荐</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"health-tips\">\r\n\t\t\t\t\t\t<text class=\"tip-item\">💡 按时用药提醒</text>\r\n\t\t\t\t\t\t<text class=\"tip-item\">📋 健康记录跟踪</text>\r\n\t\t\t\t\t\t<text class=\"tip-item\">🩺 在线问诊咨询</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view> -->\r\n\r\n\t\t\t<!-- 智能分类网格 - 完全响应式 -->\r\n\t\t\t<view class=\"smart-categories\" :style=\"{ marginTop: (statusBarHeight + 44 + 10) + 'px' }\">\r\n\t\t\t\t<view class=\"section-header\">\r\n <view class=\"category-tabs-pills\">\r\n\t\t\t\t\t <view :class=\"['tab-pill', { active: categoryTab == 'category' }]\" @click=\"categoryTab = 'category'\">\r\n <text class=\"tab-text\">智能分类</text>\r\n </view>\r\n <view :class=\"['tab-pill', { active: categoryTab == 'brand' }]\" @click=\"categoryTab = 'brand'\">\r\n <text class=\"tab-text\">品牌甄选</text>\r\n </view>\r\n </view>\r\n\t\t\t\t\t<text class=\"section-desc\">快速定位</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"category-grid\" v-if=\"categoryTab === 'category'\">\r\n\t\t\t\t\t<!-- 一级分类 -->\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tv-for=\"category in parentCategories\" \r\n\t\t\t\t\t\t:key=\"category.id\"\r\n\t\t\t\t\t\tclass=\"category-card\"\r\n\t\t\t\t\t\t@click=\"onParentCategoryClick(category)\"\r\n\t\t\t\t\t\t:style=\"{ '--card-color': category.color }\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<view class=\"card-icon\">\r\n\t\t\t\t\t\t\t<text class=\"card-icon-text\">{{ category.icon }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"card-name\">{{ category.name }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<!-- 二级分类 -->\r\n\t\t\t\t<view v-if=\"categoryTab === 'category' && showSubCategories && subCategories.length > 0\" class=\"sub-category-grid\">\r\n\t\t\t\t\t<view class=\"sub-category-header\">\r\n\t\t\t\t\t\t<text class=\"sub-category-title\">{{ selectedParentCategory?.name }}分类</text>\r\n\t\t\t\t\t\t<text class=\"sub-category-close\" @click=\"showSubCategories = false\">✕</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"sub-category-wrapper\">\r\n\t\t\t\t\t\t<view \r\n\t\t\t\t\t\t\tv-for=\"subCat in subCategories\" \r\n\t\t\t\t\t\t\t:key=\"subCat.id\"\r\n\t\t\t\t\t\t\tclass=\"sub-category-card\"\r\n\t\t\t\t\t\t\t@click=\"onSubCategoryClick(subCat)\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t<view class=\"card-icon\">\r\n\t\t\t\t\t\t\t\t<text class=\"card-icon-text\">{{ subCat.icon }}</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<text class=\"card-name\">{{ subCat.name }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n \r\n <!-- 品牌列表 -->\r\n <view class=\"category-grid\" v-if=\"categoryTab === 'brand'\">\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tv-for=\"brand in brands\" \r\n\t\t\t\t\t\t:key=\"brand.id\"\r\n\t\t\t\t\t\tclass=\"category-card\"\r\n\t\t\t\t\t\t@click=\"switchBrand(brand)\"\r\n\t\t\t\t\t\tstyle=\"--card-color: #5785e5\"\r\n\t\t\t\t\t>\r\n <view class=\"card-icon\" v-if=\"brand.logo_url == null || brand.logo_url == ''\">\r\n\t\t\t\t\t\t\t<text class=\"card-icon-text\">{{ getBrandIcon(brand.name) }}</text>\r\n\t\t\t\t\t\t</view>\r\n <image v-else :src=\"brand.logo_url\" mode=\"aspectFit\" class=\"brand-logo\" style=\"width: 40px; height: 40px; border-radius: 20px;\" />\r\n\t\t\t\t\t\t<text class=\"card-name\">{{ brand.name }}</text>\r\n\t\t\t\t\t</view>\r\n </view>\r\n\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 健康资讯轮播 (Original Position - Removed) -->\r\n\r\n\t\t\t<!-- 智能服务入口 (Hidden) -->\r\n\t\t\t<!-- <view class=\"smart-services\">\r\n\t\t\t\t<view class=\"services-grid\">\r\n\t\t\t\t\t<view class=\"service-card\" @click=\"navigateToConsultation\">\r\n\t\t\t\t\t\t<view class=\"service-icon\" style=\"background: #2196F3;\">\r\n\t\t\t\t\t\t\t<text class=\"service-icon-text\">👨‍⚕️</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"service-name\">在线问诊</text>\r\n\t\t\t\t\t\t<text class=\"service-desc\">三甲医生在线</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"service-card\" @click=\"navigateToPrescription\">\r\n\t\t\t\t\t\t<view class=\"service-icon\" style=\"background: #ff5000;\">\r\n\t\t\t\t\t\t\t<text class=\"service-icon-text\">📋</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"service-name\">电子处方</text>\r\n\t\t\t\t\t\t<text class=\"service-desc\">医生开方购药</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"service-card\" @click=\"navigateToOTC\">\r\n\t\t\t\t\t\t<view class=\"service-icon\" style=\"background: #FF9800;\">\r\n\t\t\t\t\t\t\t<text class=\"service-icon-text\">💊</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"service-name\">非处方药</text>\r\n\t\t\t\t\t\t<text class=\"service-desc\">安全自主选购</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"service-card\" @click=\"navigateToHealthTools\">\r\n\t\t\t\t\t\t<view class=\"service-icon\" style=\"background: #9C27B0;\">\r\n\t\t\t\t\t\t\t<text class=\"service-icon-text\">🩺</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"service-name\">健康工具</text>\r\n\t\t\t\t\t\t<text class=\"service-desc\">健康管理助手</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view> -->\r\n\r\n\t\t\t\t<!-- 热销药品专区 -->\r\n\t\t\t<view class=\"hot-products\">\r\n\t\t\t\t<view class=\"section-header\">\r\n\t\t\t\t\t<view class=\"title-section\">\r\n\t\t\t\t\t\t<text class=\"section-icon\">🔥</text>\r\n\t\t\t\t\t\t<text class=\"section-title\">热销商品</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"sort-tabs\">\r\n\t\t\t\t\t\t<text \r\n\t\t\t\t\t\t\tv-for=\"tab in sortTabs\" \r\n\t\t\t\t\t\t\t:key=\"tab.id\"\r\n\t\t\t\t\t\t\t:class=\"['sort-tab', { active: activeSort === tab.id }]\"\r\n\t\t\t\t\t\t\t@click=\"switchSort(tab.id)\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t{{ tab.name }}\r\n\t\t\t\t\t\t</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<view class=\"products-grid\">\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tv-for=\"product in hotProducts\" \r\n\t\t\t\t\t\t:key=\"product.id\"\r\n\t\t\t\t\t\tclass=\"product-card\"\r\n\t\t\t\t\t\t@click=\"navigateToProduct(product)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<image \r\n\t\t\t\t\t\t\tclass=\"product-image\" \r\n\t\t\t\t\t\t\t:src=\"product.main_image_url\" \r\n\t\t\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t<text class=\"product-name\" :lines=\"2\">{{ product.name }}</text>\r\n\t\t\t\t\t\t<view class=\"product-bottom\">\r\n\t\t\t\t\t\t\t<text class=\"product-price\">¥{{ product.price }}</text>\r\n\t\t\t\t\t\t\t<view class=\"product-add-btn\" @click.stop=\"addToCart(product)\">\r\n\t\t\t\t\t\t\t\t<text class=\"add-icon\">+</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n <!-- 加载状态提示 -->\r\n\t\t\t\t<view class=\"load-more-status\" v-if=\"loading || showLoadMore\">\r\n\t\t\t\t\t<text class=\"loading-text\">正在加载更多商品...</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 家庭常备药 (Hidden) -->\r\n\t\t\t<!-- <view class=\"family-medicine\">\r\n\t\t\t\t<view class=\"section-header\">\r\n\t\t\t\t\t<view class=\"title-section\">\r\n\t\t\t\t\t\t<text class=\"section-icon\">🏠</text>\r\n\t\t\t\t\t\t<text class=\"section-title\">家庭常备药</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<text class=\"section-subtitle\">守护全家健康</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<view class=\"family-grid\">\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tv-for=\"item in familyItems\" \r\n\t\t\t\t\t\t:key=\"item.id\"\r\n\t\t\t\t\t\tclass=\"family-item\"\r\n\t\t\t\t\t\t@click=\"navigateToCategory(item)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<view class=\"family-icon\" :style=\"{ backgroundColor: item.color }\">\r\n\t\t\t\t\t\t\t<text class=\"family-icon-text\">{{ item.icon }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"family-name\">{{ item.name }}</text>\r\n\t\t\t\t\t\t<text class=\"family-desc\">{{ item.desc }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view> -->\r\n\r\n\t\t\t<!-- 智能推荐模块已隐藏 -->\r\n\r\n\t\t\t<!-- 健康提醒 (Hidden) -->\r\n\t\t\t<!-- <view class=\"health-reminder\">\r\n\t\t\t\t<view class=\"reminder-content\">\r\n\t\t\t\t\t<text class=\"reminder-icon\">⏰</text>\r\n\t\t\t\t\t<view class=\"reminder-text\">\r\n\t\t\t\t\t\t<text class=\"reminder-title\">健康提醒</text>\r\n\t\t\t\t\t\t<text class=\"reminder-desc\">您有1个待用药提醒点击查看详情</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"reminder-action\" @click=\"navigateToReminders\">\r\n\t\t\t\t\t\t<text class=\"action-text\">查看</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view> -->\r\n\r\n\t\t\t<!-- 底部安全区域 -->\r\n\t\t\t<view class=\"safe-area\"></view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, reactive, onMounted } from 'vue'\r\nimport { onShow, onLoad } from '@dcloudio/uni-app'\r\nimport supabaseService from '@/utils/supabaseService.uts'\r\nimport type { Product, Category, Brand } from '@/utils/supabaseService.uts'\r\nimport { getCurrentUser } from '@/utils/store.uts'\r\n\r\n// 响应式数据\r\nconst statusBarHeight = ref(0)\r\nconst scrollHeight = ref(0)\r\nconst refreshing = ref(false)\r\nconst loading = ref(false)\r\nconst isFirstShow = ref(true)\r\nconst hasMore = ref(true)\r\nconst activeSort = ref('recommend') // 默认展示智能推荐\r\nconst activeFilter = ref('recommend')\r\nconst currentPage = ref(1)\r\nconst priceAscending = ref(true) // 价格排序方向true=升序false=降序\r\n\r\n// 小程序胶囊按钮信息类型\r\ntype CapsuleButtonInfo = {\r\n\tleft: number,\r\n\ttop: number,\r\n\tright: number,\r\n\tbottom: number,\r\n\twidth: number,\r\n\theight: number\r\n}\r\n\r\n// 小程序胶囊按钮信息\r\nconst capsuleButtonInfo = ref<CapsuleButtonInfo | null>(null)\r\nconst navBarRight = ref(0) // 导航栏右侧预留空间\r\n\r\n// 数据源\r\nconst hotProducts = ref<Product[]>([])\r\nconst recommendedProducts = ref<Product[]>([])\r\nconst hotKeywords = ref<string[]>([])\r\n\r\n// 屏幕尺寸检测\r\nconst isMobile = ref(false)\r\nconst showLoadMore = ref(false)\r\n\r\n// 导航栏显示控制\r\nconst showNavbar = ref(true)\r\nconst lastScrollTop = ref(0)\r\nconst scrollThreshold = 30 // 降低滚动阈值,使其更灵敏\r\nconst scrollingUp = ref(false)\r\n\r\n// 分类数据 - 从Supabase获取\r\nconst categoryTab = ref<string>('category')\r\nconst categories = ref<Category[]>([])\r\nconst brands = ref<Brand[]>([])\r\n\r\n// 一级分类和二级分类\r\nconst parentCategories = ref<Category[]>([])\r\nconst subCategories = ref<Category[]>([])\r\nconst selectedParentCategory = ref<Category | null>(null)\r\nconst showSubCategories = ref(false)\r\n\r\n\r\ntype SortTab = {\r\n\tid: string\r\n\tname: string\r\n}\r\n\r\n// 排序标签\r\nconst sortTabs: SortTab[] = [\r\n\t{ id: 'recommend', name: '智能推荐' },\r\n\t{ id: 'sales', name: '销量' },\r\n\t{ id: 'price', name: '价格' },\r\n\t{ id: 'new', name: '新品' },\r\n\t{ id: 'discount', name: '特价' }\r\n]\r\n\r\n\r\n// 健康资讯\r\nconst healthNews = [\r\n\t{\r\n\t\tid: 'news1',\r\n\t\ttitle: '秋季流感预防指南,科学防护健康过冬',\r\n\t\ttag: '健康科普',\r\n\t\timage: 'https://picsum.photos/800/400?random=health1'\r\n\t},\r\n\t{\r\n\t\tid: 'news2',\r\n\t\ttitle: '家庭常备药清单,为家人健康保驾护航',\r\n\t\ttag: '家庭用药',\r\n\t\timage: 'https://picsum.photos/800/400?random=health2'\r\n\t},\r\n\t{\r\n\t\tid: 'news3',\r\n\t\ttitle: '慢性病科学管理,提高生活质量',\r\n\t\ttag: '健康管理',\r\n\t\timage: 'https://picsum.photos/800/400?random=health3'\r\n\t}\r\n]\r\n\r\n// 获取一级分类数据\r\nconst loadCategories = async (): Promise<void> => {\r\n try {\r\n const categoriesData = await supabaseService.getParentCategories()\r\n parentCategories.value = categoriesData\r\n // 兼容其他使用 categories 的地方\r\n categories.value = categoriesData\r\n console.log('一级分类数据:', JSON.stringify(parentCategories.value))\r\n } catch (error) {\r\n console.error('加载分类数据失败:', error)\r\n parentCategories.value = []\r\n categories.value = []\r\n }\r\n}\r\n\r\n// 获取二级分类数据\r\nconst loadSubCategories = async (parentId: string): Promise<void> => {\r\n try {\r\n console.log('[loadSubCategories] 开始加载二级分类, parentId:', parentId)\r\n const subData = await supabaseService.getSubCategories(parentId)\r\n console.log('[loadSubCategories] 获取到二级分类数量:', subData.length)\r\n console.log('[loadSubCategories] 二级分类数据:', JSON.stringify(subData))\r\n subCategories.value = subData\r\n } catch (error) {\r\n console.error('加载子分类数据失败:', error)\r\n subCategories.value = []\r\n }\r\n}\r\n\r\n// 点击一级分类\r\nconst onParentCategoryClick = async (category: Category): Promise<void> => {\r\n console.log('[onParentCategoryClick] 点击一级分类:', category.name, 'id:', category.id)\r\n \r\n // 如果已经选中,则切换显示/隐藏二级分类\r\n if (selectedParentCategory.value != null && selectedParentCategory.value.id === category.id) {\r\n console.log('[onParentCategoryClick] 切换显示状态')\r\n showSubCategories.value = !showSubCategories.value\r\n return\r\n }\r\n \r\n // 选中新的分类\r\n selectedParentCategory.value = category\r\n showSubCategories.value = true\r\n console.log('[onParentCategoryClick] showSubCategories 设置为 true')\r\n \r\n // 加载二级分类\r\n await loadSubCategories(category.id)\r\n \r\n // 如果没有二级分类,直接跳转到分类页\r\n if (subCategories.value.length == 0) {\r\n console.log('[onParentCategoryClick] 没有二级分类,直接跳转到分类页')\r\n uni.setStorageSync('selectedCategory', category.id)\r\n uni.switchTab({\r\n url: '/pages/main/category'\r\n })\r\n }\r\n}\r\n\r\n// 点击二级分类\r\nconst onSubCategoryClick = (category: Category): void => {\r\n // 跳转到分类页面\r\n uni.setStorageSync('selectedCategory', category.id)\r\n const timestamp = Date.now()\r\n const randomParam = Math.random().toString(36).substring(2, 8)\r\n const url = `/pages/main/category?categoryId=${category.id}&name=${encodeURIComponent(category.name)}&timestamp=${timestamp}&random=${randomParam}`\r\n \r\n uni.switchTab({\r\n url: '/pages/main/category'\r\n })\r\n}\r\n\r\n// 获取品牌数据\r\nconst loadBrands = async (): Promise<void> => {\r\n try {\r\n const brandsData = await supabaseService.getBrands()\r\n brands.value = brandsData\r\n } catch (e) {\r\n console.error('加载品牌失败:', e)\r\n brands.value = []\r\n }\r\n}\r\n\r\n// 根据品牌名称获取图标\r\nconst getBrandIcon = (name: string): string => {\r\n if (name == null || name === '') {\r\n return '🏢'\r\n }\r\n // 常见品牌图标映射(使用数组方式避免 Object.keys 问题)\r\n const iconKeys = [\r\n '感冒', '发烧', '咳嗽', '消炎', '维生素', '钙片', '胃药', '止痛', '过敏', '皮肤', '眼药水', '口腔', '血压', '血糖', '血脂', '保健', '养生', '减肥', '美容', '母婴', '儿童', '老人', '男性', '女性', '维生素C', '维生素D', '蛋白粉', '鱼油', '蜂胶', '阿胶', '红枣', '枸杞', '菊花', '金银花', '口罩', '消毒液', '体温计', '创可贴', '棉签',\r\n '九芝堂', '同仁堂', '云南白药', '东阿阿胶', '太极', '江中', '三九', '华素制药', '汤臣倍健', '白云山', '修正', '葵花', '哈药', '扬子江', '恒瑞', '复星', '辉瑞', '阿斯利康', '罗氏', '默沙东', '赛诺菲', '诺华', '雅培', '雀巢', '蒙牛', '伊利', '海尔', '美的', '飞利浦', '西门子', '松下', '苏泊尔', '九阳', '华为', '小米', '苹果', '三星'\r\n ]\r\n const iconValues = [\r\n '💊', '🌡️', '😷', '🔬', '💊', '🦴', '🫁', '💉', '🌸', '🧴', '👁️', '🦷', '❤️', '🩸', '💓', '🧬', '🍵', '⚖️', '💅', '👶', '🧒', '👴', '♂️', '♀️', '🍊', '☀️', '🥛', '🐟', '🐝', '🍯', '🫘', '🌿', '🌼', '🌸', '😷', '🧴', '🌡️', '🩹', '🧺',\r\n '📜', '🏛️', '⛰️', '🍯', '☯️', '🌿', '9⃣', '💊', '💪', '⛰️', '🩹', '🌻', '🧪', '🚢', '🔬', '⭐', '🧬', '🧪', '🧬', '💊', '🧬', '🔬', '🏥', '🥣', '🐄', '🥛', '🏠', '❄️', '🪒', '⚡', '🔋', '🍳', '🥛', '📱', '🍚', '🍎', '📱'\r\n ]\r\n \r\n // 尝试精确匹配\r\n for (let i = 0; i < iconKeys.length; i++) {\r\n if (name === iconKeys[i]) {\r\n return iconValues[i]\r\n }\r\n }\r\n // 尝试模糊匹配\r\n for (let i = 0; i < iconKeys.length; i++) {\r\n if (name.indexOf(iconKeys[i]) !== -1) {\r\n return iconValues[i]\r\n }\r\n }\r\n // 默认返回品牌图标\r\n return '🏢'\r\n}\r\n\r\n// 默认加载商品数量\r\nconst defaultLoadLimit: number = 6\r\n\r\n// 前置声明内部加载函数\r\nconst doLoadHotProducts = async (targetLimit: number, resolve: (value: void) => void, reject: (reason?: any) => void): Promise<void> => {\r\n try {\r\n let products: Product[] = []\r\n const limit = targetLimit\r\n \r\n console.log('加载热销商品,当前排序方式:', activeSort.value, 'limit:', limit)\r\n \r\n switch (activeSort.value) {\r\n case 'sales':\r\n console.log('调用 getProductsBySales')\r\n products = await supabaseService.getProductsBySales(limit)\r\n break\r\n case 'price':\r\n console.log('调用 getProductsByPrice, 升序:', priceAscending.value)\r\n products = await supabaseService.getProductsByPrice(limit, priceAscending.value)\r\n break\r\n case 'new':\r\n console.log('调用 getProductsByNewest')\r\n products = await supabaseService.getProductsByNewest(limit)\r\n break\r\n case 'recommend':\r\n console.log('调用 getSmartRecommendations')\r\n products = await supabaseService.getSmartRecommendations(limit)\r\n break\r\n case 'discount':\r\n console.log('调用 getDiscountProducts')\r\n products = await supabaseService.getDiscountProducts(limit)\r\n break\r\n default:\r\n console.log('调用默认 getProductsBySales')\r\n products = await supabaseService.getProductsBySales(limit)\r\n }\r\n \r\n\t\tconsole.log('加载到的商品数量:', products.length)\r\n\t\tif (products.length > 0) {\r\n\t\t\tconsole.log('Sample Product Merchant IDs:')\r\n\t\t\tfor (let i = 0; i < Math.min(products.length, 3); i++) {\r\n\t\t\t\tconst p = products[i]\r\n\t\t\t\tconsole.log(` - Product: ${p.name}, MerchantID: ${p.merchant_id}`)\r\n\t\t\t}\r\n\t\t}\r\n\t\thotProducts.value = products\r\n } catch (error) {\r\n console.error('加载热销商品失败:', error)\r\n hotProducts.value = []\r\n }\r\n}\r\n\r\n// 获取热销商品(根据当前排序方式)\r\nfunction loadHotProducts(targetLimit: number): Promise<void> {\r\n return new Promise<void>((resolve, reject) => {\r\n doLoadHotProducts(targetLimit, resolve, reject)\r\n })\r\n}\r\n\r\n// 前置声明推荐商品加载函数\r\nconst doLoadRecommendedProducts = async (limit: number, resolve: (value: void) => void, reject: (reason?: any) => void): Promise<void> => {\r\n recommendedProducts.value = await supabaseService.getRecommendedProducts(limit)\r\n resolve()\r\n}\r\n\r\n// 获取推荐商品\r\nfunction loadRecommendedProducts(limit: number): Promise<void> {\r\n return new Promise<void>((resolve, reject) => {\r\n doLoadRecommendedProducts(limit, resolve, reject)\r\n })\r\n}\r\n\r\n// 加载热搜词\r\nconst loadHotKeywords = async (): Promise<void> => {\r\n try {\r\n const keywords = await supabaseService.getHotKeywords(10)\r\n hotKeywords.value = keywords\r\n console.log('加载热搜词:', keywords.length, '个')\r\n } catch (error) {\r\n console.error('加载热搜词失败:', error)\r\n hotKeywords.value = []\r\n }\r\n}\r\n\r\n// 点击热搜词进行搜索\r\nconst searchByKeyword = (keyword: string): void => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/search?keyword=${encodeURIComponent(keyword)}`\r\n })\r\n}\r\n\r\n// 初始化数据\r\nconst initData = async () => {\r\n\t// 首先确保用户资料已加载\r\n\ttry {\r\n\t\tawait getCurrentUser()\r\n\t\tconsole.log('主页初始化:用户资料加载完成')\r\n\t} catch (error) {\r\n\t\tconsole.error('加载用户资料失败:', error)\r\n\t}\r\n\tawait loadCategories()\r\n await loadBrands()\r\n\tawait loadHotKeywords()\r\n\tawait loadHotProducts(defaultLoadLimit)\r\n\tawait loadRecommendedProducts(defaultLoadLimit)\r\n}\r\n\r\n\r\n// 家庭常备药\r\nconst familyItems = [\r\n\t{\r\n\t\tid: 'family1',\r\n\t\tname: '创可贴',\r\n\t\tdesc: '伤口护理',\r\n\t\ticon: '🩹',\r\n\t\tcolor: '#FF5722',\r\n\t\tcategoryId: 'external'\r\n\t},\r\n\t{\r\n\t\tid: 'family2',\r\n\t\tname: '体温计',\r\n\t\tdesc: '健康监测',\r\n\t\ticon: '🌡️',\r\n\t\tcolor: '#2196F3',\r\n\t\tcategoryId: 'device'\r\n\t},\r\n\t{\r\n\t\tid: 'family3',\r\n\t\tname: '消毒酒精',\r\n\t\tdesc: '环境消毒',\r\n\t\ticon: '🧪',\r\n\t\tcolor: '#ff5000',\r\n\t\tcategoryId: 'external'\r\n\t},\r\n\t{\r\n\t\tid: 'family4',\r\n\t\tname: '口罩',\r\n\t\tdesc: '日常防护',\r\n\t\ticon: '😷',\r\n\t\tcolor: '#607D8B',\r\n\t\tcategoryId: 'device'\r\n\t},\r\n\t{\r\n\t\tid: 'family5',\r\n\t\tname: '退热贴',\r\n\t\tdesc: '物理降温',\r\n\t\ticon: '🧊',\r\n\t\tcolor: '#00BCD4',\r\n\t\tcategoryId: 'cold'\r\n\t},\r\n\t{\r\n\t\tid: 'family6',\r\n\t\tname: '棉签纱布',\r\n\t\tdesc: '伤口处理',\r\n\t\ticon: '🩹',\r\n\t\tcolor: '#FF9800',\r\n\t\tcategoryId: 'external'\r\n\t}\r\n]\r\n\r\n// 初始化页面\r\nconst initPage = () => {\r\n\tconst systemInfo = uni.getSystemInfoSync()\r\n\tstatusBarHeight.value = systemInfo.statusBarHeight\r\n\t\r\n\t// 获取小程序胶囊按钮信息\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\r\n\r\n\tnavBarRight.value = 0 // 非小程序不需要预留空间\r\n\r\n\t\r\n\t// 计算滚动区域高度 - 不再需要手动计算,使用 Flex 布局自动撑开\r\n\t// scrollHeight.value = windowHeight - 50 \r\n\t\r\n\t// 检测屏幕尺寸\r\n\tconst screenWidth = systemInfo.screenWidth\r\n\tisMobile.value = screenWidth < 768 // 小于768px为小屏幕\r\n}\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n\tinitPage()\r\n\tinitData()\r\n})\r\n\r\n// 页面显示时重置状态\r\nonShow(() => {\r\n\tconsole.log('=== index页面onShow被调用 ===')\r\n\tconsole.log('主页重新显示,重置页面状态')\r\n\t\r\n\t// 重置导航栏显示状态\r\n\tshowNavbar.value = true\r\n\tlastScrollTop.value = 0\r\n\t\r\n\t// 重置滚动位置到顶部\r\n\t// 注意这里不能直接操作scroll-view的滚动位置\r\n\t// 但可以重置一些页面状态\r\n\t\r\n\t// 注意这里不再清除selectedCategory\r\n\t// 让分类页面在成功读取后自行清除\r\n\t// 这样可以确保分类页面能正确读取到传递的数据\r\n\t\r\n\t// 每次页面显示时尝试更新用户资料\r\n\tif (!isFirstShow.value) {\r\n\t\tgetCurrentUser().then(profile => {\r\n\t\t\tif (profile != null) {\r\n\t\t\t\tconsole.log('主页onShow用户资料更新成功')\r\n\t\t\t} else {\r\n\t\t\t\tconsole.log('主页onShow用户资料为空可能未登录')\r\n\t\t\t}\r\n\t\t}).catch(error => {\r\n\t\t\tconsole.error('主页onShow加载用户资料失败:', error)\r\n\t\t})\r\n\t} else {\r\n\t\tisFirstShow.value = false\r\n\t\tconsole.log('主页首次显示跳过onShow中的用户资料检查交由initData处理')\r\n\t}\r\n\t\r\n\tconsole.log('=== index页面onShow执行完成 ===')\r\n})\r\n\r\n// 处理滚动事件\r\nconst handleScroll = (event: any) => {\r\n\ttry {\r\n\t\tconst eventObj = event as UTSJSONObject\r\n\t\tconst detailRaw = eventObj.get('detail')\r\n\t\tif (detailRaw == null) return\r\n\t\tconst detail = detailRaw as UTSJSONObject\r\n\t\tconst scrollTop = detail.getNumber('scrollTop') ?? 0\r\n\t\tconst currentTime = Date.now()\r\n\t\t\r\n\t\t// 判断滚动方向\r\n\t\tif (scrollTop > lastScrollTop.value) {\r\n\t\t\t// 向下滚动\r\n\t\t\tscrollingUp.value = false\r\n\t\t\t// 向下滚动超过阈值时隐藏导航栏\r\n\t\t\tif (scrollTop > scrollThreshold && showNavbar.value) {\r\n\t\t\t\tshowNavbar.value = false\r\n\t\t\t}\r\n\t\t} else if (scrollTop < lastScrollTop.value) {\r\n\t\t\t// 向上滚动\r\n\t\t\tscrollingUp.value = true\r\n\t\t\t// 向上滚动时显示导航栏\r\n\t\t\tif (!showNavbar.value) {\r\n\t\t\t\tshowNavbar.value = true\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// 滚动到顶部时强制显示导航栏\r\n\t\tif (scrollTop <= 10) {\r\n\t\t\tshowNavbar.value = true\r\n\t\t}\r\n\t\t\r\n\t\tlastScrollTop.value = scrollTop\r\n\t\t\r\n\t\t// 调试信息(开发时可启用)\r\n\t\t// console.log(`Scroll: ${scrollTop}, ShowNavbar: ${showNavbar.value}, ScrollingUp: ${scrollingUp.value}`)\r\n\t} catch (e) {\r\n\t\t// 忽略滚动事件处理错误\r\n\t}\r\n}\r\n\r\n// 重置导航栏显示状态(例如点击回到顶部时)\r\nconst resetNavbar = () => {\r\n\tshowNavbar.value = true\r\n\tlastScrollTop.value = 0\r\n}\r\n\r\n// 切换分类 - 跳转到分类页面并传递分类ID\r\nconst switchCategory = (category: any) => {\r\n\tconsole.log('=== switchCategory函数开始执行 ===')\r\n\t\r\n\t// 将 category 转换为 UTSJSONObject 以访问属性\r\n\tconst catObj = (category instanceof UTSJSONObject) ? (category as UTSJSONObject) : (JSON.parse(JSON.stringify(category)) as UTSJSONObject)\r\n\tconst categoryId = catObj.getString('id') ?? ''\r\n\tconst categoryName = catObj.getString('name') ?? ''\r\n\t\r\n\tconsole.log('分类ID:', categoryId, '分类名称:', categoryName)\r\n\t\r\n\t// 使用Storage传递参数确保switchTab后能被读取\r\n\tuni.setStorageSync('selectedCategory', categoryId)\r\n\t\r\n\t// 生成唯一的时间戳和随机参数,确保每次跳转都是新的页面\r\n\tconst timestamp = Date.now()\r\n\tconst randomParam = Math.random().toString(36).substring(2, 8)\r\n\t\r\n\t// 构建带参数的URL直接通过URL传递分类信息\r\n\tconst url = `/pages/main/category?categoryId=${categoryId}&name=${encodeURIComponent(categoryName)}&timestamp=${timestamp}&random=${randomParam}`\r\n\r\n uni.switchTab({\r\n url: '/pages/main/category',\r\n success: () => {\r\n // 通过 Storage 传递参数已在上面设置\r\n console.log('跳转分类页面成功categoryId:', categoryId)\r\n }\r\n })\r\n}\r\n\r\nconst switchBrand = (brand: Brand) => {\r\n // 假设跳转到搜索结果页或者分类页带 filter\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/search?keyword=${encodeURIComponent(brand.name)}&type=brand&brandId=${brand.id}`\r\n })\r\n}\r\n\r\n// 切换排序\r\nconst switchSort = (sortId: string) => {\r\n\t// 如果点击的是价格排序,切换升序/降序\r\n\tif (sortId === 'price' && activeSort.value === 'price') {\r\n\t\tpriceAscending.value = !priceAscending.value\r\n\t\tconsole.log('切换价格排序方向,升序:', priceAscending.value)\r\n\t} else {\r\n\t\t// 切换到其他排序时,重置价格排序为升序\r\n\t\tif (sortId !== 'price') {\r\n\t\t\tpriceAscending.value = true\r\n\t\t}\r\n\t\tactiveSort.value = sortId\r\n\t}\r\n\thasMore.value = true // 重置加载更多状态\r\n\t// 重新加载热销商品,排序由 Supabase 服务处理\r\n\tloadHotProducts(defaultLoadLimit)\r\n}\r\n\r\n// 切换筛选器\r\nconst switchFilter = (filterId: string) => {\r\n\tactiveFilter.value = filterId\r\n\t// 重新加载推荐商品,筛选由 Supabase 服务处理\r\n\tloadRecommendedProducts(defaultLoadLimit)\r\n}\r\n\r\n// 查看新闻详情\r\nconst viewNewsDetail = (news: any) => {\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/news/detail?id=${news.id}`\r\n\t})\r\n}\r\n\r\n// 下拉刷新\r\nconst onRefresh = async () => {\r\n\trefreshing.value = true\r\n\t\r\n\ttry {\r\n\t\t// 重新加载数据\r\n\t\tawait initData()\r\n\t} catch (e) {\r\n\t\tconsole.error('刷新数据失败:', e)\r\n\t} finally {\r\n\t\t// 延迟关闭刷新动画,确保用户能看到刷新过程\r\n\t\tsetTimeout(() => {\r\n\t\t\trefreshing.value = false\r\n\t\t\t// 延迟显示提示,避免与动画冲突\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '刷新成功',\r\n\t\t\t\t\ticon: 'success'\r\n\t\t\t\t})\r\n\t\t\t}, 200)\r\n\t\t}, 800)\r\n\t}\r\n}\r\n\r\n// 加载更多\r\nconst loadMore = async () => {\r\n\tconsole.log('=== 触发触底事件 ===')\r\n\tif (loading.value) {\r\n\t\tconsole.log('正在加载中,跳过')\r\n\t\treturn \r\n\t}\r\n\t\r\n\tshowLoadMore.value = true\r\n\tloading.value = true\r\n\ttry {\r\n\t\t// 获取当前热销商品的数量\r\n\t\tconst currentCount = hotProducts.value.length\r\n\t\tconst nextPage = Math.floor(currentCount / 6) + 1\r\n\t\tconst additionalLimit = 6\r\n\t\t\r\n\t\tconsole.log('开始加载更多,当前数量:', currentCount, '页码:', nextPage)\r\n\t\t\r\n\t\t// 加载更多商品\r\n\t\tlet newProducts: Product[] = []\r\n\t\tswitch (activeSort.value) {\r\n\t\t\tcase 'sales':\r\n\t\t\t\tnewProducts = await supabaseService.getProductsBySales(currentCount + additionalLimit)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'price':\r\n\t\t\t\tnewProducts = await supabaseService.getProductsByPrice(currentCount + additionalLimit, priceAscending.value)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'new':\r\n\t\t\t\tnewProducts = await supabaseService.getProductsByNewest(currentCount + additionalLimit)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'recommend':\r\n\t\t\t\tnewProducts = await supabaseService.getSmartRecommendations(currentCount + additionalLimit)\r\n\t\t\t\tbreak\r\n\t\t\tcase 'discount':\r\n\t\t\t\tnewProducts = await supabaseService.getDiscountProducts(currentCount + additionalLimit)\r\n\t\t\t\tbreak\r\n\t\t\tdefault:\r\n\t\t\t\tnewProducts = await supabaseService.getProductsBySales(currentCount + additionalLimit)\r\n\t\t}\r\n\t\t\r\n\t\tconsole.log('加载到的新商品数量:', newProducts.length)\r\n\t\t\r\n\t\t// 检查是否还有更多数据\r\n\t\tif (newProducts.length <= currentCount) {\r\n\t\t\thasMore.value = false\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '没有更多了',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t} else {\r\n\t\t\t// 更新商品列表\r\n\t\t\thotProducts.value = newProducts\r\n\t\t}\r\n\t} catch (error) {\r\n\t\tconsole.error('加载更多失败:', error)\r\n\t} finally {\r\n\t\tloading.value = false\r\n\t\t// 稍微延迟隐藏加载条,让用户看到\r\n\t\tsetTimeout(() => {\r\n\t\t\tshowLoadMore.value = false\r\n\t\t}, 500)\r\n\t}\r\n}\r\n\r\n// 添加到购物车\r\nconst addToCart = async (product: any) => {\r\n\tuni.showLoading({ title: '检查商品...' })\r\n\ttry {\r\n\t\t// 将 product 转换为 UTSJSONObject 以访问属性\r\n\t\tconst prodObj = (product instanceof UTSJSONObject) ? (product as UTSJSONObject) : (JSON.parse(JSON.stringify(product)) as UTSJSONObject)\r\n\t\tconst productId = prodObj.getString('id') ?? ''\r\n\t\tconst merchantId = prodObj.getString('merchant_id') ?? ''\r\n\t\t\r\n\t\t// 检查商品是否有SKU\r\n\t\tconst skus = await supabaseService.getProductSkus(productId)\r\n\t\tuni.hideLoading()\r\n\t\t\r\n\t\tif (skus.length > 0) {\r\n\t\t\t// 有规格,提示并跳转到商品详情页选择规格\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '请选择规格',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tuni.navigateTo({\r\n\t\t\t\t\turl: '/pages/mall/consumer/product-detail?id=' + productId\r\n\t\t\t\t})\r\n\t\t\t}, 500)\r\n\t\t} else {\r\n\t\t\t// 无规格,直接加入购物车\r\n\t\t\tuni.showLoading({ title: '添加中...' })\r\n\t\t\tconst success = await supabaseService.addToCart(productId, 1, '', merchantId)\r\n\t\t\tuni.hideLoading()\r\n\t\t\tif (success) {\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '已添加到购物车',\r\n\t\t\t\t\ticon: 'success'\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '添加失败,请先登录',\r\n\t\t\t\t\ticon: 'none'\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t} catch (e) {\r\n\t\tconsole.error('添加到购物车异常', e)\r\n\t\tuni.hideLoading()\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '操作异常',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t}\r\n}\r\n\r\n// 扫码功能\r\nconst onScan = (): void => {\r\n uni.scanCode({\r\n success: (res) => {\r\n console.log('扫码成功:', res)\r\n uni.showToast({\r\n title: '扫码成功: ' + res.result,\r\n icon: 'none'\r\n })\r\n },\r\n fail: (err) => {\r\n console.error('扫码失败:', err)\r\n }\r\n })\r\n}\r\n\r\n// 相机功能\r\nconst onCamera = (): void => {\r\n uni.chooseImage({\r\n count: 1,\r\n sourceType: ['camera'],\r\n success: (res) => {\r\n console.log('相机拍摄成功:', res.tempFilePaths[0])\r\n uni.showToast({\r\n title: '已拍摄,正在识别...',\r\n icon: 'loading'\r\n })\r\n setTimeout(() => {\r\n uni.showToast({\r\n title: '识别成功',\r\n icon: 'success'\r\n })\r\n }, 1000)\r\n },\r\n fail: (err) => {\r\n console.error('相机调用失败:', err)\r\n }\r\n })\r\n}\r\n\r\n// 导航函数\r\nconst navigateToSearch = (): void => { uni.navigateTo({ url: '/pages/mall/consumer/search' }) }\r\nconst navigateToNews = (): void => { uni.navigateTo({ url: '/pages/news/list' }) }\r\nconst navigateToProduct = (product: any) => {\r\n\t// 将 product 转换为 UTSJSONObject 以访问属性\r\n\tconst prodObj = (product instanceof UTSJSONObject) ? (product as UTSJSONObject) : (JSON.parse(JSON.stringify(product)) as UTSJSONObject)\r\n\t\r\n\t// 使用productId如果存在作为跳转的商品ID否则使用id\r\n\tconst productId = prodObj.getString('productId') ?? prodObj.getString('id') ?? ''\r\n\tconst name = prodObj.getString('name') ?? ''\r\n\t// 使用 main_image_url\r\n\tconst image = prodObj.getString('main_image_url') ?? prodObj.getString('image') ?? '/static/images/default-product.png'\r\n\tconst price = (prodObj.getNumber('base_price') ?? prodObj.getNumber('price') ?? 0).toString()\r\n\tconst marketPrice = prodObj.getNumber('market_price') ?? prodObj.getNumber('original_price') ?? (parseFloat(price) * 1.2)\r\n\tconst originalPrice = marketPrice.toString()\r\n \r\n // 手动构建URL避免双重编码问题\r\n\tuni.navigateTo({ \r\n\t\turl: `/pages/mall/consumer/product-detail?id=${productId}&price=${price}&originalPrice=${originalPrice}&name=${encodeURIComponent(name)}&image=${encodeURIComponent(image)}` \r\n\t})\r\n}\r\nconst navigateToCategory = (item: any) => {\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/search?keyword=${encodeURIComponent(item.name)}&type=family`\r\n\t})\r\n}\r\nconst navigateToConsultation = () => uni.navigateTo({ url: '/pages/medicine/consultation' })\r\nconst navigateToPrescription = () => uni.navigateTo({ url: '/pages/medicine/prescription' })\r\nconst navigateToOTC = () => uni.navigateTo({ url: '/pages/medicine/otc' })\r\nconst navigateToHealthTools = () => uni.navigateTo({ url: '/pages/medicine/tools' })\r\nconst navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders' })\r\n</script>\r\n\r\n<style>\r\n/* 全局重置 removed - uniapp-x does not support * selector */\r\n/* .medic-home * {\r\n\tbox-sizing: border-box;\r\n\tmargin: 0;\r\n\tpadding: 0;\r\n} */\r\n\r\n.medic-home {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\toverflow: hidden;\r\n\tbackground: #f8fafc;\r\n\tfont-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', sans-serif;\r\n\tline-height: 1.5;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.main-scroll {\r\n\tflex: 1;\r\n\theight: 1px; /* 让 flex 生效并允许滚动 */\r\n\twidth: 100%;\r\n}\r\n\r\n/* 智能导航栏 - 重新设计布局 */\r\n.smart-navbar {\r\n\tposition: fixed;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\tright: 0;\r\n\tbackground-color: #ff5000;\r\n\tz-index: 1000;\r\n\tbox-shadow: 0 2px 12px rgba(255, 80, 0, 0.15);\r\n\ttransition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n/* 导航栏搜索框容器内边距调整 */\r\n.search-container {\r\n\theight: 44px;\r\n\tpadding: 0 16px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmax-width: 1400px;\r\n\tmargin: 0 auto;\r\n\twidth: 100%;\r\n}\r\n\r\n/* 搜索框 hover 效果 */\r\n.search-box:hover {\r\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n/* 导航栏搜索框容器内边距调整 */\r\n.search-box {\r\n\tflex: 1;\r\n\tmax-width: 600px;\r\n\tbackground: #f0f0f0;\r\n\tborder-radius: 20px;\r\n\tpadding: 0 4px 0 12px;\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 显式设置 row */\r\n\talign-items: center;\r\n\t/* cursor: pointer; removed for uniapp-x support */\r\n\ttransition: all 0.3s ease;\r\n\twidth: 100%;\r\n\theight: 32px; /* 减小高度与顶部高度44px适配略小于顶部高度 */\r\n}\r\n\r\n.search-placeholder {\r\n\tfont-size: 14px;\r\n\tcolor: #999;\r\n\tflex: 1;\r\n\twhite-space: nowrap;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n}\r\n\r\n.nav-icon-btn {\r\n\tpadding: 4px 8px 4px 4px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tborder-right: 1px solid #ddd;\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.nav-icon {\r\n\tfont-size: 18px;\r\n}\r\n\r\n/* 搜索按钮高度微调 */\r\n.nav-inner-search-btn {\r\n\tpadding: 0 12px; /* 减小内边距 */\r\n\tbackground-color: #87CEEB; /* 天空蓝 */\r\n\tborder-radius: 16px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\theight: 24px; /* 随搜索框高度减小而减小 */\r\n}\r\n\r\n.nav-inner-search-text {\r\n\tfont-size: 12px;\r\n\tcolor: #ffffff;\r\n\tfont-weight: normal;\r\n}\r\n\r\n/* 导航栏占位符 */\r\n.navbar-placeholder {\r\n\twidth: 100%;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.nav-camera-btn {\r\n\tpadding: 4px 8px 4px 4px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tborder-right-width: 1px;\r\n\tborder-right-style: solid;\r\n\tborder-right-color: #ddd;\r\n\tborder-right: 1px solid #ddd; /* 修复UVUE样式 */\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.nav-camera-icon {\r\n\tfont-size: 20px;\r\n}\r\n\r\n/* 主内容区域 */\r\n.main-scroll {\r\n\tflex: 1;\r\n\tpadding: 0 16px 16px;\r\n\tmax-width: 1400px;\r\n\tmargin-left: auto;\r\n\tmargin-right: auto;\r\n\twidth: 100%;\r\n}\r\n\r\n/* 智能健康卡片 */\r\n.smart-health-card {\r\n\tbackground: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);\r\n\tborder-radius: 16px;\r\n\tpadding: 20px;\r\n\tmargin-bottom: 20px;\r\n\t/* margin-top 由 style 动态控制 */\r\n\tcolor: white;\r\n}\r\n\r\n.health-content {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\t/* gap: 12px; removed for uniapp-x support */\r\n}\r\n\r\n.health-header {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\t/* gap: 4px; removed for uniapp-x support */\r\n\tmargin-bottom: 12px; /* acts as gap for health-content */\r\n}\r\n\r\n.health-title {\r\n\tfont-size: 20px;\r\n\tfont-weight: bold;\r\n\tmargin-bottom: 4px; /* acts as gap for health-header */\r\n}\r\n\r\n.health-subtitle {\r\n\tfont-size: 14px;\r\n\topacity: 0.9;\r\n}\r\n\r\n.health-tips {\r\n\tdisplay: flex;\r\n\tflex-wrap: wrap;\r\n\t/* gap: 12px; removed for uniapp-x support */\r\n\tmargin-top: 8px;\r\n}\r\n\r\n.tip-item {\r\n\tfont-size: 13px;\r\n\tpadding: 6px 12px;\r\n\tbackground: rgba(255, 255, 255, 0.2);\r\n\tborder-radius: 20px;\r\n\t/* backdrop-filter: blur(10px); removed for uniapp-x support */\r\n\tmargin-right: 12px;\r\n\tmargin-bottom: 12px; /* acts as gap for health-tips */\r\n}\r\n\r\n/* 智能分类网格 */\r\n.smart-categories {\r\n\tbackground: white;\r\n\tborder-radius: 16px;\r\n\tpadding: 20px;\r\n\tmargin-bottom: 20px;\r\n\tbox-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.section-header {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: flex-start;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.category-tabs-pills {\r\n display: flex;\r\n flex-direction: row;\r\n background-color: #f0f2f5;\r\n padding: 3px;\r\n border-radius: 20px;\r\n align-items: center;\r\n}\r\n\r\n.tab-pill {\r\n padding: 6px 18px;\r\n border-radius: 17px;\r\n transition: all 0.3s;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.tab-pill.active {\r\n background-color: #fff;\r\n box-shadow: 0 2px 8px rgba(0,0,0,0.08);\r\n}\r\n\r\n.tab-text {\r\n font-size: 14px;\r\n color: #888;\r\n font-weight: normal;\r\n}\r\n\r\n.tab-pill.active .tab-text {\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.section-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #666;\r\n transition: color 0.3s;\r\n}\r\n\r\n.section-title.active {\r\n color: #ff5000;\r\n font-size: 20px;\r\n}\r\n\r\n.section-desc {\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n}\r\n\r\n/* 分类网格布局 */\r\n.category-grid {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* Ensure items are in row */\r\n\tflex-wrap: wrap;\r\n\t/* gap: 16px; removed for uniapp-x support */\r\n\tmargin: 0 -1.5%;\r\n}\r\n\r\n.category-card {\r\n\twidth: 18%; /* 一行5个 */\r\n\tmargin: 0 1% 12px 1%;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tpadding: 10px;\r\n\tbackground: #f8f9fa;\r\n\tborder-radius: 10px;\r\n\t/* cursor: pointer; removed for uniapp-x support */\r\n\ttransition: all 0.3s ease;\r\n\tborder: 1px solid transparent;\r\n\tposition: relative;\r\n}\r\n\r\n.category-card:hover {\r\n\ttransform: translateY(-2px);\r\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\r\n\tborder-color: var(--card-color, #ff5000);\r\n}\r\n\r\n/* 二级分类样式 */\r\n.sub-category-grid {\r\n\tbackground: #f8f9fa;\r\n\tborder-radius: 12px;\r\n\tpadding: 16px;\r\n\tmargin-top: 16px;\r\n}\r\n\r\n.sub-category-header {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 12px;\r\n\tpadding-bottom: 8px;\r\n\tborder-bottom: 1px solid #e0e0e0;\r\n}\r\n\r\n.sub-category-title {\r\n\tfont-size: 14px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.sub-category-close {\r\n\tfont-size: 16px;\r\n\tcolor: #999;\r\n\tpadding: 4px 8px;\r\n}\r\n\r\n.sub-category-wrapper {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: flex-start;\r\n}\r\n\r\n.sub-category-card {\r\n\twidth: 23%;\r\n\tbackground: white;\r\n\tborder-radius: 8px;\r\n\tpadding: 10px 4px;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tborder: 1px solid #eee;\r\n\tmargin-right: 2%;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.sub-category-card .card-icon {\r\n\twidth: 36px;\r\n\theight: 36px;\r\n\tborder-radius: 18px;\r\n\tmargin-bottom: 6px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.sub-category-card .card-icon-text {\r\n\tfont-size: 18px;\r\n}\r\n\r\n.sub-category-card .card-name {\r\n\tfont-size: 11px;\r\n\tcolor: #333;\r\n\ttext-align: center;\r\n\tlines: 1;\r\n\ttext-overflow: ellipsis;\r\n}\r\n\r\n.card-icon {\r\n\twidth: 44px;\r\n\theight: 44px;\r\n\tborder-radius: 22px;\r\n\tbackground: var(--card-color, #ff5000);\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.card-icon-text {\r\n\tfont-size: 20px;\r\n\tcolor: white;\r\n}\r\n\r\n.card-name {\r\n\tfont-size: 12px;\r\n\tfont-weight: normal;\r\n\tcolor: #333;\r\n\tmargin-bottom: 4px;\r\n\ttext-align: center;\r\n\twidth: 100%;\r\n}\r\n\r\n.card-desc {\r\n\tfont-size: 12px;\r\n\tcolor: #666;\r\n\ttext-align: center;\r\n}\r\n\r\n/* 二级分类样式 */\r\n.sub-category-grid {\r\n\tbackground: #f8f9fa;\r\n\tborder-radius: 12px;\r\n\tpadding: 16px;\r\n\tmargin-top: 16px;\r\n}\r\n\r\n.sub-category-header {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 12px;\r\n\tpadding-bottom: 8px;\r\n\tborder-bottom: 1px solid #e0e0e0;\r\n}\r\n\r\n.sub-category-title {\r\n\tfont-size: 14px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.sub-category-close {\r\n\tfont-size: 16px;\r\n\tcolor: #999;\r\n\tpadding: 4px 8px;\r\n}\r\n\r\n.sub-category-wrapper {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: flex-start;\r\n}\r\n\r\n.sub-category-card {\r\n\twidth: 23%;\r\n\tbackground: white;\r\n\tborder-radius: 8px;\r\n\tpadding: 10px 4px;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tborder: 1px solid #eee;\r\n\tmargin-right: 2%;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.sub-category-card .card-icon {\r\n\twidth: 36px;\r\n\theight: 36px;\r\n\tborder-radius: 18px;\r\n\tmargin-bottom: 6px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.sub-category-card .card-icon-text {\r\n\tfont-size: 18px;\r\n}\r\n\r\n.sub-category-card .card-name {\r\n\tfont-size: 11px;\r\n\tcolor: #333;\r\n\ttext-align: center;\r\n\tlines: 1;\r\n\ttext-overflow: ellipsis;\r\n}\r\n\r\n/* 健康资讯 */\r\n.health-news {\r\n\tbackground: white;\r\n\tborder-radius: 16px;\r\n\tpadding: 20px;\r\n\tmargin-bottom: 20px;\r\n\tbox-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.news-header {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 16px;\r\n}\r\n\r\n.news-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.news-more {\r\n\tfont-size: 14px;\r\n\tcolor: #ff5000;\r\n\t/* cursor: pointer; removed for uvue support */\r\n}\r\n\r\n.news-swiper {\r\n\theight: 200px;\r\n\tborder-radius: 12px;\r\n\toverflow: hidden;\r\n}\r\n\r\n.news-content {\r\n\tposition: relative;\r\n\theight: 100%;\r\n\tborder-radius: 12px;\r\n\toverflow: hidden;\r\n}\r\n\r\n.news-image {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tdisplay: flex;\r\n}\r\n\r\n.news-caption {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tline-height: 1.4;\r\n\tdisplay: flex;\r\n}\r\n\r\n/* 智能服务 */\r\n.smart-services {\r\n\tbackground: white;\r\n\tborder-radius: 16px;\r\n\tpadding: 20px;\r\n\tmargin-bottom: 20px;\r\n\tbox-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.services-grid {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* Ensure items are in row */\r\n\tflex-wrap: wrap;\r\n\t/* gap: 20px; removed for uniapp-x support */\r\n\tmargin: 0 -1.5%;\r\n}\r\n\r\n.service-card {\r\n\twidth: 47%; /* 50 - 3 */\r\n\tmargin: 0 1.5% 20px 1.5%;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tpadding: 20px;\r\n\tbackground: #f8f9fa;\r\n\tborder-radius: 12px;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.3s ease;\r\n}\r\n\r\n.service-card:hover {\r\n\ttransform: translateY(-4px);\r\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.service-icon {\r\n\twidth: 60px;\r\n\theight: 60px;\r\n\tborder-radius: 30px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.service-icon-text {\r\n\tfont-size: 28px;\r\n\tcolor: white;\r\n}\r\n\r\n.service-name {\r\n\tfont-size: 15px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n\tmargin-bottom: 4px;\r\n}\r\n\r\n.service-desc {\r\n\tfont-size: 12px;\r\n\tcolor: #666;\r\n}\r\n\r\n/* 热搜词区域 */\r\n.hot-keywords-section {\r\n\tbackground: white;\r\n\tborder-radius: 16px;\r\n\tpadding: 20px;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.keywords-list {\r\n\tdisplay: flex;\r\n\tflex-wrap: wrap;\r\n\tmargin-top: 15px;\r\n}\r\n\r\n.keyword-item {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tpadding: 8px 16px;\r\n\tbackground: #f5f5f5;\r\n\tborder-radius: 20px;\r\n\ttransition: all 0.2s ease;\r\n}\r\n\r\n.keyword-item:hover {\r\n\tbackground: #fff0f0;\r\n}\r\n\r\n.keyword-rank {\r\n\twidth: 20px;\r\n\theight: 20px;\r\n\tborder-radius: 10px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tfont-size: 12px;\r\n\tfont-weight: bold;\r\n\tcolor: #999;\r\n\tbackground: #eee;\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.keyword-rank.top-three {\r\n\tbackground: #ff4757;\r\n\tcolor: white;\r\n}\r\n\r\n.keyword-text {\r\n\tfont-size: 14px;\r\n\tcolor: #333;\r\n}\r\n\r\n/* 热销药品 */\r\n.hot-products {\r\n\tbackground: white;\r\n\tborder-radius: 16px;\r\n\tpadding: 20px;\r\n\tmargin-bottom: 20px;\r\n\tbox-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.section-header {\r\n\tdisplay: flex;\r\n\tflex-direction: column; /* 标题和筛选器垂直排列 */\r\n\talign-items: flex-start;\r\n\tmargin-bottom: 20px;\r\n\twidth: 100%;\r\n}\r\n\r\n.title-section {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\t/* gap: 8px; removed */\r\n\twidth: 100%;\r\n}\r\n\r\n.section-icon {\r\n\tfont-size: 20px;\r\n\tcolor: #ff5000;\r\n margin-right: 8px; /* Replacement for gap */\r\n}\r\n\r\n.sort-tabs {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 显式设置 row */\r\n\t/* gap: 8px; removed */\r\n\talign-items: center;\r\n\tflex-wrap: wrap; /* 允许换行,实现自适应 */\r\n\tjustify-content: flex-start;\r\n\twidth: 100%;\r\n\tmargin-top: 12px;\r\n}\r\n\r\n.sort-tab {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tpadding: 8px 12px; /* 增加左右内边距 */\r\n\tborder-radius: 20px;\r\n\tborder: 1px solid #e0e0e0;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.2s ease;\r\n\twhite-space: nowrap;\r\n\tflex: 1; /* 均分宽度 */\r\n\tmin-width: 70px; /* 设置最小宽度防止过窄 */\r\n\ttext-align: center;\r\n\tdisplay: flex;\r\n\tjustify-content: center;\r\n\talign-items: center;\r\n margin-right: 8px; /* Replacement for gap */\r\n}\r\n\r\n.sort-tab.active {\r\n\tbackground: #ff5000;\r\n\tcolor: white;\r\n\tborder-color: #ff5000;\r\n}\r\n\r\n.sort-tab:hover {\r\n\tbackground: #f5f5f5;\r\n}\r\n\r\n.sort-tab.active:hover {\r\n\tbackground: #e64a00;\r\n}\r\n\r\n/* 产品网格 */\r\n.products-grid {\r\n\tdisplay: flex; /* 替换 block 为 flex */\r\n\tflex-direction: row; /* 确保横向排列 */\r\n\tflex-wrap: wrap; /* 确保网格布局 */\r\n\t/* gap: 10px; removed for uniapp-x support */\r\n justify-content: space-between; /* use space-between instead of gap */\r\n\tmargin-top: 20px;\r\n\tmin-height: 500px; /* 确保有足够高度触发滚动 */\r\n\tpadding-bottom: 20px;\r\n}\r\n\r\n.product-card {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tbackground: #fff;\r\n\tborder-radius: 8px;\r\n\toverflow: hidden;\r\n\twidth: 48%;\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.product-image {\r\n\twidth: 100%;\r\n\theight: 170px;\r\n\tborder-radius: 8px;\r\n\tmargin-bottom: 8px;\r\n\tbackground: #f5f5f5;\r\n}\r\n\r\n.product-name {\r\n\tfont-size: 13px;\r\n\tcolor: #333;\r\n\tmargin-bottom: 5px;\r\n\tline-height: 1.4;\r\n\theight: 36px;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\tpadding: 0 8px;\r\n}\r\n\r\n.product-bottom {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 0 8px 8px;\r\n}\r\n\r\n.product-price {\r\n\tfont-size: 15px;\r\n\tcolor: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.product-add-btn {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tbackground-color: #ff5000;\r\n\tborder-radius: 12px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.add-icon {\r\n\tcolor: #fff;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.cart-btn {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\t/* gap: 6px; removed */\r\n\tbackground: #ff5000;\r\n\tcolor: white;\r\n\tpadding: 8px 12px;\r\n\tborder-radius: 8px;\r\n\tfont-size: 13px;\r\n\tfont-weight: bold;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.2s ease;\r\n}\r\n\r\n.cart-btn:hover {\r\n\tbackground: #388E3C;\r\n}\r\n\r\n.cart-icon {\r\n\tfont-size: 14px;\r\n margin-right: 6px; /* Replacement for gap */\r\n}\r\n\r\n.cart-text {\r\n\tfont-size: 13px;\r\n}\r\n\r\n/* 家庭常备药 */\r\n.family-medicine {\r\n\tbackground: white;\r\n\tborder-radius: 16px;\r\n\tpadding: 20px;\r\n\tmargin-bottom: 20px;\r\n\tbox-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.section-subtitle {\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n\tmargin-left: 12px;\r\n}\r\n\r\n.family-grid {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* Ensure items are in row */\r\n\tflex-wrap: wrap;\r\n\t/* gap: 16px; removed for uniapp-x support */\r\n\tmargin: 0 -1.5%;\r\n\tmargin-top: 20px;\r\n}\r\n\r\n.family-item {\r\n\twidth: 47%; /* 50 - 3 */\r\n\tmargin: 0 1.5% 16px 1.5%;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tpadding: 16px;\r\n\tbackground: #f8f9fa;\r\n\tborder-radius: 12px;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.3s ease;\r\n}\r\n\r\n.family-item:hover {\r\n\ttransform: translateY(-2px);\r\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.family-icon {\r\n\twidth: 48px;\r\n\theight: 48px;\r\n\tborder-radius: 24px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.family-icon-text {\r\n\tfont-size: 20px;\r\n\tcolor: white;\r\n}\r\n\r\n.family-name {\r\n\tfont-size: 14px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n\tmargin-bottom: 4px;\r\n}\r\n\r\n.family-desc {\r\n\tfont-size: 12px;\r\n\tcolor: #666;\r\n}\r\n\r\n/* 智能推荐 */\r\n.smart-recommend {\r\n\tbackground: white;\r\n\tborder-radius: 16px;\r\n\tpadding: 20px;\r\n\tmargin-bottom: 20px;\r\n\tbox-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.recommend-filters {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 显式设置 row */\r\n\t/* gap: 8px; removed for uniapp-x support */\r\n\talign-items: center;\r\n\tflex-wrap: wrap; /* 允许换行,实现自适应 */\r\n\tjustify-content: flex-start;\r\n\twidth: 100%;\r\n\tmargin-top: 12px;\r\n}\r\n\r\n.filter-item {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tpadding: 8px 12px; /* 增加左右内边距 */\r\n\tborder-radius: 20px;\r\n\tborder: 1px solid #e0e0e0;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.2s ease;\r\n\twhite-space: nowrap;\r\n\tflex: 1; /* 均分宽度 */\r\n\tmin-width: 80px; /* 设置最小宽度防止过窄 */\r\n\ttext-align: center;\r\n\tdisplay: flex;\r\n\tjustify-content: center;\r\n\talign-items: center;\r\n\tmargin-right: 8px;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.filter-item.active {\r\n\tbackground: #ff5000;\r\n\tcolor: white;\r\n\tborder-color: #ff5000;\r\n}\r\n\r\n.filter-item:hover {\r\n\tbackground: #f5f5f5;\r\n}\r\n\r\n.filter-item.active:hover {\r\n\tbackground: #e64a00;\r\n}\r\n\r\n.recommend-grid {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* Ensure items are in row */\r\n\tflex-wrap: wrap;\r\n\t/* gap: 20px; removed for uniapp-x support */\r\n\tmargin: 0 -1.5%;\r\n\tmargin-top: 20px;\r\n}\r\n\r\n.recommend-product {\r\n\twidth: 97%; /* 1 col */\r\n\tmargin: 0 1.5% 20px 1.5%;\r\n\tbackground: #f8f9fa;\r\n\tborder-radius: 12px;\r\n\toverflow: hidden;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.3s ease;\r\n\tborder: 1px solid #e0e0e0;\r\n}\r\n\r\n.recommend-product:hover {\r\n\ttransform: translateY(-4px);\r\n\tbox-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\r\n}\r\n\r\n.product-image-container {\r\n\tposition: relative;\r\n\theight: 180px;\r\n}\r\n\r\n.product-image-container .product-image {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tbackground: white;\r\n}\r\n\r\n.product-tags {\r\n\tposition: absolute;\r\n\ttop: 12px;\r\n\tleft: 12px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\t/* gap: 8px; removed */\r\n}\r\n\r\n.product-tag, .featured-tag {\r\n\tpadding: 4px 10px;\r\n\tborder-radius: 10px;\r\n\tfont-size: 11px;\r\n\tfont-weight: bold;\r\n\tcolor: white;\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.product-tag {\r\n\tbackground: rgba(76, 175, 80, 0.9);\r\n}\r\n\r\n.featured-tag {\r\n\tbackground: rgba(255, 87, 34, 0.9);\r\n}\r\n\r\n.product-details {\r\n\tpadding: 16px;\r\n}\r\n\r\n.product-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n\tmargin-bottom: 4px;\r\n\tline-height: 1.4;\r\n\tdisplay: flex;\r\n}\r\n\r\n.product-specification {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tmargin-bottom: 12px;\r\n\tdisplay: flex;\r\n}\r\n\r\n.product-rating {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\t/* gap: 8px; removed */\r\n\tmargin-bottom: 12px;\r\n\tfont-size: 13px;\r\n}\r\n\r\n.rating-stars {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\t/* gap: 4px; removed */\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.star-icon {\r\n\tfont-size: 14px;\r\n\tcolor: #FFC107;\r\n\tmargin-right: 2px;\r\n}\r\n\r\n.rating-value {\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.reviews-count {\r\n\tcolor: #666;\r\n}\r\n\r\n.product-actions {\r\n\tdisplay: flex;\r\n\tjustify-content: flex-end;\r\n\tmargin-top: 12px;\r\n}\r\n\r\n.add-to-cart {\r\n\twidth: 36px;\r\n\theight: 36px;\r\n\tborder-radius: 18px;\r\n\tbackground: #ff5000;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.2s ease;\r\n}\r\n\r\n.add-to-cart:hover {\r\n\tbackground: #e64a00;\r\n\ttransform: scale(1.1);\r\n}\r\n\r\n.cart-icon {\r\n\tfont-size: 16px;\r\n\tcolor: white;\r\n}\r\n\r\n/* 健康提醒 */\r\n.health-reminder {\r\n\tbackground: linear-gradient(135deg, #FF9800 0%, #F57C00 100%);\r\n\tborder-radius: 16px;\r\n\tpadding: 16px 20px;\r\n\tmargin-bottom: 20px;\r\n\tcolor: white;\r\n}\r\n\r\n.reminder-content {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\t/* gap: 12px; removed */\r\n}\r\n\r\n.reminder-icon {\r\n\tfont-size: 24px;\r\n\tmargin-right: 12px;\r\n}\r\n\r\n.reminder-text {\r\n\tflex: 1;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\t/* gap: 4px; removed */\r\n}\r\n\r\n.reminder-title {\r\n\tfont-size: 15px;\r\n\tfont-weight: bold;\r\n\tmargin-bottom: 4px;\r\n}\r\n\r\n.reminder-desc {\r\n\tfont-size: 13px;\r\n\topacity: 0.9;\r\n}\r\n\r\n.reminder-action {\r\n\tpadding: 6px 16px;\r\n\tbackground: rgba(255, 255, 255, 0.2);\r\n\tborder-radius: 20px;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.2s ease;\r\n}\r\n\r\n.reminder-action:hover {\r\n\tbackground: rgba(255, 255, 255, 0.3);\r\n}\r\n\r\n.action-text {\r\n\tfont-size: 13px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n/* 加载状态 */\r\n.loading-state {\r\n\tpadding: 40px 0;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.loading-spinner {\r\n\twidth: 32px;\r\n\theight: 32px;\r\n\tborder: 3px solid #f0f0f0;\r\n\tborder-top-color: #ff5000;\r\n\tborder-radius: 16px;\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.loading-text {\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n}\r\n\r\n.no-more {\r\n\tpadding: 30px 0;\r\n\ttext-align: center;\r\n\tborder-top: 1px solid #f0f0f0;\r\n\tmargin-top: 10px;\r\n}\r\n\r\n.no-more-text {\r\n\tfont-size: 13px;\r\n\tcolor: #999;\r\n}\r\n\r\n/* 安全区域 */\r\n.safe-area {\r\n\theight: 20px;\r\n\twidth: 100%;\r\n}\r\n\r\n/* ===== 响应式设计 ===== */\r\n\r\n/* 小屏手机 (小于414px) */\r\n@media screen and (max-width: 414px) {\r\n\t.search-container {\r\n\t\tpadding: 0 12px;\r\n\t\theight: 44px;\r\n\t}\r\n\t\r\n\t.search-box {\r\n\t\tpadding: 0 4px 0 12px;\r\n\t\tmargin: 0;\r\n\t}\r\n\t\r\n\t.main-scroll {\r\n\t\tpadding: 0 12px 12px;\r\n\t}\r\n\t\r\n\t.category-grid {\r\n\t\tmargin: 0 -1%;\r\n\t\tpadding: 0 4px;\r\n\t}\r\n\t\r\n\t.category-grid .category-card {\r\n\t\twidth: 18%;\r\n\t\tmargin: 0 1% 6px 1%;\r\n\t\tpadding: 4px 0;\r\n\t\tbackground: transparent;\r\n\t\tbox-shadow: none;\r\n\t\tborder: none;\r\n\t}\r\n\t\r\n\t.category-card:hover {\r\n\t\ttransform: none;\r\n\t\tbox-shadow: none;\r\n\t}\r\n\t\r\n\t.card-icon {\r\n\t\twidth: 36px;\r\n\t\theight: 36px;\r\n\t\tborder-radius: 18px;\r\n\t\tmargin-bottom: 4px;\r\n\t}\r\n\t\r\n\t.card-icon-text {\r\n\t\tfont-size: 18px;\r\n\t}\r\n\t\r\n\t.card-name {\r\n\t\tfont-size: 10px;\r\n\t\tfont-weight: normal;\r\n\t\tcolor: #333;\r\n\t}\r\n\t\r\n\t.card-desc {\r\n\t\tdisplay: none;\r\n\t}\r\n\t\r\n\t.services-grid .service-card {\r\n\t\twidth: 23%; /* 4 cols */\r\n\t\tmargin: 0 1% 8px 1%;\r\n\t}\r\n\t\r\n\t.service-card {\r\n\t\tpadding: 10px 4px;\r\n\t\tbackground: #f8f9fa; /* 保持淡色背景 */\r\n\t}\r\n\t\r\n\t.service-icon {\r\n\t\twidth: 40px;\r\n\t\theight: 40px;\r\n\t\tborder-radius: 20px;\r\n\t\tmargin-bottom: 8px;\r\n\t}\r\n\t\r\n\t.service-icon-text {\r\n\t\tfont-size: 20px;\r\n\t}\r\n\t\r\n\t.service-name {\r\n\t\tfont-size: 11px;\r\n\t}\r\n\t\r\n\t.service-desc {\r\n\t\tdisplay: none; /* 隐藏描述 */\r\n\t}\r\n\t\r\n\t.load-more-status {\r\n\t\tpadding: 20px;\r\n\t\tdisplay: flex;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\twidth: 100%;\r\n\t}\r\n\t\r\n\t.loading-text {\r\n\t\tcolor: #888;\r\n\t\tfont-size: 14px;\r\n\t}\r\n\r\n\t.products-grid {\r\n\t\t/* column-count: 2; removed for flex */\r\n\t\t/* column-gap: 8px; removed */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 48%;\r\n\t\tmargin: 0 1% 8px 1%;\r\n\t}\r\n\r\n\t.product-info,\r\n\t.product-details {\r\n\t\tpadding: 6px; /* 极小内边距 */\r\n\t}\r\n\r\n\t.product-name,\r\n\t.product-title {\r\n\t\tfont-size: 13px; /* 调整字体大小 */\r\n\t\theight: 36px; /* 限制高度,防止参差不齐 */\r\n\t\toverflow: hidden;\r\n\t\ttext-overflow: ellipsis;\r\n /* display: webkit-box removed for compatibility */\r\n display: flex;\r\n white-space: nowrap;\r\n\t}\r\n\r\n\t.product-image,\r\n\t.product-image-container {\r\n\t\theight: 140px; /* 稍微减小图片高度适配双列 */\r\n\t}\r\n\r\n\t/* 手机端商品卡片极简模式(热销 & 推荐) */\r\n\t.hot-products .product-spec,\r\n\t.hot-products .manufacturer,\r\n\t.hot-products .original-price,\r\n\t.hot-products .cart-text,\r\n\t.hot-products .sales-info,\r\n\t.hot-products .product-action, /* 隐藏热销区加购按钮 */\r\n\t.smart-recommend .product-specification,\r\n\t.smart-recommend .product-rating,\r\n\t.smart-recommend .original-price,\r\n\t.smart-recommend .product-actions /* 隐藏推荐区加购按钮 */\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n\t\r\n\t.hot-products .product-info,\r\n\t.smart-recommend .product-details {\r\n\t\tpadding: 6px; /* 极小内边距 */\r\n\t}\r\n\t\r\n\t.hot-products .product-image,\r\n\t.hot-products .product-image-container,\r\n\t.smart-recommend .product-image,\r\n\t.smart-recommend .product-image-container {\r\n\t\theight: 110px; /* 进一步减小图片高度 */\r\n\t}\r\n\t\r\n\t.hot-products .product-name,\r\n\t.smart-recommend .product-title {\r\n\t\tmargin-bottom: 2px;\r\n\t\tfont-size: 12px;\r\n\t\tline-height: 1.3;\r\n\t\theight: 32px; /* 限制2行高度 */\r\n\t}\r\n\t\r\n\t.hot-products .price-section,\r\n\t.smart-recommend .price-section {\r\n\t\tmargin-bottom: 0;\r\n\t\tmargin-top: 4px;\r\n\t}\r\n\t\r\n\t.hot-products .price-symbol,\r\n\t.smart-recommend .price-symbol {\r\n\t\tfont-size: 10px;\r\n\t\tcolor: #FF5722;\r\n\t}\r\n\t\r\n\t.hot-products .price-value,\r\n\t.smart-recommend .price-value {\r\n\t\tfont-size: 14px; /* 字体变小 */\r\n\t\tfont-weight: bold;\r\n\t}\r\n\t\r\n\t.family-grid .family-item {\r\n\t\twidth: 23%; /* 4 cols */\r\n\t\tmargin: 0 1% 8px 1%;\r\n\t\tpadding: 8px 4px;\r\n\t\tbackground: #f8f9fa;\r\n\t}\r\n\t\r\n\r\n\t\r\n\t.family-icon {\r\n\t\twidth: 36px;\r\n\t\theight: 36px;\r\n\t\tborder-radius: 18px;\r\n\t\tmargin-bottom: 6px;\r\n\t}\r\n\t\r\n\t.family-icon-text {\r\n\t\tfont-size: 18px;\r\n\t}\r\n\t\r\n\t.family-name {\r\n\t\tfont-size: 11px;\r\n\t}\r\n\t\r\n\t.family-desc {\r\n\t\tdisplay: none;\r\n\t}\r\n\t\r\n\t.news-swiper {\r\n\t\theight: 160px;\r\n\t}\r\n\t\r\n\t.sort-tabs,\r\n\t.recommend-filters {\r\n\t\t/* gap: 8px; removed */\r\n\t\tjustify-content: flex-start; /* 保持左对齐 */\r\n\t\t/* overflow-x: auto; REMOVED for uniapp-x support - use generic view wrapping instead */\r\n\t\t/* flex-wrap: nowrap; REMOVED to allow wrapping on mobile since overflow-x not supported on view */\r\n\t\tpadding-bottom: 4px; /* 滚动条空间 */\r\n\t}\r\n\t\r\n\t.sort-tab,\r\n\t.filter-item {\r\n\t\tpadding: 5px 12px;\r\n\t\tfont-size: 12px;\r\n\t\tflex-shrink: 0; /* 防止被压缩 */\r\n\t\tmin-width: 0; /* CHANGED from auto to 0 */\r\n\t\tflex: 0 0 auto; /* 取消均分 */\r\n\t\tmargin-right: 8px;\r\n\t\tmargin-bottom: 8px; /* Added spacing for wrapped items */\r\n\t}\r\n\t\r\n\t.section-header {\r\n\t\tflex-direction: column;\r\n\t\talign-items: stretch;\r\n\t\t/* gap: 12px; removed */\r\n\t}\r\n\t\r\n\t.title-section {\r\n\t\tjustify-content: center;\r\n\t\tmargin-bottom: 12px;\r\n\t}\r\n\t\r\n\t.sort-tabs,\r\n\t.recommend-filters {\r\n\t\twidth: 100%;\r\n\t\tjustify-content: center;\r\n\t}\r\n}\r\n\r\n/* 中屏手机/小平板 (415px-768px) */\r\n@media screen and (min-width: 415px) and (max-width: 768px) {\r\n\t.search-container {\r\n\t\tpadding: 0 16px;\r\n\t\theight: 44px;\r\n\t}\r\n\t\r\n\t.main-scroll {\r\n\t\t/* 移除 margin-top */\r\n\t}\r\n\t\r\n\t.category-grid .category-card {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.services-grid .service-card {\r\n\t\twidth: 47%;\r\n\t}\r\n\t\r\n\t.products-grid {\r\n\t\t/* column-count: 2; removed */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 47%;\r\n\t}\r\n\t\r\n\t.family-grid .family-item {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.sort-tabs,\r\n\t.recommend-filters {\r\n\t\t/* gap: 10px; removed */\r\n\t\tjustify-content: flex-start;\r\n\t}\r\n\t\r\n\t.sort-tab,\r\n\t.filter-item {\r\n\t\tpadding: 6px 14px;\r\n\t\tfont-size: 12px;\r\n\t\tflex-grow: 0;\r\n\t\tmargin-right: 10px;\r\n\t}\r\n\t\r\n\t.section-header {\r\n\t\tflex-direction: column;\r\n\t\talign-items: stretch;\r\n\t\t/* gap: 12px; removed */\r\n\t}\r\n\t\r\n\t.title-section {\r\n\t\tjustify-content: center;\r\n\t\tmargin-bottom: 12px;\r\n\t}\r\n\t\r\n\t.sort-tabs,\r\n\t.recommend-filters {\r\n\t\twidth: 100%;\r\n\t\tjustify-content: center;\r\n\t}\r\n}\r\n\r\n/* 平板设备 (769px-1024px) */\r\n@media screen and (min-width: 769px) and (max-width: 1024px) {\r\n\t.search-container {\r\n\t\tpadding: 0 24px;\r\n\t\theight: 44px;\r\n\t}\r\n\t\r\n\t.nav-search-tools .nav-tool-item {\r\n\t\tdisplay: none;\r\n\t}\r\n\t\r\n\t.main-scroll {\r\n\t\tpadding: 0 24px 20px;\r\n\t}\r\n\t\r\n\t.category-grid .category-card {\r\n\t\twidth: 22%;\r\n\t}\r\n\t\r\n\t.services-grid .service-card {\r\n\t\twidth: 22%;\r\n\t}\r\n\t\r\n\t.product-card {\r\n\t\t/* width: calc((100% - 20px) / 3); */\r\n width: 32%; /* Fallback for calc */\r\n /* margin-right: 1.33%; */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 47%;\r\n\t}\r\n\t\r\n\t.family-grid .family-item {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.news-swiper {\r\n\t\theight: 240px;\r\n\t}\r\n}\r\n\r\n/* 桌面端 (1025px以上) */\r\n@media screen and (min-width: 1025px) {\r\n\t.search-container {\r\n\t\tpadding: 0 32px;\r\n\t\theight: 44px;\r\n\t}\r\n\t\r\n\t.main-scroll {\r\n\t\tpadding: 0 32px 24px;\r\n\t}\r\n\t\r\n\t.category-grid .category-card {\r\n\t\twidth: 13.66%;\r\n\t}\r\n\t\r\n\t.services-grid .service-card {\r\n\t\twidth: 22%;\r\n\t}\r\n\t\r\n\t.product-card {\r\n\t\t/* width: calc((100% - 30px) / 4); */\r\n width: 23%;\r\n /* margin-right: 2%; */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.family-grid .family-item {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.news-swiper {\r\n\t\theight: 260px;\r\n\t}\r\n}\r\n\r\n/* 大桌面端 (1400px以上) */\r\n@media screen and (min-width: 1400px) {\r\n\t.category-grid {\r\n\t\tmargin-right: -24px;\r\n\t}\r\n\t.category-grid .category-card {\r\n\t\twidth: 17%;\r\n\t\tmargin: 0 1.5% 24px 1.5%;\r\n\t}\r\n\t\r\n\t.product-card {\r\n\t\t/* width: calc((100% - 30px) / 4); */\r\n width: 23%;\r\n /* margin-right: 2%; */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 22%;\r\n\t}\r\n}\r\n\r\n/* 暗黑模式适配 */\r\n@media (prefers-color-scheme: dark) {\r\n\t.medic-home {\r\n\t\tbackground: #121212;\r\n\t}\r\n\t\r\n\t.smart-categories,\r\n\t.health-news,\r\n\t.smart-services,\r\n\t.hot-products,\r\n\t.family-medicine,\r\n\t.smart-recommend {\r\n\t\tbackground: #1e1e1e;\r\n\t\tborder-color: #333;\r\n\t}\r\n\t\r\n\t.nav-search-box {\r\n\t\tbackground: rgba(30, 30, 30, 0.95);\r\n\t\tborder-color: #444;\r\n\t}\r\n\t\r\n\t.category-card,\r\n\t.service-card,\r\n\t.product-card,\r\n\t.family-item,\r\n\t.recommend-product {\r\n\t\tbackground: #2d2d2d;\r\n\t\tborder-color: #444;\r\n\t}\r\n\t\r\n\t.section-title,\r\n\t.card-name,\r\n\t.service-name,\r\n\t.product-name,\r\n\t.family-name,\r\n\t.product-title,\r\n\t.section-desc,\r\n\t.card-desc,\r\n\t.service-desc,\r\n\t.product-spec,\r\n\t.product-specification,\r\n\t.family-desc {\r\n\t\tcolor: #aaa;\r\n\t}\r\n\t\r\n\t.sort-tab,\r\n\t.filter-item {\r\n\t\tbackground: #333;\r\n\t\tborder-color: #444;\r\n\t\tcolor: #ccc;\r\n\t}\r\n\t\r\n\t.sort-tab.active,\r\n\t.filter-item.active {\r\n\t\tbackground: #ff5000;\r\n\t\tcolor: white;\r\n\t}\r\n\t\r\n\t.sort-tab:hover,\r\n\t.filter-item:hover {\r\n\t\tbackground: #444;\r\n\t}\r\n\t\r\n\t.sort-tab.active:hover,\r\n\t.filter-item.active:hover {\r\n\t\tbackground: #e64a00;\r\n\t}\r\n\t\r\n\t.nav-tool-item {\r\n\t\tbackground: rgba(255, 80, 0, 0.2);\r\n\t\tcolor: #ff5000;\r\n\t}\r\n\t\r\n\t.manufacturer,\r\n\t.sales-count,\r\n\t.reviews-count,\r\n\t.original-price {\r\n\t\tcolor: #888;\r\n\t}\r\n}\r\n</style>\r\n\r\n","<template>\r\n <view class=\"category-page\">\r\n <!-- 顶部搜索栏 -->\r\n <view class=\"search-bar\" :style=\"{ paddingTop: statusBarHeight + 'px' }\">\r\n <view class=\"search-container\" :style=\"{ paddingRight: (navBarRight > 0 ? navBarRight : 16) + 'px' }\">\r\n <view class=\"search-box\" @click=\"navigateToSearch\" :style=\"{ height: '30px' }\">\r\n <!-- 模拟输入框 -->\r\n <text class=\"search-placeholder\">请输入商品名称、店铺</text>\r\n \r\n <!-- 扫码图标 -->\r\n <view class=\"nav-icon-btn\" @click.stop=\"onScan\">\r\n <text class=\"nav-icon\">🔳</text>\r\n </view>\r\n\r\n <!-- 相机图标 -->\r\n <view class=\"nav-camera-btn\" @click.stop=\"onCamera\">\r\n <text class=\"nav-camera-icon\">📷</text>\r\n </view>\r\n \r\n <!-- 搜索按钮 -->\r\n <view class=\"nav-inner-search-btn\" :style=\"{ height: '22px' }\">\r\n <text class=\"nav-inner-search-text\">搜索</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 导航栏占位 - 需要包含statusBarHeight + 搜索框高度44px -->\r\n <view class=\"navbar-placeholder\" :style=\"{ height: (statusBarHeight + 44) + 'px' }\"></view>\r\n \r\n <!-- 分类内容区 -->\r\n <view class=\"category-content\">\r\n <!-- 左侧一级分类 -->\r\n <scroll-view \r\n :scroll-y=\"true\" \r\n class=\"primary-category\" \r\n :scroll-top=\"scrollTop\" \r\n :scroll-with-animation=\"true\"\r\n >\r\n <view \r\n v-for=\"item in primaryCategories\" \r\n :key=\"item.id\"\r\n :class=\"['primary-item', { active: isPrimaryActive(item.id) }]\"\r\n @click=\"selectPrimaryCategory(item.id)\"\r\n :style=\"{ backgroundColor: getPrimaryItemBgColor(item) }\"\r\n >\r\n <text class=\"primary-icon\">{{ item.icon }}</text>\r\n <text class=\"primary-name\">{{ item.name }}</text>\r\n </view>\r\n </scroll-view>\r\n \r\n <!-- 右侧商品列表 -->\r\n <scroll-view \r\n :scroll-y=\"true\" \r\n class=\"product-content\"\r\n @scrolltolower=\"loadMore\"\r\n :lower-threshold=\"50\"\r\n >\r\n <!-- 分类标题 -->\r\n <view class=\"category-header\">\r\n <text class=\"category-title\">{{ currentCategoryName }}</text>\r\n <text class=\"category-desc\">{{ currentCategoryDesc }}</text>\r\n </view>\r\n \r\n <!-- 二级分类 -->\r\n <view v-if=\"subCategories.length > 0\" class=\"sub-category-section\">\r\n <view class=\"sub-category-list\">\r\n <view \r\n v-for=\"sub in subCategories\" \r\n :key=\"sub.id\"\r\n :class=\"['sub-category-item', { active: isSubActive(sub.id) }]\"\r\n @click=\"selectSubCategory(sub.id)\"\r\n >\r\n <text class=\"sub-category-icon\">{{ sub.icon }}</text>\r\n <text class=\"sub-category-name\">{{ sub.name }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 商品网格 -->\r\n <view v-if=\"productList.length > 0\" class=\"product-grid\">\r\n <view \r\n v-for=\"product in productList\" \r\n :key=\"product.id\"\r\n class=\"product-card\"\r\n @click=\"navigateToProduct(product)\"\r\n >\r\n <image \r\n class=\"product-image\" \r\n :src=\"product.main_image_url\" \r\n mode=\"aspectFill\"\r\n />\r\n <text class=\"product-name\" :lines=\"2\">{{ product.name }}</text>\r\n <view class=\"product-bottom\">\r\n <text class=\"product-price\">¥{{ product.base_price ?? product.price ?? 0 }}</text>\r\n <view class=\"product-add-btn\" @click.stop=\"addToCart(product)\">\r\n <text class=\"add-icon\">+</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 加载中状态 -->\r\n <view v-else-if=\"loading\" class=\"loading-state\">\r\n <view class=\"loading-spinner\"></view>\r\n <text class=\"loading-text\">加载中...</text>\r\n </view>\r\n \r\n <!-- 空状态 - 仅在非加载状态且无商品时显示 -->\r\n <view v-else class=\"empty-state\">\r\n <text class=\"empty-icon\"><3E></text>\r\n <text class=\"empty-text\">暂无相关商品</text>\r\n <text class=\"empty-desc\">该分类下暂无商品,敬请期待</text>\r\n </view>\r\n \r\n <!-- 加载更多提示 -->\r\n <view v-if=\"hasMore\" class=\"load-more\">\r\n <text class=\"load-text\">上拉加载更多</text>\r\n </view>\r\n </scroll-view>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted } from 'vue'\r\nimport { onLoad, onShow } from '@dcloudio/uni-app'\r\nimport supabaseService from '@/utils/supabaseService.uts'\r\nimport type { Product } from '@/utils/supabaseService.uts'\r\n\r\ntype LocalCategory = {\r\n\tid: string\r\n\tname: string\r\n\ticon: string\r\n\tdescription: string\r\n\tcolor: string\r\n}\r\n\r\n// 响应式数据\r\nconst statusBarHeight = ref(0)\r\nconst headerHeight = ref(44)\r\n\r\n// 小程序胶囊按钮信息类型\r\ntype CapsuleButtonInfo = {\r\n\tleft: number,\r\n\ttop: number,\r\n\tright: number,\r\n\tbottom: number,\r\n\twidth: number,\r\n\theight: number\r\n}\r\n\r\n// 小程序胶囊按钮信息\r\nconst capsuleButtonInfo = ref<CapsuleButtonInfo | null>(null)\r\nconst navBarRight = ref(0) // 导航栏右侧预留空间\r\n\r\nconst primaryCategories = ref<LocalCategory[]>([])\r\nconst subCategories = ref<LocalCategory[]>([]) // 二级分类列表\r\nconst productList = ref<Product[]>([])\r\nconst activePrimary = ref<string>('')\r\nconst activeSubCategory = ref<string>('') // 当前选中的二级分类\r\nconst selectedParentId = ref<string>('') // 当前选中的一级分类ID用于高亮显示\r\nconst cartCount = ref(3)\r\nconst hasMore = ref(true)\r\nconst hasLoadedFromParams = ref(false)\r\nconst currentPage = ref(1)\r\nconst loading = ref(false)\r\nconst scrollTop = ref(0)\r\nconst pendingCategoryId = ref('') // 待处理的分类ID从其他页面跳转过来时暂存\r\n\r\n// 获取当前分类信息\r\nconst currentCategoryName = ref('')\r\nconst currentCategoryDesc = ref('')\r\n\r\n// 页面参数\r\nconst pageParams = ref<any>({})\r\n\r\n// 加载商品数据\r\nasync function loadProducts(): Promise<void> {\r\n if (loading.value) return\r\n if (activePrimary.value == '') {\r\n console.warn('activePrimary为空无法加载商品')\r\n return\r\n }\r\n\r\n loading.value = true\r\n try {\r\n console.log('开始加载商品分类ID:', activePrimary.value, '页码:', currentPage.value)\r\n const response = await supabaseService.getProductsByCategory(activePrimary.value, currentPage.value)\r\n console.log('商品加载结果:', {\r\n dataCount: response.data.length,\r\n total: response.total,\r\n hasmore: response.hasmore,\r\n page: currentPage.value\r\n })\r\n \r\n if (currentPage.value == 1) {\r\n productList.value = response.data\r\n } else {\r\n productList.value.push(...response.data)\r\n }\r\n \r\n hasMore.value = response.hasmore\r\n \r\n // 更新当前分类信息 - 先在一级分类中查找,再在二级分类中查找\r\n let foundCat: LocalCategory | null = null\r\n for (let i = 0; i < primaryCategories.value.length; i++) {\r\n if (primaryCategories.value[i].id == activePrimary.value) {\r\n foundCat = primaryCategories.value[i]\r\n break\r\n }\r\n }\r\n if (foundCat == null) {\r\n for (let i = 0; i < subCategories.value.length; i++) {\r\n if (subCategories.value[i].id == activePrimary.value) {\r\n foundCat = subCategories.value[i]\r\n break\r\n }\r\n }\r\n }\r\n if (foundCat != null) {\r\n currentCategoryName.value = foundCat.name\r\n currentCategoryDesc.value = foundCat.description\r\n }\r\n \r\n console.log('商品列表加载完成,当前总数量:', productList.value.length)\r\n } catch (error) {\r\n console.error('加载商品数据失败:', error)\r\n if (currentPage.value == 1) {\r\n productList.value = []\r\n }\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n\r\n// 加载二级分类\r\nasync function loadSubCategories(parentId: string): Promise<void> {\r\n console.log('加载二级分类父级ID:', parentId)\r\n try {\r\n const subCats = await supabaseService.getSubCategories(parentId)\r\n console.log('获取到二级分类数量:', subCats.length)\r\n \r\n const categories: LocalCategory[] = []\r\n for (let i = 0; i < subCats.length; i++) {\r\n const cat = subCats[i]\r\n categories.push({\r\n id: cat.id,\r\n name: cat.name,\r\n icon: cat.icon,\r\n description: cat.description,\r\n color: cat.color\r\n })\r\n }\r\n subCategories.value = categories\r\n } catch (e) {\r\n console.error('加载二级分类失败:', e)\r\n subCategories.value = []\r\n }\r\n}\r\n\r\n// 判断一级分类是否选中\r\nfunction isPrimaryActive(categoryId: string): boolean {\r\n return selectedParentId.value == categoryId\r\n}\r\n\r\n// 判断二级分类是否选中\r\nfunction isSubActive(subCategoryId: string): boolean {\r\n return activeSubCategory.value == subCategoryId || activePrimary.value == subCategoryId\r\n}\r\n\r\n// 获取一级分类的背景色\r\nfunction getPrimaryItemBgColor(item: LocalCategory): string {\r\n if (isPrimaryActive(item.id)) {\r\n return item.color\r\n }\r\n return 'transparent'\r\n}\r\n\r\n// 选择二级分类\r\nasync function selectSubCategory(subCategoryId: string): Promise<void> {\r\n console.log('选择二级分类:', subCategoryId)\r\n activeSubCategory.value = subCategoryId\r\n \r\n // 使用二级分类ID加载商品\r\n currentPage.value = 1\r\n hasMore.value = true\r\n activePrimary.value = subCategoryId // 临时设置为二级分类ID用于加载商品\r\n await loadProducts()\r\n}\r\n\r\n// 选择一级分类 - 必须在 loadCategories 之前定义\r\n// originalCategoryId: 可能是一级分类ID也可能是二级分类ID\r\nasync function selectPrimaryCategory(originalCategoryId: string): Promise<void> {\r\n console.log('=== selectPrimaryCategory函数开始执行 ===')\r\n console.log('传入的categoryId:', originalCategoryId)\r\n \r\n if (originalCategoryId == '') {\r\n console.error('categoryId为空尝试使用第一个分类')\r\n if (primaryCategories.value.length > 0) {\r\n originalCategoryId = primaryCategories.value[0].id\r\n } else {\r\n console.error('没有可用的分类')\r\n return\r\n }\r\n }\r\n \r\n // 检查传入的是否是一级分类ID\r\n let targetParentId = originalCategoryId\r\n let targetSubId = ''\r\n console.log('当前一级分类列表长度:', primaryCategories.value.length)\r\n let foundInPrimary: LocalCategory | null = null\r\n for (let i = 0; i < primaryCategories.value.length; i++) {\r\n if (primaryCategories.value[i].id == originalCategoryId) {\r\n foundInPrimary = primaryCategories.value[i]\r\n break\r\n }\r\n }\r\n console.log('在一级分类中查找结果:', foundInPrimary != null)\r\n \r\n if (foundInPrimary == null) {\r\n // 传入的可能是二级分类ID需要查找其父级分类\r\n console.log('传入的ID不在一级分类中可能是二级分类ID尝试查找父级分类')\r\n \r\n // 从服务器获取分类信息以确定父级\r\n try {\r\n const categoryInfo = await supabaseService.getCategoryById(originalCategoryId)\r\n if (categoryInfo != null && categoryInfo.parent_id != null && categoryInfo.parent_id != '') {\r\n console.log('找到父级分类ID:', categoryInfo.parent_id)\r\n \r\n // 检查父级分类ID是否在一级分类列表中\r\n console.log('查找父级分类ID:', categoryInfo.parent_id)\r\n let parentInPrimary: LocalCategory | null = null\r\n for (let i = 0; i < primaryCategories.value.length; i++) {\r\n if (primaryCategories.value[i].id == categoryInfo.parent_id) {\r\n parentInPrimary = primaryCategories.value[i]\r\n break\r\n }\r\n }\r\n console.log('父级分类查找结果:', parentInPrimary != null)\r\n if (parentInPrimary != null) {\r\n console.log('父级分类在列表中找到:', parentInPrimary.name)\r\n targetParentId = categoryInfo.parent_id!\r\n targetSubId = originalCategoryId // 记住要选中的二级分类\r\n } else {\r\n console.log('父级分类不在列表中,使用第一个分类')\r\n // 打印当前列表中的所有分类ID\r\n for (let i = 0; i < primaryCategories.value.length; i++) {\r\n console.log('列表中的分类:', primaryCategories.value[i].id, primaryCategories.value[i].name)\r\n }\r\n if (primaryCategories.value.length > 0) {\r\n targetParentId = primaryCategories.value[0].id\r\n }\r\n }\r\n } else {\r\n console.log('未找到父级分类,使用第一个分类')\r\n if (primaryCategories.value.length > 0) {\r\n targetParentId = primaryCategories.value[0].id\r\n }\r\n }\r\n } catch (e) {\r\n console.error('获取分类信息失败:', e)\r\n if (primaryCategories.value.length > 0) {\r\n targetParentId = primaryCategories.value[0].id\r\n }\r\n }\r\n }\r\n \r\n console.log('最终选中的一级分类ID:', targetParentId)\r\n console.log('需要选中的二级分类ID:', targetSubId)\r\n \r\n // 设置一级分类高亮\r\n selectedParentId.value = targetParentId\r\n activePrimary.value = targetParentId\r\n \r\n // 加载二级分类\r\n await loadSubCategories(targetParentId)\r\n \r\n // 如果有要选中的二级分类\r\n if (targetSubId != '') {\r\n activeSubCategory.value = targetSubId\r\n } else {\r\n // 如果没有指定二级分类,但有二级分类列表,默认选中第一个\r\n if (subCategories.value.length > 0) {\r\n activeSubCategory.value = subCategories.value[0].id\r\n targetSubId = subCategories.value[0].id\r\n console.log('默认选中第一个二级分类:', subCategories.value[0].name)\r\n } else {\r\n activeSubCategory.value = ''\r\n }\r\n }\r\n \r\n // 自动滚动到选中位置\r\n let foundIndex = -1\r\n for (let i = 0; i < primaryCategories.value.length; i++) {\r\n if (primaryCategories.value[i].id == targetParentId) {\r\n foundIndex = i\r\n break\r\n }\r\n }\r\n if (foundIndex != -1) {\r\n // 获取系统信息\r\n const systemInfo = uni.getSystemInfoSync()\r\n \r\n let itemHeight = 70\r\n if (systemInfo.windowWidth > 1025) {\r\n itemHeight = 80\r\n }\r\n \r\n const scrollViewHeight = systemInfo.windowHeight - systemInfo.statusBarHeight - 44\r\n const targetScrollTop = (foundIndex * itemHeight) - (scrollViewHeight / 2) + (itemHeight / 2)\r\n scrollTop.value = Math.max(0, targetScrollTop)\r\n console.log(`滚动左侧菜单: index=${foundIndex}, target=${scrollTop.value}`)\r\n }\r\n \r\n // 查找分类信息\r\n let foundCategory: LocalCategory | null = null\r\n for (let i = 0; i < primaryCategories.value.length; i++) {\r\n if (primaryCategories.value[i].id == targetParentId) {\r\n foundCategory = primaryCategories.value[i]\r\n break\r\n }\r\n }\r\n if (foundCategory != null) {\r\n currentCategoryName.value = foundCategory.name\r\n currentCategoryDesc.value = foundCategory.description\r\n } else {\r\n console.log('分类信息未找到,使用第一个分类的信息')\r\n if (primaryCategories.value.length > 0) {\r\n const firstCategory = primaryCategories.value[0]\r\n currentCategoryName.value = firstCategory.name\r\n currentCategoryDesc.value = firstCategory.description\r\n }\r\n }\r\n \r\n currentPage.value = 1\r\n hasMore.value = true\r\n \r\n // 如果有选中的二级分类使用二级分类ID加载商品否则使用一级分类ID\r\n const categoryIdForProducts = (targetSubId != '') ? targetSubId : targetParentId\r\n activePrimary.value = categoryIdForProducts // 临时设置为要加载的分类ID\r\n await loadProducts()\r\n}\r\n\r\nasync function loadCategories(): Promise<void> {\r\n try {\r\n // 只获取一级分类parent_id 为 null 的分类)\r\n const categoriesData = await supabaseService.getParentCategories()\r\n console.log('加载一级分类数据成功,数量:', categoriesData.length)\r\n \r\n // 映射数据并添加默认颜色,防止选中时背景透明导致文字看不清\r\n // 过滤掉医药健康相关分类\r\n const categories: LocalCategory[] = []\r\n for (let i = 0; i < categoriesData.length; i++) {\r\n const cat = categoriesData[i]\r\n const name = cat.name\r\n console.log('一级分类:', cat.id, name)\r\n if (name.includes('医药') || name.includes('健康')) {\r\n console.log('过滤掉分类:', name)\r\n continue\r\n }\r\n categories.push({\r\n id: cat.id,\r\n name: cat.name,\r\n icon: cat.icon,\r\n description: cat.description,\r\n color: cat.color\r\n })\r\n }\r\n \r\n console.log('最终一级分类列表数量:', categories.length)\r\n\r\n if (categories.length > 0) {\r\n primaryCategories.value = categories\r\n \r\n // 检查是否有待处理的分类ID从其他页面跳转过来时暂存\r\n if (pendingCategoryId.value != '') {\r\n console.log('发现待处理的分类ID:', pendingCategoryId.value)\r\n // 直接调用 selectPrimaryCategory它会处理一级或二级分类ID\r\n const idToSelect = pendingCategoryId.value\r\n pendingCategoryId.value = '' // 清除暂存\r\n selectPrimaryCategory(idToSelect)\r\n return\r\n }\r\n \r\n // 检查是否有预设的分类ID\r\n if (activePrimary.value != '') {\r\n console.log('有预设的分类ID:', activePrimary.value)\r\n const target = categories.find((c: LocalCategory): boolean => c.id == activePrimary.value)\r\n if (target != null) {\r\n console.log('找到目标分类,执行选中:', target.name)\r\n selectPrimaryCategory(activePrimary.value)\r\n return\r\n }\r\n }\r\n \r\n // 默认选中第一个分类或\"厨具\"分类\r\n const defaultCategory = categories.find((c: LocalCategory): boolean => c.name.includes('厨具')) ?? categories[0]\r\n if (defaultCategory != null) {\r\n console.log('设置默认分类:', defaultCategory.name)\r\n selectPrimaryCategory(defaultCategory.id)\r\n }\r\n } else {\r\n console.warn('从Supabase获取的分类数据为空')\r\n }\r\n } catch (error) {\r\n console.error('加载分类数据失败:', error)\r\n }\r\n}\r\n\r\n// 加载更多\r\nfunction loadMore(): void {\r\n if (hasMore.value && !loading.value) {\r\n currentPage.value++\r\n loadProducts()\r\n }\r\n}\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n\tloadCategories().then(() => {\r\n\t\tsetTimeout(() => {\r\n\t\t\tif (!hasLoadedFromParams.value && activePrimary.value != '') {\r\n\t\t\t\tloadProducts()\r\n\t\t\t}\r\n\t\t}, 300)\r\n\t})\r\n})\r\n\r\n// 页面显示时检查是否有参数传递过来\r\nonShow(() => {\r\n console.log('=== category页面onShow被调用 ===')\r\n \r\n // 检查是否有存储的分类选择\r\n const savedCategoryId = uni.getStorageSync('selectedCategory')\r\n console.log('onShow检查Storage:', savedCategoryId)\r\n \r\n if (savedCategoryId != null && savedCategoryId != '') {\r\n const targetId = savedCategoryId as string\r\n console.log('onShow发现存储的分类ID:', targetId)\r\n \r\n // 清除存储,避免下次进入默认选中\r\n uni.removeStorageSync('selectedCategory')\r\n \r\n // 确保分类数据已加载\r\n if (primaryCategories.value.length > 0) {\r\n // 如果当前未选中或选中的不是目标分类,则切换\r\n if (activePrimary.value != targetId) {\r\n console.log('onShow执行切换分类:', targetId)\r\n selectPrimaryCategory(targetId)\r\n } else {\r\n console.log('当前已是目标分类:', targetId)\r\n }\r\n } else {\r\n // 如果分类数据未加载暂存ID等待loadCategories完成后处理\r\n console.log('分类数据尚未加载暂存ID等待加载')\r\n pendingCategoryId.value = targetId\r\n }\r\n }\r\n})\r\n // 页面加载时处理参数 - 这是处理分类切换的主要入口\r\nonLoad((options: any) => {\r\n\t const systemInfo = uni.getSystemInfoSync()\r\n statusBarHeight.value = systemInfo.statusBarHeight\r\n \r\n // 获取小程序胶囊按钮信息\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n \r\n console.log('=== category页面onLoad被调用 ===')\r\n\t\r\n\tlet categoryId = ''\r\n\tlet categoryName = ''\r\n\t\r\n\t// 首先检查传入的options参数\r\n\tconst optObj = (options instanceof UTSJSONObject) ? (options as UTSJSONObject) : (JSON.parse(JSON.stringify(options ?? {})) as UTSJSONObject)\r\n\tconst optCategoryId = optObj.getString('categoryId') ?? ''\r\n\tif (optCategoryId !== '') {\r\n\t\tcategoryId = optCategoryId\r\n\t\tcategoryName = optObj.getString('name') ?? ''\r\n\t\tconsole.log('✅ onLoad中找到分类参数:', categoryId, categoryName)\r\n\t}\r\n\t\r\n\t// 如果options中没有尝试从getCurrentPages()获取\r\n\tif (categoryId == '') {\r\n\t\tconst pages = getCurrentPages()\r\n\t\tif (pages.length > 0) {\r\n\t\t\tconst currentPage = pages[pages.length - 1]\r\n\t\t\tconst rawPageOptions = currentPage.options ?? {}\r\n\t\t\tconsole.log('从getCurrentPages()获取参数:', rawPageOptions)\r\n\t\t\tconst pageOptObj = (rawPageOptions instanceof UTSJSONObject) ? (rawPageOptions as UTSJSONObject) : (JSON.parse(JSON.stringify(rawPageOptions)) as UTSJSONObject)\r\n\t\t\tconst pageCategoryId = pageOptObj.getString('categoryId') ?? ''\r\n\t\t\tif (pageCategoryId !== '') {\r\n\t\t\t\tcategoryId = pageCategoryId\r\n\t\t\t\tcategoryName = pageOptObj.getString('name') ?? ''\r\n\t\t\t\tconsole.log('✅ 从getCurrentPages()找到分类参数:', categoryId, categoryName)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// 如果有找到分类ID则选中对应的分类\r\n\tif (categoryId != '') {\r\n\t\thasLoadedFromParams.value = true\r\n\t\tconsole.log('✅ 准备选中分类:', categoryId)\r\n\t\tconsole.log('分类名称:', categoryName ?? '未指定')\r\n\t\t\r\n\t\t// 检查是否需要更新分类\r\n\t\tif (activePrimary.value !== categoryId) {\r\n\t\t\tconsole.log('当前分类:', activePrimary.value, '与目标分类:', categoryId, '不同,需要更新')\r\n\t\t\tconsole.log('准备调用selectPrimaryCategory函数...')\r\n\t\t\tselectPrimaryCategory(categoryId)\r\n\t\t} else {\r\n\t\t\tconsole.log('当前分类已经是目标分类,但可能用户想要刷新页面')\r\n\t\t\tconsole.log('当前分类:', activePrimary.value, '目标分类:', categoryId)\r\n\t\t\t// 即使分类相同,也重新加载数据,确保数据是最新的\r\n\t\t\t// 添加一个小的延迟,确保页面完全显示后再更新数据\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tselectPrimaryCategory(categoryId)\r\n\t\t\t}, 100)\r\n\t\t}\r\n\t} else {\r\n\t\tconsole.log('⚠️ onLoad中未找到分类参数将使用从数据库加载的第一个分类')\r\n\t\t// 不再使用硬编码的默认分类loadCategories 会设置第一个分类\r\n\t}\r\n\t\r\n\tconsole.log('=== category页面onLoad执行完成 ===')\r\n})\r\n\r\n\r\n// 添加到购物车\r\nasync function addToCart(product: Product): Promise<void> {\r\n uni.showLoading({ title: '检查商品...' })\r\n try {\r\n const pid = (product.id ?? '').toString()\r\n const merchantId = product.merchant_id ?? ''\r\n if (pid === '') {\r\n uni.hideLoading()\r\n uni.showToast({ title: '商品无效', icon: 'none' })\r\n return\r\n }\r\n \r\n // 检查商品是否有SKU\r\n const skus = await supabaseService.getProductSkus(pid)\r\n uni.hideLoading()\r\n \r\n if (skus.length > 0) {\r\n // 有规格,提示并跳转到商品详情页选择规格\r\n uni.showToast({\r\n title: '请选择规格',\r\n icon: 'none'\r\n })\r\n setTimeout(() => {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/product-detail?id=' + pid\r\n })\r\n }, 500)\r\n } else {\r\n // 无规格,直接加入购物车\r\n uni.showLoading({ title: '添加中...' })\r\n const success = await supabaseService.addToCart(pid, 1, '', merchantId)\r\n uni.hideLoading()\r\n if (success) {\r\n uni.showToast({\r\n title: '已添加到购物车',\r\n icon: 'success'\r\n })\r\n cartCount.value++\r\n } else {\r\n uni.showToast({\r\n title: '添加失败,请先登录',\r\n icon: 'none'\r\n })\r\n }\r\n }\r\n } catch (e) {\r\n console.error('添加到购物车异常', e)\r\n uni.hideLoading()\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n }\r\n}\r\n\r\n// 导航函数\r\nfunction navigateToSearch(): void { uni.navigateTo({ url: '/pages/mall/consumer/search' }) }\r\nfunction navigateToCart(): void { uni.navigateTo({ url: '/pages/main/cart' }) }\r\nfunction navigateToProduct(product: Product): void {\r\n const id = (product.id ?? '').toString()\r\n if (id === '') return\r\n const price = (product.base_price ?? 0).toString()\r\n const originalPrice = (product.market_price ?? '').toString()\r\n const name = encodeURIComponent(product.name ?? '')\r\n const image = encodeURIComponent(product.main_image_url ?? '')\r\n \r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/product-detail?id=${id}&productId=${id}&price=${price}&originalPrice=${originalPrice}&name=${name}&image=${image}`\r\n })\r\n}\r\n\r\n// 相机功能\r\nfunction onCamera(): void {\r\n uni.chooseImage({\r\n count: 1,\r\n sourceType: ['camera'],\r\n success: (res) => {\r\n console.log('相机拍摄成功:', res.tempFilePaths[0])\r\n uni.showToast({\r\n title: '已拍摄,正在识别...',\r\n icon: 'loading'\r\n })\r\n // 这里可以添加后续的识别逻辑\r\n setTimeout(() => {\r\n uni.showToast({\r\n title: '识别成功',\r\n icon: 'success'\r\n })\r\n }, 1000)\r\n },\r\n fail: (err) => {\r\n console.error('相机调用失败:', err)\r\n }\r\n })\r\n}\r\n\r\n// 扫码功能\r\nfunction onScan(): void {\r\n uni.scanCode({\r\n success: (res) => {\r\n console.log('扫码成功:', res)\r\n uni.showToast({\r\n title: '扫码成功: ' + res.result,\r\n icon: 'none'\r\n })\r\n },\r\n fail: (err) => {\r\n console.error('扫码失败:', err)\r\n }\r\n })\r\n}\r\n</script>\r\n\r\n<style>\r\n.category-page {\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n background-color: #f8fafc;\r\n display: flex;\r\n flex-direction: column;\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', sans-serif;\r\n}\r\n/* 搜索栏 */\r\n.search-bar {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n background-color: #ff5000;\r\n z-index: 1000;\r\n box-shadow: 0 2px 12px rgba(255, 80, 0, 0.15);\r\n}\r\n\r\n/* 导航栏占位 */\r\n.navbar-placeholder {\r\n flex-shrink: 0;\r\n}\r\n\r\n/* 搜索栏 */\r\n/* 导航栏搜索框容器内边距调整 */\r\n.search-container {\r\n height: 44px;\r\n padding: 0 16px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: center;\r\n max-width: 1400px;\r\n margin: 0 auto;\r\n width: 100%;\r\n}\r\n\r\n/* 搜索框 hover 效果 */\r\n.search-box:hover {\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n/* 导航栏搜索框容器内边距调整 */\r\n.search-box {\r\n flex: 1;\r\n max-width: 600px;\r\n background: #f0f0f0;\r\n border-radius: 20px;\r\n padding: 0 4px 0 12px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n transition: all 0.3s ease;\r\n width: 100%;\r\n height: 32px;\r\n}\r\n\r\n.search-placeholder {\r\n font-size: 14px;\r\n color: #999;\r\n flex: 1;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.nav-inner-search-text {\r\n font-size: 12px; /* 字体稍微变小 */\r\n color: #ffffff;\r\n font-weight: normal;\r\n}\r\n\r\n.icon {\r\n font-size: 22px;\r\n color: white;\r\n}\r\n\r\n.nav-icon-btn {\r\n padding: 4px 8px 4px 4px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: center;\r\n border-right: 1px solid #ddd;\r\n margin-right: 8px;\r\n}\r\n\r\n.nav-icon {\r\n font-size: 18px;\r\n}\r\n\r\n.nav-camera-btn {\r\n padding: 4px 8px 4px 4px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: center;\r\n border-right-width: 1px;\r\n border-right-style: solid;\r\n border-right-color: #ddd;\r\n border-right: 1px solid #ddd; /* 修复UVUE样式 */\r\n margin-right: 8px;\r\n}\r\n\r\n.nav-camera-icon {\r\n font-size: 20px;\r\n}\r\n\r\n/* 搜索按钮高度微调 */\r\n.nav-inner-search-btn {\r\n padding: 0 12px; /* 减小内边距 */\r\n background-color: #87CEEB; /* 天空蓝 */\r\n border-radius: 16px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: center;\r\n height: 24px; /* 随搜索框高度减小而减小 */\r\n}\r\n\r\n.cart-badge {\r\n position: absolute;\r\n top: -5px;\r\n right: -5px;\r\n background: #FF5722;\r\n color: white;\r\n font-size: 10px;\r\n min-width: 18px;\r\n height: 18px;\r\n border-radius: 9px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 0 4px;\r\n border: 2px solid #ff5000;\r\n}\r\n\r\n/* 分类内容区 */\r\n.category-content {\r\n flex: 1;\r\n height: 0px;\r\n display: flex;\r\n flex-direction: row;\r\n padding: 0 16px;\r\n max-width: 1400px;\r\n margin-left: auto;\r\n margin-right: auto;\r\n width: 100%;\r\n overflow: hidden;\r\n}\r\n\r\n/* 左侧一级分类 */\r\n.primary-category {\r\n width: 63px;\r\n height: 100%;\r\n margin-right: 6px;\r\n background: white;\r\n border-radius: 8px;\r\n padding: 6px 0;\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\r\n flex-shrink: 0;\r\n}\r\n\r\n.primary-item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 12px 4px;\r\n margin: 4px 6px;\r\n border-radius: 8px;\r\n transition: all 0.2s ease;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n.primary-item:hover {\r\n transform: translateY(-2px);\r\n}\r\n\r\n.primary-item.active {\r\n color: white !important;\r\n font-weight: bold;\r\n}\r\n\r\n.primary-icon {\r\n font-size: 20px;\r\n margin-bottom: 4px;\r\n margin-right: 0;\r\n text-align: center;\r\n}\r\n\r\n.primary-name {\r\n font-size: 11px;\r\n line-height: 1.25;\r\n}\r\n\r\n/* 右侧内容区 */\r\n.product-content {\r\n flex: 1;\r\n height: 100%;\r\n padding: 0;\r\n}\r\n\r\n.category-header {\r\n margin-bottom: 16px;\r\n padding: 16px 8px 0 8px;\r\n /* position: sticky; REMOVED for uniapp-x support */\r\n /* top: 0; */\r\n background-color: #f8fafc;\r\n z-index: 10;\r\n}\r\n\r\n.category-title {\r\n font-size: 20px;\r\n font-weight: bold;\r\n color: #333;\r\n margin-bottom: 4px;\r\n}\r\n\r\n.category-desc {\r\n font-size: 14px;\r\n color: #666;\r\n}\r\n\r\n/* 二级分类 */\r\n.sub-category-section {\r\n padding: 8px 8px;\r\n background: white;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.sub-category-list {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: nowrap;\r\n width: 100%;\r\n justify-content: space-between;\r\n}\r\n\r\n.sub-category-item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 8px 4px;\r\n background: #f5f5f5;\r\n border-radius: 8px;\r\n flex: 1;\r\n min-width: 50px;\r\n}\r\n\r\n.sub-category-item.active {\r\n background: #ff5000;\r\n}\r\n\r\n.sub-category-item.active .sub-category-name {\r\n color: white;\r\n}\r\n\r\n.sub-category-icon {\r\n font-size: 16px;\r\n margin-bottom: 4px;\r\n}\r\n\r\n.sub-category-name {\r\n font-size: 11px;\r\n color: #333;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n text-align: center;\r\n width: 100%;\r\n}\r\n\r\n/* 商品网格 */\r\n.product-grid {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n /* grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); REMOVED for uniapp-x support */\r\n /* gap: 20px; removed for compatibility */\r\n padding: 10px; /* add padding to compensate */\r\n width: 100%;\r\n}\r\n\r\n.product-card {\r\n display: flex;\r\n flex-direction: column;\r\n background: #fff;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n width: 48%;\r\n margin-bottom: 12px;\r\n}\r\n\r\n.product-image {\r\n width: 100%;\r\n height: 170px;\r\n border-radius: 8px;\r\n margin-bottom: 8px;\r\n background: #f5f5f5;\r\n}\r\n\r\n.product-name {\r\n font-size: 13px;\r\n color: #333;\r\n margin-bottom: 5px;\r\n line-height: 1.4;\r\n height: 36px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n padding: 0 8px;\r\n}\r\n\r\n.product-bottom {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 0 8px 8px;\r\n}\r\n\r\n.product-price {\r\n font-size: 15px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.product-add-btn {\r\n width: 24px;\r\n height: 24px;\r\n background-color: #ff5000;\r\n border-radius: 12px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.add-icon {\r\n color: #fff;\r\n font-size: 16px;\r\n font-weight: bold;\r\n}\r\n\r\n.product-grid {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n justify-content: space-between;\r\n padding: 8px;\r\n}\r\n\r\n.product-action {\r\n margin-top: 12px;\r\n}\r\n\r\n.cart-btn {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n /* gap: 6px; */\r\n background: #ff5000;\r\n color: white;\r\n padding: 8px 12px;\r\n border-radius: 8px;\r\n font-size: 13px;\r\n font-weight: bold;\r\n /* cursor: pointer; removed for uniapp-x support */\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.cart-btn:hover {\r\n background: #388E3C;\r\n}\r\n\r\n.cart-icon {\r\n font-size: 14px;\r\n margin-right: 6px; /* gap replacement */\r\n}\r\n\r\n/* 加载中状态 */\r\n.loading-state {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 60px 20px;\r\n text-align: center;\r\n}\r\n\r\n.loading-spinner {\r\n width: 40px;\r\n height: 40px;\r\n border: 3px solid #f0f0f0;\r\n border-top-color: #ff5000;\r\n border-radius: 50%;\r\n animation: spin 1s linear infinite;\r\n margin-bottom: 15px;\r\n}\r\n\r\n@keyframes spin {\r\n to {\r\n transform: rotate(360deg);\r\n }\r\n}\r\n\r\n.loading-text {\r\n font-size: 14px;\r\n color: #999;\r\n}\r\n\r\n/* 空状态 */\r\n.empty-state {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 60px 20px;\r\n text-align: center;\r\n}\r\n\r\n.empty-icon {\r\n font-size: 60px;\r\n color: #ff5000;\r\n margin-bottom: 15px;\r\n}\r\n\r\n.empty-text {\r\n font-size: 18px;\r\n color: #333;\r\n font-weight: bold;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.empty-desc {\r\n font-size: 14px;\r\n color: #666;\r\n}\r\n\r\n/* 加载更多 */\r\n.load-more {\r\n text-align: center;\r\n padding: 20px 0;\r\n color: #999;\r\n font-size: 14px;\r\n}\r\n\r\n.load-text {\r\n /* display: inline-block; REMOVED for uniapp-x support */\r\n padding: 8px 16px;\r\n background: #f5f5f5;\r\n border-radius: 20px;\r\n}\r\n\r\n/* ===== 响应式设计 ===== */\r\n\r\n/* 小屏手机 (小于414px) */\r\n@media screen and (max-width: 414px) {\r\n .search-container {\r\n padding: 0 12px;\r\n height: 44px;\r\n }\r\n \r\n .search-box {\r\n padding: 0 4px 0 12px;\r\n margin: 0;\r\n height: 30px;\r\n }\r\n \r\n .category-content {\r\n padding: 0 4px;\r\n }\r\n \r\n .primary-category {\r\n width: 56px;\r\n padding: 4px 0;\r\n margin-right: 4px;\r\n border-radius: 8px;\r\n }\r\n \r\n .primary-item {\r\n margin: 2px 2px;\r\n padding: 4px 2px;\r\n border-radius: 6px;\r\n }\r\n \r\n .primary-icon {\r\n margin-right: 0;\r\n margin-bottom: 2px;\r\n font-size: 16px;\r\n }\r\n \r\n .primary-name {\r\n font-size: 9px;\r\n line-height: 1.1;\r\n }\r\n \r\n .product-grid {\r\n padding: 0 4px 20px 4px;\r\n }\r\n\r\n .product-card {\r\n width: 48%;\r\n margin: 1%;\r\n }\r\n \r\n .product-spec,\r\n .manufacturer,\r\n .original-price,\r\n .sales-info,\r\n .product-badge {\r\n display: none;\r\n }\r\n \r\n .product-info {\r\n padding: 6px;\r\n }\r\n \r\n .product-image {\r\n height: 100px;\r\n }\r\n \r\n .product-name {\r\n font-size: 12px;\r\n height: 32px;\r\n line-height: 1.3;\r\n margin-bottom: 2px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n lines: 2;\r\n }\r\n \r\n .price-section {\r\n margin-bottom: 0;\r\n margin-top: 4px;\r\n justify-content: space-between;\r\n width: 100%;\r\n }\r\n \r\n .price-symbol {\r\n font-size: 10px;\r\n }\r\n \r\n .price-value {\r\n font-size: 14px;\r\n }\r\n \r\n .product-meta {\r\n display: none;\r\n }\r\n \r\n .category-header {\r\n padding: 8px 4px 0 4px;\r\n }\r\n \r\n .category-title {\r\n font-size: 14px;\r\n }\r\n \r\n .category-desc {\r\n font-size: 11px;\r\n }\r\n}\r\n\r\n/* 中屏手机 (415px-768px) */\r\n@media screen and (min-width: 415px) and (max-width: 768px) {\r\n .search-container {\r\n padding: 0 16px;\r\n height: 44px;\r\n }\r\n \r\n .primary-category {\r\n width: 62px;\r\n padding: 6px 0;\r\n margin-right: 6px;\r\n border-radius: 8px;\r\n }\r\n \r\n .primary-item {\r\n margin: 2px 2px;\r\n padding: 5px 2px;\r\n border-radius: 6px;\r\n }\r\n \r\n .primary-icon {\r\n font-size: 18px;\r\n margin-bottom: 3px;\r\n }\r\n \r\n .primary-name {\r\n font-size: 10px;\r\n line-height: 1.1;\r\n }\r\n \r\n .product-card {\r\n width: 46%;\r\n margin: 2%;\r\n }\r\n}\r\n\r\n/* 平板设备 (769px-1024px) */\r\n@media screen and (min-width: 769px) and (max-width: 1024px) {\r\n .search-container {\r\n padding: 0 16px;\r\n height: 44px;\r\n }\r\n \r\n .primary-category {\r\n width: 100px;\r\n padding: 10px 0;\r\n margin-right: 16px;\r\n }\r\n \r\n .primary-item {\r\n margin: 4px 4px;\r\n padding: 10px 6px;\r\n }\r\n \r\n .primary-icon {\r\n font-size: 22px;\r\n margin-bottom: 5px;\r\n }\r\n \r\n .primary-name {\r\n font-size: 12px;\r\n }\r\n \r\n .product-card {\r\n width: 30%;\r\n margin: 1.5%;\r\n }\r\n}\r\n\r\n/* 桌面端 (1025px以上) */\r\n@media screen and (min-width: 1025px) {\r\n .search-container {\r\n padding: 0 16px;\r\n height: 44px;\r\n }\r\n \r\n .category-content {\r\n padding: 0 24px;\r\n }\r\n \r\n .primary-category {\r\n width: 160px;\r\n padding: 16px 0;\r\n margin-right: 30px;\r\n }\r\n \r\n .primary-item {\r\n padding: 16px 20px;\r\n margin: 6px 12px;\r\n border-radius: 10px;\r\n }\r\n \r\n .primary-icon {\r\n font-size: 20px;\r\n min-width: 28px;\r\n }\r\n \r\n .primary-name {\r\n font-size: 15px;\r\n }\r\n \r\n .product-content {\r\n padding: 20px 0;\r\n }\r\n \r\n .category-header {\r\n margin-bottom: 24px;\r\n padding: 0 12px;\r\n }\r\n \r\n .category-title {\r\n font-size: 24px;\r\n }\r\n \r\n .category-desc {\r\n font-size: 15px;\r\n }\r\n \r\n .product-card {\r\n border-radius: 14px;\r\n width: 22%;\r\n margin: 1.5%;\r\n }\r\n \r\n .product-info {\r\n padding: 20px;\r\n }\r\n \r\n .product-name {\r\n font-size: 16px;\r\n }\r\n \r\n .product-spec {\r\n font-size: 14px;\r\n }\r\n \r\n .price-value {\r\n font-size: 22px;\r\n }\r\n}\r\n\r\n/* 大桌面端 (1400px以上) */\r\n@media screen and (min-width: 1400px) {\r\n .category-content {\r\n max-width: 1600px;\r\n padding: 0 32px;\r\n }\r\n \r\n .primary-category {\r\n margin-right: 40px;\r\n }\r\n \r\n .primary-category {\r\n width: 200px;\r\n padding: 20px 0;\r\n }\r\n \r\n .primary-item {\r\n padding: 18px 24px;\r\n margin: 8px 16px;\r\n border-radius: 12px;\r\n }\r\n \r\n .primary-icon {\r\n font-size: 22px;\r\n min-width: 32px;\r\n }\r\n \r\n .primary-name {\r\n font-size: 16px;\r\n }\r\n \r\n .product-content {\r\n padding: 24px 0;\r\n }\r\n \r\n .category-header {\r\n margin-bottom: 28px;\r\n padding: 0 16px;\r\n }\r\n \r\n .category-title {\r\n font-size: 26px;\r\n }\r\n \r\n .category-desc {\r\n font-size: 16px;\r\n }\r\n \r\n .product-card {\r\n border-radius: 16px;\r\n width: 17%;\r\n margin: 1.5%;\r\n }\r\n \r\n .product-image {\r\n height: 180px;\r\n }\r\n \r\n .product-info {\r\n padding: 24px;\r\n }\r\n \r\n .product-name {\r\n font-size: 17px;\r\n }\r\n \r\n .product-spec {\r\n font-size: 15px;\r\n }\r\n \r\n .price-value {\r\n font-size: 24px;\r\n }\r\n}\r\n</style>\r\n\r\n","<!-- pages/main/cart.uvue -->\r\n<template>\r\n\t<view class=\"cart-page\">\r\n\t\t<!-- 智能顶部导航栏 - 与消息页保持一致 -->\r\n\t\t<view class=\"smart-navbar\" :style=\"{ paddingTop: statusBarHeight + 'px' }\">\r\n\t\t\t<view class=\"nav-container\" :style=\"{ paddingRight: (navBarRight > 0 ? navBarRight : 16) + 'px' }\">\r\n\t\t\t\t<text class=\"nav-title\">购物车</text>\r\n\t\t\t\t<view class=\"nav-actions\">\r\n\t\t\t\t\t<view class=\"action-btn\" @click=\"toggleManageMode\">\r\n\t\t\t\t\t\t<text class=\"action-icon\">{{ isManageMode ? '✓' : '⚙️' }}</text>\r\n\t\t\t\t\t\t<text class=\"action-text\">{{ isManageMode ? '完成' : '管理' }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<!-- 导航栏占位符 - 需要包含statusBarHeight + 导航栏高度44px -->\r\n\t\t<view class=\"navbar-placeholder\" :style=\"{ height: (statusBarHeight + 44) + 'px' }\"></view>\r\n\t\t\r\n\t\t<!-- 购物车内容 -->\r\n\t\t<scroll-view \r\n :scroll-y=\"true\" \r\n class=\"cart-content\" \r\n :show-scrollbar=\"false\" \r\n :enhanced=\"true\" \r\n :bounces=\"true\"\r\n >\r\n\t\t\t<!-- 空购物车 -->\r\n\t\t\t<view v-if=\"!loading && cartItems.length === 0\" class=\"empty-cart\">\r\n\t\t\t\t<text class=\"empty-icon\">🛒</text>\r\n\t\t\t\t<text class=\"empty-title\">购物车是空的</text>\r\n\t\t\t\t<text class=\"empty-desc\">快去挑选喜欢的商品吧</text>\r\n\t\t\t\t<button class=\"go-shopping-btn\" @click=\"goShopping\">去逛逛</button>\r\n\t\t\t</view>\r\n\t\t\t\r\n\t\t\t<!-- 购物车商品列表 -->\r\n\t\t\t<view v-else class=\"cart-list\">\r\n\t\t\t\t<view \r\n\t\t\t\t\tv-for=\"group in cartGroups\" \r\n\t\t\t\t\t:key=\"group.shopId\"\r\n\t\t\t\t\tclass=\"shop-group\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<!-- 店铺头部 -->\r\n\t\t\t\t\t<view class=\"shop-header\">\r\n\t\t\t\t\t\t<view class=\"shop-select\" @click=\"toggleShopSelect(group.shopId)\">\r\n\t\t\t\t\t\t\t<text v-if=\"isShopSelected(group.shopId)\" class=\"selected-icon\">✓</text>\r\n\t\t\t\t\t\t\t<text v-else class=\"unselected-icon\"></text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"shop-icon\" @click=\"navigateToShop(group.shopId, group.merchantId)\">🏪</text>\r\n\t\t\t\t\t\t<text class=\"shop-name\" :lines=\"1\" @click=\"navigateToShop(group.shopId, group.merchantId)\">{{ group.shopName }}</text>\r\n\t\t\t\t\t\t<text class=\"shop-arrow\" @click=\"navigateToShop(group.shopId, group.merchantId)\">></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 店铺商品 -->\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tv-for=\"item in group.items\" \r\n\t\t\t\t\t\t:key=\"item.id\"\r\n\t\t\t\t\t\tclass=\"cart-item\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<view class=\"item-select\" @click=\"toggleSelect(item.id)\">\r\n\t\t\t\t\t\t\t<text v-if=\"item.selected\" class=\"selected-icon\">✓</text>\r\n\t\t\t\t\t\t\t<text v-else class=\"unselected-icon\"></text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t<image \r\n\t\t\t\t\t\t\tclass=\"item-image\" \r\n\t\t\t\t\t\t\t:src=\"item.image\" \r\n\t\t\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t\t\t\t@click=\"navigateToProduct(item)\"\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t<view class=\"item-info\">\r\n\t\t\t\t\t\t\t<view class=\"info-top\">\r\n\t\t\t\t\t\t\t\t<text class=\"item-name\" :lines=\"1\">{{ item.name }}</text>\r\n\t\t\t\t\t\t\t\t<text class=\"item-spec\">{{ item.spec }}</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t<view class=\"item-footer\">\r\n\t\t\t\t\t\t\t\t<text class=\"item-price\">¥{{ item.price }}</text>\r\n\t\t\t\t\t\t\t\t<view class=\"quantity-control\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"quantity-btn\" @click=\"decreaseQuantity(item.id)\">-</text>\r\n\t\t\t\t\t\t\t\t\t<text class=\"quantity-value\">{{ item.quantity }}</text>\r\n\t\t\t\t\t\t\t\t\t<text class=\"quantity-btn\" @click=\"increaseQuantity(item.id)\">+</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t\r\n\t\t\t<!-- 购物车操作栏 (移至推荐商品上方) -->\r\n <view v-if=\"cartItems.length > 0\" class=\"cart-action-bar\">\r\n <view class=\"action-bar-content\">\r\n <view class=\"action-left\">\r\n <view class=\"select-all\" @click=\"toggleSelectAll\">\r\n <text v-if=\"allSelected\" class=\"selected-icon\">✓</text>\r\n <text v-else class=\"unselected-icon\"></text>\r\n <text class=\"select-all-text\">全选</text>\r\n </view>\r\n </view>\r\n \r\n <view class=\"action-right\">\r\n <view v-if=\"!isManageMode\" class=\"total-info\">\r\n <text class=\"total-text\">合计:</text>\r\n <text class=\"total-price\">¥{{ totalPrice }}</text>\r\n <text v-if=\"parseFloat(memberSavedAmount) > 0\" class=\"member-saved\">会员已省¥{{ memberSavedAmount }}</text>\r\n </view>\r\n <button v-if=\"!isManageMode\" class=\"checkout-btn\" @click=\"goToCheckout\">\r\n 去结算({{ selectedCount }})\r\n </button>\r\n <button v-else class=\"delete-btn\" @click=\"deleteSelectedItems\">\r\n 删除({{ selectedCount }})\r\n </button>\r\n </view>\r\n </view>\r\n </view>\r\n\t\t\t\r\n\t\t\t<!-- 推荐商品 -->\r\n\t\t\t<view v-if=\"recommendProducts.length > 0\" class=\"recommend-section\">\r\n\t\t\t\t<view class=\"section-header\">\r\n\t\t\t\t\t<text class=\"section-title\">猜你喜欢</text>\r\n\t\t\t\t\t<view class=\"refresh-btn\" @click=\"refreshRecommend\">\r\n\t\t\t\t\t\t<text class=\"refresh-icon\">🔄</text>\r\n\t\t\t\t\t\t<text class=\"refresh-text\">换一批</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"recommend-list\">\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tv-for=\"product in recommendProducts\" \r\n\t\t\t\t\t\t:key=\"product.id\"\r\n\t\t\t\t\t\tclass=\"recommend-item\"\r\n\t\t\t\t\t\t@click=\"navigateToProduct(product)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<image \r\n\t\t\t\t\t\t\tclass=\"recommend-image\" \r\n\t\t\t\t\t\t\t:src=\"product.image\" \r\n\t\t\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t<text class=\"recommend-name\" :lines=\"2\">{{ product.name }}</text>\r\n\t\t\t\t\t\t<view class=\"recommend-bottom\">\r\n\t\t\t\t\t\t\t<text class=\"recommend-price\">¥{{ product.price }}</text>\r\n\t\t\t\t\t\t\t<view class=\"recommend-add-btn\" @click.stop=\"addToCart(product)\">\r\n\t\t\t\t\t\t\t\t<text class=\"recommend-add-icon\">+</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n <!-- 底部占位符:确保内容不被原生 TabBar 遮挡 -->\r\n <view class=\"tabbar-safe-area\"></view>\r\n\t\t</scroll-view>\r\n\t\t\r\n\t\t<!-- 底部结算栏 - 已移除,移动到内容区域 -->\r\n\t\t<!-- <view v-if=\"cartItems.length > 0\" class=\"cart-footer\">\r\n\t\t\t<view class=\"footer-content\">\r\n\t\t\t\t<view class=\"footer-left\">\r\n\t\t\t\t\t<view class=\"select-all\" @click=\"toggleSelectAll\">\r\n\t\t\t\t\t\t<text v-if=\"allSelected\" class=\"selected-icon\">✓</text>\r\n\t\t\t\t\t\t<text v-else class=\"unselected-icon\"></text>\r\n\t\t\t\t\t\t<text class=\"select-all-text\">全选</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<view class=\"footer-right\">\r\n\t\t\t\t\t<view v-if=\"!isManageMode\" class=\"total-info\">\r\n\t\t\t\t\t\t<text class=\"total-text\">合计:</text>\r\n\t\t\t\t\t\t<text class=\"total-price\">¥{{ totalPrice }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<button v-if=\"!isManageMode\" class=\"checkout-btn\" @click=\"goToCheckout\">\r\n\t\t\t\t\t\t去结算({{ selectedCount }})\r\n\t\t\t\t\t</button>\r\n\t\t\t\t\t<button v-else class=\"delete-btn\" @click=\"deleteSelectedItems\">\r\n\t\t\t\t\t\t删除({{ selectedCount }})\r\n\t\t\t\t\t</button>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view> -->\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, computed, onMounted } from 'vue'\r\nimport { onShow } from '@dcloudio/uni-app'\r\nimport { supabaseService, type CartItem as SupabaseCartItem, type Product } from '@/utils/supabaseService.uts'\r\n\r\ntype LocalCartItem = {\r\n id: string\r\n shopId: string\r\n shopName: string\r\n name: string\r\n price: number\r\n originalPrice: number // 原价\r\n memberPrice: number // 会员价\r\n image: string\r\n spec: string\r\n quantity: number\r\n selected: boolean\r\n productId: string\r\n skuId: string\r\n merchantId: string\r\n}\r\n\r\ntype CartGroup = {\r\n\tshopId: string\r\n\tshopName: string\r\n\tmerchantId: string\r\n\titems: LocalCartItem[]\r\n}\r\n\r\nconst compareStrings = (a: string, b: string): boolean => {\r\n\tconsole.log('[compareStrings] a length:', a.length, 'b length:', b.length)\r\n\tconsole.log('[compareStrings] a type:', typeof a, 'b type:', typeof b)\r\n\tconsole.log('[compareStrings] a value:', JSON.stringify(a))\r\n\tconsole.log('[compareStrings] b value:', JSON.stringify(b))\r\n\t\r\n\tif (a.length !== b.length) return false\r\n\tfor (let i = 0; i < a.length; i++) {\r\n\t\tconst aCode = a.charCodeAt(i)\r\n\t\tconst bCode = b.charCodeAt(i)\r\n\t\tif (aCode != null && bCode != null && aCode !== bCode) {\r\n\t\t\tconsole.log('[compareStrings] mismatch at index:', i, 'a:', aCode, 'b:', bCode)\r\n\t\t\treturn false\r\n\t\t}\r\n\t}\r\n\treturn true\r\n}\r\n\r\ntype RecommendProduct = {\r\n\tid: string\r\n\tshopId: string\r\n\tshopName: string\r\n\tname: string\r\n\tprice: number\r\n\timage: string\r\n\tskuId: string\r\n\tmerchant_id: string\r\n}\r\n\r\n// 响应式数据\r\nconst cartItems = ref<LocalCartItem[]>([])\r\nconst recommendProducts = ref<RecommendProduct[]>([])\r\nconst recommendPage = ref<number>(1)\r\nconst loading = ref<boolean>(false)\r\nconst statusBarHeight = ref(0)\r\nconst isManageMode = ref(false)\r\nconst updatingItems = ref<Set<string>>(new Set()) // Track items being updated to prevent race conditions\r\n\r\n// 小程序胶囊按钮信息类型\r\ntype CapsuleButtonInfo = {\r\n\tleft: number,\r\n\ttop: number,\r\n\tright: number,\r\n\tbottom: number,\r\n\twidth: number,\r\n\theight: number\r\n}\r\n\r\n// 小程序胶囊按钮信息\r\nconst capsuleButtonInfo = ref<CapsuleButtonInfo | null>(null)\r\nconst navBarRight = ref(0) // 导航栏右侧预留空间\r\n\r\n// 计算属性\r\nconst cartGroups = computed<CartGroup[]>(() => {\r\n\tconsole.log('[cartGroups] 计算购物车分组, cartItems count:', cartItems.value.length)\r\n\tconst groups = new Map<string, CartGroup>()\r\n\r\n\tcartItems.value.forEach((item: LocalCartItem) => {\r\n\t\tconsole.log('[cartGroups] item:', item.id, 'shopId:', item.shopId, 'shopName:', item.shopName)\r\n\t\tconst shopKey = item.shopId\r\n\t\tif (!groups.has(shopKey)) {\r\n\t\t\tgroups.set(shopKey, {\r\n\t\t\t\tshopId: item.shopId,\r\n\t\t\t\tshopName: item.shopName,\r\n\t\t\t\tmerchantId: item.merchantId,\r\n\t\t\t\titems: []\r\n\t\t\t})\r\n\t\t}\r\n\r\n\t\tconst group = groups.get(shopKey)\r\n\t\tif (group != null) {\r\n\t\t\tgroup.items.push(item)\r\n\t\t}\r\n\t})\r\n\r\n\tconst groupArray: CartGroup[] = []\r\n\tgroups.forEach((value: CartGroup) => {\r\n\t\tconsole.log('[cartGroups] group:', value.shopId, 'items count:', value.items.length)\r\n\t\tgroupArray.push(value)\r\n\t})\r\n\treturn groupArray\r\n})\r\n\r\nconst allSelected = computed(() => {\r\n\treturn cartItems.value.length > 0 && cartItems.value.every((item: LocalCartItem) => item.selected)\r\n})\r\n\r\nconst selectedCount = computed(() => {\r\n\treturn cartItems.value.filter((item: LocalCartItem) => item.selected).reduce((sum: number, item: LocalCartItem) => sum + item.quantity, 0)\r\n})\r\n\r\nconst totalPrice = computed(() => {\r\n\treturn cartItems.value\r\n\t\t.filter((item: LocalCartItem) => item.selected)\r\n\t\t.reduce((sum: number, item: LocalCartItem) => {\r\n\t\t\t// 优先使用会员价,如果没有会员价则使用原价\r\n\t\t\tconst finalPrice = item.memberPrice > 0 && item.memberPrice < item.price ? item.memberPrice : item.price\r\n\t\t\treturn sum + finalPrice * item.quantity\r\n\t\t}, 0)\r\n\t\t.toFixed(2)\r\n})\r\n\r\n// 计算会员节省金额\r\nconst memberSavedAmount = computed(() => {\r\n\treturn cartItems.value\r\n\t\t.filter((item: LocalCartItem) => item.selected && item.memberPrice > 0 && item.memberPrice < item.price)\r\n\t\t.reduce((sum: number, item: LocalCartItem) => sum + (item.price - item.memberPrice) * item.quantity, 0)\r\n\t\t.toFixed(2)\r\n})\r\n\r\n// 检查店铺是否全选\r\nconst isShopSelected = (shopId: string): boolean => {\r\n\tconst shopItems: LocalCartItem[] = []\r\n\tfor (let i = 0; i < cartItems.value.length; i++) {\r\n\t\tif (compareStrings(cartItems.value[i].shopId, shopId)) {\r\n\t\t\tshopItems.push(cartItems.value[i])\r\n\t\t}\r\n\t}\r\n\tif (shopItems.length === 0) return false\r\n\tfor (let i = 0; i < shopItems.length; i++) {\r\n\t\tif (!shopItems[i].selected) return false\r\n\t}\r\n\treturn true\r\n}\r\n\r\nconst toggleManageMode = () => {\r\n\tisManageMode.value = !isManageMode.value\r\n}\r\n\r\n// 初始化页面数据\r\nconst initPage = () => {\r\n\tconst systemInfo = uni.getSystemInfoSync()\r\n\tstatusBarHeight.value = systemInfo.statusBarHeight ?? 0\r\n\t\r\n\t// 获取小程序胶囊按钮信息\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n}\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n\tinitPage()\r\n})\r\n\r\n// 提取更新列表的辅助函数以减少重复代码\r\nconst updateRecommendList = (recommends: Product[]) => {\r\n recommendProducts.value = recommends.map((p: Product): RecommendProduct => {\r\n return {\r\n id: p.id,\r\n shopId: p.merchant_id ?? 'unknown',\r\n shopName: p.shop_name ?? '商城推荐',\r\n name: p.name,\r\n price: p.base_price ?? p.market_price ?? 0,\r\n image: p.main_image_url ?? p.image_url ?? '/static/images/default-product.png',\r\n skuId: '',\r\n merchant_id: p.merchant_id ?? ''\r\n }\r\n })\r\n}\r\n\r\nconst refreshRecommend = async () => {\r\n try {\r\n // 1. 模拟市面加载感,锁定按钮防止连续快速点击\r\n if (loading.value) return\r\n loading.value = true\r\n \r\n uni.showLoading({\r\n title: '正在挑选...',\r\n mask: true\r\n })\r\n \r\n // 2. 模拟市面“随机性”逻辑:\r\n // 淘宝京东不会按顺序翻页,而是跳跃选取页码,并打乱排序规则\r\n const maxOffsetPages = 20 // 假设数据库中至少有 20 页热推商品\r\n const sorts = ['sales', 'price_asc', 'rating']\r\n \r\n // 随机页码 + 随机排序 = 每次点击都有新发现\r\n const nextRandomPage = Math.floor(Math.random() * maxOffsetPages) + 1\r\n const randomSort = sorts[Math.floor(Math.random() * sorts.length)]\r\n \r\n console.log(`[refreshRecommend] 换一批: 随机页=${nextRandomPage}, 随机排=${randomSort}`)\r\n \r\n const hotResp = await supabaseService.searchProducts('', nextRandomPage, 6, randomSort)\r\n let recommends = hotResp.data\r\n \r\n // 3. 兜底逻辑:如果随机到的页码没数据,回退到第 1 页\r\n if (recommends.length === 0) {\r\n const fallbackResp = await supabaseService.searchProducts('', 1, 6, 'sales')\r\n recommends = fallbackResp.data\r\n }\r\n \r\n // 4. 前端打乱 (Shuffle):即使是同一页数据,乱序排布也会增加“新鲜感”\r\n if (recommends.length > 0) {\r\n recommends.sort(() => Math.random() - 0.5)\r\n updateRecommendList(recommends)\r\n \r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '已为你换一批好物',\r\n icon: 'none',\r\n duration: 1000\r\n })\r\n } else {\r\n uni.hideLoading()\r\n }\r\n } catch (error) {\r\n uni.hideLoading()\r\n console.error('刷新推荐失败:', error)\r\n uni.showToast({ title: '加载失败,请重试', icon: 'none' })\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n\r\n// 加载数据\r\nconst loadCartData = async () => {\r\n\tloading.value = true\r\n\t\r\n\ttry {\r\n\t\t// 获取会员折扣信息\r\n\t\tlet memberDiscount = 1.0\r\n\t\ttry {\r\n\t\t\tconst memberInfo = await supabaseService.getUserMemberInfo()\r\n\t\t\tconst discountRaw = memberInfo.get('discount')\r\n\t\t\tif (discountRaw != null) {\r\n\t\t\t\tmemberDiscount = discountRaw as number\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\tconsole.log('获取会员信息失败,使用默认折扣:', e)\r\n\t\t}\r\n\t\t\r\n\t\t// 从Supabase加载购物车数据\r\n\t\tconst supabaseCartItems = await supabaseService.getCartItems()\r\n\t\t\r\n\t\t// 转换数据格式以匹配前端界面\r\n\t\tconst transformedItems = supabaseCartItems.map((item: SupabaseCartItem): LocalCartItem => {\r\n\t\t\t// 调试日志:打印每条商品数据的关键字段\r\n\t\t\tconsole.log(`CartItem raw: id=${item.id}, shop_id=${item.shop_id}, shop_name=${item.shop_name}, name=${item.product_name}, price=${item.product_price}`);\r\n\t\t\t\r\n\t\t\t// 关键修复确保shopId有值如果后端返回null/undefined使用'default_shop'作为分组键\r\n\t\t\tconst shopId = (item.shop_id != null && item.shop_id !== '') ? item.shop_id : 'default_shop'\r\n\t\t\tconst shopName = (item.shop_name != null && item.shop_name !== '') ? item.shop_name : '商城优选'\r\n\t\t\t\r\n\t\t\t// 计算会员价\r\n\t\t\tconst originalPrice = item.product_price != null ? item.product_price : 0\r\n\t\t\tlet memberPrice = 0\r\n\t\t\tif (memberDiscount > 0 && memberDiscount < 1 && originalPrice > 0) {\r\n\t\t\t\tmemberPrice = Math.round(originalPrice * memberDiscount * 100) / 100\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn {\r\n\t\t\t\tid: item.id,\r\n\t\t\t\tshopId: shopId,\r\n\t\t\t\tshopName: shopName,\r\n\t\t\t\tname: item.product_name ?? '未知商品',\r\n\t\t\t\tprice: originalPrice,\r\n\t\t\t\toriginalPrice: originalPrice,\r\n\t\t\t\tmemberPrice: memberPrice,\r\n\t\t\t\timage: item.product_image ?? '/static/images/default-product.png',\r\n\t\t\t\tspec: item.product_specification ?? '标准规格',\r\n\t\t\t\tquantity: item.quantity ?? 1,\r\n\t\t\t\tselected: item.selected ?? false,\r\n\t\t\t\tproductId: item.product_id ?? '',\r\n\t\t\t\tskuId: item.sku_id ?? '',\r\n\t\t\t\tmerchantId: item.merchant_id ?? ''\r\n\t\t\t} as LocalCartItem\r\n\t\t})\r\n\t\t\r\n\t\tconsole.log('Transformed items count:', transformedItems.length);\r\n\t\tcartItems.value = transformedItems\r\n\t\t\r\n\t\t// 加载推荐商品(优先获取推荐位商品,如果没有则通过搜索获取热销商品)\r\n let recommends = await supabaseService.getRecommendedProducts(6)\r\n \r\n // 如果没有设置推荐商品,则获取热销商品作为补充\r\n if (recommends.length === 0) {\r\n const hotResp = await supabaseService.searchProducts('', 1, 6, 'sales')\r\n recommends = hotResp.data\r\n }\r\n\r\n if (recommends.length > 0) {\r\n recommendProducts.value = recommends.map((p: Product): RecommendProduct => {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tid: p.id,\r\n\t\t\t\t\tshopId: p.merchant_id ?? 'unknown',\r\n\t\t\t\t\tshopName: p.shop_name ?? '商城推荐',\r\n\t\t\t\t\tname: p.name,\r\n\t\t\t\t\tprice: p.base_price ?? p.market_price ?? 0,\r\n\t\t\t\t\timage: p.main_image_url ?? p.image_url ?? '/static/images/default-product.png',\r\n\t\t\t\t\tskuId: '',\r\n\t\t\t\t\tmerchant_id: p.merchant_id ?? ''\r\n\t\t\t\t}\r\n\t\t\t})\r\n } else {\r\n recommendProducts.value = []\r\n }\r\n\t} catch (error) {\r\n\t\tconsole.error('加载购物车数据失败:', error)\r\n\t\tcartItems.value = []\r\n\t} finally {\r\n\t\tloading.value = false\r\n\t}\r\n}\r\n\r\nonShow(() => {\r\n\tloadCartData()\r\n})\r\n\r\n// 商品操作 - 更新选中状态到Supabase\r\nconst toggleSelect = async (itemId: string) => {\r\n // 乐观更新\r\n\tconst index = cartItems.value.findIndex(item => item.id === itemId)\r\n\tif (index !== -1) {\r\n\t\tconst newSelected = !cartItems.value[index].selected\r\n\t\tcartItems.value[index].selected = newSelected\r\n\t\tcartItems.value = [...cartItems.value] // 触发响应式更新\r\n\t\t\r\n\t\t// 更新到Supabase\r\n\t\tconst success = await supabaseService.updateCartItemSelection(itemId, newSelected)\r\n\t\tif (!success) {\r\n\t\t\tconsole.error('更新选中状态失败')\r\n\t\t\t// 恢复状态\r\n\t\t\tcartItems.value[index].selected = !newSelected\r\n\t\t\tcartItems.value = [...cartItems.value]\r\n uni.showToast({ title: '网络异常,请重试', icon: 'none' })\r\n\t\t}\r\n\t}\r\n}\r\n\r\nconst toggleShopSelect = async (shopId: string) => {\r\n\tconsole.log('[toggleShopSelect] shopId:', shopId)\r\n\tconsole.log('[toggleShopSelect] shopId length:', shopId.length)\r\n\tconsole.log('[toggleShopSelect] cartItems.value.length:', cartItems.value.length)\r\n\t\r\n\t// 用 for 循环替代 filter避免安卓端 UTS filter 的问题\r\n\tconst shopItems: LocalCartItem[] = []\r\n\tfor (let i = 0; i < cartItems.value.length; i++) {\r\n\t\tconst item = cartItems.value[i]\r\n\t\tconst itemShopId = item.shopId\r\n\t\t// 安卓端字符串比较问题:使用 localeCompare 或逐字符比较\r\n\t\tconst isMatch = compareStrings(itemShopId, shopId)\r\n\t\tconsole.log('[toggleShopSelect] checking item:', item.id, 'item.shopId:', itemShopId, 'match:', isMatch)\r\n\t\tif (isMatch) {\r\n\t\t\tshopItems.push(item)\r\n\t\t}\r\n\t}\r\n\tconsole.log('[toggleShopSelect] shopItems count:', shopItems.length)\r\n\t\r\n\tif (shopItems.length === 0) return\r\n\t\r\n\t// 用 for 循环替代 every\r\n\tlet allSelected = true\r\n\tfor (let i = 0; i < shopItems.length; i++) {\r\n\t\tif (!shopItems[i].selected) {\r\n\t\t\tallSelected = false\r\n\t\t\tbreak\r\n\t\t}\r\n\t}\r\n\tconst newState = !allSelected\r\n\tconsole.log('[toggleShopSelect] allSelected:', allSelected, 'newState:', newState)\r\n\t\r\n\tconst shopItemIds: string[] = []\r\n\tfor (let i = 0; i < shopItems.length; i++) {\r\n\t\tshopItemIds.push(shopItems[i].id)\r\n\t}\r\n\tconsole.log('[toggleShopSelect] shopItemIds:', shopItemIds)\r\n\t\r\n\t// 创建全新的数组来触发响应式更新\r\n\tconst newCartItems: LocalCartItem[] = []\r\n\tfor (let i = 0; i < cartItems.value.length; i++) {\r\n\t\tconst item = cartItems.value[i]\r\n\t\tconst isMatch = compareStrings(item.shopId, shopId)\r\n\t\tif (isMatch) {\r\n\t\t\tconsole.log('[toggleShopSelect] updating item:', item.id, 'to selected:', newState)\r\n\t\t\t// 创建新的对象\r\n\t\t\tconst newItem: LocalCartItem = {\r\n\t\t\t\tid: item.id,\r\n\t\t\t\tshopId: item.shopId,\r\n\t\t\t\tshopName: item.shopName,\r\n\t\t\t\tname: item.name,\r\n\t\t\t\tprice: item.price,\r\n\t\t\t\toriginalPrice: item.originalPrice,\r\n\t\t\t\tmemberPrice: item.memberPrice,\r\n\t\t\t\timage: item.image,\r\n\t\t\t\tspec: item.spec,\r\n\t\t\t\tquantity: item.quantity,\r\n\t\t\t\tselected: newState,\r\n\t\t\t\tproductId: item.productId,\r\n\t\t\t\tskuId: item.skuId,\r\n\t\t\t\tmerchantId: item.merchantId\r\n\t\t\t}\r\n\t\t\tnewCartItems.push(newItem)\r\n\t\t} else {\r\n\t\t\tnewCartItems.push(item)\r\n\t\t}\r\n\t}\r\n\t// 替换整个数组\r\n\tcartItems.value = newCartItems\r\n\r\n\t// 批量更新到Supabase\r\n\tconst success = await supabaseService.batchUpdateCartItemSelection(shopItemIds, newState)\r\n\t\r\n\tif (!success) {\r\n\t\tconsole.error('批量更新店铺商品选中状态失败')\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '操作失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\t// 重新加载数据以确保状态一致\r\n\t\tloadCartData()\r\n\t}\r\n}\r\n\r\nconst toggleSelectAll = async () => {\r\n // 目标状态:如果当前全选,则取消全选;否则全选\r\n\tconst newSelectedState = !allSelected.value\r\n \r\n // 乐观更新\r\n\tconst oldItems = JSON.parse(JSON.stringify(cartItems.value)) as LocalCartItem[]\r\n\tconst selectedItems = cartItems.value.map((item): LocalCartItem => {\r\n item.selected = newSelectedState\r\n return item\r\n })\r\n cartItems.value = selectedItems\r\n\t\r\n\t// 更新到Supabase\r\n\tconst itemIds = cartItems.value.map(item => item.id)\r\n if (itemIds.length === 0) return\r\n\r\n\tconst success = await supabaseService.batchUpdateCartItemSelection(itemIds, newSelectedState)\r\n\t\r\n\tif (!success) {\r\n\t\tconsole.error('批量更新选中状态失败')\r\n\t\tcartItems.value = oldItems\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '操作失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t}\r\n}\r\n\r\nconst increaseQuantity = async (itemId: string) => {\r\n if (updatingItems.value.has(itemId)) return\r\n \r\n\tconst index = cartItems.value.findIndex(item => item.id === itemId)\r\n\tif (index !== -1) {\r\n updatingItems.value.add(itemId)\r\n\t\tconst newQuantity = cartItems.value[index].quantity + 1\r\n\t\tcartItems.value[index].quantity = newQuantity\r\n\t\tcartItems.value = [...cartItems.value]\r\n\t\t\r\n\t\t// 更新到Supabase\r\n\t\tconst success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)\r\n updatingItems.value.delete(itemId)\r\n\r\n\t\tif (!success) {\r\n\t\t\tconsole.error('更新商品数量失败')\r\n\t\t\t// 恢复状态\r\n\t\t\tcartItems.value[index].quantity = newQuantity - 1\r\n\t\t\tcartItems.value = [...cartItems.value]\r\n uni.showToast({ title: '更新失败', icon: 'none' })\r\n\t\t}\r\n\t}\r\n}\r\n\r\nconst decreaseQuantity = async (itemId: string) => {\r\n if (updatingItems.value.has(itemId)) return\r\n\r\n\tconst index = cartItems.value.findIndex(item => item.id === itemId)\r\n\tif (index !== -1) {\r\n\t\tif (cartItems.value[index].quantity > 1) {\r\n updatingItems.value.add(itemId)\r\n\t\t\tconst newQuantity = cartItems.value[index].quantity - 1\r\n\t\t\tcartItems.value[index].quantity = newQuantity\r\n\t\t\tcartItems.value = [...cartItems.value]\r\n\t\t\t\r\n\t\t\t// 更新到Supabase\r\n\t\t\tconst success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)\r\n updatingItems.value.delete(itemId)\r\n\r\n\t\t\tif (!success) {\r\n\t\t\t\tconsole.error('更新商品数量失败')\r\n\t\t\t\t// 恢复状态\r\n\t\t\t\tcartItems.value[index].quantity = newQuantity + 1\r\n\t\t\t\tcartItems.value = [...cartItems.value]\r\n uni.showToast({ title: '更新失败', icon: 'none' })\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\t// 数量为1时询问是否删除\r\n\t\t\tuni.showModal({\r\n\t\t\t\ttitle: '提示',\r\n\t\t\t\tcontent: '确定要从购物车移除该商品吗?',\r\n\t\t\t\tsuccess: (res) => {\r\n\t\t\t\t\tif (res.confirm) {\r\n\t\t\t\t\t\t// 从Supabase删除\r\n\t\t\t\t\t\tsupabaseService.deleteCartItem(itemId).then((success) => {\r\n\t\t\t\t\t\t\tif (success) {\r\n\t\t\t\t\t\t\t\tcartItems.value.splice(index, 1)\r\n\t\t\t\t\t\t\t\tcartItems.value = [...cartItems.value]\r\n\t\t\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\t\t\ttitle: '已移除',\r\n\t\t\t\t\t\t\t\t\ticon: 'none'\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tconsole.error('删除商品失败')\r\n\t\t\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\t\t\ttitle: '删除失败',\r\n\t\t\t\t\t\t\t\t\ticon: 'none'\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// 删除商品 - 增加保存逻辑\r\nconst deleteSelectedItems = async () => {\r\n\tif (selectedCount.value === 0) {\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '请选择要删除的商品',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\treturn\r\n\t}\r\n\t\r\n\tuni.showModal({\r\n\t\ttitle: '提示',\r\n\t\tcontent: `确定要删除选中的 ${selectedCount.value} 件商品吗?`,\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\t// 获取选中的商品ID\r\n\t\t\t\tconst selectedItemIds = cartItems.value\r\n\t\t\t\t\t.filter(item => item.selected)\r\n\t\t\t\t\t.map(item => item.id)\r\n\t\t\t\t\r\n\t\t\t\t// 批量删除到Supabase\r\n\t\t\t\tsupabaseService.batchDeleteCartItems(selectedItemIds).then((success) => {\r\n\t\t\t\t\tif (success) {\r\n\t\t\t\t\t\t// 从本地列表移除\r\n\t\t\t\t\t\tcartItems.value = cartItems.value.filter(item => !item.selected)\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// 如果购物车删空了,退出管理模式\r\n\t\t\t\t\t\tif (cartItems.value.length === 0) {\r\n\t\t\t\t\t\t\tisManageMode.value = false\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\ttitle: '删除成功',\r\n\t\t\t\t\t\t\ticon: 'success'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tconsole.error('批量删除商品失败')\r\n\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\ttitle: '删除失败',\r\n\t\t\t\t\t\t\ticon: 'none'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\nconst addToCart = async (product: RecommendProduct) => {\r\n\tuni.showLoading({ title: '检查商品...' })\r\n\ttry {\r\n\t\tconst productId = product.id\r\n\t\tconst skuId = product.skuId\r\n\t\tconst merchantId = product.merchant_id\r\n\t\t\r\n\t\t// 检查商品是否有SKU\r\n\t\tconst skus = await supabaseService.getProductSkus(productId)\r\n\t\tuni.hideLoading()\r\n\t\t\r\n\t\tif (skus.length > 0) {\r\n\t\t\t// 有规格,提示并跳转到商品详情页选择规格\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '请选择规格',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tuni.navigateTo({\r\n\t\t\t\t\turl: '/pages/mall/consumer/product-detail?id=' + productId\r\n\t\t\t\t})\r\n\t\t\t}, 500)\r\n\t\t} else {\r\n\t\t\t// 无规格,直接加入购物车\r\n\t\t\tuni.showLoading({ title: '添加中...' })\r\n\t\t\tconst success = await supabaseService.addToCart(productId, 1, skuId, merchantId)\r\n\t\t\tuni.hideLoading()\r\n\t\t\tif (success) {\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '已添加到购物车',\r\n\t\t\t\t\ticon: 'success'\r\n\t\t\t\t})\r\n\t\t\t\t\r\n\t\t\t\t// 重新加载购物车数据\r\n\t\t\t\tloadCartData()\r\n\t\t\t} else {\r\n\t\t\t\tconsole.error('添加商品到购物车失败')\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '添加失败',\r\n\t\t\t\t\ticon: 'none'\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t} catch (error) {\r\n\t\tconsole.error('添加商品到购物车异常:', error)\r\n\t\tuni.hideLoading()\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '添加失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t}\r\n}\r\n\r\n// 导航函数\r\nconst navigateToShop = (shopId: string, merchantId: any) => {\r\n // Prevent navigation for invalid shops\r\n if (shopId == '' || shopId === 'default_shop' || shopId === 'unknown') return\r\n \r\n\tlet url = `/pages/mall/consumer/shop-detail?id=${shopId}`\r\n\tif (merchantId != null) {\r\n const mId = `${merchantId}`\r\n if (mId !== '' && mId !== 'null' && mId !== 'undefined' && mId !== 'false') {\r\n url += `&merchantId=${mId}`\r\n }\r\n\t}\r\n\tuni.navigateTo({ url })\r\n}\r\n\r\nconst goShopping = () => {\r\n\tuni.switchTab({ url: '/pages/main/index' })\r\n}\r\n\r\nconst navigateToProduct = (product: any) => {\r\n\tconsole.log('navigateToProduct', product)\r\n\t\r\n\t// 使用 JSON 转换确保可以作为 JSONObject 处理,兼容 LocalCartItem 类型和普通对象\r\n\tconst productJson = JSON.parse(JSON.stringify(product)) as UTSJSONObject\r\n\t\r\n\t// 使用productId如果存在作为跳转的商品ID否则使用id\r\n\tlet productId = productJson.getString('productId')\r\n\tif (productId == null || productId == '') {\r\n\t\tproductId = productJson.getString('id')\r\n\t}\r\n\t\r\n\tif (productId == null || productId == '') {\r\n\t\tconsole.error('无法获取商品ID', product)\r\n\t\treturn\r\n\t}\r\n\r\n\t// 传递完整的参数,确保商品详情页能正确加载\r\n\tlet paramsArr: string[] = []\r\n\tparamsArr.push('id=' + encodeURIComponent(productId))\r\n\tparamsArr.push('productId=' + encodeURIComponent(productId))\r\n\t\r\n\tconst price = productJson.getNumber('price') ?? 0\r\n\tparamsArr.push('price=' + price)\r\n\t\r\n\tlet originalPrice = productJson.getNumber('original_price')\r\n\tif (originalPrice == null) {\r\n\t\toriginalPrice = productJson.getNumber('originalPrice')\r\n\t}\r\n\tif (originalPrice == null) {\r\n\t\toriginalPrice = parseFloat((price * 1.2).toFixed(2))\r\n\t}\r\n\tparamsArr.push('originalPrice=' + originalPrice)\r\n\r\n\tconst name = productJson.getString('name') ?? ''\r\n\tparamsArr.push('name=' + encodeURIComponent(name))\r\n\t\r\n\tconst image = productJson.getString('image') ?? '/static/images/default-product.png'\r\n\tparamsArr.push('image=' + encodeURIComponent(image))\r\n\t\r\n\tconst url = `/pages/mall/consumer/product-detail?${paramsArr.join('&')}`\r\n\tconsole.log('Navigate to:', url)\r\n\t\r\n\tuni.navigateTo({ \r\n\t\turl: url\r\n\t})\r\n}\r\n\r\nconst goToCheckout = () => {\r\n\tif (selectedCount.value === 0) {\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '请选择商品',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\treturn\r\n\t}\r\n\t\r\n\t// 获取选中的商品 (直接过滤cartItems不依赖cartGroups确保扁平化传递)\r\n\tconst selectedItems = cartItems.value\r\n\t\t.filter(item => item.selected)\r\n\t\t.map(item => ({\r\n\t\t\tid: item.id,\r\n\t\t\tproduct_id: item.productId ?? item.id,\r\n\t\t\tsku_id: item.skuId ?? item.id,\r\n\t\t\tproduct_name: item.name,\r\n shop_id: item.shopId, // 关键保留shopId用于分组\r\n shop_name: item.shopName, // 关键保留shopName\r\n\t\t\tmerchant_id: item.merchantId,\r\n\t\t\tproduct_image: item.image,\r\n\t\t\tsku_specifications: item.spec,\r\n\t\t\tprice: item.price, // 确保是数字\r\n\t\t\tquantity: item.quantity // 确保是数字\r\n\t\t}))\r\n\r\n // 关键修复:将结算数据写入 Storage确保 checkout 页面能稳定获取\r\n uni.setStorageSync('checkout_type', 'cart')\r\n // 使用纯JSON序列化防止复杂对象引发的问题\r\n try {\r\n uni.setStorageSync('checkout_items', JSON.stringify(selectedItems))\r\n } catch (e) {\r\n console.error('存储结算数据失败', e)\r\n uni.showToast({ title: '系统异常,请重试', icon: 'none' })\r\n return\r\n }\r\n\r\n\t// 跳转到结算页面并传递数据\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/checkout'\r\n\t})\r\n}\r\n</script>\r\n\r\n<style>\r\n.cart-page {\r\n\tflex: 1; /* 使用 Flex 撑满 */\r\n height: 100%; /* 兼容性考虑,部分环境需要 */\r\n\tbackground-color: #f5f5f5;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n overflow: hidden; /* 防止整页滚动 */\r\n}\r\n\r\n/* 智能导航栏 */\r\n\t.smart-navbar {\r\n\t\tposition: fixed;\r\n\t\ttop: 0;\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\tbackground-color: #ff5000;\r\n\t\tz-index: 1000;\r\n\t\tbox-shadow: 0 2px 12px rgba(255, 80, 0, 0.15);\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\tjustify-content: flex-start;\r\n\t\tflex-shrink: 0;\r\n\t}\r\n\r\n.nav-container {\r\n\tpadding: 0 16px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n\twidth: 100%;\r\n\tmax-width: 1400px;\r\n\tmargin: 0 auto;\r\n\theight: 44px; /* 统一高度 44px */\r\n}\r\n\r\n.nav-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: white;\r\n}\r\n\r\n.nav-actions {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n}\r\n\r\n.action-btn {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tbackground: rgba(255, 255, 255, 0.2);\r\n\tpadding: 4px 12px;\r\n\tborder-radius: 20px;\r\n\t/* cursor: pointer; REMOVED */\r\n\ttransition: all 0.2s ease;\r\n}\r\n\r\n.action-btn:hover {\r\n\tbackground: rgba(255, 255, 255, 0.3);\r\n}\r\n\r\n.action-icon {\r\n\tfont-size: 14px;\r\n\tmargin-right: 4px;\r\n\tcolor: white;\r\n}\r\n\r\n.action-text {\r\n\tfont-size: 12px;\r\n\tcolor: white;\r\n\tfont-weight: bold;\r\n}\r\n\r\n/* 导航栏占位符 */\r\n.navbar-placeholder {\r\n\twidth: 100%;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n/* 内容区 */\r\n.cart-content {\r\n\tflex: 1;\r\n /* 必须设置 height: 0 或 overflow: hidden 可以在 flex 容器中正确收缩 */\r\n height: 0px; \r\n width: 100%;\r\n\tpadding-bottom: 20px; /* 减小内边距,因为结算栏已在内容流中 */\r\n}\r\n\r\n/* 购物车操作栏 (移至推荐商品上方) */\r\n.cart-action-bar {\r\n background-color: white;\r\n margin: 10px;\r\n border-radius: 12px;\r\n box-shadow: 0 2px 10px rgba(0,0,0,0.05);\r\n z-index: 10;\r\n}\r\n\r\n.action-bar-content {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 12px 15px;\r\n}\r\n\r\n.action-left {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.action-right {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.select-all {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.select-all-text {\r\n font-size: 14px;\r\n color: #333;\r\n margin-left: 8px;\r\n}\r\n\r\n.total-info {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: baseline;\r\n margin-right: 15px;\r\n}\r\n\r\n.total-text {\r\n font-size: 14px;\r\n color: #666;\r\n}\r\n\r\n.total-price {\r\n font-size: 18px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n margin-left: 4px;\r\n}\r\n\r\n.checkout-btn {\r\n background-color: #ff5000;\r\n color: white;\r\n border: none;\r\n border-radius: 20px;\r\n padding: 8px 15px;\r\n font-size: 14px;\r\n font-weight: bold;\r\n min-width: 100px;\r\n}\r\n\r\n.delete-btn {\r\n background-color: #fff;\r\n color: #ff5000;\r\n border: 1px solid #ff5000;\r\n border-radius: 20px;\r\n padding: 8px 15px;\r\n font-size: 14px;\r\n font-weight: bold;\r\n min-width: 100px;\r\n}\r\n\r\n/* 空购物车 */\r\n.empty-cart {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpadding: 60px 20px;\r\n\ttext-align: center;\r\n}\r\n\r\n.empty-icon {\r\n\tfont-size: 80px;\r\n\tcolor: #ddd;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.empty-title {\r\n\tfont-size: 18px;\r\n\tcolor: #666;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.empty-desc {\r\n\tfont-size: 14px;\r\n\tcolor: #999;\r\n\tmargin-bottom: 30px;\r\n}\r\n\r\n.go-shopping-btn {\r\n\tbackground-color: #ff5000;\r\n\tcolor: white;\r\n\tborder: none;\r\n\tborder-radius: 25px;\r\n\tpadding: 10px 40px;\r\n\tfont-size: 16px;\r\n}\r\n\r\n/* 购物车商品列表 */\r\n.cart-list {\r\n\tbackground-color: transparent; /* 背景透明,因为每个店铺有自己的卡片 */\r\n\tmargin: 10px;\r\n\tborder-radius: 0;\r\n\toverflow: visible;\r\n}\r\n\r\n.shop-group {\r\n\tbackground-color: white;\r\n\tborder-radius: 12px;\r\n\tmargin-bottom: 12px;\r\n\toverflow: hidden;\r\n}\r\n\r\n.shop-header {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* 强制横向排列 */\r\n\talign-items: center;\r\n\tjustify-content: flex-start; /* 靠左对齐 */\r\n\tpadding: 12px;\r\n\tborder-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.shop-select {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin-right: 8px;\r\n\tflex-shrink: 0; /* 防止被压缩 */\r\n}\r\n\r\n.shop-icon {\r\n\tfont-size: 16px;\r\n\tmargin-right: 6px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.shop-name {\r\n\tfont-size: 14px;\r\n\tfont-weight: 700;\r\n\tcolor: #333;\r\n\tmargin-right: 4px;\r\n\t/* 自适应宽度,但不超过剩余空间 */\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\twhite-space: nowrap;\r\n}\r\n\r\n.shop-arrow {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.cart-item {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* 显式横向排列 */\r\n\tpadding: 12px; /* 减小内边距 */\r\n\tborder-bottom: 1px solid #f5f5f5;\r\n\talign-items: center;\r\n\theight: 100px; /* 固定高度节省空间 */\r\n}\r\n\r\n.cart-item:last-child {\r\n\tborder-bottom: none;\r\n}\r\n\r\n.item-select {\r\n\twidth: 30px; /* 减小选择框区域 */\r\n\theight: 100%;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin-right: 5px;\r\n}\r\n\r\n.selected-icon {\r\n\twidth: 18px;\r\n\theight: 18px;\r\n\tbackground-color: #ff5000;\r\n\tcolor: white;\r\n\tborder-radius: 9px;\r\n\ttext-align: center;\r\n\tline-height: 18px;\r\n\tfont-size: 12px;\r\n}\r\n\r\n.unselected-icon {\r\n\twidth: 18px;\r\n\theight: 18px;\r\n\tborder: 1px solid #ddd;\r\n\tborder-radius: 9px;\r\n}\r\n\r\n.item-image {\r\n\twidth: 70px; /* 减小图片尺寸 */\r\n\theight: 70px;\r\n\tborder-radius: 6px;\r\n\tmargin-right: 10px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.item-info {\r\n\tflex: 1;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tjustify-content: space-between;\r\n\theight: 70px; /* 与图片高度一致 */\r\n\toverflow: hidden;\r\n}\r\n\r\n.info-top {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.item-name {\r\n\tfont-size: 14px; /* 稍微减小字体 */\r\n\tcolor: #333;\r\n\tmargin-bottom: 2px;\r\n\t/* display: -webkit-box; REMOVED */\r\n\t/* -webkit-line-clamp: 1; REMOVED */\r\n\t/* -webkit-box-orient: vertical; REMOVED */\r\n\toverflow: hidden;\r\n\tfont-weight: bold;\r\n\ttext-overflow: ellipsis;\r\n}\r\n\r\n.item-spec {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n\tmargin-bottom: auto; /* 自动占据中间空间 */\r\n}\r\n\r\n.item-footer {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* 显式设置横向排列 */\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\twidth: 100%; /* 确保占满宽度 */\r\n}\r\n\r\n.item-price {\r\n\tfont-size: 16px;\r\n\tcolor: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.quantity-control {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tbackground-color: #f5f5f5;\r\n\tborder-radius: 12px;\r\n\toverflow: hidden;\r\n\theight: 28px;\r\n}\r\n\r\n.quantity-btn {\r\n\twidth: 28px;\r\n\theight: 28px;\r\n\ttext-align: center;\r\n\tline-height: 28px;\r\n\tfont-size: 16px;\r\n\tcolor: #333;\r\n\tbackground-color: #eee;\r\n}\r\n\r\n.quantity-value {\r\n\tmin-width: 36px;\r\n\ttext-align: center;\r\n\tfont-size: 14px;\r\n\tline-height: 28px;\r\n\tcolor: #333;\r\n}\r\n\r\n/* 推荐商品 */\r\n.recommend-section {\r\n\tmargin: 20px 10px;\r\n\tbackground-color: white;\r\n\tborder-radius: 10px;\r\n\tpadding: 15px;\r\n}\r\n\r\n.section-header {\r\n\tmargin-bottom: 15px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n}\r\n\r\n.refresh-btn {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tpadding: 4px 8px;\r\n}\r\n\r\n.refresh-icon {\r\n\tfont-size: 14px;\r\n\tmargin-right: 4px;\r\n}\r\n\r\n.refresh-text {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n}\r\n\r\n.section-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.recommend-list {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: space-between;\r\n\t/* grid-template-columns: repeat(2, 1fr); REMOVED */\r\n\t/* gap: 12px; REMOVED */\r\n}\r\n\r\n.recommend-item {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tbackground: #fff;\r\n\tborder-radius: 8px;\r\n\toverflow: hidden;\r\n\twidth: 48%; /* 替换 grid 1fr auto fit */\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.recommend-image {\r\n\twidth: 100%;\r\n\theight: 170px; /* 显式高度 */\r\n\t/* aspect-ratio: 1; REMOVED */\r\n\t/* object-fit: cover; REMOVED */\r\n\tborder-radius: 8px;\r\n\tmargin-bottom: 8px;\r\n\tbackground: #f5f5f5;\r\n}\r\n\r\n\t.recommend-name {\r\n\t\tfont-size: 13px;\r\n\t\tcolor: #333;\r\n\t\tmargin-bottom: 5px;\r\n\t\tline-height: 1.4;\r\n\t\theight: 36px;\r\n\t\toverflow: hidden;\r\n\t\t/* display: -webkit-box; REMOVED */\r\n\t\t/* -webkit-line-clamp: 2; REMOVED */\r\n\t\t/* -webkit-box-orient: vertical; REMOVED */\r\n\t\ttext-overflow: ellipsis;\r\n\t}\r\n\r\n.recommend-bottom {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row;\r\n\t\tjustify-content: space-between;\r\n\t\talign-items: center;\r\n\t\tpadding-right: 8px;\r\n\t}\r\n\r\n\t.recommend-price {\r\n\t\tfont-size: 15px;\r\n\t\tcolor: #ff5000;\r\n\t\tfont-weight: bold;\r\n\t}\r\n\t\r\n\t.recommend-add-btn {\r\n\t\twidth: 24px;\r\n\t\theight: 24px;\r\n\t\tbackground-color: #ff5000;\r\n\t\tborder-radius: 12px;\r\n\t\tdisplay: flex;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t}\r\n\t\r\n\t.recommend-add-icon {\r\n\t\tcolor: white;\r\n\t\tfont-size: 16px;\r\n\t\tline-height: 1;\r\n\t\tfont-weight: bold;\r\n\t}\r\n\r\n\t/* 响应式布局优化 */\r\n@media screen and (min-width: 768px) {\r\n\t.cart-list, \r\n\t.recommend-section {\r\n\t\tmargin: 20px auto;\r\n\t\twidth: 95%; /* max-width -> width */\r\n\t}\r\n\r\n\t.recommend-list {\r\n\t\t/* grid-template-columns: repeat(4, 1fr); REMOVED */\r\n\t\t/* gap: 16px; REMOVED */\r\n\t\t/* Flex 布局参数调整在下方 update */\r\n\t}\r\n\t.recommend-item {\r\n\t\twidth: 23%;\r\n\t\tmargin-bottom: 16px;\r\n\t}\r\n}\r\n\r\n@media screen and (min-width: 1024px) {\r\n\t/* 桌面端整体布局调整 */\r\n\t.cart-content {\r\n\t\tpadding: 20px 40px;\r\n\t\tbackground-color: #f5f5f5;\r\n\t}\r\n\r\n\t.cart-list, \r\n\t.recommend-section {\r\n\t\tmargin: 20px auto;\r\n\t\twidth: 96%; /* max-width -> width: percentage is safer */\r\n\t\tmax-width: 1200px;\r\n\t}\r\n\r\n\t/* 店铺分组在桌面端显示为网格布局 */\r\n\t.shop-group {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\tbackground: transparent;\r\n\t\tbox-shadow: none;\r\n\t\tborder-radius: 0;\r\n\t\toverflow: visible;\r\n\t}\r\n\r\n\t.shop-header {\r\n\t\tbackground: white;\r\n\t\tborder-radius: 12px;\r\n\t\tmargin-bottom: 12px;\r\n\t\tpadding: 16px 80px 16px 24px; /* 同步增加右侧内边距 */\r\n\t}\r\n\r\n\t/* 购物车商品列表转为列表布局 */\r\n\t.cart-item {\r\n\t\tbackground: white;\r\n\t\tborder-radius: 0;\r\n\t\tpadding: 15px 80px 15px 30px; /* 进一步增加右侧内边距 */\r\n\t\theight: 80px; /* 固定高度 */\r\n\t\tborder-bottom: 1px solid #eee;\r\n\t\tbox-shadow: none;\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row; /* 显式设置横向排列 */\r\n\t\talign-items: center; /* 垂直居中 */\r\n\t\twidth: 100%;\r\n\t}\r\n \r\n .cart-item:hover {\r\n background-color: #f9f9f9;\r\n transform: none;\r\n box-shadow: none;\r\n }\r\n\r\n\t.item-image {\r\n\t\twidth: 50px;\r\n\t\theight: 50px;\r\n\t\tmargin-right: 20px;\r\n\t\tflex-shrink: 0;\r\n\t}\r\n\r\n\t.item-info {\r\n\t\tflex: 1;\r\n height: 100%;\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row; /* 信息区域横向排列 */\r\n\t\talign-items: center;\r\n justify-content: space-between;\r\n overflow: visible;\r\n\t}\r\n \r\n .info-top {\r\n flex: 1;\r\n\t\tdisplay: flex;\r\n flex-direction: row; /* 名称和规格横向排列 */\r\n align-items: center;\r\n margin-right: 20px;\r\n\t\theight: 100%;\r\n }\r\n\r\n\t.item-name {\r\n\t\tfont-size: 14px;\r\n width: 250px; /* 固定名称宽度 */\r\n margin-right: 20px;\r\n\t\t/* 限制行数 */\r\n\t\twhite-space: nowrap;\r\n\t\toverflow: hidden;\r\n\t\ttext-overflow: ellipsis;\r\n margin-bottom: 0;\r\n\t}\r\n \r\n .item-spec {\r\n width: 150px;\r\n margin-bottom: 0;\r\n\t\twhite-space: nowrap;\r\n\t\toverflow: hidden;\r\n\t\ttext-overflow: ellipsis;\r\n }\r\n \r\n .item-footer {\r\n width: auto;\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row;\r\n justify-content: flex-end;\r\n\t\talign-items: center;\r\n /* gap: 40px; REMOVED */\r\n\t\theight: 100%;\r\n }\r\n \r\n .item-price {\r\n width: 100px;\r\n text-align: right;\r\n\t\tmargin-bottom: 0;\r\n\t\tmargin-right: 40px; /* Replace gap */\r\n }\r\n \r\n .quantity-control {\r\n margin-left: 0;\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row;\r\n }\r\n\r\n\t/* 推荐商品优化 */\r\n\t.recommend-list {\r\n\t\t/* grid-template-columns: repeat(5, 1fr); REMOVED */\r\n\t\t/* gap: 20px; REMOVED */\r\n\t}\r\n\t.recommend-item {\r\n\t\twidth: 18%; /* 5列 */\r\n\t\tmargin-bottom: 20px;\r\n\t}\r\n \r\n .recommend-image {\r\n height: 200px; /* 强制高度 */\r\n }\r\n\t\r\n\t/* 底部结算栏优化 */\r\n\t.cart-footer {\r\n\t\tpadding: 0 40px;\r\n\t\twidth: 100%; /* max-width -> width */\r\n\t\tmargin: 0 auto;\r\n\t\tbox-shadow: 0 -2px 10px rgba(0,0,0,0.05);\r\n\t}\r\n\t\r\n\t.footer-content {\r\n\t\tmax-width: 1200px;\r\n\t\tmargin: 0 auto;\r\n\t\twidth: 100%;\r\n\t\tdisplay: flex;\r\n\t\tjustify-content: space-between;\r\n\t\talign-items: center;\r\n\t}\r\n}\r\n\r\n@media screen and (min-width: 1400px) {\r\n\t.cart-list, \r\n\t.recommend-section {\r\n\t\twidth: 1400px;\r\n\t}\r\n\r\n\t/* 大屏下购物车商品显示3列 - 移除,保持单列列表 */\r\n\t/* .cart-list .shop-group > view:not(.shop-header) {\r\n\t\tgrid-template-columns: repeat(3, 1fr);\r\n\t} */\r\n\r\n\t.recommend-list {\r\n\t\t/* grid-template-columns: repeat(6, 1fr); REMOVED */\r\n\t}\r\n\t.recommend-item {\r\n\t\twidth: 15%; /* 6 columns approx */\r\n\t}\r\n\t\r\n\t.footer-content {\r\n\t\twidth: 1400px;\r\n\t}\r\n}\r\n\r\n\t/* 购物车操作栏样式 - 自适应横向排列 */\r\n\t.cart-action-bar {\r\n\t\tbackground-color: white;\r\n\t\tmargin: 10px;\r\n\t\tborder-radius: 12px;\r\n\t\tpadding: 10px 15px; /* 减小内边距 */\r\n\t\tbox-shadow: 0 2px 8px rgba(0,0,0,0.05);\r\n\t}\r\n\r\n\t.action-bar-content {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row; /* 强制横向 */\r\n\t\talign-items: center;\r\n\t\tjustify-content: space-between;\r\n\t\twidth: 100%;\r\n /* gap: 20px; REMOVED from .action-bar-content usually in desktop */\r\n\t}\r\n\r\n\t.action-left {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row;\r\n\t\talign-items: center;\r\n\t\tflex-shrink: 0;\r\n\t}\r\n\r\n\t.action-right {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row; /* 强制横向 */\r\n\t\talign-items: center;\r\n\t\tjustify-content: flex-end;\r\n\t\tflex: 1;\r\n\t\tmin-width: 0;\r\n\t}\r\n\t\r\n\t/* 合计信息区域 */\r\n\t.total-info {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row; /* 强制横向 */\r\n\t\talign-items: center;\r\n\t\tmargin-right: 10px;\r\n\t\tflex-shrink: 1; /* 允许压缩 */\r\n\t\toverflow: hidden;\r\n\t}\r\n\t\r\n\t.total-text {\r\n\t\tfont-size: 14px;\r\n\t\tcolor: #333;\r\n\t\tmargin-right: 2px;\r\n\t\twhite-space: nowrap;\r\n\t\tflex-shrink: 0;\r\n\t}\r\n\t\r\n\t.total-price {\r\n\t\tfont-size: 16px;\r\n\t\tcolor: #ff5000;\r\n\t\tfont-weight: bold;\r\n\t\twhite-space: nowrap;\r\n\t\toverflow: hidden;\r\n\t\ttext-overflow: ellipsis;\r\n\t}\r\n\t\r\n\t/* 结算按钮 */\r\n\t.checkout-btn, .delete-btn {\r\n\t\tbackground-color: #ff5000;\r\n\t\tcolor: white;\r\n\t\tborder: none;\r\n\t\tborder-radius: 25px;\r\n\t\tpadding: 6px 16px; /* 减小按钮内边距 */\r\n\t\tfont-size: 14px;\r\n\t\twhite-space: nowrap;\r\n\t\tflex-shrink: 0;\r\n\t\tmargin: 0; /* 移除可能的margin */\r\n\t}\r\n\t\r\n\t.delete-btn {\r\n\t\tbackground-color: #ff3b30;\r\n\t\tpadding: 6px 20px;\r\n\t}\r\n\t\r\n\t/* 全选区域 */\r\n\t.select-all {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row; /* 强制横向 */\r\n\t\talign-items: center;\r\n\t}\r\n\t\r\n\t.select-all-text {\r\n\t\tmargin-left: 8px;\r\n\t\tfont-size: 14px;\r\n\t\tcolor: #333;\r\n\t\twhite-space: nowrap;\r\n\t}\r\n\t\r\n\t/* 响应式调整 */\r\n\t/* 手机端小屏幕优化 */\r\n\t@media screen and (max-width: 375px) {\r\n\t\t.cart-action-bar {\r\n\t\t\tpadding: 10px;\r\n\t\t\tmargin: 10px 5px; /* 减小外边距增加可用宽度 */\r\n\t\t}\r\n\t\t\r\n\t\t.total-text {\r\n\t\t\tfont-size: 12px;\r\n\t\t}\r\n\t\t\r\n\t\t.total-price {\r\n\t\t\tfont-size: 15px;\r\n\t\t}\r\n\t\t\r\n\t\t.checkout-btn, .delete-btn {\r\n\t\t\tpadding: 6px 12px;\r\n\t\t\tfont-size: 12px;\r\n\t\t}\r\n\t\t\r\n\t\t.select-all-text {\r\n\t\t\tfont-size: 13px;\r\n\t\t\tmargin-left: 4px;\r\n\t\t}\r\n\t}\r\n\t\r\n\t/* 平板端优化 */\r\n\t@media screen and (min-width: 768px) {\r\n\t\t.cart-action-bar {\r\n\t\t\tmargin: 20px auto;\r\n\t\t\twidth: 95%; /* max-width -> width */\r\n\t\t\tpadding: 20px;\r\n\t\t}\r\n\t\t\r\n\t\t.action-bar-content {\r\n\t\t\t/* gap: 20px; REMOVED */\r\n\t\t}\r\n\t\t\r\n\t\t.total-price {\r\n\t\t\tfont-size: 20px;\r\n\t\t}\r\n\t\t\r\n\t\t.checkout-btn, .delete-btn {\r\n\t\t\tpadding: 10px 30px;\r\n\t\t\tfont-size: 16px;\r\n\t\t\tmargin-left: 20px; /* Replace gap */\r\n\t\t}\r\n\t}\r\n\t\r\n\t/* 桌面端优化 */\r\n\t@media screen and (min-width: 1024px) {\r\n\t\t.cart-action-bar {\r\n\t\t\twidth: 1200px; /* max-width -> width */\r\n\t\t\tpadding: 20px 30px;\r\n\t\t}\r\n\t\t\r\n\t\t.action-bar-content {\r\n\t\t\tjustify-content: space-between;\r\n\t\t}\r\n\t\t\r\n\t\t.total-info {\r\n\t\t\tmargin-right: 30px;\r\n\t\t}\r\n\t\t\r\n\t\t.total-text {\r\n\t\t\tfont-size: 16px;\r\n\t\t}\r\n\t\t\r\n\t\t.total-price {\r\n\t\t\tfont-size: 22px;\r\n\t\t}\r\n\t\t\r\n\t\t.checkout-btn, .delete-btn {\r\n\t\t\tpadding: 12px 40px;\r\n\t\t\tfont-size: 16px;\r\n\t\t}\r\n\t\t\r\n\t\t.select-all-text {\r\n\t\t\tfont-size: 16px;\r\n\t\t}\r\n\t}\r\n\t\r\n\t/* 大屏幕优化 */\r\n\t@media screen and (min-width: 1400px) {\r\n\t\t.cart-action-bar {\r\n\t\t\twidth: 1400px; /* max-width -> width */\r\n\t\t}\r\n\t}\r\n\r\n .tabbar-safe-area {\r\n height: 100px;\r\n width: 100%;\r\n background-color: transparent;\r\n }\r\n</style>\r\n\r\n","<!-- 消费者端 - 个人中心 -->\r\n<template>\r\n <view class=\"consumer-profile\">\r\n <!-- 智能顶部导航栏 - 与消息页保持一致 -->\r\n <view class=\"smart-navbar\" :style=\"{ paddingTop: statusBarHeight + 'px' }\">\r\n <view class=\"nav-container\" :style=\"{ paddingRight: (navBarRight > 0 ? navBarRight : 16) + 'px' }\">\r\n <!-- 基础用户信息:头像和昵称 -->\r\n <view class=\"nav-user-basic\" @click=\"editProfile\">\r\n <image \r\n :src=\"userInfo.avatar_url != '' ? userInfo.avatar_url : '/static/images/default-product.png'\" \r\n class=\"nav-avatar\" \r\n />\r\n <text class=\"nav-user-name\">{{ userInfo.nickname != '' ? userInfo.nickname : userInfo.phone }}</text>\r\n </view>\r\n \r\n <!-- 用户资产横向排列 (积分、余额、优惠券) -->\r\n <view class=\"nav-user-stats\">\r\n <view class=\"nav-stat-item\" @click=\"goToPoints\">\r\n <text class=\"nav-stat-label\">积分</text>\r\n <text class=\"nav-stat-value\">{{ userStats.points }}</text>\r\n </view>\r\n \r\n <view class=\"nav-stat-item\" @click=\"goToWallet\">\r\n <text class=\"nav-stat-label\">余额</text>\r\n <text class=\"nav-stat-value\">¥{{ userStats.balance }}</text>\r\n </view>\r\n \r\n <view class=\"nav-stat-item\" @click=\"goToCoupons\">\r\n <text class=\"nav-stat-label\">券</text>\r\n <text class=\"nav-stat-value\">{{ serviceCounts.coupons }}</text>\r\n </view>\r\n </view>\r\n \r\n\r\n <!-- 设置按钮 (安卓端和电脑端显示,小程序不显示) -->\r\n <view class=\"nav-actions\">\r\n <view class=\"action-btn\" @click=\"goToSettings\">\r\n <text class=\"action-icon\">⚙️</text>\r\n </view>\r\n </view>\r\n\r\n </view>\r\n </view>\r\n\r\n <scroll-view class=\"profile-scroll-content\" :scroll-y=\"true\">\r\n <!-- 导航栏占位符 - 需要包含statusBarHeight + 导航栏高度44px -->\r\n <view :style=\"{ height: (statusBarHeight + 44) + 'px' }\"></view>\r\n \r\n <!-- 我的服务 (移到订单上方) -->\r\n <view class=\"my-services\" style=\"margin-top: 10px;\">\r\n <view class=\"section-title\">我的服务</view>\r\n <view class=\"service-grid\">\r\n <view class=\"service-item\" @click=\"goToMessages\">\r\n <text class=\"service-icon\">💬</text>\r\n <text class=\"service-text\">消息中心</text>\r\n <text v-if=\"serviceCounts.unreadMessages > 0\" class=\"service-badge\">{{ serviceCounts.unreadMessages }}</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToCoupons\">\r\n <text class=\"service-icon\">🎫</text>\r\n <text class=\"service-text\">优惠券</text>\r\n <text v-if=\"serviceCounts.coupons > 0\" class=\"service-badge\">{{ serviceCounts.coupons }}</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToAddress\">\r\n <text class=\"service-icon\">📍</text>\r\n <text class=\"service-text\">收货地址</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToFavorites\">\r\n <text class=\"service-icon\">❤️</text>\r\n <text class=\"service-text\">我的收藏</text>\r\n <text v-if=\"serviceCounts.favorites > 0\" class=\"service-badge\">{{ serviceCounts.favorites }}</text>\r\n </view>\r\n\r\n <view class=\"service-item\" @click=\"goToFootprint\">\r\n <text class=\"service-icon\">👣</text>\r\n <text class=\"service-text\">浏览足迹</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToRefund\">\r\n <text class=\"service-icon\">🔄</text>\r\n <text class=\"service-text\">退款/售后</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToOrderReviews\">\r\n <text class=\"service-icon\">📝</text>\r\n <text class=\"service-text\">评价</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToFollowedShops\">\r\n <text class=\"service-icon\">⭐</text>\r\n <text class=\"service-text\">关注店铺</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToPoints\">\r\n <text class=\"service-icon\">💰</text>\r\n <text class=\"service-text\">我的积分</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToBalance\">\r\n <text class=\"service-icon\">💵</text>\r\n <text class=\"service-text\">我的余额</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToShare\">\r\n <text class=\"service-icon\">🔗</text>\r\n <text class=\"service-text\">我的分享</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToMember\">\r\n <text class=\"service-icon\">👑</text>\r\n <text class=\"service-text\">会员中心</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToSettings\">\r\n <text class=\"service-icon\">⚙️</text>\r\n <text class=\"service-text\">设置</text>\r\n </view>\r\n </view>\r\n </view>\r\n\r\n <!-- 订单状态快捷入口 -->\r\n <view class=\"order-shortcuts\">\r\n <view class=\"section-header-row\">\r\n <text class=\"section-title\">我的订单</text>\r\n <text class=\"view-all\" @click=\"goToOrders(currentOrderTab)\">查看更多 </text>\r\n </view>\r\n <view class=\"order-tabs\">\r\n <view class=\"order-tab\" :class=\"{ active: currentOrderTab === 'all' }\" @click=\"switchOrderTab('all')\">\r\n <text class=\"tab-icon\">📋</text>\r\n <text class=\"tab-text\">全部</text>\r\n <text v-if=\"orderCounts.total > 0\" class=\"tab-badge\">{{ orderCounts.total }}</text>\r\n </view>\r\n <view class=\"order-tab\" :class=\"{ active: currentOrderTab === 'pending' }\" @click=\"switchOrderTab('pending')\">\r\n <text class=\"tab-icon\">💰</text>\r\n <text class=\"tab-text\">待支付</text>\r\n <text v-if=\"orderCounts.pending > 0\" class=\"tab-badge\">{{ orderCounts.pending }}</text>\r\n </view>\r\n <view class=\"order-tab\" :class=\"{ active: currentOrderTab === 'toship' }\" @click=\"switchOrderTab('toship')\">\r\n <text class=\"tab-icon\">🚚</text>\r\n <text class=\"tab-text\">待发货</text>\r\n <text v-if=\"orderCounts.toship > 0\" class=\"tab-badge\">{{ orderCounts.toship }}</text>\r\n </view>\r\n <view class=\"order-tab\" :class=\"{ active: currentOrderTab === 'shipped' }\" @click=\"switchOrderTab('shipped')\">\r\n <text class=\"tab-icon\">📦</text>\r\n <text class=\"tab-text\">待收货</text>\r\n <text v-if=\"orderCounts.shipped > 0\" class=\"tab-badge\">{{ orderCounts.shipped }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n\r\n <!-- 最近订单列表 (根据Tab切换显示) -->\r\n <view class=\"recent-orders\">\r\n <view class=\"section-header\">\r\n <text class=\"section-title\">{{ getOrderSectionTitle() }}</text>\r\n </view>\r\n \r\n <view v-if=\"filteredOrders.length === 0\" class=\"empty-orders\">\r\n <text class=\"empty-text\">暂无相关订单记录</text>\r\n <button class=\"start-shopping\" @click=\"goShopping\">去逛逛</button>\r\n </view>\r\n \r\n <view v-for=\"order in filteredOrders\" :key=\"order.id\" class=\"order-item\" @click=\"viewOrderDetail(order)\">\r\n <view class=\"order-item-header\">\r\n <view class=\"order-shop\">\r\n <text class=\"shop-icon\">🏪</text>\r\n <text class=\"shop-name\">{{ getOrderShopName(order) }}</text>\r\n <text class=\"shop-arrow\"> </text>\r\n </view>\r\n <view class=\"status-row\">\r\n <text class=\"order-status-text\" :class=\"getOrderStatusClass(order.status)\">{{ getOrderStatusText(order.status) }}</text>\r\n <text class=\"more-btn\" @click.stop=\"showOrderMenu(order)\">⋯</text>\r\n </view>\r\n </view>\r\n <view class=\"order-item-content\">\r\n <image :src=\"getOrderMainImage(order)\" class=\"order-item-image\" mode=\"aspectFill\" @click.stop=\"goToProductFromOrder(order)\" />\r\n <view class=\"order-item-info\">\r\n <view class=\"order-title-row\">\r\n <text class=\"order-item-title\">{{ getOrderTitle(order) }}</text>\r\n <text class=\"order-item-price\">¥{{ order.actual_amount }}</text>\r\n </view>\r\n <view class=\"order-spec-row\">\r\n <text class=\"order-item-spec\">{{ getOrderSpec(order) }}</text>\r\n <text class=\"order-item-num\">x{{ getOrderItemCount(order) }}</text>\r\n </view>\r\n <text class=\"order-item-time\">{{ formatDateTime(order.created_at) }}</text>\r\n </view>\r\n </view>\r\n <view class=\"order-item-actions\">\r\n <button v-if=\"order.status === 1\" class=\"order-action-btn pay\" @click.stop=\"payOrder(order)\">立即支付</button>\r\n <button v-if=\"order.status === 3\" class=\"order-action-btn confirm\" @click.stop=\"confirmReceive(order)\">确认收货</button>\r\n <button v-if=\"order.status === 4\" class=\"order-action-btn review\" @click.stop=\"reviewOrder(order)\">评价</button>\r\n <button v-if=\"order.status === 2 || order.status === 3\" class=\"order-action-btn secondary\" @click.stop=\"viewOrderDetail(order)\">查看物流</button>\r\n </view>\r\n </view>\r\n </view>\r\n\r\n <!-- 我的服务 -->\r\n <!-- <view class=\"my-services\">\r\n <view class=\"section-title\">我的服务</view>\r\n <view class=\"service-grid\">\r\n <view class=\"service-item\" @click=\"goToCoupons\">\r\n <text class=\"service-icon\">🎫</text>\r\n <text class=\"service-text\">优惠券</text>\r\n <text v-if=\"serviceCounts.coupons > 0\" class=\"service-badge\">{{ serviceCounts.coupons }}</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToAddress\">\r\n <text class=\"service-icon\">📍</text>\r\n <text class=\"service-text\">收货地址</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToFavorites\">\r\n <text class=\"service-icon\">❤️</text>\r\n <text class=\"service-text\">我的收藏</text>\r\n <text v-if=\"serviceCounts.favorites > 0\" class=\"service-badge\">{{ serviceCounts.favorites }}</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToFootprint\">\r\n <text class=\"service-icon\">👣</text>\r\n <text class=\"service-text\">浏览足迹</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToRefund\">\r\n <text class=\"service-icon\">🔄</text>\r\n <text class=\"service-text\">退款/售后</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"contactService\">\r\n <text class=\"service-icon\">💬</text>\r\n <text class=\"service-text\">在线客服</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToMySubscriptions\">\r\n <text class=\"service-icon\">🧩</text>\r\n <text class=\"service-text\">我的订阅</text>\r\n </view>\r\n <view class=\"service-item\" @click=\"goToSubscriptions\">\r\n <text class=\"service-icon\">🧩</text>\r\n <text class=\"service-text\">软件订阅</text>\r\n </view>\r\n </view>\r\n </view> -->\r\n\r\n <!-- 消费统计 -->\r\n <view class=\"consumption-stats\">\r\n <view class=\"section-title\">消费统计</view>\r\n <view class=\"stats-period\">\r\n <text v-for=\"period in statsPeriods\" :key=\"period.key\" \r\n class=\"period-tab\" \r\n :class=\"{ active: activeStatsPeriod === period.key }\"\r\n @click=\"switchStatsPeriod(period.key)\">{{ period.label }}</text>\r\n </view>\r\n \r\n <view class=\"stats-content\">\r\n <view class=\"stat-card\">\r\n <text class=\"stat-value\">¥{{ currentStats.total_amount }}</text>\r\n <text class=\"stat-label\">总消费</text>\r\n </view>\r\n <view class=\"stat-card\">\r\n <text class=\"stat-value\">{{ currentStats.order_count }}</text>\r\n <text class=\"stat-label\">订单数</text>\r\n </view>\r\n <view class=\"stat-card\">\r\n <text class=\"stat-value\">¥{{ currentStats.avg_amount }}</text>\r\n <text class=\"stat-label\">平均消费</text>\r\n </view>\r\n <view class=\"stat-card\">\r\n <text class=\"stat-value\">{{ currentStats.save_amount }}</text>\r\n <text class=\"stat-label\">节省金额</text>\r\n </view>\r\n </view>\r\n </view>\r\n\r\n <!-- 账户安全 -->\r\n <!-- <view class=\"account-security\">\r\n <view class=\"section-title\">账户安全</view>\r\n <view class=\"security-items\">\r\n <view class=\"security-item\" @click=\"changePassword\">\r\n <text class=\"security-icon\">🔒</text>\r\n <text class=\"security-text\">修改密码</text>\r\n <text class=\"security-arrow\">></text>\r\n </view>\r\n <view class=\"security-item\" @click=\"bindPhone\">\r\n <text class=\"security-icon\">📱</text>\r\n <text class=\"security-text\">手机绑定</text>\r\n <view class=\"security-status\">\r\n <text class=\"status-text\" :class=\"{ bound: userInfo.phone }\">{{ userInfo.phone ? '已绑定' : '未绑定' }}</text>\r\n <text class=\"security-arrow\">></text>\r\n </view>\r\n </view>\r\n <view class=\"security-item\" @click=\"bindEmail\">\r\n <text class=\"security-icon\">📧</text>\r\n <text class=\"security-text\">邮箱绑定</text>\r\n <view class=\"security-status\">\r\n <text class=\"status-text\" :class=\"{ bound: userInfo.email }\">{{ userInfo.email ? '已绑定' : '未绑定' }}</text>\r\n <text class=\"security-arrow\">></text>\r\n </view>\r\n </view>\r\n </view>\r\n </view> -->\r\n </scroll-view>\r\n </view>\r\n</template>\r\n\r\n<script>\r\nimport { UserType } from '@/types/mall-types.uts'\r\nimport supabaseService from '@/utils/supabaseService.uts'\r\n\r\ntype UserStatsType = {\r\n points: number\r\n balance: number\r\n level: number\r\n}\r\n\r\ntype OrderCountsType = {\r\n total: number\r\n pending: number\r\n toship: number\r\n shipped: number\r\n review: number\r\n}\r\n\r\ntype ServiceCountsType = {\r\n coupons: number\r\n favorites: number\r\n unreadMessages: number\r\n}\r\n\r\ntype ConsumptionStatsType = {\r\n total_amount: number\r\n order_count: number\r\n avg_amount: number\r\n save_amount: number\r\n}\r\n\r\ntype StatsPeriodType = {\r\n key: string\r\n label: string\r\n}\r\n\r\ntype OrderItemType = {\r\n id: string\r\n order_no: string\r\n status: number\r\n actual_amount: number\r\n created_at: string\r\n ml_order_items: any | null\r\n ml_shops: any | null\r\n items_count: number\r\n}\r\n\r\nexport default {\r\n data() {\r\n return {\r\n userInfo: {\r\n id: '',\r\n phone: '',\r\n email: '',\r\n nickname: '',\r\n avatar_url: '',\r\n gender: 0,\r\n user_type: 0,\r\n status: 0,\r\n created_at: ''\r\n } as UserType,\r\n userStats: {\r\n points: 0,\r\n balance: 0,\r\n level: 1\r\n } as UserStatsType,\r\n orderCounts: {\r\n total: 0,\r\n pending: 0,\r\n toship: 0,\r\n shipped: 0,\r\n review: 0\r\n } as OrderCountsType,\r\n serviceCounts: {\r\n coupons: 0,\r\n favorites: 0,\r\n unreadMessages: 0\r\n } as ServiceCountsType,\r\n recentOrders: [] as Array<OrderItemType>,\r\n statsPeriods: [\r\n { key: 'month', label: '本月' },\r\n { key: 'quarter', label: '本季度' },\r\n { key: 'year', label: '本年' },\r\n { key: 'all', label: '全部' }\r\n ] as Array<StatsPeriodType>,\r\n activeStatsPeriod: 'month',\r\n currentStats: {\r\n total_amount: 0,\r\n order_count: 0,\r\n avg_amount: 0,\r\n save_amount: 0\r\n } as ConsumptionStatsType,\r\n statusBarHeight: 0,\r\n navBarRight: 0, // 导航栏右侧预留空间(小程序胶囊按钮)\r\n currentOrderTab: 'all' as string,\r\n allOrders: [] as Array<OrderItemType>\r\n }\r\n },\r\n onLoad() {\r\n this.initPage()\r\n this.loadUserProfile()\r\n this.loadOrders()\r\n \r\n // 监听订单更新事件\r\n uni.$on('orderUpdated', this.handleOrderUpdated)\r\n },\r\n onShow() {\r\n this.refreshData()\r\n },\r\n onUnload() {\r\n // 移除事件监听\r\n uni.$off('orderUpdated', this.handleOrderUpdated)\r\n },\r\n computed: {\r\n filteredOrders(): Array<OrderItemType> {\r\n const result: Array<OrderItemType> = []\r\n if (this.currentOrderTab === 'all') {\r\n for (let i: number = 0; i < this.allOrders.length; i++) {\r\n result.push(this.allOrders[i])\r\n }\r\n return result\r\n }\r\n let targetStatus: number = 0\r\n if (this.currentOrderTab === 'pending') {\r\n targetStatus = 1\r\n } else if (this.currentOrderTab === 'toship') {\r\n targetStatus = 2\r\n } else if (this.currentOrderTab === 'shipped') {\r\n targetStatus = 3\r\n } else if (this.currentOrderTab === 'review') {\r\n targetStatus = 4\r\n } else {\r\n return result\r\n }\r\n for (let i: number = 0; i < this.allOrders.length; i++) {\r\n if (this.allOrders[i].status === targetStatus) {\r\n result.push(this.allOrders[i])\r\n }\r\n }\r\n return result\r\n }\r\n },\r\n methods: {\r\n async loadOrders() {\r\n try {\r\n const orders = await supabaseService.getOrders()\r\n \r\n const mappedOrders: Array<OrderItemType> = []\r\n for (let i: number = 0; i < orders.length; i++) {\r\n const rawItem = orders[i]\r\n const o = JSON.parse(JSON.stringify(rawItem)) as UTSJSONObject\r\n \r\n let status = o.getNumber('status')\r\n if (status == null) {\r\n const orderStatus = o.getNumber('order_status')\r\n status = orderStatus != null ? orderStatus : 0\r\n }\r\n \r\n let actualAmount = o.getNumber('actual_amount')\r\n if (actualAmount == null) {\r\n const totalAmount = o.getNumber('total_amount')\r\n actualAmount = totalAmount != null ? totalAmount : 0\r\n }\r\n \r\n const mlOrderItems = o.get('ml_order_items')\r\n \r\n let itemsCount = 0\r\n if (mlOrderItems != null && Array.isArray(mlOrderItems)) {\r\n itemsCount = (mlOrderItems as any[]).length\r\n }\r\n \r\n const orderItem: OrderItemType = {\r\n id: o.getString('id') ?? '',\r\n order_no: o.getString('order_no') ?? '',\r\n status: status,\r\n actual_amount: actualAmount,\r\n created_at: o.getString('created_at') ?? '',\r\n ml_order_items: mlOrderItems,\r\n ml_shops: o.get('ml_shops'),\r\n items_count: itemsCount\r\n }\r\n \r\n mappedOrders.push(orderItem)\r\n }\r\n \r\n for (let i: number = 0; i < mappedOrders.length; i++) {\r\n for (let j: number = i + 1; j < mappedOrders.length; j++) {\r\n const dateA = mappedOrders[i]['created_at'] as string\r\n const dateB = mappedOrders[j]['created_at'] as string\r\n const timeA = new Date(dateA != null ? dateA : '1970-01-01').getTime()\r\n const timeB = new Date(dateB != null ? dateB : '1970-01-01').getTime()\r\n if (timeA < timeB) {\r\n const temp = mappedOrders[i]\r\n mappedOrders[i] = mappedOrders[j]\r\n mappedOrders[j] = temp\r\n }\r\n }\r\n }\r\n \r\n this.allOrders = mappedOrders\r\n \r\n const recentList: Array<OrderItemType> = []\r\n const limit = mappedOrders.length < 5 ? mappedOrders.length : 5\r\n for (let i: number = 0; i < limit; i++) {\r\n recentList.push(mappedOrders[i])\r\n }\r\n this.recentOrders = recentList\r\n \r\n let total = 0\r\n let pending = 0\r\n let toship = 0\r\n let shipped = 0\r\n let review = 0\r\n \r\n for (let i: number = 0; i < mappedOrders.length; i++) {\r\n total++\r\n const status = mappedOrders[i].status\r\n if (status === 1) pending++\r\n else if (status === 2) toship++\r\n else if (status === 3) shipped++\r\n else if (status === 4) review++\r\n }\r\n \r\n this.orderCounts = {\r\n total: total,\r\n pending: pending,\r\n toship: toship,\r\n shipped: shipped,\r\n review: review\r\n }\r\n } catch (e) {\r\n console.error('加载订单异常', e)\r\n }\r\n },\r\n \r\n // 切换订单Tab\r\n switchOrderTab(tab: string) {\r\n this.currentOrderTab = tab\r\n },\r\n \r\n // 获取当前订单部分标题\r\n getOrderSectionTitle(): string {\r\n if (this.currentOrderTab === 'all') return '全部订单'\r\n if (this.currentOrderTab === 'pending') return '待支付订单'\r\n if (this.currentOrderTab === 'shipped') return '待收货订单'\r\n if (this.currentOrderTab === 'review') return '待评价订单'\r\n return '我的订单'\r\n },\r\n\r\n initPage() {\r\n const systemInfo = uni.getSystemInfoSync()\r\n this.statusBarHeight = systemInfo.statusBarHeight ?? 0\r\n \r\n // 获取小程序胶囊按钮信息\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n \r\n\r\n this.navBarRight = 0\r\n\r\n },\r\n async loadUserProfile() {\r\n try {\r\n // 获取用户资料\r\n const profile = await supabaseService.getUserProfile()\r\n if (profile != null) {\r\n // 映射字段\r\n let uId = ''\r\n let uPhone = ''\r\n let uEmail = ''\r\n let uNickname = ''\r\n let uAvatar = ''\r\n let uGender = 0\r\n \r\n if (profile instanceof UTSJSONObject) {\r\n uId = profile.getString('user_id') ?? ''\r\n uPhone = profile.getString('phone') ?? ''\r\n uEmail = profile.getString('email') ?? ''\r\n uNickname = profile.getString('nickname') ?? ''\r\n uAvatar = profile.getString('avatar_url') ?? ''\r\n uGender = profile.getNumber('gender') ?? 0\r\n } else {\r\n // 必须使用 JSON.parse(JSON.stringify()) 转换为 UTSJSONObject\r\n const profileObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject\r\n uId = profileObj.getString('user_id') ?? ''\r\n uPhone = profileObj.getString('phone') ?? ''\r\n uEmail = profileObj.getString('email') ?? ''\r\n uNickname = profileObj.getString('nickname') ?? ''\r\n uAvatar = profileObj.getString('avatar_url') ?? ''\r\n uGender = profileObj.getNumber('gender') ?? 0\r\n }\r\n \r\n if (uNickname === '' && uPhone !== '') {\r\n uNickname = uPhone.substring(0, 3) + '****' + uPhone.substring(7)\r\n }\r\n\r\n this.userInfo = {\r\n id: uId,\r\n phone: uPhone,\r\n email: uEmail,\r\n nickname: uNickname != '' ? uNickname : '微信用户',\r\n avatar_url: uAvatar != '' ? uAvatar : '/static/images/default-product.png',\r\n gender: uGender,\r\n user_type: 1,\r\n status: 1,\r\n created_at: new Date().toISOString()\r\n } as UserType\r\n } else {\r\n // 如果获取失败未登录或无档案尝试获取当前登录ID\r\n const userId = supabaseService.getCurrentUserId()\r\n if (userId != null) {\r\n this.userInfo.id = userId\r\n this.userInfo.nickname = '用户' + userId.substring(0, 4)\r\n } else {\r\n this.userInfo.nickname = '未登录'\r\n }\r\n }\r\n\r\n // 获取积分和余额顺序获取UTS不支持Promise.all数组解构\r\n const balanceResult = await supabaseService.getUserBalance()\r\n const points = await supabaseService.getUserPoints()\r\n\r\n const balanceValue = balanceResult.getNumber('balance') ?? 0\r\n\r\n this.userStats = {\r\n points: points,\r\n balance: balanceValue,\r\n level: this.calculateLevel(points) // 根据积分计算等级\r\n } as UserStatsType\r\n\r\n } catch (e) {\r\n console.error('加载用户信息失败', e)\r\n // 保持默认或显示错误\r\n }\r\n },\r\n \r\n calculateLevel(points: number): number {\r\n if (points < 1000) return 0\r\n if (points < 5000) return 1\r\n if (points < 20000) return 2\r\n if (points < 50000) return 3\r\n return 4\r\n },\r\n \r\n loadConsumptionStats() {\r\n if (this.activeStatsPeriod === 'month') {\r\n this.currentStats = {\r\n total_amount: 1280.50,\r\n order_count: 8,\r\n avg_amount: 160.06,\r\n save_amount: 85.20\r\n } as ConsumptionStatsType\r\n } else if (this.activeStatsPeriod === 'quarter') {\r\n this.currentStats = {\r\n total_amount: 3680.80,\r\n order_count: 18,\r\n avg_amount: 204.49,\r\n save_amount: 256.30\r\n } as ConsumptionStatsType\r\n } else if (this.activeStatsPeriod === 'year') {\r\n this.currentStats = {\r\n total_amount: 15680.90,\r\n order_count: 56,\r\n avg_amount: 280.02,\r\n save_amount: 986.50\r\n } as ConsumptionStatsType\r\n } else {\r\n this.currentStats = {\r\n total_amount: 25680.50,\r\n order_count: 89,\r\n avg_amount: 288.55,\r\n save_amount: 1580.20\r\n } as ConsumptionStatsType\r\n }\r\n },\r\n \r\n refreshData() {\r\n // 刷新页面数据\r\n this.loadUserProfile()\r\n this.loadOrders()\r\n this.updateCouponCount() // 更新优惠券数量\r\n },\r\n \r\n async updateCouponCount() {\r\n // 从 Supabase 获取真实的优惠券数量\r\n try {\r\n const count = await supabaseService.getUserCouponCount()\r\n this.serviceCounts.coupons = count\r\n } catch (e) {\r\n console.error('获取优惠券数量失败', e)\r\n this.serviceCounts.coupons = 0\r\n }\r\n },\r\n \r\n getUserLevel(): string {\r\n const levels = ['新手', '铜牌会员', '银牌会员', '金牌会员', '钻石会员']\r\n if (this.userStats.level >= 0 && this.userStats.level < levels.length) {\r\n return levels[this.userStats.level]\r\n }\r\n return '新手'\r\n },\r\n \r\n getOrderStatusText(status: number): string {\r\n if (status === 6) return '退款中'\r\n if (status === 7) return '已退款'\r\n const statusTexts = ['异常', '待支付', '待发货', '待收货', '已完成', '已取消']\r\n if (status >= 0 && status < statusTexts.length) {\r\n return statusTexts[status]\r\n }\r\n return '未知'\r\n },\r\n \r\n getOrderStatusClass(status: number): string {\r\n if (status === 6) return 'refunding'\r\n if (status === 7) return 'refunded'\r\n const statusClasses = ['error', 'pending', 'processing', 'shipping', 'completed', 'cancelled']\r\n if (status >= 0 && status < statusClasses.length) {\r\n return statusClasses[status]\r\n }\r\n return 'error'\r\n },\r\n \r\n showOrderMenu(order: OrderItemType) {\r\n const status = order.status\r\n let actions: string[] = []\r\n \r\n if (status === 1) {\r\n actions = ['取消订单', '联系卖家']\r\n } else if (status === 2) {\r\n actions = ['提醒发货', '申请退款', '联系卖家']\r\n } else if (status === 3) {\r\n actions = ['查看物流', '确认收货', '申请退款', '联系卖家']\r\n } else if (status === 4) {\r\n actions = ['申请售后', '再次购买', '联系卖家']\r\n } else if (status === 5) {\r\n actions = ['删除订单', '再次购买', '联系卖家']\r\n } else if (status === 6) {\r\n actions = ['退款进度', '联系卖家']\r\n } else if (status === 7) {\r\n actions = ['再次购买', '联系卖家']\r\n }\r\n \r\n uni.showActionSheet({\r\n itemList: actions,\r\n success: (res) => {\r\n const action = actions[res.tapIndex]\r\n this.handleOrderAction(order, action)\r\n }\r\n })\r\n },\r\n \r\n handleOrderAction(order: OrderItemType, action: string) {\r\n if (action === '取消订单') {\r\n this.cancelOrderAction(order)\r\n } else if (action === '联系卖家') {\r\n this.contactSeller(order)\r\n } else if (action === '提醒发货') {\r\n this.remindShipping(order)\r\n } else if (action === '申请退款' || action === '申请售后') {\r\n this.applyRefund(order)\r\n } else if (action === '查看物流') {\r\n this.viewLogistics(order.id)\r\n } else if (action === '确认收货') {\r\n this.confirmReceive(order)\r\n } else if (action === '再次购买') {\r\n this.repurchase(order)\r\n } else if (action === '删除订单') {\r\n this.deleteOrder(order.id)\r\n } else if (action === '退款进度') {\r\n this.viewRefundProgress(order.id)\r\n }\r\n },\r\n \r\n cancelOrderAction(order: OrderItemType) {\r\n uni.showModal({\r\n title: '确认取消',\r\n content: '确定要取消此订单吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.showLoading({ title: '取消中...' })\r\n supabaseService.cancelOrder(order.id).then(() => {\r\n uni.hideLoading()\r\n uni.showToast({ title: '订单已取消', icon: 'success' })\r\n this.loadOrders()\r\n }).catch(() => {\r\n uni.hideLoading()\r\n uni.showToast({ title: '取消失败', icon: 'none' })\r\n })\r\n }\r\n }\r\n })\r\n },\r\n \r\n contactSeller(order: OrderItemType) {\r\n const merchantId = order.ml_shops != null ? this.getMerchantIdFromOrder(order) : ''\r\n if (merchantId !== '') {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/chat?merchantId=${merchantId}`\r\n })\r\n } else {\r\n uni.showToast({ title: '暂无卖家联系方式', icon: 'none' })\r\n }\r\n },\r\n \r\n getMerchantIdFromOrder(order: OrderItemType): string {\r\n const shopsRaw = order.ml_shops\r\n if (shopsRaw != null) {\r\n const shopStr = JSON.stringify(shopsRaw)\r\n const shopParsed = JSON.parse(shopStr)\r\n if (shopParsed != null) {\r\n const shopObj = shopParsed as UTSJSONObject\r\n return shopObj.getString('merchant_id') ?? ''\r\n }\r\n }\r\n return ''\r\n },\r\n \r\n remindShipping(order: OrderItemType) {\r\n uni.showLoading({ title: '提醒中...' })\r\n const merchantId = order.ml_shops != null ? this.getMerchantIdFromOrder(order) : ''\r\n if (merchantId !== '') {\r\n const message = `你好,我的订单[${order.order_no}]还没有发货,请尽快安排,谢谢。`\r\n supabaseService.sendChatMessage(message, merchantId).then(() => {\r\n uni.hideLoading()\r\n uni.showToast({ title: '已提醒卖家发货', icon: 'success' })\r\n }).catch(() => {\r\n uni.hideLoading()\r\n uni.showToast({ title: '提醒失败', icon: 'none' })\r\n })\r\n } else {\r\n uni.hideLoading()\r\n uni.showToast({ title: '无法联系卖家', icon: 'none' })\r\n }\r\n },\r\n \r\n applyRefund(order: OrderItemType) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/apply-refund?orderId=${order.id}`\r\n })\r\n },\r\n \r\n viewLogistics(orderId: string) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/logistics?orderId=${orderId}`\r\n })\r\n },\r\n \r\n repurchase(order: OrderItemType) {\r\n uni.showLoading({ title: '处理中...' })\r\n const itemsRaw = order.ml_order_items\r\n if (itemsRaw == null || (itemsRaw as any[]).length === 0) {\r\n uni.hideLoading()\r\n uni.showToast({ title: '订单无商品', icon: 'none' })\r\n return\r\n }\r\n \r\n const items = itemsRaw as any[]\r\n let completed = 0\r\n const total = items.length\r\n let successCount = 0\r\n \r\n for (let i = 0; i < items.length; i++) {\r\n const itemStr = JSON.stringify(items[i])\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed == null) {\r\n completed++\r\n if (completed === total) {\r\n uni.hideLoading()\r\n if (successCount > 0) {\r\n uni.showToast({ title: `已添加${successCount}件商品`, icon: 'success' })\r\n } else {\r\n uni.showToast({ title: '添加失败', icon: 'none' })\r\n }\r\n }\r\n continue\r\n }\r\n \r\n const itemObj = itemParsed as UTSJSONObject\r\n const productId = itemObj.getString('product_id') ?? ''\r\n const merchantId = order.ml_shops != null ? this.getMerchantIdFromOrder(order) : ''\r\n \r\n if (productId !== '') {\r\n supabaseService.addToCart(productId, 1, '', merchantId).then((success) => {\r\n completed++\r\n if (success) successCount++\r\n if (completed === total) {\r\n uni.hideLoading()\r\n if (successCount > 0) {\r\n uni.showToast({ title: `已添加${successCount}件商品`, icon: 'success' })\r\n } else {\r\n uni.showToast({ title: '添加失败', icon: 'none' })\r\n }\r\n }\r\n }).catch(() => {\r\n completed++\r\n if (completed === total) {\r\n uni.hideLoading()\r\n if (successCount > 0) {\r\n uni.showToast({ title: `已添加${successCount}件商品`, icon: 'success' })\r\n } else {\r\n uni.showToast({ title: '添加失败', icon: 'none' })\r\n }\r\n }\r\n })\r\n } else {\r\n completed++\r\n if (completed === total) {\r\n uni.hideLoading()\r\n if (successCount > 0) {\r\n uni.showToast({ title: `已添加${successCount}件商品`, icon: 'success' })\r\n } else {\r\n uni.showToast({ title: '添加失败', icon: 'none' })\r\n }\r\n }\r\n }\r\n }\r\n },\r\n \r\n deleteOrder(orderId: string) {\r\n uni.showModal({\r\n title: '删除订单',\r\n content: '确定要删除此订单吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.showLoading({ title: '删除中...' })\r\n supabaseService.deleteOrder(orderId).then(() => {\r\n uni.hideLoading()\r\n uni.showToast({ title: '订单已删除', icon: 'success' })\r\n this.loadOrders()\r\n }).catch(() => {\r\n uni.hideLoading()\r\n uni.showToast({ title: '删除失败', icon: 'none' })\r\n })\r\n }\r\n }\r\n })\r\n },\r\n \r\n viewRefundProgress(orderId: string) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/refund?orderId=${orderId}`\r\n })\r\n },\r\n \r\n getOrderShopName(order: OrderItemType): string {\r\n const shopsRaw = order.ml_shops\r\n if (shopsRaw != null) {\r\n const shopStr = JSON.stringify(shopsRaw)\r\n const shopParsed = JSON.parse(shopStr)\r\n if (shopParsed != null) {\r\n const shopObj = shopParsed as UTSJSONObject\r\n const name = shopObj.getString('shop_name')\r\n if (name != null && name !== '') return name\r\n }\r\n }\r\n return '自营店铺'\r\n },\r\n \r\n getOrderMainImage(order: OrderItemType): string {\r\n const itemsRaw = order.ml_order_items\r\n if (itemsRaw == null) return '/static/images/default-product.png'\r\n const items = itemsRaw as any[]\r\n if (items.length > 0) {\r\n const firstItem = items[0]\r\n const itemStr = JSON.stringify(firstItem)\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed == null) return '/static/images/default-product.png'\r\n const itemObj = itemParsed as UTSJSONObject\r\n const imgUrl = itemObj.getString('image_url')\r\n const prodImg = itemObj.getString('product_image')\r\n const img = (imgUrl != null && imgUrl !== '') ? imgUrl : prodImg\r\n if (img != null && img !== '') return img\r\n }\r\n return '/static/images/default-product.png'\r\n },\r\n \r\n getOrderTitle(order: OrderItemType): string {\r\n const itemsRaw = order.ml_order_items\r\n if (itemsRaw == null) return '精选商品'\r\n const items = itemsRaw as any[]\r\n if (items.length > 0) {\r\n const firstItem = items[0]\r\n const itemStr = JSON.stringify(firstItem)\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed == null) return '精选商品'\r\n const itemObj = itemParsed as UTSJSONObject\r\n const pName = itemObj.getString('product_name')\r\n const name = (pName != null && pName !== '') ? pName : '商品'\r\n \r\n return name\r\n }\r\n return '精选商品'\r\n },\r\n \r\n getOrderSpec(order: OrderItemType): string {\r\n const itemsRaw = order.ml_order_items\r\n if (itemsRaw == null) return ''\r\n const items = itemsRaw as any[]\r\n if (items.length > 0) {\r\n const firstItem = items[0]\r\n const itemStr = JSON.stringify(firstItem)\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed == null) return ''\r\n const itemObj = itemParsed as UTSJSONObject\r\n const specRaw = itemObj.get('specifications')\r\n if (specRaw == null) return ''\r\n \r\n if (typeof specRaw === 'string') {\r\n const specStr = specRaw as string\r\n if (specStr.startsWith('{')) {\r\n try {\r\n const specObj = JSON.parse(specStr) as UTSJSONObject\r\n const parts: string[] = []\r\n const color = specObj.get('Color')\r\n if (color != null) parts.push('颜色: ' + color)\r\n const size = specObj.get('Size')\r\n if (size != null) parts.push('尺码: ' + size)\r\n \r\n if (parts.length > 0) return parts.join(' ')\r\n return specStr.replace(/[{}\"]/g, '')\r\n } catch (e) {\r\n return specStr\r\n }\r\n }\r\n return specStr\r\n }\r\n return JSON.stringify(specRaw).replace(/[{}\"]/g, '')\r\n }\r\n return ''\r\n },\r\n\r\n getOrderItemCount(order: OrderItemType): number {\r\n if (order.items_count != null && order.items_count > 0) {\r\n return order.items_count\r\n }\r\n return 1\r\n },\r\n\r\n getOrderShopName(order: OrderItemType): string {\r\n const shopsRaw = order.ml_shops\r\n if (shopsRaw != null) {\r\n const shopStr = JSON.stringify(shopsRaw)\r\n const shopParsed = JSON.parse(shopStr)\r\n if (shopParsed != null) {\r\n const shopObj = shopParsed as UTSJSONObject\r\n const name = shopObj.getString('shop_name')\r\n if (name != null && name !== '') return name\r\n }\r\n }\r\n return '自营店铺'\r\n },\r\n \r\n formatDateTime(timeStr: string): string {\r\n if (timeStr == null || timeStr === '') return ''\r\n const date = new Date(timeStr)\r\n const y = date.getFullYear()\r\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\r\n const d = date.getDate().toString().padStart(2, '0')\r\n const h = date.getHours().toString().padStart(2, '0')\r\n const i = date.getMinutes().toString().padStart(2, '0')\r\n return `${y}-${m}-${d} ${h}:${i}`\r\n },\r\n\r\n formatTime(timeStr: string): string {\r\n const date = new Date(timeStr)\r\n const now = new Date()\r\n const diff = now.getTime() - date.getTime()\r\n const days = Math.floor(diff / (1000 * 60 * 60 * 24))\r\n \r\n if (days === 0) {\r\n return '今天'\r\n } else if (days === 1) {\r\n return '昨天'\r\n } else {\r\n return `${days}天前`\r\n }\r\n },\r\n \r\n switchStatsPeriod(period: string) {\r\n this.activeStatsPeriod = period\r\n this.loadConsumptionStats()\r\n },\r\n \r\n editProfile() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/edit-profile'\r\n })\r\n },\r\n \r\n // 跳转设置\r\n goToSettings() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/settings'\r\n })\r\n },\r\n \r\n // 跳转钱包\r\n goToWallet() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/wallet'\r\n })\r\n },\r\n \r\n goToOrders(type: string) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/orders?type=${type}`\r\n })\r\n },\r\n \r\n goShopping() {\r\n uni.switchTab({\r\n url: '/pages/main/index'\r\n })\r\n },\r\n \r\n viewOrderDetail(order: OrderItemType) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/order-detail?orderId=${order.id}`\r\n })\r\n },\r\n \r\n goToProductFromOrder(order: OrderItemType) {\r\n const itemsRaw = order.ml_order_items\r\n if (itemsRaw == null) return\r\n const items = itemsRaw as any[]\r\n if (items.length > 0) {\r\n const firstItem = items[0]\r\n const itemStr = JSON.stringify(firstItem)\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed == null) return\r\n const itemObj = itemParsed as UTSJSONObject\r\n const productId = itemObj.getString('product_id')\r\n if (productId != null && productId !== '') {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/product-detail?id=${productId}`\r\n })\r\n }\r\n }\r\n },\r\n \r\n payOrder(order: OrderItemType) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/payment?orderId=${order.id}`\r\n })\r\n },\r\n \r\n confirmReceive(order: OrderItemType) {\r\n uni.showModal({\r\n title: '确认收货',\r\n content: '确认已收到商品吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.showLoading({ title: '处理中...' })\r\n supabaseService.confirmOrderReceived(order.id).then(() => {\r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '确认收货成功',\r\n icon: 'success'\r\n })\r\n this.loadOrders()\r\n }).catch(() => {\r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '操作失败',\r\n icon: 'none'\r\n })\r\n })\r\n }\r\n }\r\n })\r\n },\r\n \r\n reviewOrder(order: OrderItemType) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/review?orderId=${order.id}`\r\n })\r\n },\r\n \r\n goToCoupons() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/coupons'\r\n })\r\n },\r\n \r\n goToMessages() {\r\n uni.navigateTo({\r\n url: '/pages/main/messages'\r\n })\r\n },\r\n\r\n goToPoints() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/points/index'\r\n })\r\n },\r\n \r\n goToAddress() {\r\n // 暂时跳转到设置页的地址管理\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/address-list'\r\n })\r\n },\r\n \r\n goToFavorites() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/favorites'\r\n })\r\n },\r\n \r\n goToFootprint() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/footprint'\r\n })\r\n },\r\n \r\n goToRefund() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/orders?type=refund'\r\n })\r\n },\r\n \r\n contactService() {\r\n uni.navigateTo({\r\n url: '/pages/mall/service/chat'\r\n })\r\n },\r\n goToOrderReviews() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/orders?type=review'\r\n })\r\n },\r\n goToMySubscriptions() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/subscription/my-subscriptions'\r\n })\r\n },\r\n goToFollowedShops() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/subscription/followed-shops'\r\n })\r\n },\r\n goToPoints() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/points/index'\r\n })\r\n },\r\n \r\n goToBalance() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/balance/index'\r\n })\r\n },\r\n \r\n goToShare() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/share/index'\r\n })\r\n },\r\n \r\n goToMember() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/member/index'\r\n })\r\n },\r\n \r\n changePassword() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/change-password'\r\n })\r\n },\r\n \r\n bindPhone() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/bind-phone'\r\n })\r\n },\r\n \r\n bindEmail() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/bind-email'\r\n })\r\n },\r\n \r\n handleOrderUpdated(data: any) {\r\n console.log('收到订单更新事件:', data)\r\n this.refreshData()\r\n \r\n const dataObj = data as UTSJSONObject\r\n const status = dataObj.getNumber('status')\r\n if (status === 1) {\r\n uni.showToast({\r\n title: '订单已保存到待支付',\r\n icon: 'success'\r\n })\r\n } else if (status === 2) {\r\n uni.showToast({\r\n title: '支付成功,订单待发货',\r\n icon: 'success'\r\n })\r\n }\r\n }\r\n }\r\n}\r\n</script>\r\n\r\n<style>\r\n.consumer-profile {\r\n background-color: #f5f5f5;\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n width: 100%;\r\n}\r\n\r\n.profile-scroll-content {\r\n flex: 1;\r\n}\r\n/* 智能顶部导航栏 */\r\n.smart-navbar {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n background-color: #ff5000;\r\n z-index: 1000;\r\n box-shadow: 0 2px 12px rgba(255, 80, 0, 0.15);\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: flex-start;\r\n}\r\n\r\n.nav-container {\r\n padding: 0 16px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: space-between;\r\n width: 100%;\r\n max-width: 1400px;\r\n margin: 0 auto;\r\n height: 44px;\r\n}\r\n\r\n/* 导航栏用户信息区域 */\r\n.nav-user-basic {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n flex-shrink: 0;\r\n margin-right: 8px;\r\n}\r\n\r\n.nav-user-stats {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: flex-end; /* 将积分、余额、券推向右侧,但在设置按钮之前 */\r\n margin-right: 8px;\r\n}\r\n\r\n.nav-user-name {\r\n font-size: 14px;\r\n font-weight: bold;\r\n color: white;\r\n width: 70px; /* 进一步压缩名字宽度 */\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.nav-stat-item {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n background: rgba(255, 255, 255, 0.15); /* 降低透明度更清爽 */\r\n border-radius: 10px;\r\n padding: 2px 6px;\r\n margin-right: 4px; /* 减小间距 */\r\n flex-shrink: 0;\r\n}\r\n\r\n.nav-stat-label {\r\n font-size: 10px; /* 调小字体 */\r\n color: rgba(255, 255, 255, 0.85);\r\n margin-right: 2px;\r\n}\r\n\r\n.nav-stat-value {\r\n font-size: 11px; /* 调小字体 */\r\n font-weight: bold;\r\n color: white;\r\n}\r\n\r\n.nav-avatar {\r\n width: 32px; /* 缩小头像 */\r\n height: 32px;\r\n border-radius: 16px;\r\n border: 1.5px solid rgba(255, 255, 255, 0.8);\r\n margin-right: 6px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.nav-actions {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n flex-shrink: 0;\r\n}\r\n\r\n.action-btn {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 32px;\r\n height: 32px;\r\n background: rgba(255, 255, 255, 0.2);\r\n border-radius: 16px;\r\n /* cursor: pointer; REMOVED */\r\n}\r\n\r\n.action-icon {\r\n font-size: 18px;\r\n color: white;\r\n}\r\n\r\n/* 导航栏占位符 */\r\n.navbar-placeholder {\r\n width: 100%;\r\n flex-shrink: 0;\r\n}\r\n\r\n.order-shortcuts, .recent-orders, .my-services, .consumption-stats, .account-security {\r\n background-color: #fff;\r\n margin: 15px 15px; /* 顶部恢复 margin */\r\n border-radius: 12px; /* 统一圆角 */\r\n padding: 20px;\r\n box-shadow: 0 2px 8px rgba(0,0,0,0.05);\r\n}\r\n\r\n.section-title {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n margin-bottom: 16px;\r\n}\r\n\r\n.section-header-row {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: space-between;\r\n margin-bottom: 16px;\r\n}\r\n\r\n.section-header-row .section-title {\r\n margin-bottom: 0;\r\n}\r\n\r\n.view-all {\r\n font-size: 14px;\r\n color: #999;\r\n}\r\n\r\n.order-tabs {\r\n display: flex;\r\n flex-direction: row; /* 显式横向排列 */\r\n justify-content: space-between;\r\n width: 100%;\r\n}\r\n\r\n.order-tab {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: row; /* 关键:改为横向排列 */\r\n align-items: center;\r\n justify-content: center; /* 居中 */\r\n position: relative;\r\n padding: 8px 0;\r\n}\r\n\r\n.tab-icon {\r\n font-size: 20px;\r\n margin-right: 6px; /* 图标和文字间距 */\r\n margin-bottom: 0; /* 移除底部间距 */\r\n}\r\n\r\n.tab-text {\r\n font-size: 14px;\r\n color: #333;\r\n}\r\n\r\n/* 选中状态的Tab */\r\n.order-tab.active .tab-icon,\r\n.order-tab.active .tab-text {\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.order-tab.active {\r\n background-color: #fff8f5;\r\n border-radius: 8px;\r\n}\r\n\r\n.tab-badge {\r\n position: absolute;\r\n top: 0;\r\n right: 10%; /* 调整位置 */\r\n background-color: #ff5000;\r\n color: #fff;\r\n font-size: 10px;\r\n padding: 1px 5px;\r\n border-radius: 8px;\r\n min-width: 14px;\r\n text-align: center;\r\n line-height: 1.2;\r\n}\r\n\r\n.empty-orders {\r\n text-align: center;\r\n padding: 80rpx 0;\r\n}\r\n\r\n.empty-text {\r\n font-size: 28rpx;\r\n color: #999;\r\n margin-bottom: 30rpx;\r\n}\r\n\r\n.start-shopping {\r\n background-color: #007aff;\r\n color: #fff;\r\n padding: 20rpx 40rpx;\r\n border-radius: 25rpx;\r\n font-size: 26rpx;\r\n border: none;\r\n}\r\n\r\n.order-item {\r\n background-color: #fff;\r\n border-radius: 16rpx;\r\n padding: 24rpx;\r\n margin-bottom: 20rpx;\r\n box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.order-item-header {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 20rpx;\r\n}\r\n\r\n.status-row {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.more-btn {\r\n font-size: 18px;\r\n color: #999;\r\n margin-left: 8px;\r\n padding: 0 5px;\r\n}\r\n\r\n.order-shop {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.shop-icon {\r\n font-size: 32rpx;\r\n margin-right: 8rpx;\r\n}\r\n\r\n.shop-name {\r\n font-size: 28rpx;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n\r\n.shop-arrow {\r\n font-size: 28rpx;\r\n color: #ccc;\r\n margin-left: 4rpx;\r\n}\r\n\r\n.order-status-text {\r\n font-size: 26rpx;\r\n font-weight: bold;\r\n}\r\n\r\n.order-status-text.pending {\r\n color: #ff5000;\r\n}\r\n\r\n.order-status-text.processing {\r\n color: #ff9500;\r\n}\r\n\r\n.order-status-text.shipping {\r\n color: #007aff;\r\n}\r\n\r\n.order-status-text.completed {\r\n color: #34c759;\r\n}\r\n\r\n.order-status-text.refunding {\r\n color: #ff9500;\r\n}\r\n\r\n.order-status-text.refunded {\r\n color: #999;\r\n}\r\n\r\n.order-status-text.cancelled {\r\n color: #999;\r\n}\r\n\r\n.order-item-content {\r\n display: flex;\r\n flex-direction: row;\r\n margin-bottom: 24rpx;\r\n}\r\n\r\n.order-item-image {\r\n width: 140rpx;\r\n height: 140rpx;\r\n border-radius: 12rpx;\r\n margin-right: 20rpx;\r\n background-color: #f8f8f8;\r\n}\r\n\r\n.order-item-info {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: flex-start;\r\n}\r\n\r\n.order-title-row {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: flex-start;\r\n margin-bottom: 12rpx;\r\n}\r\n\r\n.order-item-title {\r\n flex: 1;\r\n font-size: 28rpx;\r\n color: #333;\r\n line-height: 1.4;\r\n margin-right: 20rpx;\r\n lines: 2;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.order-item-price {\r\n font-size: 32rpx;\r\n color: #333;\r\n font-weight: bold;\r\n text-align: right;\r\n}\r\n\r\n.order-spec-row {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-top: 4rpx;\r\n margin-bottom: 8rpx;\r\n}\r\n\r\n.order-item-spec {\r\n flex: 1;\r\n font-size: 24rpx;\r\n color: #999;\r\n margin-right: 12rpx;\r\n lines: 1;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.order-item-time {\r\n font-size: 22rpx;\r\n color: #999;\r\n margin-top: auto;\r\n}\r\n\r\n.order-item-num {\r\n font-size: 24rpx;\r\n color: #999;\r\n}\r\n\r\n.order-item-actions {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: flex-end;\r\n}\r\n\r\n.order-action-btn {\r\n margin-left: 16rpx;\r\n padding: 10rpx 28rpx;\r\n border-radius: 30rpx;\r\n font-size: 24rpx;\r\n border: 1px solid #ddd;\r\n background-color: #fff;\r\n}\r\n\r\n.order-action-btn.pay {\r\n background-color: #ff5000;\r\n color: #fff;\r\n border-color: #ff5000;\r\n}\r\n\r\n.order-action-btn.confirm {\r\n background-color: #ff5000;\r\n color: #fff;\r\n border-color: #ff5000;\r\n}\r\n\r\n.order-action-btn.review {\r\n color: #ff5000;\r\n border-color: #ff5000;\r\n}\r\n\r\n.order-action-btn.secondary {\r\n color: #666;\r\n border-color: #ddd;\r\n}\r\n\r\n.service-grid {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap; /* 允许换行 */\r\n /* gap: 16px 0; REMOVED */\r\n justify-content: flex-start; /* 从左开始排列 */\r\n}\r\n\r\n.service-item {\r\n width: 25%; /* 每行4个 */\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n position: relative;\r\n box-sizing: border-box; /* 确保 padding 不影响宽度 */\r\n margin-bottom: 16px; /* Replace gap row */\r\n}\r\n\r\n.service-icon {\r\n font-size: 48rpx;\r\n margin-bottom: 15rpx;\r\n}\r\n\r\n.service-text {\r\n font-size: 24rpx;\r\n color: #333;\r\n}\r\n\r\n.service-badge {\r\n position: absolute;\r\n top: -5rpx;\r\n right: 10rpx;\r\n background-color: #ff5000;\r\n color: #fff;\r\n font-size: 18rpx;\r\n padding: 4rpx 6rpx;\r\n border-radius: 8rpx;\r\n min-width: 24rpx;\r\n text-align: center;\r\n}\r\n\r\n.stats-period {\r\n display: flex;\r\n /* gap: 30rpx; REMOVED */\r\n margin-bottom: 30rpx;\r\n}\r\n\r\n.period-tab {\r\n font-size: 26rpx;\r\n color: #666;\r\n padding: 12rpx 24rpx;\r\n border-radius: 20rpx;\r\n margin-right: 30rpx; /* Replace gap */\r\n background-color: #f0f0f0;\r\n}\r\n\r\n.period-tab.active {\r\n background-color: #007aff;\r\n color: #fff;\r\n}\r\n\r\n.stats-content {\r\n display: flex;\r\n /* gap: 20rpx; REMOVED */\r\n}\r\n\r\n.stat-card {\r\n flex: 1;\r\n text-align: center;\r\n padding: 30rpx 0;\r\n background-color: #f8f9fa;\r\n border-radius: 10rpx;\r\n margin-right: 20rpx; /* Replace gap */\r\n}\r\n\r\n.stat-card:last-child {\r\n margin-right: 0;\r\n}\r\n\r\n.stat-value {\r\n font-size: 32rpx;\r\n font-weight: bold;\r\n color: #333;\r\n margin-bottom: 8rpx;\r\n}\r\n\r\n.stat-label {\r\n font-size: 22rpx;\r\n color: #666;\r\n}\r\n\r\n.security-items {\r\n margin-top: 25rpx;\r\n}\r\n\r\n.security-item {\r\n display: flex;\r\n align-items: center;\r\n padding: 25rpx 0;\r\n border-bottom: 1rpx solid #f5f5f5;\r\n}\r\n\r\n.security-item:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.security-icon {\r\n font-size: 32rpx;\r\n margin-right: 20rpx;\r\n}\r\n\r\n.security-text {\r\n flex: 1;\r\n font-size: 28rpx;\r\n color: #333;\r\n}\r\n\r\n.security-status {\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.status-text {\r\n font-size: 24rpx;\r\n color: #999;\r\n margin-right: 10rpx;\r\n}\r\n\r\n.status-text.bound {\r\n color: #4caf50;\r\n}\r\n\r\n.security-arrow {\r\n font-size: 24rpx;\r\n color: #999;\r\n}\r\n</style>\r\n\r\n","<!-- 设置页面 -->\r\n<template>\r\n\t<view class=\"settings-page\">\r\n\t\t<!-- 顶部栏 -->\r\n\t\t<!--<view class=\"settings-header\" :style=\"{ paddingTop: statusBarHeight + 'px' }\">\r\n\t\t\t<text class=\"back-btn\" @click=\"goBack\"></text>\r\n\t\t\t<text class=\"header-title\">设置</text>\r\n\t\t</view>-->\r\n\r\n\t\t<scroll-view class=\"settings-content\" direction=\"vertical\">\r\n\t\t\t<!-- 账户设置 -->\r\n\t\t\t<view class=\"settings-section\">\r\n\t\t\t\t<text class=\"section-title\">账户设置</text>\r\n\t\t\t\t<view class=\"section-list\">\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"goToProfile\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">👤</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">个人资料</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"goToAddressList\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">📍</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">收货地址</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"changePassword\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🔒</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">修改密码</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"bindPhone\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">📱</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">手机绑定</text>\r\n\t\t\t\t\t\t<view class=\"item-right\">\r\n\t\t\t\t\t\t\t<text class=\"item-status\" :class=\"userInfo.phone != null && userInfo.phone != '' ? 'bound' : ''\">\r\n\t\t\t\t\t\t\t\t{{ userInfo.phone != null && userInfo.phone != '' ? '已绑定' : '未绑定' }}\r\n\t\t\t\t\t\t\t</text>\r\n\t\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"bindEmail\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">📧</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">邮箱绑定</text>\r\n\t\t\t\t\t\t<view class=\"item-right\">\r\n\t\t\t\t\t\t\t<text class=\"item-status\" :class=\"userInfo.email != null && userInfo.email != '' ? 'bound' : ''\">\r\n\t\t\t\t\t\t\t\t{{ userInfo.email != null && userInfo.email != '' ? '已绑定' : '未绑定' }}\r\n\t\t\t\t\t\t\t</text>\r\n\t\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 消息通知 -->\r\n\t\t\t<view class=\"settings-section\">\r\n\t\t\t\t<text class=\"section-title\">消息通知</text>\r\n\t\t\t\t<view class=\"section-list\">\r\n\t\t\t\t\t<view class=\"list-item\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🔔</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">订单消息</text>\r\n\t\t\t\t\t\t<switch class=\"settings-switch\" :checked=\"notifications.order\" @change=\"toggleNotification('order')\" />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🎁</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">促销活动</text>\r\n\t\t\t\t\t\t<switch class=\"settings-switch\" :checked=\"notifications.promotion\" @change=\"toggleNotification('promotion')\" />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">⭐</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">评价提醒</text>\r\n\t\t\t\t\t\t<switch class=\"settings-switch\" :checked=\"notifications.review\" @change=\"toggleNotification('review')\" />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 隐私设置 -->\r\n\t\t\t<view class=\"settings-section\">\r\n\t\t\t\t<text class=\"section-title\">隐私设置</text>\r\n\t\t\t\t<view class=\"section-list\">\r\n\t\t\t\t\t<view class=\"list-item\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">👁️</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">隐藏购物记录</text>\r\n\t\t\t\t\t\t<switch class=\"settings-switch\" :checked=\"privacy.hidePurchase\" @change=\"togglePrivacy('hidePurchase')\" />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🔍</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">允许通过手机号找到我</text>\r\n\t\t\t\t\t\t<switch class=\"settings-switch\" :checked=\"privacy.allowSearchByPhone\" @change=\"togglePrivacy('allowSearchByPhone')\" />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">💬</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">接收商家消息</text>\r\n\t\t\t\t\t\t<switch class=\"settings-switch\" :checked=\"privacy.receiveMerchantMsg\" @change=\"togglePrivacy('receiveMerchantMsg')\" />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 通用设置 -->\r\n\t\t\t<view class=\"settings-section\">\r\n\t\t\t\t<text class=\"section-title\">通用设置</text>\r\n\t\t\t\t<view class=\"section-list\">\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"clearCache\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🗑️</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">清除缓存</text>\r\n\t\t\t\t\t\t<view class=\"item-right\">\r\n\t\t\t\t\t\t\t<text class=\"item-cache\">{{ cacheSize }}</text>\r\n\t\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"changeLanguage\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🌐</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">语言设置</text>\r\n\t\t\t\t\t\t<view class=\"item-right\">\r\n\t\t\t\t\t\t\t<text class=\"item-status\">{{ currentLanguage }}</text>\r\n\t\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"changeTheme\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🎨</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">主题设置</text>\r\n\t\t\t\t\t\t<view class=\"item-right\">\r\n\t\t\t\t\t\t\t<text class=\"item-status\">{{ currentTheme }}</text>\r\n\t\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 我的服务 -->\r\n\t\t\t<view class=\"settings-section\">\r\n\t\t\t\t<text class=\"section-title\">我的服务</text>\r\n\t\t\t\t<view class=\"section-list\">\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"goToMyReviews\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">📝</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">我的评价</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 关于我们 -->\r\n\t\t\t<view class=\"settings-section\">\r\n\t\t\t\t<text class=\"section-title\">关于我们</text>\r\n\t\t\t\t<view class=\"section-list\">\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"aboutUs\">\r\n\t\t\t\t\t\t<text class=\"item-icon\"></text>\r\n\t\t\t\t\t\t<text class=\"item-text\">关于商城</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"userAgreement\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">📜</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">用户协议</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"privacyPolicy\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🛡️</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">隐私政策</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"checkUpdate\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">🔄</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">检查更新</text>\r\n\t\t\t\t\t\t<view class=\"item-right\">\r\n\t\t\t\t\t\t\t<text class=\"item-status\">{{ appVersion }}</text>\r\n\t\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 客服与反馈 -->\r\n\t\t\t<view class=\"settings-section\">\r\n\t\t\t\t<text class=\"section-title\">客服与反馈</text>\r\n\t\t\t\t<view class=\"section-list\">\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"contactService\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">💬</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">联系客服</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"feedback\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">📝</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">意见反馈</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"list-item\" @click=\"rateApp\">\r\n\t\t\t\t\t\t<text class=\"item-icon\">⭐</text>\r\n\t\t\t\t\t\t<text class=\"item-text\">给个好评</text>\r\n\t\t\t\t\t\t<text class=\"item-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 退出登录 -->\r\n\t\t\t<view class=\"logout-section\">\r\n\t\t\t\t<button class=\"logout-btn\" @click=\"logout\">退出登录</button>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 账号注销 -->\r\n\t\t\t<view class=\"delete-account-section\">\r\n\t\t\t\t<text class=\"delete-account\" @click=\"deleteAccount\">注销账号</text>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted } from 'vue'\r\nimport { onBackPress } from '@dcloudio/uni-app'\r\nimport supa from '@/components/supadb/aksupainstance.uts'\r\n\r\n// 拦截返回事件,强制跳转到个人中心页\r\nonBackPress((options) => {\r\n\t// 无论是什么触发的返回系统返回键或导航栏返回按钮都跳转到profile\r\n\t// 注意onBackPress 只能在 page 中使用component 中无效\r\n\tuni.switchTab({\r\n\t\turl: '/pages/main/profile'\r\n\t})\r\n\t// 返回 true 表示阻止默认返回行为\r\n\treturn true\r\n})\r\n\r\ntype UserType = {\r\n\tid: string\r\n\tphone: string | null\r\n\temail: string | null\r\n\tnickname: string | null\r\n\tavatar_url: string | null\r\n}\r\n\r\ntype NotificationType = {\r\n\torder: boolean\r\n\tpromotion: boolean\r\n\treview: boolean\r\n}\r\n\r\ntype PrivacyType = {\r\n\thidePurchase: boolean\r\n\tallowSearchByPhone: boolean\r\n\treceiveMerchantMsg: boolean\r\n}\r\n\r\nconst userInfo = ref<UserType>({\r\n\tid: '',\r\n\tphone: null,\r\n\temail: null,\r\n\tnickname: null,\r\n\tavatar_url: null\r\n})\r\nconst notifications = ref<NotificationType>({\r\n\torder: true,\r\n\tpromotion: true,\r\n\treview: true\r\n})\r\nconst privacy = ref<PrivacyType>({\r\n\thidePurchase: false,\r\n\tallowSearchByPhone: true,\r\n\treceiveMerchantMsg: true\r\n})\r\nconst cacheSize = ref<string>('0.0 MB')\r\nconst currentLanguage = ref<string>('简体中文')\r\nconst currentTheme = ref<string>('自动')\r\nconst appVersion = ref<string>('1.0.0')\r\n\r\nconst statusBarHeight = ref<number>(0)\r\n\r\nconst loadUserInfo = () => {\r\n\tconst userStore = uni.getStorageSync('userInfo')\r\n\tif (userStore != null) {\r\n\t\tconst storeObj = userStore as UTSJSONObject\r\n\t\tconst user: UserType = {\r\n\t\t\tid: storeObj.getString('id') ?? '',\r\n\t\t\tphone: storeObj.getString('phone'),\r\n\t\t\temail: storeObj.getString('email'),\r\n\t\t\tnickname: storeObj.getString('nickname'),\r\n\t\t\tavatar_url: storeObj.getString('avatar_url')\r\n\t\t} as UserType\r\n\t\tuserInfo.value = user\r\n\t}\r\n}\r\n\r\nconst loadSettings = () => {\r\n\tconst savedNotifications = uni.getStorageSync('userNotifications')\r\n\tif (savedNotifications != null) {\r\n\t\tconst notifObj = savedNotifications as UTSJSONObject\r\n\t\tconst notif: NotificationType = {\r\n\t\t\torder: notifObj.getBoolean('order') ?? true,\r\n\t\t\tpromotion: notifObj.getBoolean('promotion') ?? true,\r\n\t\t\treview: notifObj.getBoolean('review') ?? true\r\n\t\t} as NotificationType\r\n\t\tnotifications.value = notif\r\n\t}\r\n\t\r\n\tconst savedPrivacy = uni.getStorageSync('userPrivacy')\r\n\tif (savedPrivacy != null) {\r\n\t\tconst privacyObj = savedPrivacy as UTSJSONObject\r\n\t\tconst priv: PrivacyType = {\r\n\t\t\thidePurchase: privacyObj.getBoolean('hidePurchase') ?? false,\r\n\t\t\tallowSearchByPhone: privacyObj.getBoolean('allowSearchByPhone') ?? true,\r\n\t\t\treceiveMerchantMsg: privacyObj.getBoolean('receiveMerchantMsg') ?? true\r\n\t\t} as PrivacyType\r\n\t\tprivacy.value = priv\r\n\t}\r\n\t\r\n\tcacheSize.value = '12.5 MB'\r\n\t\r\n\tconst appInfo = uni.getAppBaseInfo()\r\n\tif (appInfo != null) {\r\n\t\tconst infoObj = appInfo as UTSJSONObject\r\n\t\tconst version = infoObj.getString('appVersion')\r\n\t\tif (version != null) {\r\n\t\t\tappVersion.value = version\r\n\t\t}\r\n\t}\r\n}\r\n\r\nonMounted(() => {\r\n\tconst systemInfo = uni.getSystemInfoSync()\r\n\tstatusBarHeight.value = systemInfo.statusBarHeight ?? 0\r\n\tloadUserInfo()\r\n\tloadSettings()\r\n})\r\n\r\n// 跳转到个人资料\r\nconst goToProfile = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/user/profile'\r\n\t})\r\n}\r\n\r\n// 跳转到地址管理\r\nconst goToAddressList = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/address-list'\r\n\t})\r\n}\r\n\r\n// 修改密码\r\nconst changePassword = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/user/change-password'\r\n\t})\r\n}\r\n\r\n// 绑定手机\r\nconst bindPhone = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/user/bind-phone'\r\n\t})\r\n}\r\n\r\n// 绑定邮箱\r\nconst bindEmail = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/user/bind-email'\r\n\t})\r\n}\r\n\r\n// 切换通知设置\r\nconst toggleNotification = (type: string) => {\r\n\tif (type === 'order') {\r\n\t\tnotifications.value.order = notifications.value.order === false\r\n\t} else if (type === 'promotion') {\r\n\t\tnotifications.value.promotion = notifications.value.promotion === false\r\n\t} else if (type === 'review') {\r\n\t\tnotifications.value.review = notifications.value.review === false\r\n\t}\r\n\tuni.setStorageSync('userNotifications', notifications.value)\r\n}\r\n\r\n// 切换隐私设置\r\nconst togglePrivacy = (type: string) => {\r\n\tif (type === 'hidePurchase') {\r\n\t\tprivacy.value.hidePurchase = privacy.value.hidePurchase === false\r\n\t} else if (type === 'allowSearchByPhone') {\r\n\t\tprivacy.value.allowSearchByPhone = privacy.value.allowSearchByPhone === false\r\n\t} else if (type === 'receiveMerchantMsg') {\r\n\t\tprivacy.value.receiveMerchantMsg = privacy.value.receiveMerchantMsg === false\r\n\t}\r\n\tuni.setStorageSync('userPrivacy', privacy.value)\r\n}\r\n\r\n// 清除缓存\r\nconst clearCache = () => {\r\n\tuni.showModal({\r\n\t\ttitle: '清除缓存',\r\n\t\tcontent: `确定要清除 ${cacheSize.value} 缓存吗?`,\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\t// 这里应该清除实际缓存\r\n\t\t\t\tuni.showLoading({\r\n\t\t\t\t\ttitle: '清除中...'\r\n\t\t\t\t})\r\n\t\t\t\t\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tcacheSize.value = '0.0 MB'\r\n\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\ttitle: '缓存已清除',\r\n\t\t\t\t\t\ticon: 'success'\r\n\t\t\t\t\t})\r\n\t\t\t\t}, 1000)\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 切换语言\r\nconst changeLanguage = () => {\r\n\tuni.showActionSheet({\r\n\t\titemList: ['简体中文', 'English', '日本語'],\r\n\t\tsuccess: (res) => {\r\n\t\t\tconst languages = ['简体中文', 'English', '日本語']\r\n\t\t\tcurrentLanguage.value = languages[res.tapIndex]\r\n\t\t\tuni.setStorageSync('appLanguage', currentLanguage.value)\r\n\t\t\t\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '语言已切换',\r\n\t\t\t\ticon: 'success'\r\n\t\t\t})\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 切换主题\r\nconst changeTheme = () => {\r\n\tuni.showActionSheet({\r\n\t\titemList: ['自动', '浅色模式', '深色模式'],\r\n\t\tsuccess: (res) => {\r\n\t\t\tconst themes = ['自动', '浅色模式', '深色模式']\r\n\t\t\tcurrentTheme.value = themes[res.tapIndex]\r\n\t\t\tuni.setStorageSync('appTheme', currentTheme.value)\r\n\t\t\t\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '主题已切换',\r\n\t\t\t\ticon: 'success'\r\n\t\t\t})\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 我的评价\r\nconst goToMyReviews = () => {\r\n\t// 跳转到订单列表的已完成或者是评价相关的页面\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/orders?status=completed'\r\n\t})\r\n}\r\n\r\n// 关于我们\r\nconst aboutUs = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/user/terms?type=about'\r\n\t})\r\n}\r\n\r\n// 用户协议\r\nconst userAgreement = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/user/terms?type=agreement'\r\n\t})\r\n}\r\n\r\n// 隐私政策\r\nconst privacyPolicy = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/user/terms?type=privacy'\r\n\t})\r\n}\r\n\r\n// 检查更新\r\nconst checkUpdate = () => {\r\n\tuni.showLoading({\r\n\t\ttitle: '检查更新中...'\r\n\t})\r\n\t\r\n\tsetTimeout(() => {\r\n\t\tuni.hideLoading()\r\n\t\tuni.showModal({\r\n\t\t\ttitle: '检查更新',\r\n\t\t\tcontent: '当前已是最新版本',\r\n\t\t\tshowCancel: false\r\n\t\t})\r\n\t}, 1000)\r\n}\r\n\r\n// 联系客服\r\nconst contactService = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/chat'\r\n\t})\r\n}\r\n\r\n// 意见反馈\r\nconst feedback = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/info/feedback'\r\n\t})\r\n}\r\n\r\nconst rateApp = () => {\r\n\tuni.showModal({\r\n\t\ttitle: '给个好评',\r\n\t\tcontent: '如果喜欢我们的应用,请给个好评吧!感谢您的支持!',\r\n\t\tconfirmText: '好的',\r\n\t\tshowCancel: false\r\n\t})\r\n}\r\n\r\n// 退出登录\r\nconst logout = () => {\r\n\tuni.showModal({\r\n\t\ttitle: '退出登录',\r\n\t\tcontent: '确定要退出登录吗?',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tuni.showLoading({\r\n\t\t\t\t\ttitle: '正在退出...'\r\n\t\t\t\t})\r\n\t\t\t\t\r\n\t\t\t\tuni.removeStorageSync('userInfo')\r\n\t\t\t\tuni.removeStorageSync('user_id')\r\n\t\t\t\tuni.removeStorageSync('access_token')\r\n\t\t\t\t\r\n\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '已退出登录',\r\n\t\t\t\t\ticon: 'success'\r\n\t\t\t\t})\r\n\t\t\t\t\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tuni.reLaunch({\r\n\t\t\t\t\t\turl: '/pages/user/login'\r\n\t\t\t\t\t})\r\n\t\t\t\t}, 1000)\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\nconst deleteAccount = () => {\r\n\tuni.showModal({\r\n\t\ttitle: '注销账号',\r\n\t\tcontent: '确定要注销账号吗?此操作不可恢复,所有数据将被删除!',\r\n\t\tconfirmText: '注销',\r\n\t\tconfirmColor: '#ff4757',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tuni.showLoading({\r\n\t\t\t\t\ttitle: '注销中...'\r\n\t\t\t\t})\r\n \r\n let userId: string | null = userInfo.value.id\r\n if (userId == null || userId === '') {\r\n const storageId = uni.getStorageSync('user_id')\r\n userId = (storageId != null) ? storageId as string : null\r\n }\r\n \r\n if (userId != null) {\r\n const updateObj: UTSJSONObject = new UTSJSONObject()\r\n updateObj.set('status', 3)\r\n supa\r\n .from('ml_user_profiles')\r\n .update(updateObj)\r\n .eq('user_id', userId)\r\n .execute()\r\n }\r\n\t\t\t\t\r\n\t\t\t\tuni.removeStorageSync('userInfo')\r\n\t\t\t\tuni.removeStorageSync('user_id')\r\n\t\t\t\tuni.removeStorageSync('access_token')\r\n\t\t\t\t\r\n\t\t\t\tuni.hideLoading()\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '账号已注销',\r\n\t\t\t\t\ticon: 'success',\r\n\t\t\t\t\tduration: 2000\r\n\t\t\t\t})\r\n\t\t\t\t\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tuni.reLaunch({\r\n\t\t\t\t\t\turl: '/pages/user/login'\r\n\t\t\t\t\t})\r\n\t\t\t\t}, 1500)\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n/* 响应式布局优化 */\r\n.section-list {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: flex-start;\r\n}\r\n\r\n.list-item {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tpadding: 15px;\r\n\tborder-bottom: 1px solid #f5f5f5;\r\n\tbackground-color: #ffffff;\r\n\t\r\n\t/* 手机端每行显示4个自适应排到下一行 */\r\n\twidth: 25%;\r\n\tflex-direction: column; /* 内容改为垂直排列,图标在上文字在下 */\r\n\tjustify-content: center;\r\n\ttext-align: center;\r\n\tbox-sizing: border-box;\r\n\tborder-right: 1px solid #f5f5f5; /* 添加右边框分隔 */\r\n}\r\n\r\n.item-icon {\r\n\tfont-size: 24px;\r\n\tmargin-right: 0; /* 移除右侧间距 */\r\n\tmargin-bottom: 5px; /* 添加底部间距 */\r\n}\r\n\r\n.item-text {\r\n\tfont-size: 12px;\r\n\tcolor: #333333;\r\n\t/* 文字太长可能需要处理,这里暂时不做截断 */\r\n}\r\n\r\n.item-arrow {\r\n\tdisplay: none; /* 网格模式下通常不需要箭头 */\r\n}\r\n\r\n.item-right {\r\n\tdisplay: none; /* 简化显示,隐藏右侧状态/箭头等复杂内容 */\r\n}\r\n\r\n/* 针对 switch 组件的特殊处理,如果需要显示开关,可能需要调整布局 */\r\n.settings-switch {\r\n\ttransform: scale(0.7);\r\n\tmargin-top: 5px;\r\n}\r\n\r\n/* 屏幕宽度大于 480px (大屏手机/平板/PC) 时,启用更宽的网格布局或列表布局 */\r\n@media screen and (min-width: 480px) {\r\n\t.list-item {\r\n\t\twidth: 47%; /* width: calc(50% - 10px); REPLACED */\r\n\t\tmargin: 5px;\r\n\t\tborder: 1px solid #f0f0f0;\r\n\t\tborder-radius: 8px;\r\n\t\tborder-bottom: 1px solid #f0f0f0; \r\n\t\tflex-direction: row; /* 恢复水平排列 */\r\n\t\ttext-align: left;\r\n\t\tjustify-content: flex-start;\r\n\t}\r\n\t\r\n\t.item-icon {\r\n\t\tmargin-right: 15px;\r\n\t\tmargin-bottom: 0;\r\n\t}\r\n\t\r\n\t.item-text {\r\n\t\tfont-size: 14px;\r\n\t}\r\n\t\r\n\t.item-arrow, .item-right {\r\n\t\tdisplay: flex; /* 恢复显示 */\r\n\t\tmargin-left: auto; /* 推到右侧 */\r\n\t}\r\n}\r\n\r\n/* 增加针对手机横屏的媒体查询 */\r\n@media screen and (orientation: landscape) and (max-height: 500px) {\r\n .list-item {\r\n\t\twidth: 22%; /* width: calc(25% - 10px); REPLACED */\r\n\t\tmargin: 5px;\r\n\t\tborder: 1px solid #f0f0f0;\r\n\t\tborder-radius: 8px;\r\n flex-direction: column;\r\n\t}\r\n}\r\n\r\n/* 屏幕宽度大于 1024px (大屏PC) 时 */\r\n@media screen and (min-width: 1024px) {\r\n\t.settings-page {\r\n\t\tflex-direction: row; /* 整体左右布局 */\r\n\t}\r\n\r\n\t.settings-header {\r\n\t\tdisplay: none;\r\n\t}\r\n\t\r\n\t.settings-content {\r\n\t\twidth: 100%;\r\n\t\tmax-width: 1200px;\r\n\t\tmargin: 0 auto;\r\n\t\tpadding: 20px;\r\n\t}\r\n\t\r\n\t.list-item {\r\n\t\twidth: 31%; /* width: calc(33.33% - 10px); REPLACED */\r\n\t\tflex-direction: row; /* PC端保持水平排列 */\r\n justify-content: flex-start;\r\n text-align: left;\r\n\t}\r\n \r\n .item-icon {\r\n\t\tmargin-right: 15px;\r\n\t\tmargin-bottom: 0;\r\n\t}\r\n \r\n .item-arrow, .item-right {\r\n\t\tdisplay: flex;\r\n margin-left: auto;\r\n\t}\r\n}\r\n\r\n.settings-page {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tbackground-color: #f5f5f5;\r\n}\r\n\r\n.settings-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tborder-bottom: 1px solid #e5e5e5;\r\n}\r\n\r\n.back-btn {\r\n\tfont-size: 24px;\r\n\tcolor: #333333;\r\n\tpadding: 5px;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.header-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.settings-content {\r\n\tflex: 1;\r\n\twidth: 100%;\r\n\theight: 100px;\r\n}\r\n\r\n.settings-section {\r\n\tbackground-color: #ffffff;\r\n\tmargin-bottom: 10px;\r\n\tpadding: 15px;\r\n}\r\n\r\n.section-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n/* 删除多余的 .section-list 定义 */\r\n/* 删除多余的 .list-item 定义 */\r\n/* 删除多余的 .list-item:last-child 定义 */\r\n\r\n.item-icon {\r\n\tfont-size: 20px;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.item-text {\r\n\tflex: 1;\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n}\r\n\r\n.item-arrow {\r\n\tcolor: #999999;\r\n\tfont-size: 16px;\r\n\tmargin-left: 10px;\r\n}\r\n\r\n.item-right {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n}\r\n\r\n.item-status {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n\tmargin-right: 10px;\r\n}\r\n\r\n.item-status.bound {\r\n\tcolor: #4caf50;\r\n}\r\n\r\n.item-cache {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n\tmargin-right: 10px;\r\n}\r\n\r\n.logout-section {\r\n\tbackground-color: #ffffff;\r\n\tmargin-top: 10px;\r\n\tpadding: 15px;\r\n}\r\n\r\n.logout-btn {\r\n\tbackground-color: #ffffff;\r\n\tcolor: #ff4757;\r\n\theight: 45px;\r\n\tborder: 1px solid #ff4757;\r\n\tborder-radius: 22.5px;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.delete-account-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 20px 15px;\r\n\ttext-align: center;\r\n}\r\n\r\n.delete-account {\r\n\tcolor: #999999;\r\n\tfont-size: 14px;\r\n}/* text-decoration: underline; REMOVED */\r\n</style>\r\n\r\n","<!-- 钱包页面 -->\r\n<template>\r\n\t<view class=\"wallet-page\">\r\n\t\t<!-- 顶部栏 -->\r\n\t\t<!--<view class=\"wallet-header\">\r\n\t\t\t<text class=\"back-btn\" @click=\"goBack\"></text>\r\n\t\t</view>-->\r\n\r\n\t\t<scroll-view class=\"wallet-content\" scroll-y>\r\n\t\t\t<view class=\"dashboard-container\">\r\n\t\t\t\t<!-- 左侧/顶部区域:资产信息 -->\r\n\t\t\t\t<view class=\"dashboard-main\">\r\n\t\t\t\t\t<!-- 余额概览 -->\r\n\t\t\t\t\t<view class=\"balance-overview\">\r\n\t\t\t\t\t\t<text class=\"balance-label\">账户余额</text>\r\n\t\t\t\t\t\t<text class=\"balance-value\">¥{{ balance.toFixed(2) }}</text>\r\n\t\t\t\t\t\t<view class=\"balance-actions\">\r\n\t\t\t\t\t\t\t<button class=\"action-btn recharge\" @click=\"recharge\">充值</button>\r\n\t\t\t\t\t\t\t<button class=\"action-btn withdraw\" @click=\"withdraw\">提现</button>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\r\n\t\t\t\t\t<!-- 资产统计 -->\r\n\t\t\t\t\t<view class=\"assets-stats\">\r\n\t\t\t\t\t\t<view class=\"stat-item\">\r\n\t\t\t\t\t\t\t<text class=\"stat-label\">累计充值</text>\r\n\t\t\t\t\t\t\t<text class=\"stat-value\">¥{{ stats.totalRecharge.toFixed(2) }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"stat-item\">\r\n\t\t\t\t\t\t\t<text class=\"stat-label\">累计消费</text>\r\n\t\t\t\t\t\t\t<text class=\"stat-value\">¥{{ stats.totalConsume.toFixed(2) }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"stat-item\">\r\n\t\t\t\t\t\t\t<text class=\"stat-label\">累计提现</text>\r\n\t\t\t\t\t\t\t<text class=\"stat-value\">¥{{ stats.totalWithdraw.toFixed(2) }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\r\n\t\t\t\t\t<!-- 快捷功能 -->\r\n\t\t\t\t\t<view class=\"quick-actions\">\r\n\t\t\t\t\t\t<view class=\"action-grid\">\r\n\t\t\t\t\t\t\t<view class=\"action-item\" @click=\"goToCoupons\">\r\n\t\t\t\t\t\t\t\t<text class=\"action-icon\">🎫</text>\r\n\t\t\t\t\t\t\t\t<text class=\"action-text\">优惠券</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"action-item\" @click=\"goToRedPackets\">\r\n\t\t\t\t\t\t\t\t<text class=\"action-icon\">🧧</text>\r\n\t\t\t\t\t\t\t\t<text class=\"action-text\">红包</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"action-item\" @click=\"goToPoints\">\r\n\t\t\t\t\t\t\t\t<text class=\"action-icon\">⭐</text>\r\n\t\t\t\t\t\t\t\t<text class=\"action-text\">积分</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"action-item\" @click=\"goToBankCards\">\r\n\t\t\t\t\t\t\t\t<text class=\"action-icon\">💳</text>\r\n\t\t\t\t\t\t\t\t<text class=\"action-text\">银行卡</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 安全提示 (移动端在底部PC端在左侧底部) -->\r\n\t\t\t\t\t<view class=\"security-tips\">\r\n\t\t\t\t\t\t<text class=\"tip-title\">安全提示</text>\r\n\t\t\t\t\t\t<text class=\"tip-item\">1. 请妥善保管您的支付密码</text>\r\n\t\t\t\t\t\t<text class=\"tip-item\">2. 不要向他人透露您的账户信息</text>\r\n\t\t\t\t\t\t<text class=\"tip-item\">3. 定期修改密码以确保账户安全</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\r\n\t\t\t\t<!-- 右侧/底部区域:交易记录 -->\r\n\t\t\t\t<view class=\"dashboard-side\">\r\n\t\t\t\t\t<!-- 交易记录 -->\r\n\t\t\t\t\t<view class=\"transactions-section\">\r\n\t\t\t\t\t\t<view class=\"section-header\">\r\n\t\t\t\t\t\t\t<text class=\"section-title\">交易记录</text>\r\n\t\t\t\t\t\t\t<view class=\"filter-tabs\">\r\n\t\t\t\t\t\t\t\t<text :class=\"['filter-tab', { active: activeFilter === 'all' }]\" \r\n\t\t\t\t\t\t\t\t\t\t\t@click=\"changeFilter('all')\">全部</text>\r\n\t\t\t\t\t\t\t\t<text :class=\"['filter-tab', { active: activeFilter === 'income' }]\" \r\n\t\t\t\t\t\t\t\t\t\t\t@click=\"changeFilter('income')\">收入</text>\r\n\t\t\t\t\t\t\t\t<text :class=\"['filter-tab', { active: activeFilter === 'expense' }]\" \r\n\t\t\t\t\t\t\t\t\t\t\t@click=\"changeFilter('expense')\">支出</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\r\n\t\t\t\t\t\t<!-- 空状态 -->\r\n\t\t\t\t\t\t<view v-if=\"transactions.length === 0 && isLoading === false\" class=\"empty-transactions\">\r\n\t\t\t\t\t\t\t<text class=\"empty-icon\">💰</text>\r\n\t\t\t\t\t\t\t<text class=\"empty-text\">暂无交易记录</text>\r\n\t\t\t\t\t\t\t<text class=\"empty-subtext\">快去使用钱包功能吧</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\r\n\t\t\t\t\t\t<!-- 交易列表 -->\r\n\t\t\t\t\t\t<view class=\"transactions-list\">\r\n\t\t\t\t\t\t\t<view v-for=\"transaction in transactions\" \r\n\t\t\t\t\t\t\t\t\t\t:key=\"transaction.id\" \r\n\t\t\t\t\t\t\t\t\t\tclass=\"transaction-item\">\r\n\t\t\t\t\t\t\t\t<view class=\"transaction-left\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"transaction-icon\">{{ getTransactionIcon(transaction.type) }}</text>\r\n\t\t\t\t\t\t\t\t\t<view class=\"transaction-info\">\r\n\t\t\t\t\t\t\t\t\t\t<text class=\"transaction-title\">{{ getTransactionTitle(transaction.type) }}</text>\r\n\t\t\t\t\t\t\t\t\t\t<text class=\"transaction-time\">{{ formatTime(transaction.created_at) }}</text>\r\n\t\t\t\t\t\t\t\t\t\t<text v-if=\"transaction.remark\" class=\"transaction-remark\">{{ transaction.remark }}</text>\r\n\t\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t\t<view class=\"transaction-right\">\r\n\t\t\t\t\t\t\t\t\t<text :class=\"['transaction-amount', \r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t { income: transaction.amount > 0, expense: transaction.amount < 0 }]\">\r\n\t\t\t\t\t\t\t\t\t\t{{ transaction.amount > 0 ? '+' : '' }}¥{{ Math.abs(transaction.amount).toFixed(2) }}\r\n\t\t\t\t\t\t\t\t\t</text>\r\n\t\t\t\t\t\t\t\t\t<text class=\"transaction-balance\">余额: ¥{{ transaction.current_balance.toFixed(2) }}</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\r\n\t\t\t\t\t\t<!-- 加载更多 -->\r\n\t\t\t\t\t\t<view v-if=\"isLoading\" class=\"loading-more\">\r\n\t\t\t\t\t\t\t<text class=\"loading-text\">加载中...</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view v-if=\"hasMore === false && transactions.length > 0\" class=\"no-more\">\r\n\t\t\t\t\t\t\t<text class=\"no-more-text\">没有更多记录了</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\r\n\t\t<!-- 充值弹窗 -->\r\n\t\t<view v-if=\"showRechargePopup\" class=\"recharge-popup\">\r\n\t\t\t<view class=\"popup-mask\" @click=\"closeRechargePopup\"></view>\r\n\t\t\t<view class=\"popup-content\">\r\n\t\t\t\t<view class=\"popup-header\">\r\n\t\t\t\t\t<text class=\"popup-title\">充值</text>\r\n\t\t\t\t\t<text class=\"popup-close\" @click=\"closeRechargePopup\">×</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"popup-body\">\r\n\t\t\t\t\t<text class=\"amount-label\">充值金额</text>\r\n\t\t\t\t\t<view class=\"amount-input\">\r\n\t\t\t\t\t\t<text class=\"currency-symbol\">¥</text>\r\n\t\t\t\t\t\t<input class=\"amount-field\" \r\n\t\t\t\t\t\t\t\t\t v-model=\"rechargeAmount\" \r\n\t\t\t\t\t\t\t\t\t type=\"number\" \r\n\t\t\t\t\t\t\t\t\t placeholder=\"请输入充值金额\"\r\n\t\t\t\t\t\t\t\t\t focus />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"quick-amounts\">\r\n\t\t\t\t\t\t<text v-for=\"amount in quickAmounts\" \r\n\t\t\t\t\t\t\t\t\t:key=\"amount\" \r\n\t\t\t\t\t\t\t\t\t:class=\"['quick-amount', { active: rechargeAmount === amount.toString() }]\"\r\n\t\t\t\t\t\t\t\t\t@click=\"selectQuickAmount(amount)\">\r\n\t\t\t\t\t\t\t¥{{ amount }}\r\n\t\t\t\t\t\t</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<text class=\"recharge-tip\">单笔充值最低10元最高5000元</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"popup-footer\">\r\n\t\t\t\t\t<button class=\"cancel-btn\" @click=\"closeRechargePopup\">取消</button>\r\n\t\t\t\t\t<button class=\"confirm-btn\" \r\n\t\t\t\t\t\t\t\t\t:class=\"{ disabled: canRecharge === false }\"\r\n\t\t\t\t\t\t\t\t\t@click=\"confirmRecharge\">\r\n\t\t\t\t\t\t确认充值\r\n\t\t\t\t\t</button>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, computed, watch } from 'vue'\r\nimport { onShow } from '@dcloudio/uni-app'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype WalletType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tbalance: number\r\n\ttotal_recharge: number\r\n\ttotal_consume: number\r\n\ttotal_withdraw: number\r\n\tupdated_at: string\r\n}\r\n\r\ntype TransactionType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tchange_amount: number\r\n\tamount: number\r\n\tcurrent_balance: number\r\n\tchange_type: string\r\n\ttype: string\r\n\trelated_id: string | null\r\n\tremark: string | null\r\n\tcreated_at: string\r\n}\r\n\r\ntype StatsType = {\r\n\ttotalRecharge: number\r\n\ttotalConsume: number\r\n\ttotalWithdraw: number\r\n}\r\n\r\nconst balance = ref<number>(0)\r\nconst stats = ref<StatsType>({\r\n\ttotalRecharge: 0,\r\n\ttotalConsume: 0,\r\n\ttotalWithdraw: 0\r\n})\r\nconst transactions = ref<Array<TransactionType>>([])\r\nconst activeFilter = ref<string>('all')\r\nconst isLoading = ref<boolean>(false)\r\nconst currentPage = ref<number>(1)\r\nconst pageSize = ref<number>(20)\r\nconst hasMore = ref<boolean>(true)\r\nconst showRechargePopup = ref<boolean>(false)\r\nconst rechargeAmount = ref<string>('')\r\nconst quickAmounts = [50, 100, 200, 500, 1000]\r\n\r\n// 获取当前用户ID\r\nconst getCurrentUserId = (): string => {\r\n\tconst userStore = uni.getStorageSync('userInfo')\r\n\tif (userStore == null) return ''\r\n\tconst userInfo = userStore as UTSJSONObject\r\n\treturn userInfo.getString('id') ?? ''\r\n}\r\n\r\n// 重置交易记录\r\nconst resetTransactions = (): void => {\r\n\ttransactions.value = []\r\n\tcurrentPage.value = 1\r\n\thasMore.value = true\r\n}\r\n\r\n// 加载余额信息\r\nconst loadBalance = async (): Promise<void> => {\r\n try {\r\n const realBalance = await supabaseService.getUserBalanceNumber()\r\n balance.value = realBalance\r\n \r\n const statsData: StatsType = {\r\n totalRecharge: 0,\r\n totalConsume: 0,\r\n totalWithdraw: 0\r\n } as StatsType\r\n stats.value = statsData\r\n } catch (err) {\r\n console.error('加载钱包异常:', err)\r\n }\r\n}\r\n\r\n// 加载交易记录\r\nconst loadTransactions = async (loadMore: boolean): Promise<void> => {\r\n\tif (isLoading.value || (hasMore.value === false && loadMore)) {\r\n\t\treturn\r\n\t}\r\n\r\n\tisLoading.value = true\r\n\r\n\ttry {\r\n const userId = getCurrentUserId()\r\n if (userId == '') {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const page = loadMore ? currentPage.value + 1 : 1\r\n const limit = 20\r\n \r\n const data = await supabaseService.getTransactions(page, limit)\r\n \r\n const mappedData: Array<TransactionType> = []\r\n for (let i: number = 0; i < data.length; i++) {\r\n const item = data[i]\r\n let id = ''\r\n let amount = 0\r\n let balanceAfter = 0\r\n let type = ''\r\n let remark = ''\r\n let createdAt = ''\r\n \r\n if (item instanceof UTSJSONObject) {\r\n id = item.getString('id') ?? ''\r\n amount = item.getNumber('amount') ?? 0\r\n balanceAfter = item.getNumber('balance_after') ?? 0\r\n type = item.getString('type') ?? 'consume'\r\n remark = item.getString('description') ?? ''\r\n createdAt = item.getString('created_at') ?? ''\r\n } else {\r\n const itemObj = item as UTSJSONObject\r\n id = itemObj.getString('id') ?? ''\r\n amount = itemObj.getNumber('amount') ?? 0\r\n balanceAfter = itemObj.getNumber('balance_after') ?? 0\r\n type = itemObj.getString('type') ?? 'consume'\r\n remark = itemObj.getString('description') ?? ''\r\n createdAt = itemObj.getString('created_at') ?? ''\r\n }\r\n \r\n const transaction: TransactionType = {\r\n id: id,\r\n user_id: userId,\r\n change_amount: amount,\r\n amount: amount,\r\n current_balance: balanceAfter,\r\n change_type: type,\r\n type: type,\r\n related_id: null,\r\n remark: remark,\r\n created_at: createdAt\r\n } as TransactionType\r\n mappedData.push(transaction)\r\n }\r\n \r\n if (loadMore) {\r\n for (let i: number = 0; i < mappedData.length; i++) {\r\n transactions.value.push(mappedData[i])\r\n }\r\n currentPage.value = page\r\n } else {\r\n transactions.value = mappedData\r\n currentPage.value = 1\r\n }\r\n \r\n hasMore.value = mappedData.length >= limit\r\n } catch (err) {\r\n console.error('加载交易记录失败:', err)\r\n } finally {\r\n isLoading.value = false\r\n }\r\n}\r\n\r\n// 加载钱包数据\r\nconst loadWalletData = async (): Promise<void> => {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId == '') {\r\n\t\treturn\r\n\t}\r\n\r\n\tloadBalance()\r\n\tloadTransactions(false)\r\n}\r\n\r\n// 计算属性\r\nconst canRecharge = computed((): boolean => {\r\n\tconst amount = parseFloat(rechargeAmount.value)\r\n\tif (amount == null || amount < 10 || amount > 5000) {\r\n\t\treturn false\r\n\t}\r\n\treturn true\r\n})\r\n\r\n// 监听过滤器变化\r\nwatch(activeFilter, () => {\r\n\tresetTransactions()\r\n\tloadTransactions(false)\r\n})\r\n\r\n// 生命周期\r\nonShow(() => {\r\n\tloadWalletData()\r\n})\r\n\r\n// 获取交易图标\r\nconst getTransactionIcon = (type: string): string => {\r\n\tif (type === 'recharge') return '💳'\r\n\tif (type === 'consume') return '🛒'\r\n\tif (type === 'withdraw') return '🏦'\r\n\tif (type === 'refund') return '🔄'\r\n\tif (type === 'reward') return '🎁'\r\n\tif (type === 'income') return '💰'\r\n\tif (type === 'expense') return '📤'\r\n\treturn '💰'\r\n}\r\n\r\n// 获取交易标题\r\nconst getTransactionTitle = (type: string): string => {\r\n\tif (type === 'recharge') return '账户充值'\r\n\tif (type === 'consume') return '商品消费'\r\n\tif (type === 'withdraw') return '余额提现'\r\n\tif (type === 'refund') return '订单退款'\r\n\tif (type === 'reward') return '活动奖励'\r\n\tif (type === 'income') return '收入'\r\n\tif (type === 'expense') return '支出'\r\n\treturn '交易'\r\n}\r\n\r\n// 格式化时间\r\nconst formatTime = (timeStr: string): string => {\r\n\tconst date = new Date(timeStr)\r\n\tconst month = (date.getMonth() + 1).toString().padStart(2, '0')\r\n\tconst day = date.getDate().toString().padStart(2, '0')\r\n\tconst hours = date.getHours().toString().padStart(2, '0')\r\n\tconst minutes = date.getMinutes().toString().padStart(2, '0')\r\n\treturn `${month}-${day} ${hours}:${minutes}`\r\n}\r\n\r\n// 显示更多操作\r\nconst showMoreActions = () => {\r\n\tuni.showActionSheet({\r\n\t\titemList: ['交易记录', '安全设置', '帮助中心'],\r\n\t\tsuccess: (res) => {\r\n\t\t\tswitch (res.tapIndex) {\r\n\t\t\t\tcase 0:\r\n\t\t\t\t\t// 交易记录已经在当前页\r\n\t\t\t\t\tbreak\r\n\t\t\t\tcase 1:\r\n\t\t\t\t\tuni.navigateTo({\r\n\t\t\t\t\t\turl: '/pages/mall/consumer/settings'\r\n\t\t\t\t\t})\r\n\t\t\t\t\tbreak\r\n\t\t\t\tcase 2:\r\n\t\t\t\t\tuni.navigateTo({\r\n\t\t\t\t\t\turl: '/pages/info/help'\r\n\t\t\t\t\t})\r\n\t\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 充值\r\nconst recharge = () => {\r\n\tshowRechargePopup.value = true\r\n\trechargeAmount.value = ''\r\n}\r\n\r\n// 提现\r\nconst withdraw = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/withdraw'\r\n\t})\r\n}\r\n\r\n// 跳转到优惠券\r\nconst goToCoupons = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/coupons'\r\n\t})\r\n}\r\n\r\n// 跳转到红包\r\nconst goToRedPackets = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/red-packets/index'\r\n\t})\r\n}\r\n\r\n// 跳转到积分\r\nconst goToPoints = () => {\r\n // 使用统一的积分页面\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/points/index'\r\n\t})\r\n}\r\n\r\n// 跳转到银行卡\r\nconst goToBankCards = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/bank-cards/index'\r\n\t})\r\n}\r\n\r\n// 切换过滤器\r\nconst changeFilter = (filter: string) => {\r\n\tactiveFilter.value = filter\r\n}\r\n\r\n// 加载更多\r\nconst loadMore = () => {\r\n\tif (hasMore.value && isLoading.value === false) {\r\n\t\tloadTransactions(true)\r\n\t}\r\n}\r\n\r\n// 选择快捷金额\r\nconst selectQuickAmount = (amount: number): void => {\r\n\trechargeAmount.value = amount.toString()\r\n}\r\n\r\n// 关闭充值弹窗\r\nconst closeRechargePopup = (): void => {\r\n\tshowRechargePopup.value = false\r\n\trechargeAmount.value = ''\r\n}\r\n\r\n// 确认充值\r\nconst confirmRecharge = async (): Promise<void> => {\r\n\tif (canRecharge.value === false) return\r\n\r\n\tconst amount = parseFloat(rechargeAmount.value)\r\n\tif (amount == null || amount < 10 || amount > 5000) return\r\n\r\n uni.showLoading({ title: '处理中...' })\r\n try {\r\n const success = await supabaseService.rechargeBalance(amount)\r\n if (success) {\r\n uni.showToast({\r\n title: '充值成功',\r\n icon: 'success'\r\n })\r\n closeRechargePopup()\r\n loadWalletData()\r\n } else {\r\n uni.showToast({\r\n title: '充值失败',\r\n icon: 'none'\r\n })\r\n }\r\n } catch (e) {\r\n console.error('充值异常:', e)\r\n uni.showToast({\r\n title: '系统异常,请稍后重试',\r\n icon: 'none'\r\n })\r\n } finally {\r\n uni.hideLoading()\r\n }\r\n}\r\n\r\n// 返回\r\nconst goBack = (): void => {\r\n\tuni.navigateBack()\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n/* 基础样式 */\r\n.wallet-page {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tflex: 1; /* Fixed 100vh */\r\n\tbackground-color: #f5f5f5;\r\n}\r\n\r\n.wallet-content {\r\n\tflex: 1;\r\n}\r\n\r\n.dashboard-container {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tpadding-bottom: 20px;\r\n}\r\n\r\n.dashboard-main {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.dashboard-side {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n/* 响应式布局优化 */\r\n@media screen and (min-width: 768px) {\r\n\t.wallet-content {\r\n\t\tpadding: 20px;\r\n\t\tbackground-color: #f5f5f5;\r\n\t}\r\n\r\n\t.dashboard-container {\r\n\t\tmax-width: 800px;\r\n\t\tmargin: 0 auto;\r\n\t\twidth: 100%;\r\n\t}\r\n\t\r\n\t.balance-overview, .assets-stats, .quick-actions, .transactions-section, .security-tips {\r\n\t\tborder-radius: 12px;\r\n\t}\r\n\t\r\n\t.popup-content {\r\n\t\twidth: 400px;\r\n\t\tleft: 50%;\r\n\t\tbottom: 50%;\r\n\t\ttransform: translate(-50%, 50%);\r\n\t\tborder-radius: 15px;\r\n\t}\r\n}\r\n\r\n@media screen and (min-width: 1024px) {\r\n\t.wallet-page {\r\n\t\tflex-direction: column; /* 保持纵向,内容区内部处理横向 */\r\n\t}\r\n\t\r\n\t.wallet-content {\r\n\t\twidth: 100%;\r\n\t\tmax-width: 1200px;\r\n\t\tmargin: 0 auto;\r\n\t}\r\n\t\r\n\t.dashboard-container {\r\n\t\tflex-direction: row; /* 横向排列 */\r\n\t\talign-items: flex-start;\r\n\t\t/* gap: 20px; REMOVED */\r\n\t\t/* max-width: 100%; REMOVED */\r\n\t}\r\n\t\r\n\t.dashboard-main {\r\n\t\twidth: 400px; /* 左侧固定宽度 */\r\n\t\tflex-shrink: 0;\r\n margin-right: 20px; /* REPLACED gap */\r\n\t}\r\n\t\r\n\t.dashboard-side {\r\n\t\tflex: 1; /* 右侧自适应 */\r\n\t\tmin-width: 0;\r\n\t}\r\n\t\r\n\t/* 调整各模块间距 */\r\n\t.balance-overview, \r\n\t.assets-stats, \r\n\t.quick-actions, \r\n\t.security-tips {\r\n\t\tmargin-bottom: 20px;\r\n\t}\r\n\t\r\n\t.transactions-section {\r\n\t\tmargin-top: 0; /* 移除顶部间距,与左侧对齐 */\r\n\t\theight: 100%;\r\n\t\tmin-height: 600px; /* 保证右侧高度 */\r\n\t}\r\n}\r\n\r\n/* 模块样式 */\r\n.balance-overview {\r\n\tbackground: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n\tpadding: 30px 20px;\r\n\tcolor: #ffffff;\r\n}\r\n\r\n.balance-label {\r\n\t/* display: block; REMOVED */\r\n\tfont-size: 14px;\r\n\topacity: 0.9;\r\n\tmargin-bottom: 10px;\r\n\ttext-align: center;\r\n}\r\n\r\n.balance-value {\r\n\t/* display: block; REMOVED */\r\n\tfont-size: 36px;\r\n\tfont-weight: bold;\r\n\tmargin-bottom: 20px;\r\n\ttext-align: center;\r\n}\r\n\r\n.balance-actions {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\t/* gap: 20px; REMOVED */\r\n}\r\n\r\n.action-btn {\r\n\tflex: 1;\r\n\theight: 40px;\r\n\tborder-radius: 20px;\r\n\tfont-size: 14px;\r\n\tfont-weight: bold;\r\n\tborder: none;\r\n}\r\n\r\n.action-btn.recharge {\r\n\tbackground-color: #ffffff;\r\n\tcolor: #667eea;\r\n margin-right: 20px; /* REPLACED gap */\r\n}\r\n\r\n.action-btn.withdraw {\r\n\tbackground-color: rgba(255, 255, 255, 0.2);\r\n\tcolor: #ffffff;\r\n\tborder: 1px solid rgba(255, 255, 255, 0.5);\r\n}\r\n\r\n.assets-stats {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 20px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.stat-item {\r\n\tflex: 1;\r\n\ttext-align: center;\r\n}\r\n\r\n.stat-label {\r\n\t/* display: block; REMOVED */\r\n\tfont-size: 12px;\r\n\tcolor: #666666;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.stat-value {\r\n\t/* display: block; REMOVED */\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.quick-actions {\r\n\tbackground-color: #ffffff;\r\n\tmargin-top: 10px;\r\n\tpadding: 20px;\r\n}\r\n\r\n.action-grid {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n}\r\n\r\n.action-item {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tflex: 1;\r\n}\r\n\r\n.action-icon {\r\n\tfont-size: 28px;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.action-text {\r\n\tfont-size: 12px;\r\n\tcolor: #666666;\r\n}\r\n\r\n.transactions-section {\r\n\tbackground-color: #ffffff;\r\n\tmargin-top: 10px;\r\n\tpadding: 15px;\r\n}\r\n\r\n.section-header {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.section-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.filter-tabs {\r\n\t/* gap: 15px; REMOVED */\r\n}\r\n\r\n.filter-tab {\r\n\tfont-size: 14px;\r\n\tcolor: #666666;\r\n\tpadding: 5px 0;\r\n\tposition: relative;\r\n margin-right: 15px; /* REPLACED gap */\r\n border-bottom: 2px solid transparent; /* Prepare for active state */\r\n}\r\n\r\n.filter-tab.active {\r\n\tcolor: #007aff;\r\n\tfont-weight: bold;\r\n border-bottom: 2px solid #007aff; /* REPLACED ::after */\r\n}\r\n\r\n/* ::after removed */\r\n\r\n.empty-transactions {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpadding: 40px 20px;\r\n}\r\n\r\n.empty-icon {\r\n\tfont-size: 60px;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.empty-text {\r\n\tfont-size: 16px;\r\n\tcolor: #666666;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.empty-subtext {\r\n\tfont-size: 14px;\r\n\tcolor: #999999;\r\n}\r\n\r\n.transactions-list {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.transaction-item {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: flex-start;\r\n\tpadding: 15px 0;\r\n\tborder-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.transaction-item:last-child {\r\n\tborder-bottom: none;\r\n}\r\n\r\n.transaction-left {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: flex-start;\r\n}\r\n\r\n.transaction-icon {\r\n\tfont-size: 24px;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.transaction-info {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.transaction-title {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tfont-weight: bold;\r\n\tmargin-bottom: 5px;\r\n}\r\n\r\n.transaction-time {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n\tmargin-bottom: 3px;\r\n}\r\n\r\n.transaction-remark {\r\n\tfont-size: 12px;\r\n\tcolor: #666666;\r\n}\r\n\r\n.transaction-right {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: flex-end;\r\n}\r\n\r\n.transaction-amount {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tmargin-bottom: 5px;\r\n}\r\n\r\n.transaction-amount.income {\r\n\tcolor: #4caf50;\r\n}\r\n\r\n.transaction-amount.expense {\r\n\tcolor: #333333;\r\n}\r\n\r\n.transaction-balance {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n}\r\n\r\n.loading-more,\r\n.no-more {\r\n\tpadding: 20px;\r\n\ttext-align: center;\r\n}\r\n\r\n.loading-text,\r\n.no-more-text {\r\n\tcolor: #999999;\r\n\tfont-size: 14px;\r\n}\r\n\r\n.security-tips {\r\n\tbackground-color: #ffffff;\r\n\tmargin-top: 10px;\r\n\tpadding: 20px;\r\n}\r\n\r\n.tip-title {\r\n\t/* display: block; REMOVED */\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\t/* display: block; REMOVED */\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.tip-item {\r\n\t/* display: block; REMOVED */\r\n\tmargin-bottom: 8px;\r\n\tfont-size: 12px;\r\n\tcolor: #666666;\r\n line-height: 1.6;\r\n}\r\n\r\n.tip-item:last-child {\r\n\tmargin-bottom: 0;\r\n}\r\n\r\n.recharge-popup {\r\n\tposition: fixed;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\tright: 0;\r\n\tbottom: 0;\r\n\tz-index: 999;\r\n}\r\n\r\n.popup-mask {\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\tright: 0;\r\n\tbottom: 0;\r\n\tbackground-color: rgba(0, 0, 0, 0.5);\r\n}\r\n\r\n.popup-content {\r\n\tposition: absolute;\r\n\tbottom: 0;\r\n\tleft: 0;\r\n\tright: 0;\r\n\tbackground-color: #ffffff;\r\n\tborder-top-left-radius: 15px;\r\n\tborder-top-right-radius: 15px;\r\n\tpadding: 20px;\r\n}\r\n\r\n.popup-header {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 20px;\r\n\tpadding-bottom: 15px;\r\n\tborder-bottom: 1px solid #e5e5e5;\r\n}\r\n\r\n.popup-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.popup-close {\r\n\tfont-size: 24px;\r\n\tcolor: #999999;\r\n\tpadding: 5px;\r\n}\r\n\r\n.popup-body {\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.amount-label {\r\n\t/* display: block; REMOVED */\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.amount-input {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tmargin-bottom: 20px;\r\n\tpadding: 10px;\r\n\tborder: 1px solid #e5e5e5;\r\n\tborder-radius: 8px;\r\n}\r\n\r\n.currency-symbol {\r\n\tfont-size: 20px;\r\n\tcolor: #333333;\r\n\tmargin-right: 10px;\r\n}\r\n\r\n.amount-field {\r\n\tflex: 1;\r\n\tfont-size: 24px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.quick-amounts {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\t/* gap: 10px; REMOVED */\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.quick-amount {\r\n\tpadding: 8px 15px;\r\n\tborder: 1px solid #e5e5e5;\r\n\tborder-radius: 15px;\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n margin-right: 10px; /* REPLACED gap */\r\n margin-bottom: 10px; /* REPLACED gap */\r\n}\r\n\r\n.quick-amount.active {\r\n\tbackground-color: #007aff;\r\n\tcolor: #ffffff;\r\n\tborder-color: #007aff;\r\n}\r\n\r\n.recharge-tip {\r\n\t/* display: block; REMOVED */\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n}\r\n\r\n.popup-footer {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\t/* gap: 15px; REMOVED */\r\n}\r\n\r\n.cancel-btn,\r\n.confirm-btn {\r\n\tflex: 1;\r\n\theight: 45px;\r\n\tborder-radius: 22.5px;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tborder: none;\r\n}\r\n\r\n.cancel-btn {\r\n\tbackground-color: #f5f5f5;\r\n\tcolor: #666666;\r\n margin-right: 15px; /* REPLACED gap */\r\n}\r\n\r\n.confirm-btn {\r\n\tbackground-color: #007aff;\r\n\tcolor: #ffffff;\r\n}\r\n\r\n.confirm-btn.disabled {\r\n\tbackground-color: #cccccc;\r\n\topacity: 0.6;\r\n}\r\n</style>\r\n","<template>\r\n <view class=\"page-container\">\r\n <view class=\"card\">\r\n <view class=\"section-title\">提现至</view>\r\n \r\n <view class=\"bank-selector\" @click=\"openBankSelector\">\r\n <view class=\"bank-info\" v-if=\"selectedBank != null\">\r\n <text class=\"bank-name\">{{ selectedBank.bank_name }}</text>\r\n <text class=\"card-type\">储蓄卡</text>\r\n <text class=\"card-no\">尾号 {{ getTailNumber(selectedBank.card_number) }}</text>\r\n </view>\r\n <view class=\"bank-info placeholder\" v-else>\r\n <text>请选择到账银行卡</text>\r\n </view>\r\n <text class=\"arrow\">></text>\r\n </view>\r\n\r\n <view class=\"amount-section\">\r\n <text class=\"label\">提现金额</text>\r\n <view class=\"input-wrapper\">\r\n <text class=\"currency\">¥</text>\r\n <input \r\n class=\"amount-input\"\r\n type=\"digit\"\r\n v-model=\"amount\"\r\n placeholder=\"请输入提现金额\"\r\n />\r\n </view>\r\n <view class=\"balance-line\">\r\n <text class=\"balance-text\">当前可提现余额 ¥{{ balance }}</text>\r\n <text class=\"all-btn\" @click=\"setAll\">全部提现</text>\r\n </view>\r\n </view>\r\n\r\n <button \r\n class=\"submit-btn\" \r\n :disabled=\"isValid === false\" \r\n :loading=\"loading\"\r\n @click=\"submitWithdraw\"\r\n >\r\n {{ loading ? '处理中...' : '确认提现' }}\r\n </button>\r\n </view>\r\n\r\n <!-- 简单弹窗选择银行卡 -->\r\n <view v-if=\"showBankPopup\" class=\"popup-mask\" @click=\"showBankPopup = false\">\r\n <view class=\"popup-content\" @click.stop>\r\n <view class=\"popup-header\">\r\n <text class=\"popup-title\">选择到账银行卡</text>\r\n <text class=\"close-btn\" @click=\"showBankPopup = false\">×</text>\r\n </view>\r\n <scroll-view scroll-y=\"true\" class=\"bank-list\">\r\n <view \r\n v-for=\"(item, index) in bankCards\" \r\n :key=\"index\"\r\n class=\"bank-item\"\r\n @click=\"selectBank(item)\"\r\n >\r\n <view class=\"bank-row\">\r\n <text class=\"bank-name-popup\">{{ item.bank_name }}</text>\r\n <text class=\"card-no-popup\">({{ getTailNumber(item.card_number) }})</text>\r\n </view>\r\n <text v-if=\"selectedBank != null && selectedBank.id == item.id\" class=\"check\">✓</text>\r\n </view>\r\n <view class=\"add-card-btn\" @click=\"navigateToAddCard\">\r\n <text>+ 添加银行卡</text>\r\n </view>\r\n </scroll-view>\r\n </view>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, computed, onMounted } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype BankCard = {\r\n id: string\r\n bank_name: string\r\n card_number: string\r\n}\r\n\r\nconst amount = ref('')\r\nconst balance = ref(0.00)\r\nconst loading = ref(false)\r\nconst bankCards = ref<BankCard[]>([])\r\nconst selectedBank = ref<BankCard | null>(null)\r\nconst showBankPopup = ref(false)\r\n\r\nconst isValid = computed((): boolean => {\r\n const val = parseFloat(amount.value)\r\n // 检查 val 是否有效(替代 isNaN\r\n if (val == null || val <= 0) return false\r\n if (val > balance.value) return false\r\n if (selectedBank.value == null) return false\r\n return true\r\n})\r\n\r\nconst loadData = async (): Promise<void> => {\r\n try {\r\n const bal = await supabaseService.getUserBalanceNumber()\r\n balance.value = bal\r\n \r\n const res = await supabaseService.getUserBankCards()\r\n const list: Array<BankCard> = []\r\n for(let i: number = 0; i < res.length; i++) {\r\n const item = res[i]\r\n \r\n let id = ''\r\n let bankName = ''\r\n let cardNum = ''\r\n \r\n if (item instanceof UTSJSONObject) {\r\n id = item.getString('id') ?? ''\r\n bankName = item.getString('bank_name') ?? ''\r\n cardNum = item.getString('card_number') ?? ''\r\n } else {\r\n const itemObj = item as UTSJSONObject\r\n id = itemObj.getString('id') ?? ''\r\n bankName = itemObj.getString('bank_name') ?? ''\r\n cardNum = itemObj.getString('card_number') ?? ''\r\n }\r\n\r\n if (id != '') {\r\n const card: BankCard = {\r\n id: id,\r\n bank_name: bankName,\r\n card_number: cardNum\r\n } as BankCard\r\n list.push(card)\r\n }\r\n }\r\n \r\n bankCards.value = list\r\n if (bankCards.value.length > 0) {\r\n selectedBank.value = bankCards.value[0]\r\n }\r\n } catch (e) {\r\n console.error(e)\r\n }\r\n}\r\n\r\nonMounted(() => {\r\n loadData()\r\n})\r\n\r\nconst getTailNumber = (cardNo: string | null): string => {\r\n if (cardNo == null) return ''\r\n if (cardNo.length <= 4) return cardNo\r\n return cardNo.substring(cardNo.length - 4)\r\n}\r\n\r\nconst setAll = () => {\r\n amount.value = balance.value.toString()\r\n}\r\n\r\nconst openBankSelector = () => {\r\n showBankPopup.value = true\r\n}\r\n\r\nconst selectBank = (bank: BankCard) => {\r\n selectedBank.value = bank\r\n showBankPopup.value = false\r\n}\r\n\r\nconst navigateToAddCard = () => {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/bank-cards/add'\r\n })\r\n showBankPopup.value = false\r\n}\r\n\r\nconst submitWithdraw = async () => {\r\n if (isValid.value === false) return\r\n loading.value = true\r\n \r\n try {\r\n const val = parseFloat(amount.value)\r\n const success = await supabaseService.withdrawBalance(val)\r\n \r\n if (success) {\r\n uni.showToast({\r\n title: '提现申请已提交',\r\n icon: 'success'\r\n })\r\n setTimeout(() => {\r\n uni.navigateBack()\r\n }, 1500)\r\n } else {\r\n uni.showToast({\r\n title: '提现失败, ' + (val > balance.value ? '余额不足' : '请重试'),\r\n icon: 'none'\r\n })\r\n }\r\n } catch (e) {\r\n uni.showToast({\r\n title: '系统异常',\r\n icon: 'none'\r\n })\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.page-container {\r\n background-color: #f5f5f5;\r\n flex: 1; /* Fixed 100vh issue */\r\n padding: 20px;\r\n}\r\n.card {\r\n background-color: #fff;\r\n border-radius: 12px;\r\n padding: 20px;\r\n}\r\n.section-title {\r\n font-size: 16px;\r\n color: #333;\r\n margin-bottom: 15px;\r\n}\r\n.bank-selector {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 15px 0;\r\n border-bottom: 1px solid #eee;\r\n}\r\n.bank-info {\r\n display: flex;\r\n align-items: center;\r\n /* gap removed */\r\n}\r\n.bank-name {\r\n margin-right: 10px;\r\n font-weight: bold;\r\n}\r\n.card-type {\r\n margin-right: 10px;\r\n}\r\n.placeholder {\r\n color: #999;\r\n}\r\n.amount-section {\r\n margin-top: 20px;\r\n}\r\n.label {\r\n font-size: 14px;\r\n color: #666;\r\n margin-bottom: 10px;\r\n /* display: block removed */\r\n}\r\n.input-wrapper {\r\n display: flex;\r\n align-items: center;\r\n border-bottom: 1px solid #eee;\r\n padding-bottom: 10px;\r\n margin-bottom: 10px;\r\n}\r\n.currency {\r\n font-size: 30px;\r\n font-weight: bold;\r\n margin-right: 10px;\r\n}\r\n.amount-input {\r\n flex: 1;\r\n font-size: 30px;\r\n font-weight: bold;\r\n height: 40px;\r\n}\r\n.balance-line {\r\n display: flex;\r\n justify-content: space-between;\r\n font-size: 12px;\r\n}\r\n.balance-text {\r\n color: #999;\r\n}\r\n.all-btn {\r\n color: #5785e5;\r\n}\r\n.submit-btn {\r\n margin-top: 40px;\r\n background-color: #5785e5;\r\n color: #fff;\r\n border-radius: 25px;\r\n}\r\n.submit-btn:disabled {\r\n background-color: #ccc;\r\n}\r\n\r\n.popup-mask {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background-color: rgba(0,0,0,0.5);\r\n z-index: 999;\r\n display: flex;\r\n justify-content: center;\r\n align-items: flex-end;\r\n}\r\n.popup-content {\r\n background-color: #fff;\r\n width: 100%;\r\n border-top-left-radius: 16px;\r\n border-top-right-radius: 16px;\r\n padding: 20px;\r\n min-height: 300px;\r\n}\r\n.popup-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 20px;\r\n}\r\n.popup-title {\r\n font-size: 16px;\r\n font-weight: bold;\r\n}\r\n.close-btn {\r\n font-size: 20px;\r\n color: #999;\r\n padding: 5px;\r\n}\r\n.bank-item {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 15px 0;\r\n border-bottom: 1px solid #f5f5f5;\r\n}\r\n.add-card-btn {\r\n padding: 15px 0;\r\n text-align: center;\r\n color: #5785e5;\r\n font-weight: bold;\r\n}\r\n</style>\r\n","<template>\r\n\t<view class=\"search-page\">\r\n\t\t<!-- 搜索头部 -->\r\n\t\t<view class=\"search-header\" :style=\"{ paddingTop: statusBarHeight + 'px' }\">\r\n\t\t\t<view class=\"search-bar-container\">\r\n\t\t\t\t<!-- 返回按钮:使用转义字符的直接形式 -->\r\n\t\t\t\t<view class=\"back-btn\" @click=\"goBack\">\r\n\t\t\t\t\t<text class=\"back-icon\"></text>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<!-- 搜索框 -->\r\n\t\t\t\t<view class=\"search-input-container\">\r\n\t\t\t\t\t<input\r\n\t\t\t\t\t\tclass=\"search-input\"\r\n\t\t\t\t\t\ttype=\"text\"\r\n\t\t\t\t\t\t:value=\"searchKeyword\"\r\n\t\t\t\t\t\t@input=\"onInput\"\r\n\t\t\t\t\t\t@confirm=\"onSearch\"\r\n\t\t\t\t\t\tplaceholder=\"请输入商品名称、店铺\"\r\n\t\t\t\t\t\tplaceholder-class=\"placeholder\"\r\n\t\t\t\t\t\t:focus=\"autoFocus\"\r\n\t\t\t\t\t/>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 清除按钮 -->\r\n\t\t\t\t\t<view v-if=\"searchKeyword\" class=\"clear-btn\" @click=\"clearSearch\">\r\n\t\t\t\t\t\t<text class=\"clear-icon\">×</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 相机图标 -->\r\n\t\t\t\t\t<view class=\"camera-btn\" @click=\"openCamera\">\r\n\t\t\t\t\t\t<text class=\"camera-icon\">📷</text>\r\n\t\t\t\t\t</view>\r\n\r\n\t\t\t\t\t<!-- 搜索按钮:移入输入框内部 -->\r\n\t\t\t\t\t<view class=\"inner-search-btn\" @click=\"onSearch\">\r\n\t\t\t\t\t\t<text class=\"inner-search-text\">搜索</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<!-- 错误状态(模拟服务器超时) -->\r\n\t\t<view v-if=\"isError\" class=\"error-state\" @click=\"retryLoad\">\r\n\t\t\t<view class=\"error-content\">\r\n\t\t\t\t<text class=\"error-icon\">⚠️</text>\r\n\t\t\t\t<text class=\"error-title\">加载服务器超时</text>\r\n\t\t\t\t<text class=\"error-desc\">请点击屏幕重试</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<!-- 主内容区域:使用 scroll-view 支持安卓端滚动 -->\r\n\t\t<scroll-view \r\n\t\t\tv-else \r\n\t\t\tclass=\"main-content\"\r\n\t\t\tdirection=\"vertical\"\r\n\t\t\t:scroll-y=\"true\"\r\n\t\t\t:show-scrollbar=\"false\"\r\n\t\t>\r\n\t\t\t<!-- 初始状态(无搜索词) -->\r\n\t\t\t<view v-if=\"searchKeyword == '' && showResults == false\">\r\n\t\t\t\t<!-- 搜索历史 -->\r\n\t\t\t\t<view v-if=\"searchHistory.length > 0\" class=\"search-history\">\r\n\t\t\t\t\t<view class=\"section-header\">\r\n\t\t\t\t\t\t<text class=\"section-title\">搜索历史</text>\r\n\t\t\t\t\t\t<view class=\"header-right\" @click=\"clearHistory\">\r\n\t\t\t\t\t\t\t<text class=\"clear-text\">清空</text>\r\n\t\t\t\t\t\t\t<text class=\"clear-icon-trash\">🗑️</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"history-tags\">\r\n\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\tv-for=\"(item, index) in searchHistory\"\r\n\t\t\t\t\t\t\t:key=\"index\"\r\n\t\t\t\t\t\t\tclass=\"history-tag\"\r\n\t\t\t\t\t\t\t@click=\"searchFromHistory(item)\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t<text class=\"history-text\">{{ item }}</text>\r\n\t\t\t\t\t\t\t<view class=\"delete-tag-btn\" @click.stop=\"deleteHistoryItem(index)\">\r\n\t\t\t\t\t\t\t\t<text class=\"delete-icon\">×</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\r\n\t\t\t\t<!-- 热门搜索 -->\r\n\t\t\t\t<view class=\"hot-search\">\r\n\t\t\t\t\t<view class=\"section-header\">\r\n\t\t\t\t\t\t<text class=\"section-title\">热门搜索</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"hot-tags\">\r\n\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\tv-for=\"(item, index) in hotSearchList\"\r\n\t\t\t\t\t\t\t:key=\"index\"\r\n\t\t\t\t\t\t\tclass=\"hot-tag\"\r\n\t\t\t\t\t\t\t:class=\"item.hot == true ? 'hot' : ''\"\r\n\t\t\t\t\t\t\t@click=\"searchFromHot(item.keyword)\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t<text class=\"hot-rank\" :class=\"index < 3 ? 'top-three' : ''\">{{ index + 1 }}</text>\r\n\t\t\t\t\t\t\t<text class=\"hot-text\">{{ item.keyword }}</text>\r\n\t\t\t\t\t\t\t<text v-if=\"item.hot == true\" class=\"hot-icon\">🔥</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<!-- 猜你需要 (新增功能) -->\r\n\t\t\t\t<view class=\"guess-you-like\">\r\n\t\t\t\t\t<view class=\"section-header\">\r\n\t\t\t\t\t\t<view class=\"title-with-icon\">\r\n\t\t\t\t\t\t\t<text class=\"section-icon\">✨</text>\r\n\t\t\t\t\t\t\t<text class=\"section-title\">猜你需要</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"refresh-btn\" @click=\"refreshGuessList\">换一批</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<view class=\"guess-grid\">\r\n\t\t\t\t\t\t<view \r\n\t\t\t\t\t\t\tv-for=\"item in guessList\" \r\n\t\t\t\t\t\t\t:key=\"item.id\" \r\n\t\t\t\t\t\t\tclass=\"guess-item\"\r\n\t\t\t\t\t\t\t@click=\"viewProductDetail(item)\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t<image class=\"guess-img\" :src=\"item.image\" mode=\"aspectFill\" />\r\n\t\t\t\t\t\t\t<text class=\"guess-name\" :lines=\"2\">{{ item.name }}</text>\r\n\t\t\t\t\t\t\t<view class=\"guess-bottom\">\r\n\t\t\t\t\t\t\t\t<text class=\"guess-price\">¥{{ item.price }}</text>\r\n\t\t\t\t\t\t\t\t<view class=\"guess-add-btn\" @click.stop=\"addToCart(item)\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"guess-add-icon\">+</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 搜索建议 -->\r\n\t\t\t<view v-if=\"searchKeyword != '' && showResults == false\" class=\"search-suggestions\">\r\n\t\t\t\t<view class=\"suggestions-list\">\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tv-for=\"(suggestion, index) in searchSuggestions\"\r\n\t\t\t\t\t\t:key=\"index\"\r\n\t\t\t\t\t\tclass=\"suggestion-item\"\r\n\t\t\t\t\t\t@click=\"selectSuggestion(suggestion)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<view class=\"suggestion-icon\">🔍</view>\r\n\t\t\t\t\t\t<text class=\"suggestion-text\">{{ suggestion }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 搜索结果 -->\r\n\t\t\t<view v-if=\"showResults\" class=\"search-results\">\r\n\t\t\t\t<!-- 店铺搜索结果 -->\r\n\t\t\t\t<view v-if=\"searchShopResults.length > 0\" class=\"shop-results-section\">\r\n\t\t\t\t\t<view class=\"section-top\">\r\n\t\t\t\t\t\t<text class=\"result-title-sm\">相关店铺</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<scroll-view direction=\"horizontal\" class=\"shop-list-scroll\">\r\n\t\t\t\t\t\t<view class=\"shop-list-row\">\r\n\t\t\t\t\t\t\t<view \r\n\t\t\t\t\t\t\t\tv-for=\"shop in searchShopResults\" \r\n\t\t\t\t\t\t\t\t:key=\"shop.id\" \r\n\t\t\t\t\t\t\t\tclass=\"shop-card\"\r\n\t\t\t\t\t\t\t\t@click=\"viewShopDetail(shop)\"\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t<image class=\"shop-logo\" :src=\"shop.logo\" mode=\"aspectFill\" />\r\n\t\t\t\t\t\t\t\t<view class=\"shop-info\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"shop-name-txt\">{{ shop.name }}</text>\r\n\t\t\t\t\t\t\t\t\t<text class=\"shop-products-txt\">共{{ shop.productCount }}件商品</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</scroll-view>\r\n\t\t\t\t</view>\r\n\r\n\t\t\t\t<view class=\"results-header\">\r\n\t\t\t\t\t<text class=\"results-title\">商品结果</text>\r\n\t\t\t\t\t<view class=\"filter-tabs\">\r\n\t\t\t\t\t\t<text \r\n\t\t\t\t\t\t\tclass=\"filter-tab\" \r\n\t\t\t\t\t\t\t:class=\"{ active: activeSort === 'default' }\"\r\n\t\t\t\t\t\t\t@click=\"switchSort('default')\"\r\n\t\t\t\t\t\t>综合</text>\r\n\t\t\t\t\t\t<text \r\n\t\t\t\t\t\t\tclass=\"filter-tab\" \r\n\t\t\t\t\t\t\t:class=\"{ active: activeSort === 'sales' }\"\r\n\t\t\t\t\t\t\t@click=\"switchSort('sales')\"\r\n\t\t\t\t\t\t>销量</text>\r\n\t\t\t\t\t\t<text \r\n\t\t\t\t\t\t\tclass=\"filter-tab\" \r\n\t\t\t\t\t\t\t:class=\"{ active: activeSort === 'price' }\"\r\n\t\t\t\t\t\t\t@click=\"switchSort('price')\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t价格 {{ activeSort === 'price' ? (priceSortAsc ? '↑' : '↓') : '' }}\r\n\t\t\t\t\t\t</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<view v-if=\"searchResults.length > 0\" class=\"results-list\">\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tv-for=\"product in searchResults\"\r\n\t\t\t\t\t\t:key=\"product.id\"\r\n\t\t\t\t\t\tclass=\"result-item\"\r\n\t\t\t\t\t\t@click=\"viewProductDetail(product)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<image class=\"product-image\" :src=\"product.image\" mode=\"aspectFill\" />\r\n\t\t\t\t\t\t<text class=\"product-name\" :lines=\"2\">{{ product.name }}</text>\r\n\t\t\t\t\t\t<view class=\"product-bottom\">\r\n\t\t\t\t\t\t\t<text class=\"product-price\">¥{{ product.price }}</text>\r\n\t\t\t\t\t\t\t<view class=\"product-add-btn\" @click.stop=\"addToCart(product)\">\r\n\t\t\t\t\t\t\t\t<text class=\"add-icon\">+</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<!-- 空结果 - 仅在非加载状态且无结果时显示 -->\r\n\t\t\t\t<view v-if=\"!loading && searchResults.length === 0\" class=\"empty-result\">\r\n\t\t\t\t\t<text class=\"empty-icon\">🤔</text>\r\n\t\t\t\t\t<text class=\"empty-text\">未找到相关商品</text>\r\n\t\t\t\t\t<text class=\"empty-sub\">换个关键词试试吧</text>\r\n\t\t\t\t</view>\r\n\r\n\t\t\t\t<!-- 加载更多/加载中 - 在加载状态或有更多数据时显示 -->\r\n\t\t\t\t<view v-if=\"loading\" class=\"loading-more\">\r\n\t\t\t\t\t<view class=\"loading-spinner\"></view>\r\n\t\t\t\t\t<text class=\"loading-text\">加载中...</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<view v-if=\"!hasMore && searchResults.length > 0\" class=\"no-more\">\r\n\t\t\t\t\t<text class=\"no-more-text\">--- 到底了 ---</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 底部安全区域 -->\r\n\t\t\t<view class=\"safe-area\"></view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, reactive, onMounted, computed } from 'vue'\r\nimport { onPageScroll, onReachBottom } from '@dcloudio/uni-app'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\nimport type { Product } from '@/utils/supabaseService.uts'\r\n\r\n// 状态定义\r\nconst statusBarHeight = ref(0)\r\nconst scrollHeight = ref(0)\r\nconst searchKeyword = ref('')\r\nconst showResults = ref(false)\r\nconst loading = ref(false)\r\nconst hasMore = ref(true)\r\nconst isError = ref(false) // 错误状态控制\r\nconst autoFocus = ref(true)\r\n\r\nconst activeSort = ref('default') // 当前排序方式: default, sales, price\r\nconst priceSortAsc = ref(false) // 价格排序是否为升序\r\n\r\ntype HotSearchItemType = {\r\n\tkeyword: string\r\n\thot: boolean\r\n}\r\n\r\ntype GuessItemType = {\r\n\tid: string\r\n\tname: string\r\n\tprice: number\r\n\timage: string\r\n\tsales: number\r\n\tmerchant_id: string\r\n}\r\n\r\ntype SearchResultType = {\r\n\tid: string\r\n\tname: string\r\n\timage: string\r\n\tprice: number\r\n\tspecification: string\r\n\ttag: string\r\n\tsales: number\r\n\tmerchant_id: string\r\n}\r\n\r\ntype ShopResultType = {\r\n\tid: string\r\n\tname: string\r\n\tlogo: string\r\n\tproductCount: number\r\n}\r\n\r\nconst searchHistory = ref<Array<string>>([])\r\nconst hotSearchList = ref<Array<HotSearchItemType>>([])\r\nconst guessList = ref<Array<GuessItemType>>([])\r\nconst allGuessItems = ref<Array<GuessItemType>>([])\r\nconst searchResults = ref<Array<SearchResultType>>([])\r\nconst searchShopResults = ref<Array<ShopResultType>>([])\r\n\r\nconst loadSearchHistory = () => {\r\n\tconst history = uni.getStorageSync('searchHistory')\r\n\tif (history != null) {\r\n\t\ttry {\r\n\t\t\tconst parsed = JSON.parse(history as string)\r\n\t\t\tif (Array.isArray(parsed)) {\r\n\t\t\t\tsearchHistory.value = parsed as string[]\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\tsearchHistory.value = []\r\n\t\t}\r\n\t}\r\n}\r\n\r\nconst saveSearchHistory = () => {\r\n\tuni.setStorageSync('searchHistory', JSON.stringify(searchHistory.value))\r\n}\r\n\r\nconst addToHistory = (keyword: string) => {\r\n\tif (keyword == '') return\r\n\tconst index = searchHistory.value.indexOf(keyword)\r\n\tif (index > -1) {\r\n\t\tsearchHistory.value.splice(index, 1)\r\n\t}\r\n\tsearchHistory.value.unshift(keyword)\r\n\tif (searchHistory.value.length > 10) searchHistory.value.pop()\r\n\tsaveSearchHistory()\r\n}\r\n\r\nconst clearHistory = () => {\r\n\tuni.showModal({\r\n\t\ttitle: '提示',\r\n\t\tcontent: '确定清空搜索历史吗?',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tsearchHistory.value = []\r\n\t\t\t\tuni.removeStorageSync('searchHistory')\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\nconst deleteHistoryItem = (index: number) => {\r\n\tsearchHistory.value.splice(index, 1)\r\n\tsaveSearchHistory()\r\n}\r\n\r\nconst refreshGuessListItems = () => {\r\n if (allGuessItems.value.length > 0) {\r\n const arr: Array<GuessItemType> = []\r\n for (let i: number = 0; i < allGuessItems.value.length; i++) {\r\n arr.push(allGuessItems.value[i])\r\n }\r\n for (let i: number = arr.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1))\r\n const temp = arr[i]\r\n arr[i] = arr[j]\r\n arr[j] = temp\r\n }\r\n const result: Array<GuessItemType> = []\r\n const limit = arr.length < 6 ? arr.length : 6\r\n for (let i: number = 0; i < limit; i++) {\r\n result.push(arr[i])\r\n }\r\n guessList.value = result\r\n }\r\n}\r\n\r\nconst loadData = async (): Promise<void> => {\r\n\tisError.value = false\r\n\t\r\n\ttry {\r\n loadSearchHistory()\r\n \r\n // 获取热销商品,失败时使用空数组\r\n let hotProducts: Product[] = []\r\n try {\r\n const hotResult = await supabaseService.getHotProducts(30)\r\n hotProducts = hotResult as Product[]\r\n } catch (hotError) {\r\n console.error('获取热销商品失败,使用空列表:', hotError)\r\n hotProducts = []\r\n }\r\n \r\n const hotList: Array<HotSearchItemType> = []\r\n const limit1 = hotProducts.length < 10 ? hotProducts.length : 10\r\n for (let i: number = 0; i < limit1; i++) {\r\n const p = hotProducts[i]\r\n const item: HotSearchItemType = {\r\n keyword: p.name ?? '',\r\n hot: true\r\n }\r\n hotList.push(item)\r\n }\r\n hotSearchList.value = hotList\r\n \r\n const allItems: Array<GuessItemType> = []\r\n for (let i: number = 0; i < hotProducts.length; i++) {\r\n const p = hotProducts[i]\r\n const saleCount = p.sale_count\r\n const item: GuessItemType = {\r\n id: p.id ?? '',\r\n name: p.name ?? '',\r\n price: p.base_price ?? 0,\r\n image: p.main_image_url ?? '/static/default.jpg',\r\n sales: saleCount != null ? saleCount : 0,\r\n merchant_id: p.merchant_id ?? ''\r\n }\r\n allItems.push(item)\r\n }\r\n allGuessItems.value = allItems\r\n \r\n refreshGuessListItems()\r\n\r\n\t} catch (e) {\r\n\t\tconsole.error('Load data failed', e)\r\n\t\t// 不再显示错误页面,允许使用空数据\r\n\t\tisError.value = false\r\n\t}\r\n}\r\n\r\nconst retryLoad = () => {\r\n\tuni.showLoading({ title: '重新加载中' })\r\n\tsetTimeout(() => {\r\n\t\tuni.hideLoading()\r\n\t\tloadData()\r\n\t}, 1000)\r\n}\r\n\r\nconst searchSuggestions = ref<Array<string>>([])\r\nlet suggestTimer: number = 0\r\n\r\nconst fetchSuggestions = async (kw: string): Promise<void> => {\r\n if (kw == '' || showResults.value) return\r\n \r\n try {\r\n const res = await supabaseService.searchProducts(kw.trim(), 1, 5)\r\n if (res.data != null && res.data.length > 0) {\r\n const names: Array<string> = []\r\n for (let i: number = 0; i < res.data.length; i++) {\r\n const p = res.data[i]\r\n let name = ''\r\n if (p instanceof UTSJSONObject) {\r\n name = p.getString('name') ?? ''\r\n } else {\r\n const pObj = p as UTSJSONObject\r\n name = pObj.getString('name') ?? ''\r\n }\r\n let found = false\r\n for (let j: number = 0; j < names.length; j++) {\r\n if (names[j] === name) {\r\n found = true\r\n break\r\n }\r\n }\r\n if (found === false && name !== '') {\r\n names.push(name)\r\n }\r\n }\r\n searchSuggestions.value = names\r\n } else {\r\n searchSuggestions.value = []\r\n }\r\n } catch(e) {\r\n searchSuggestions.value = []\r\n }\r\n}\r\n\r\nconst currentPage = ref<number>(1)\r\n\r\nconst performSearch = async (): Promise<void> => {\r\n\tshowResults.value = true\r\n\tloading.value = true\r\n\tcurrentPage.value = 1\r\n\t\r\n\tconst keyword = searchKeyword.value.trim()\r\n\tif (keyword == '') {\r\n\t\tloading.value = false\r\n\t\treturn\r\n\t}\r\n\t\r\n\tconsole.log('Search execution started for keyword:', keyword)\r\n\t\r\n\tlet sortBy = 'sales'\r\n\tlet ascending = false\r\n\tif (activeSort.value === 'price') {\r\n\t\tsortBy = 'price'\r\n\t\tascending = priceSortAsc.value\r\n\t} else if (activeSort.value === 'default') {\r\n sortBy = 'default'\r\n }\r\n\t\r\n try {\r\n console.log('Calling searchProducts with params:', keyword, currentPage.value, sortBy, ascending)\r\n const prodResp = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)\r\n console.log('searchProducts response received:', prodResp.data != null ? prodResp.data.length : 0, 'items')\r\n \r\n let shopList: Array<ShopResultType> = []\r\n if (currentPage.value === 1 && activeSort.value === 'default') {\r\n const shopResp = await supabaseService.searchShops(keyword)\r\n if (shopResp.data != null && shopResp.data.length > 0) {\r\n for (let i: number = 0; i < shopResp.data.length; i++) {\r\n const s = shopResp.data[i]\r\n const shopItem: ShopResultType = {\r\n id: s.id ?? '',\r\n name: s.shop_name ?? '',\r\n logo: s.shop_logo ?? '/static/shop_logo_default.png',\r\n productCount: s.product_count ?? 0\r\n }\r\n shopList.push(shopItem)\r\n }\r\n }\r\n }\r\n searchShopResults.value = shopList\r\n\r\n const prodData = prodResp.data != null ? prodResp.data : []\r\n const resultList: Array<SearchResultType> = []\r\n for (let i: number = 0; i < prodData.length; i++) {\r\n const p = prodData[i] as Product\r\n let tag = ''\r\n const tagsRaw = p.tags\r\n if (tagsRaw != null) {\r\n try {\r\n const tagsStr = p.tags\r\n if (tagsStr != null) {\r\n const tags = JSON.parse(tagsStr as string)\r\n if (Array.isArray(tags) && tags.length > 0) {\r\n const firstTag = tags[0]\r\n tag = firstTag != null ? (firstTag as string) : ''\r\n }\r\n }\r\n } catch(e) {}\r\n }\r\n \r\n const searchItem: SearchResultType = {\r\n id: p.id ?? '',\r\n name: p.name ?? '',\r\n image: p.main_image_url ?? '/static/default.jpg',\r\n price: p.base_price ?? 0,\r\n specification: p.specification ?? '标准规格',\r\n tag: tag,\r\n sales: p.sale_count ?? 0,\r\n merchant_id: p.merchant_id ?? ''\r\n }\r\n resultList.push(searchItem)\r\n }\r\n searchResults.value = resultList\r\n \r\n hasMore.value = prodResp.hasmore\r\n } catch(e) {\r\n console.error('Search failed detailed error:', e)\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n\r\nconst initPage = () => {\r\n\ttry {\r\n\t\tconst systemInfo = uni.getSystemInfoSync()\r\n\t\tstatusBarHeight.value = systemInfo.statusBarHeight ?? 0\r\n\t\tconst windowHeight = systemInfo.windowHeight\r\n\t\tscrollHeight.value = windowHeight - (60 + statusBarHeight.value)\r\n\t\t\r\n\t\tloadData()\r\n\t\t\r\n\t\tconst pages = getCurrentPages()\r\n\t\tif (pages.length > 0) {\r\n\t\t\tconst currentPageObj = pages[pages.length - 1]\r\n\t\t\t// @ts-ignore\r\n\t\t\tconst options = currentPageObj.options\r\n\t\t\tif (options != null) {\r\n\t\t\t\tconst optObj = options as UTSJSONObject\r\n\t\t\t\tconst kwRaw = optObj.getString('keyword')\r\n\t\t\t\tif (kwRaw != null && kwRaw !== '') {\r\n\t\t\t\t\tconst decoded = decodeURIComponent(kwRaw)\r\n\t\t\t\t\tconst keyword = decoded != null ? decoded : kwRaw\r\n\t\t\t\t\tsearchKeyword.value = keyword\r\n\t\t\t\t\t\r\n\t\t\t\t\tconst typeVal = optObj.getString('type')\r\n\t\t\t\t\tif (typeVal === 'family' || typeVal === 'brand') {\r\n\t\t\t\t\t\tif (typeVal === 'family') {\r\n\t\t\t\t\t\t addToHistory(keyword)\r\n }\r\n\t\t\t\t\t\tshowResults.value = true\r\n\t\t\t\t\t\tloading.value = true\r\n\t\t\t\t\t\tperformSearch()\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t} catch (e) {\r\n\t\tconsole.error('初始化失败', e)\r\n\t\tisError.value = true\r\n\t}\r\n}\r\n\r\nonMounted(() => {\r\n\tinitPage()\r\n})\r\n\r\nconst onInput = (e: any) => {\r\n\ttry {\r\n\t\tlet val = ''\r\n\t\t// 处理 input 事件的不同事件对象格式\r\n\t\tif (e != null) {\r\n\t\t\t// UTSJSONObject 格式 (e.detail.value)\r\n\t\t\tif (e instanceof UTSJSONObject) {\r\n\t\t\t\tconst eObj = e as UTSJSONObject\r\n\t\t\t\tconst detailObj = eObj.get('detail')\r\n\t\t\t\tif (detailObj != null && detailObj instanceof UTSJSONObject) {\r\n\t\t\t\t\tconst detail = detailObj as UTSJSONObject\r\n\t\t\t\t\tconst v = detail.get('value')\r\n\t\t\t\t\tval = v != null ? (v as string) : ''\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// 尝试转换为 UTSJSONObject\r\n\t\t\t\tconst eObj = JSON.parse(JSON.stringify(e)) as UTSJSONObject\r\n\t\t\t\tconst detailObj = eObj.get('detail')\r\n\t\t\t\tif (detailObj != null) {\r\n\t\t\t\t\tconst detail = detailObj as UTSJSONObject\r\n\t\t\t\t\tconst v = detail.get('value')\r\n\t\t\t\t\tval = v != null ? (v as string) : ''\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst v = eObj.get('value')\r\n\t\t\t\t\tval = v != null ? (v as string) : ''\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tsearchKeyword.value = val\r\n\t\tif (val == '') {\r\n\t\t\tshowResults.value = false\r\n\t\t\tsearchSuggestions.value = []\r\n\t\t\treturn\r\n\t\t}\r\n\t\t\r\n\t\tif (suggestTimer > 0) clearTimeout(suggestTimer)\r\n\t\tsuggestTimer = setTimeout(() => {\r\n\t\t\tfetchSuggestions(val)\r\n\t\t}, 300)\r\n\t} catch (err) {\r\n\t\tconsole.error('onInput error:', err)\r\n\t}\r\n}\r\n\r\nconst clearSearch = () => {\r\n\tsearchKeyword.value = ''\r\n\tshowResults.value = false\r\n}\r\n\r\nconst onSearch = () => {\r\n\tif (searchKeyword.value.trim() == '') return\r\n\taddToHistory(searchKeyword.value.trim())\r\n\tperformSearch()\r\n}\r\n\r\nconst searchFromHistory = (keyword: string) => {\r\n\tsearchKeyword.value = keyword\r\n\tperformSearch()\r\n}\r\n\r\nconst searchFromHot = (keyword: string) => {\r\n\tsearchKeyword.value = keyword\r\n\taddToHistory(keyword)\r\n\tperformSearch()\r\n}\r\n\r\nconst selectSuggestion = (suggestion: string) => {\r\n\tsearchKeyword.value = suggestion\r\n\taddToHistory(suggestion)\r\n\tperformSearch()\r\n}\r\n\r\nconst switchSort = (type: string) => {\r\n\tif (type === 'price') {\r\n\t\tif (activeSort.value === 'price') {\r\n\t\t\tpriceSortAsc.value = !priceSortAsc.value\r\n\t\t} else {\r\n\t\t\tactiveSort.value = 'price'\r\n\t\t\tpriceSortAsc.value = true // 默认升序\r\n\t\t}\r\n\t} else {\r\n\t\tactiveSort.value = type\r\n\t}\r\n\t// 重新执行搜索以获取正确排序的数据\r\n\tperformSearch()\r\n}\r\n\r\nconst loadMore = async (): Promise<void> => {\r\n\tif (loading.value || hasMore.value == false || searchKeyword.value.trim() == '') return\r\n\tloading.value = true\r\n\t\r\n\tcurrentPage.value++\r\n\t\r\n\tconst keyword = searchKeyword.value.trim()\r\n\tlet sortBy = 'sales'\r\n\tlet ascending = false\r\n\tif (activeSort.value === 'price') {\r\n\t\tsortBy = 'price'\r\n\t\tascending = priceSortAsc.value\r\n\t} else if (activeSort.value === 'default') {\r\n sortBy = 'default'\r\n }\r\n try {\r\n const response = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)\r\n const respData = response.data != null ? response.data : []\r\n for (let i: number = 0; i < respData.length; i++) {\r\n const p = respData[i] as Product\r\n let tag = ''\r\n const tagsRaw = p.tags\r\n if (tagsRaw != null) {\r\n try {\r\n const tagsStr = p.tags\r\n if (tagsStr != null) {\r\n const tags = JSON.parse(tagsStr as string)\r\n if (Array.isArray(tags) && tags.length > 0) {\r\n const firstTag = tags[0]\r\n tag = firstTag != null ? (firstTag as string) : ''\r\n }\r\n }\r\n } catch(e) {}\r\n }\r\n \r\n const searchItem: SearchResultType = {\r\n id: p.id ?? '',\r\n name: p.name ?? '',\r\n image: p.main_image_url ?? '/static/default.jpg',\r\n price: p.base_price ?? 0,\r\n specification: p.specification ?? '标准规格',\r\n tag: tag,\r\n sales: p.sale_count ?? 0,\r\n merchant_id: p.merchant_id ?? ''\r\n }\r\n searchResults.value.push(searchItem)\r\n }\r\n hasMore.value = response.hasmore\r\n } catch(e) {\r\n console.error('Load more failed', e)\r\n hasMore.value = false\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n\r\nonReachBottom(() => {\r\n if (showResults.value) {\r\n loadMore()\r\n }\r\n})\r\n\r\nconst refreshGuessList = () => {\r\n\tuni.showLoading({ title: '刷新中' })\r\n setTimeout(() => {\r\n refreshGuessListItems()\r\n uni.hideLoading()\r\n }, 500)\r\n}\r\n\r\nconst viewProductDetail = (item: SearchResultType | GuessItemType) => {\r\n\tconst id = (item as GuessItemType).id\r\n\tconst price = (item as GuessItemType).price\r\n\tconst name = (item as GuessItemType).name\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/product-detail?productId=${id}&price=${price}&name=${encodeURIComponent(name)}`\r\n\t})\r\n}\r\n\r\nconst viewShopDetail = (shop: ShopResultType) => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/shop-detail?id=${shop.id}`\r\n })\r\n}\r\n\r\nconst addToCart = async (product: SearchResultType | GuessItemType) => {\r\n uni.showLoading({ title: '检查商品...' })\r\n try {\r\n // 统一转换为 UTSJSONObject 访问属性\r\n const prodObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject\r\n const productId = prodObj.getString('id') ?? ''\r\n const merchantId = prodObj.getString('merchant_id') ?? ''\r\n \r\n // 检查商品是否有SKU\r\n const skus = await supabaseService.getProductSkus(productId)\r\n uni.hideLoading()\r\n \r\n if (skus.length > 0) {\r\n // 有规格,提示并跳转到商品详情页选择规格\r\n uni.showToast({ title: '请选择规格', icon: 'none' })\r\n setTimeout(() => {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/product-detail?id=' + productId\r\n })\r\n }, 500)\r\n } else {\r\n // 无规格,直接加入购物车\r\n uni.showLoading({ title: '添加中...' })\r\n const success = await supabaseService.addToCart(productId, 1, '', merchantId)\r\n uni.hideLoading()\r\n if (success) {\r\n uni.showToast({ title: '已添加到购物车', icon: 'success' })\r\n } else {\r\n uni.showToast({ title: '添加失败,请先登录', icon: 'none' })\r\n }\r\n }\r\n } catch (e) {\r\n console.error('添加到购物车异常', e)\r\n uni.hideLoading()\r\n uni.showToast({ title: '操作异常', icon: 'none' })\r\n }\r\n}\r\n\r\nconst openCamera = () => {\r\n\tuni.chooseImage({\r\n\t\tcount: 1,\r\n\t\tsourceType: ['camera'],\r\n\t\tsuccess: (res) => {\r\n\t\t\tconsole.log('拍摄图片路径:', res.tempFilePaths[0])\r\n\t\t\tuni.showToast({ title: '已启用相机', icon: 'none' })\r\n\t\t},\r\n\t\tfail: (err) => {\r\n\t\t\tconsole.error('启用相机失败', err)\r\n\t\t}\r\n\t})\r\n}\r\n\r\nconst goBack = () => {\r\n\tif (showResults.value) {\r\n\t\t// 如果在搜索结果页,先返回到搜索初始页\r\n\t\tshowResults.value = false\r\n\t\tsearchKeyword.value = ''\r\n\t} else {\r\n\t\t// 如果在搜索初始页,则返回上一页\r\n const pages = getCurrentPages()\r\n if (pages.length > 1) {\r\n\t\t uni.navigateBack()\r\n } else {\r\n // 如果只有一页(由于深链接或重定向),返回首页\r\n uni.switchTab({\r\n url: '/pages/main/index'\r\n })\r\n }\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.search-page {\r\n\twidth: 100%;\r\n\tflex: 1; /* Fixed 100vh */\r\n\tbackground-color: #f5f5f5;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tmin-height: 100vh; /* 确保背景色覆盖全屏 */\r\n}\r\n\r\n/* 店铺搜索结果 */\r\n.shop-results-section {\r\n background-color: #fff;\r\n margin-bottom: 10px;\r\n padding: 10px 0;\r\n}\r\n\r\n.section-top {\r\n padding: 0 12px 10px;\r\n}\r\n\r\n.result-title-sm {\r\n font-size: 14px;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n\r\n.shop-list-scroll {\r\n width: 100%;\r\n white-space: nowrap;\r\n}\r\n\r\n.shop-list-row {\r\n display: flex;\r\n flex-direction: row;\r\n padding: 0 12px;\r\n}\r\n\r\n.shop-card {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n width: 80px;\r\n margin-right: 15px;\r\n background-color: #f9f9f9;\r\n padding: 10px 5px;\r\n border-radius: 8px;\r\n}\r\n\r\n.shop-logo {\r\n width: 48px;\r\n height: 48px;\r\n border-radius: 24px;\r\n margin-bottom: 5px;\r\n border: 1px solid #f0f0f0;\r\n background-color: white;\r\n}\r\n\r\n.shop-info {\r\n width: 100%;\r\n text-align: center;\r\n}\r\n\r\n.shop-name-txt {\r\n font-size: 12px;\r\n color: #333;\r\n width: 100%;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n /* display: block; REMOVED */\r\n margin-bottom: 2px;\r\n}\r\n\r\n.shop-products-txt {\r\n font-size: 10px;\r\n color: #999;\r\n}\r\n\r\n/* 头部样式 */\r\n.search-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding-bottom: 10px;\r\n\r\n\r\n\r\n\tflex-shrink: 0; /* 禁止头部被压缩 */\r\n}\r\n\r\n.search-bar-container {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 必须显式设置 row */\r\n\talign-items: center;\r\n\tpadding: 10px 16px;\r\n box-sizing: border-box;\r\n}\r\n\r\n.back-btn {\r\n\tpadding: 4px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\twidth: 32px; /* 固定宽度防止压缩 */\r\n\theight: 32px;\r\n\tmargin-right: 12px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.back-icon {\r\n\tfont-size: 20px;\r\n\tcolor: #333;\r\n}\r\n\r\n.search-input-container {\r\n\tflex: 1; /* 占据剩余空间 */\r\n\theight: 40px; /*稍微增高一点以容纳按钮*/\r\n\tbackground-color: #f0f0f0;\r\n\tborder-radius: 20px;\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 必须显式设置 row */\r\n\talign-items: center;\r\n\tpadding: 0 4px 0 12px;\r\n}\r\n\r\n.search-input {\r\n\tflex: 1;\r\n\tfont-size: 14px;\r\n\tcolor: #333;\r\n\theight: 100%;\r\n\tbackground-color: transparent; /* 确保背景透明 */\r\n}\r\n\r\n.placeholder {\r\n\tcolor: #999;\r\n}\r\n\r\n.clear-btn {\r\n\tpadding: 4px;\r\n\tmargin-right: 2px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.clear-icon {\r\n\tfont-size: 16px;\r\n\tcolor: #999;\r\n}\r\n\r\n.camera-btn {\r\n\tpadding: 4px 8px 4px 4px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tborder-right-width: 1px; /* UVUE 边框写法 */\r\n\tborder-right-style: solid;\r\n\tborder-right-color: #ddd;\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.camera-icon {\r\n\tfont-size: 20px;\r\n}\r\n\r\n/* 内部搜索按钮样式 */\r\n.inner-search-btn {\r\n\tpadding: 0 16px;\r\n\tbackground-color: #87CEEB;\r\n\tborder-radius: 16px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\theight: 32px;\r\n}\r\n\r\n.inner-search-text {\r\n\tfont-size: 13px;\r\n\tcolor: #ffffff;\r\n\tfont-weight: bold;\r\n}\r\n\r\n/* 内容区域 */\r\n.main-content {\r\n\tflex: 1;\r\n\tpadding: 12px;\r\n\tbox-sizing: border-box;\r\n\theight: 0; /* 配合 flex: 1 实现自适应高度 */\r\n}\r\n\r\n/* 模块通用头部 */\r\n.section-header {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 显式设置 row */\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 12px;\r\n\tmargin-top: 8px;\r\n\twidth: 100%;\r\n}\r\n\r\n.section-title {\r\n\tfont-size: 15px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n\tflex: 1; /* 占据左侧空间 */\r\n}\r\n\r\n.header-right {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 显式设置 row */\r\n\talign-items: center;\r\n\t/* gap: 4px; REMOVED */\r\n\tflex-shrink: 0; /* 防止被压缩 */\r\n}\r\n\r\n.clear-text {\r\n margin-right: 4px; /* REPLACED gap */\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n}\r\n\r\n.clear-icon-trash {\r\n\tfont-size: 14px;\r\n}\r\n\r\n/* 搜索历史 */\r\n.search-history {\r\n\tmargin-bottom: 24px;\r\n\tpadding: 0 4px; /* 微调内边距 */\r\n}\r\n\r\n.history-tags {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 显式设置 row */\r\n\t/* gap: 10px; REMOVED */\r\n\tflex-wrap: wrap; /* 允许换行 */\r\n\tpadding: 0 4px;\r\n\talign-items: center;\r\n}\r\n\r\n.history-tag {\r\n\tbackground-color: #fff;\r\n\tpadding: 6px 12px;\r\n\tborder-radius: 16px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\t/* gap: 6px; REMOVED */\r\n\tflex-shrink: 0; /* 防止被压缩 */\r\n margin-right: 10px; /* REPLACED gap */\r\n margin-bottom: 10px; /* REPLACED gap */\r\n}\r\n\r\n.history-text {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\twhite-space: nowrap;\r\n margin-right: 6px; /* REPLACED gap */\r\n}\r\n\r\n.delete-tag-btn {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\twidth: 16px;\r\n\theight: 16px;\r\n\tborder-radius: 8px;\r\n\tbackground-color: #f0f0f0;\r\n}\r\n\r\n.delete-icon {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n\tline-height: 1;\r\n}\r\n\r\n/* 热门搜索 */\r\n.hot-search {\r\n\tmargin-bottom: 24px;\r\n}\r\n\r\n.hot-tags {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* UVUE 显式设置 row */\r\n\tflex-wrap: wrap; /* 允许换行 */\r\n\t/* gap: 10px; REMOVED */\r\n\tpadding: 0 4px;\r\n}\r\n\r\n.hot-tag {\r\n /* ... existing styles ... */\r\n margin-right: 10px; /* REPLACED gap */\r\n margin-bottom: 10px; /* REPLACED gap */\r\n}\r\n\r\n.hot-tag {\r\n\tbackground-color: #fff;\r\n\tpadding: 6px 12px;\r\n\tborder-radius: 16px; /* 增加圆角,像胶囊一样 */\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tflex-shrink: 0; /* 防止被压缩 */\r\n\tmargin-right: 10px; /* REPLACED gap */\r\n margin-bottom: 10px; /* REPLACED gap */\r\n}\r\n\r\n.hot-tag.hot {\r\n\tbackground-color: #fff0f0;\r\n}\r\n\r\n.hot-rank {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n\tfont-weight: bold;\r\n\tmargin-right: 6px;\r\n}\r\n\r\n.hot-rank.top-three {\r\n\tcolor: #ff5000;\r\n}\r\n\r\n.hot-text {\r\n\tfont-size: 13px;\r\n\tcolor: #333;\r\n}\r\n\r\n.hot-icon {\r\n\tfont-size: 12px;\r\n\tmargin-left: 4px;\r\n}\r\n\r\n/* 猜你需要 */\r\n.guess-you-like {\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.title-with-icon {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n}\r\n\r\n.section-icon {\r\n\tfont-size: 16px;\r\n\tmargin-right: 6px;\r\n}\r\n\r\n.refresh-btn {\r\n\tfont-size: 12px;\r\n\tcolor: #4CAF50;\r\n}\r\n\r\n.guess-grid {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: space-between;\r\n\tpadding: 0 4px;\r\n}\r\n\r\n.guess-item {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tbackground: #fff;\r\n\tborder-radius: 8px;\r\n\toverflow: hidden;\r\n\twidth: 48%; /* 手机端 2列 */\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n/* 猜测列表响应式,参照 index.uvue 的 hot-products */\r\n@media screen and (min-width: 769px) {\r\n .guess-item {\r\n width: 32%; /* 平板 3列 */\r\n }\r\n}\r\n\r\n@media screen and (min-width: 1025px) {\r\n .guess-item {\r\n width: 23%; /* 电脑 4列 */\r\n }\r\n}\r\n\r\n.guess-img {\r\n\twidth: 100%;\r\n\theight: 170px;\r\n\tborder-radius: 8px;\r\n\tmargin-bottom: 8px;\r\n\tbackground: #f5f5f5;\r\n\tobject-fit: cover;\r\n}\r\n\r\n.guess-name {\r\n\tfont-size: 13px;\r\n\tcolor: #333;\r\n\tmargin-bottom: 5px;\r\n\tline-height: 1.4;\r\n\theight: 36px;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\tpadding: 0 8px;\r\n}\r\n\r\n.guess-bottom {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 0 8px 8px;\r\n}\r\n\r\n.guess-price {\r\n\tfont-size: 15px;\r\n\tcolor: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.guess-add-btn {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tbackground-color: #ff5000;\r\n\tborder-radius: 12px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.guess-add-icon {\r\n\tcolor: #fff;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n/* 搜索建议列表 */\r\n.search-suggestions {\r\n\tbackground-color: #fff;\r\n\tborder-radius: 8px;\r\n\tpadding: 0 12px;\r\n}\r\n\r\n.suggestion-item {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\theight: 44px;\r\n\tborder-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.suggestion-icon {\r\n\tmargin-right: 10px;\r\n\tfont-size: 14px;\r\n\tcolor: #999;\r\n}\r\n\r\n.suggestion-text {\r\n\tfont-size: 14px;\r\n\tcolor: #333;\r\n}\r\n\r\n.results-header {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 10px 12px;\r\n\tbackground-color: #fff;\r\n\tmargin-bottom: 2px;\r\n}\r\n\r\n.results-title {\r\n\tfont-size: 15px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.filter-tabs {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: flex-start;\r\n}\r\n\r\n.filter-tab {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tpadding: 8px 12px;\r\n\tborder-radius: 20px;\r\n\tborder: 1px solid #e0e0e0;\r\n\ttransition: all 0.2s ease;\r\n\twhite-space: nowrap;\r\n\ttext-align: center;\r\n\tdisplay: flex;\r\n\tjustify-content: center;\r\n\talign-items: center;\r\n\tmargin-left: 8px;\r\n}\r\n\r\n.filter-tab.active {\r\n\tbackground: #ff5000;\r\n\tcolor: white;\r\n\tborder-color: #ff5000;\r\n}\r\n\r\n.filter-tab:hover {\r\n\tbackground: #f5f5f5;\r\n}\r\n\r\n/* 搜索结果列表 */\r\n.search-results {\r\n\tpadding-bottom: 20px;\r\n\twidth: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: flex-start; /* 核心修复:确保内容不居中缩进 */\r\n}\r\n\r\n.results-header {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 10px 12px;\r\n\tbackground-color: #fff;\r\n\tmargin-bottom: 2px;\r\n width: 100%; /* 核心修复:确保标题栏撑满宽度 */\r\n box-sizing: border-box;\r\n}\r\n\r\n.results-list {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: flex-start;\r\n\tpadding: 10px;\r\n\twidth: 100%;\r\n\tbox-sizing: border-box;\r\n margin-top: 5px;\r\n background-color: #fff; /* 为列表添加背景色 */\r\n}\r\n\r\n.result-item {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tbackground: #fff;\r\n\tborder-radius: 8px;\r\n\toverflow: hidden;\r\n\t/* width: calc(50% - 20px); 手机端一行2个 */\r\n width: 48%;\r\n\tmargin-bottom: 12px;\r\n margin-right: 2%;\r\n border: 1px solid #f0f0f0; /* 添加微弱边框增加层次感 */\r\n}\r\n\r\n/* 电脑端响应式覆盖 - 强制拉伸宽度 */\r\n@media screen and (min-width: 1025px) {\r\n .main-content {\r\n width: 1200px; /* 改为固定宽度或更大百分比 */\r\n max-width: 95%; \r\n margin: 0 auto;\r\n padding: 20px 32px;\r\n }\r\n\r\n .result-item {\r\n width: 23%; /* 4列布局 */\r\n margin-right: 2%;\r\n }\r\n}\r\n\r\n/* 大桌面端 (1400px以上) */\r\n@media screen and (min-width: 1400px) {\r\n\t.result-item {\r\n\t\twidth: 23%; /* 保持一行4个或者根据需要调整为 18% (一行5个) */\r\n\t}\r\n}\r\n\r\n.product-image {\r\n\twidth: 100%;\r\n height: 170px; /* 与主页一致 */\r\n\t/* aspect-ratio: 1 / 1; */\r\n\tobject-fit: cover;\r\n\tbackground-color: #f5f5f5;\r\n border-radius: 8px;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.product-name {\r\n\tfont-size: 13px;\r\n\tcolor: #333;\r\n\tmargin-bottom: 5px;\r\n\tline-height: 1.4;\r\n\theight: 36px;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\tpadding: 0 8px;\r\n}\r\n\r\n.product-bottom {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 0 8px 8px;\r\n}\r\n\r\n.product-price {\r\n\tfont-size: 15px;\r\n\tcolor: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.product-add-btn {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tbackground-color: #ff5000;\r\n\tborder-radius: 12px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.add-icon {\r\n\tcolor: #fff;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n/* 错误状态 */\r\n.error-state {\r\n\tflex: 1;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tbackground-color: #fff;\r\n}\r\n\r\n.error-content {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n}\r\n\r\n.error-icon {\r\n\tfont-size: 48px;\r\n margin-bottom: 12px;\r\n}\r\n\r\n.error-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n margin-bottom: 12px;\r\n}\r\n\r\n.error-desc {\r\n\tfont-size: 14px;\r\n\tcolor: #999;\r\n}\r\n\r\n/* 加载更多 */\r\n.loading-more {\r\n\tpadding: 20px 0;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n}\r\n\r\n.loading-spinner {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tborder: 2px solid #f0f0f0;\r\n\tborder-top-color: #4CAF50;\r\n\tborder-radius: 12px;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.loading-text {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n}\r\n\r\n.no-more {\r\n\tpadding: 20px 0;\r\n\ttext-align: center;\r\n}\r\n\r\n.no-more-text {\r\n\tfont-size: 12px;\r\n\tcolor: #ccc;\r\n}\r\n\r\n.empty-result {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tpadding: 40px 0;\r\n}\r\n\r\n.empty-icon {\r\n\tfont-size: 40px;\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.empty-text {\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n\tmargin-bottom: 4px;\r\n}\r\n\r\n.empty-sub {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n}\r\n\r\n.safe-area {\r\n\theight: 20px;\r\n}\r\n</style>\r\n\r\n","<template>\r\n <view class=\"shop-detail-page\">\r\n <scroll-view class=\"page-scroll\" scroll-y=\"true\" @scrolltolower=\"onScrollToLower\" refresher-enabled=\"true\" @refresherrefresh=\"onRefresherRefresh\" :refresher-triggered=\"isRefresherTriggered\">\r\n <!-- 店铺头部信息 -->\r\n <view class=\"shop-header\">\r\n <image :src=\"merchant.shop_banner != '' ? merchant.shop_banner : '/static/default-banner.png'\" class=\"shop-banner\" mode=\"aspectFill\" />\r\n <view class=\"shop-info-card\">\r\n <image :src=\"merchant.shop_logo != '' ? merchant.shop_logo : '/static/default-shop.png'\" class=\"shop-logo\" />\r\n <view class=\"shop-basic-info\">\r\n <text class=\"shop-name\">{{ merchant.shop_name }}</text>\r\n <view class=\"shop-stats\">\r\n <text class=\"stat-item\">⭐ {{ merchant.rating.toFixed(1) }}</text>\r\n <text class=\"stat-item\">销量 {{ merchant.total_sales }}</text>\r\n </view>\r\n </view>\r\n <view class=\"shop-actions\">\r\n <view class=\"action-btn chat-btn\" @click=\"contactService\">\r\n <text class=\"action-text\">客服</text>\r\n </view>\r\n <view class=\"action-btn follow-btn\" @click=\"toggleFollow\">\r\n <text class=\"action-text\" :class=\"{ followed: isFollowed }\">{{ isFollowed ? '已关注' : '+ 关注' }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n <text class=\"shop-desc\">{{ merchant.shop_description != '' ? merchant.shop_description : '这家店很懒,什么都没写~' }}</text>\r\n \r\n <!-- 优惠券列表 (新增) -->\r\n <view class=\"shop-coupons\" v-if=\"coupons.length > 0\">\r\n <scroll-view scroll-x=\"true\" class=\"coupon-scroll\" show-scrollbar=\"false\">\r\n <view class=\"coupon-wrapper\">\r\n <view class=\"coupon-card\" v-for=\"coupon in coupons\" :key=\"coupon.id\" @click=\"claimCoupon(coupon)\">\r\n <view class=\"coupon-left\">\r\n <text class=\"coupon-amount\"><text style=\"font-size:10px\">¥</text>{{ coupon.discount_value }}</text>\r\n <text class=\"coupon-cond\" v-if=\"coupon.min_order_amount > 0\">满{{ coupon.min_order_amount }}</text>\r\n <text class=\"coupon-cond\" v-else>无门槛</text>\r\n </view>\r\n <view class=\"coupon-right\">\r\n <text class=\"coupon-btn-label\">领取</text>\r\n </view>\r\n </view>\r\n </view>\r\n </scroll-view>\r\n </view>\r\n </view>\r\n\r\n <!-- 商品列表 -->\r\n <view class=\"product-section\">\r\n <view class=\"results-header\">\r\n <text class=\"results-title\">全部商品</text>\r\n <view class=\"filter-tabs\">\r\n <text class=\"filter-tab active\">综合</text>\r\n <text class=\"filter-tab\">销量</text>\r\n <text class=\"filter-tab\">价格</text>\r\n </view>\r\n </view>\r\n \r\n <view class=\"results-list\">\r\n <view v-for=\"product in products\" :key=\"product.id\" class=\"result-item\" @click=\"goToProduct(product.id)\">\r\n <image :src=\"product.images[0]\" class=\"product-image\" mode=\"aspectFill\" />\r\n <text class=\"product-name\" :lines=\"2\">{{ product.name }}</text>\r\n <view class=\"product-bottom\">\r\n <text class=\"product-price\">¥{{ product.price }}</text>\r\n <view class=\"product-add-btn\" @click.stop=\"addToCart(product)\">\r\n <text class=\"add-icon\">+</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </scroll-view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted } from 'vue'\r\nimport { MerchantType, ProductType } from '@/types/mall-types.uts'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\n// 优惠券类型定义\r\ntype CouponType = {\r\n\tid: string\r\n\tdiscount_value: number\r\n\tmin_order_amount: number\r\n\tname: string\r\n\tstart_time: string\r\n\tend_time: string\r\n\tstatus: number\r\n}\r\n\r\n// 分页相关状态\r\nconst currentPage = ref(1)\r\nconst pageSize = ref(6) // 默认显示六个\r\nconst hasMore = ref(true)\r\nconst isLoading = ref(false)\r\nconst currentMerchantId = ref('')\r\n\r\nconst merchant = ref<MerchantType>({\r\n id: '',\r\n user_id: '',\r\n shop_name: '',\r\n shop_logo: '',\r\n shop_banner: '',\r\n shop_description: '',\r\n contact_name: '',\r\n contact_phone: '',\r\n shop_status: 0,\r\n rating: 0,\r\n total_sales: 0,\r\n created_at: ''\r\n} as MerchantType)\r\n\r\nconst products = ref<ProductType[]>([])\r\nconst isFollowed = ref(false)\r\nconst coupons = ref<CouponType[]>([]) // 新增优惠券\r\nconst isRefresherTriggered = ref(false)\r\n\r\n// 函数定义必须在 onMounted 之前\r\n// checkFollowStatus 必须在 loadShopData 之前定义\r\nconst checkFollowStatus = async (shopId: string) => {\r\n const userId = supabaseService.getCurrentUserId()\r\n if (userId != null && userId != '') {\r\n try {\r\n // @ts-ignore\r\n isFollowed.value = await supabaseService.isShopFollowed(shopId, userId)\r\n } catch(e) {\r\n console.warn('isShopFollowed method not found')\r\n }\r\n }\r\n}\r\n\r\nconst loadShopData = async (id: string) => {\r\n console.log('Loading shop data for:', id)\r\n const shop = await supabaseService.getShopByMerchantId(id)\r\n \r\n if (shop != null) {\r\n console.log('Shop loaded successfully:', shop.shop_name)\r\n // 使用显式类型转换\r\n const merchantData: MerchantType = {\r\n id: shop.id,\r\n user_id: shop.merchant_id,\r\n shop_name: shop.shop_name,\r\n shop_logo: shop.shop_logo != null ? shop.shop_logo : '/static/default-shop.png',\r\n shop_banner: shop.shop_banner != null ? shop.shop_banner : '/static/default-banner.png',\r\n shop_description: shop.description != null ? shop.description : '',\r\n contact_name: shop.contact_name != null ? shop.contact_name : '',\r\n contact_phone: shop.contact_phone != null ? shop.contact_phone : '',\r\n shop_status: 1,\r\n rating: shop.rating_avg != null ? shop.rating_avg : 5.0,\r\n total_sales: shop.total_sales != null ? shop.total_sales : 0,\r\n created_at: shop.created_at != null ? shop.created_at : ''\r\n }\r\n merchant.value = merchantData\r\n \r\n // 检查关注状态\r\n checkFollowStatus(shop.id)\r\n } else {\r\n console.warn('Shop data is null for ID:', id)\r\n uni.showToast({\r\n title: '未找到店铺信息',\r\n icon: 'none',\r\n duration: 3000\r\n })\r\n }\r\n}\r\n\r\nconst loadCoupons = async (id: string) => {\r\n try {\r\n // @ts-ignore\r\n const res = await supabaseService.fetchShopCoupons(id)\r\n if (res != null && Array.isArray(res)) {\r\n const couponList: CouponType[] = []\r\n for (let i = 0; i < res.length; i++) {\r\n const item = res[i]\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n couponList.push({\r\n id: itemObj.getString('id') ?? '',\r\n discount_value: itemObj.getNumber('discount_value') ?? 0,\r\n min_order_amount: itemObj.getNumber('min_order_amount') ?? 0,\r\n name: itemObj.getString('name') ?? '',\r\n start_time: itemObj.getString('start_time') ?? '',\r\n end_time: itemObj.getString('end_time') ?? '',\r\n status: itemObj.getNumber('status') ?? 1\r\n } as CouponType)\r\n }\r\n coupons.value = couponList\r\n }\r\n } catch(e1) {\r\n console.warn('SupabaseService.fetchShopCoupons method missing. Please rebuild project.')\r\n }\r\n}\r\n\r\nconst loadShopProducts = async (id: string) => {\r\n if (isLoading.value) return\r\n isLoading.value = true\r\n \r\n // 保存当前使用的MerchantID供下拉/触底使用\r\n if (currentPage.value === 1) {\r\n currentMerchantId.value = id\r\n }\r\n\r\n console.log(`shop-detail loadShopProducts for: ${id} page: ${currentPage.value}`)\r\n \r\n let res: any = {}\r\n try {\r\n // @ts-ignore\r\n res = await supabaseService.getProductsByMerchantId(id, currentPage.value, pageSize.value)\r\n } catch(e) {\r\n console.error('getProductsByMerchantId missing or error', e)\r\n isLoading.value = false\r\n uni.stopPullDownRefresh()\r\n return\r\n }\r\n \r\n console.log(`shop-detail getProductsByMerchantId result count: ${res.data?.length}`)\r\n \r\n const rawList = res.data\r\n if (rawList != null && Array.isArray(rawList) && rawList.length > 0) {\r\n // 过滤掉已经在列表中的重复商品 (防止分页计算错误导致的重复)\r\n const newItems: ProductType[] = []\r\n const existingIds = products.value.map(p => p.id)\r\n \r\n const list = rawList.map((item: any): ProductType => {\r\n // 解析图片数组\r\n let images: string[] = []\r\n \r\n // 转换为 UTSJSONObject 安全访问属性\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n // 1. 尝试 main_image_url\r\n const mainImageUrl = itemObj.getString('main_image_url')\r\n if (mainImageUrl != null && mainImageUrl != '') {\r\n images.push(mainImageUrl)\r\n }\r\n\r\n // 2. 尝试 image_urls (如果 main 为空,或者需要展示多图)\r\n const imageUrls = itemObj.get('image_urls')\r\n if (imageUrls != null) {\r\n try {\r\n if (Array.isArray(imageUrls)) {\r\n const arr = imageUrls as string[]\r\n if (arr.length > 0) {\r\n if (images.length == 0) images.push(...arr)\r\n }\r\n } else if (typeof imageUrls === 'string') {\r\n const rawUrl = imageUrls as string\r\n if (rawUrl.startsWith('[')) {\r\n const parsed = JSON.parse(rawUrl)\r\n if (Array.isArray(parsed)) {\r\n const arr = parsed as string[]\r\n if (images.length == 0) images.push(...arr)\r\n }\r\n } else {\r\n if (images.indexOf(rawUrl) === -1) images.push(rawUrl)\r\n }\r\n }\r\n } catch(e) { \r\n console.error('解析图片数组失败:', e)\r\n }\r\n }\r\n \r\n // 没有任何图片则使用默认\r\n if (images.length === 0) {\r\n images.push('/static/default-product.png')\r\n }\r\n \r\n return {\r\n id: itemObj.getString('id') ?? '',\r\n merchant_id: itemObj.getString('merchant_id') ?? '',\r\n category_id: itemObj.getString('category_id') ?? '',\r\n name: itemObj.getString('name') ?? '未知商品',\r\n description: itemObj.getString('description') ?? '',\r\n images: images,\r\n price: itemObj.getNumber('base_price') ?? 0,\r\n original_price: itemObj.getNumber('market_price') ?? 0,\r\n stock: itemObj.getNumber('total_stock') ?? 0,\r\n sales: itemObj.getNumber('sale_count') ?? 0,\r\n status: 1,\r\n created_at: itemObj.getString('created_at') ?? ''\r\n } as ProductType\r\n })\r\n \r\n // 只有在 currentPage > 1 时才需要过滤currentPage = 1 时直接替换\r\n if (currentPage.value === 1) {\r\n products.value = list\r\n } else {\r\n for (let i = 0; i < list.length; i++) {\r\n if (existingIds.indexOf(list[i].id) === -1) {\r\n newItems.push(list[i])\r\n }\r\n }\r\n if (newItems.length > 0) {\r\n products.value.push(...newItems)\r\n }\r\n }\r\n \r\n currentPage.value++\r\n hasMore.value = list.length >= pageSize.value\r\n } else {\r\n hasMore.value = false\r\n }\r\n \r\n isLoading.value = false\r\n uni.stopPullDownRefresh()\r\n}\r\n\r\nonMounted(() => {\r\n const pages = getCurrentPages()\r\n const options = pages[pages.length - 1].options as UTSJSONObject\r\n // Search传递的是 id (shop_id), 其他地方可能传递 merchantId\r\n const mId = options.get('merchantId')\r\n const pId = options.get('id')\r\n const paramId = (mId != null ? mId : pId) as string\r\n \r\n if (paramId != null && paramId != '') {\r\n console.log('Page mounted with params:', paramId)\r\n // 优先加载店铺信息\r\n loadShopData(paramId).then(() => {\r\n // 加载成功后,使用确定的 merchant_id 来查询关联数据 (商品/优惠券通常是关联在 merchant_id 上的)\r\n const realMerchantId = merchant.value.user_id // 这里 user_id 映射了 DB 中的 merchant_id\r\n if (realMerchantId != null && realMerchantId != '') {\r\n console.log('Chain loading products for Corrected Merchant ID:', realMerchantId)\r\n currentMerchantId.value = realMerchantId // 更新当前上下文ID\r\n loadShopProducts(realMerchantId)\r\n loadCoupons(realMerchantId)\r\n } else {\r\n // 防御性策略:如果没能获取 merchant_id尝试用传入 ID\r\n console.warn('Shop load failed or id empty, fallback using original id:', paramId)\r\n currentMerchantId.value = paramId\r\n loadShopProducts(paramId)\r\n loadCoupons(paramId)\r\n }\r\n })\r\n } else {\r\n console.error('No ID passed to shop-detail')\r\n uni.showToast({title: '参数错误', icon: 'error'})\r\n }\r\n})\r\n\r\nconst onRefresherRefresh = () => {\r\n isRefresherTriggered.value = true\r\n currentPage.value = 1\r\n hasMore.value = true\r\n isLoading.value = false\r\n \r\n if (currentMerchantId.value != '') {\r\n const id = currentMerchantId.value\r\n Promise.all([\r\n loadShopData(id),\r\n loadCoupons(id),\r\n loadShopProducts(id)\r\n ]).then(() => {\r\n isRefresherTriggered.value = false\r\n })\r\n } else {\r\n setTimeout(() => {\r\n isRefresherTriggered.value = false\r\n }, 500)\r\n }\r\n}\r\n\r\nconst onScrollToLower = () => {\r\n if (hasMore.value && !isLoading.value && currentMerchantId.value != '') {\r\n console.log('Scroll to lower, loading more...')\r\n loadShopProducts(currentMerchantId.value)\r\n }\r\n}\r\n\r\nonPullDownRefresh(() => {\r\n onRefresherRefresh()\r\n})\r\n\r\nonReachBottom(() => {\r\n onScrollToLower()\r\n})\r\n\r\nconst claimCoupon = async (coupon: any) => {\r\n const userId = supabaseService.getCurrentUserId()\r\n if (userId == null) {\r\n uni.navigateTo({ url: '/pages/auth/login' })\r\n return\r\n }\r\n uni.showLoading({ title: '领取中' })\r\n \r\n // 转换为 UTSJSONObject 安全访问属性\r\n const couponObj = JSON.parse(JSON.stringify(coupon)) as UTSJSONObject\r\n const couponId = couponObj.getString('id') ?? ''\r\n \r\n let success = false\r\n try {\r\n // @ts-ignore\r\n success = await supabaseService.claimShopCoupon(couponId, userId)\r\n } catch(e1) {\r\n try {\r\n // @ts-ignore\r\n success = await supabaseService.claimCoupon(couponId, userId)\r\n } catch(e2) {\r\n console.warn('claimCoupon not found')\r\n }\r\n }\r\n\r\n uni.hideLoading()\r\n if (success) {\r\n uni.showToast({ title: '领取成功', icon: 'success' })\r\n } else {\r\n uni.showToast({ title: '领取失败', icon: 'none' })\r\n }\r\n}\r\n\r\nconst toggleFollow = async () => {\r\n const userId = supabaseService.getCurrentUserId()\r\n if (userId == null) {\r\n uni.navigateTo({ url: '/pages/auth/login' })\r\n return\r\n }\r\n\r\n // 这里的 merchant.value.id 假如是 ML_SHOPS.id\r\n const shopId = merchant.value.id \r\n if (shopId == null || shopId == '') return\r\n\r\n uni.showLoading({ title: '处理中' })\r\n \r\n // @ts-ignore\r\n if (isFollowed.value) {\r\n // 取消关注\r\n // @ts-ignore\r\n const success = await supabaseService.unfollowShop(shopId, userId)\r\n if (success) {\r\n isFollowed.value = false\r\n uni.showToast({ title: '已取消关注', icon: 'none' })\r\n } else {\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n }\r\n } else {\r\n // 关注\r\n // @ts-ignore\r\n const success = await supabaseService.followShop(shopId, userId)\r\n if (success) {\r\n isFollowed.value = true\r\n uni.showToast({ title: '关注成功', icon: 'success' })\r\n } else {\r\n uni.showToast({ title: '关注失败', icon: 'none' })\r\n }\r\n }\r\n uni.hideLoading()\r\n}\r\n\r\nconst contactService = () => {\r\n const currentUser = supabaseService.getCurrentUserId()\r\n if (currentUser == null) {\r\n uni.navigateTo({ url: '/pages/user/login' })\r\n return\r\n }\r\n\r\n if (merchant.value.user_id != null && merchant.value.user_id != '') {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/chat?merchantId=${merchant.value.user_id}&merchantName=${encodeURIComponent(merchant.value.shop_name)}`\r\n })\r\n } else {\r\n uni.showToast({ title: '无法联系商家', icon: 'none'})\r\n }\r\n}\r\n\r\nconst addToCart = async (product: ProductType) => {\r\n uni.showLoading({ title: '检查商品...' })\r\n \r\n try {\r\n // 使用店铺的 merchant_id\r\n const merchantId = merchant.value.user_id ?? ''\r\n \r\n // 检查商品是否有SKU\r\n const skus = await supabaseService.getProductSkus(product.id)\r\n uni.hideLoading()\r\n \r\n if (skus.length > 0) {\r\n // 有规格,提示并跳转到商品详情页选择规格\r\n uni.showToast({\r\n title: '请选择规格',\r\n icon: 'none'\r\n })\r\n setTimeout(() => {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/product-detail?id=' + product.id\r\n })\r\n }, 500)\r\n } else {\r\n // 无规格,直接加入购物车\r\n uni.showLoading({ title: '添加中...' })\r\n const success = await supabaseService.addToCart(product.id, 1, '', merchantId)\r\n uni.hideLoading()\r\n \r\n if (success) {\r\n uni.showToast({\r\n title: '已添加到购物车',\r\n icon: 'success'\r\n })\r\n } else {\r\n uni.showToast({\r\n title: '添加失败,请重试',\r\n icon: 'none'\r\n })\r\n }\r\n }\r\n } catch (e) {\r\n console.error('添加到购物车异常', e)\r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '操作失败',\r\n icon: 'none'\r\n })\r\n }\r\n}\r\n\r\nconst goToProduct = (id: string) => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/product-detail?productId=${id}`\r\n })\r\n}\r\n</script>\r\n\r\n<style>\r\n.shop-detail-page {\r\n background-color: #f5f5f5;\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.page-scroll {\r\n flex: 1;\r\n height: 0;\r\n width: 100%;\r\n}\r\n\r\n.shop-header {\r\n background-color: #fff;\r\n padding-bottom: 20px;\r\n margin-bottom: 10px;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.shop-banner {\r\n width: 100%;\r\n height: 150px;\r\n background-color: #eee;\r\n}\r\n\r\n.shop-info-card {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: flex-start;\r\n padding: 0 15px;\r\n margin-top: -30px;\r\n position: relative;\r\n z-index: 1;\r\n width: 100%;\r\n box-sizing: border-box;\r\n}\r\n\r\n.shop-logo {\r\n width: 60px;\r\n height: 60px;\r\n border-radius: 8px;\r\n border: 2px solid #fff;\r\n background-color: #fff;\r\n margin-right: 12px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.shop-basic-info {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n padding-top: 35px;\r\n}\r\n\r\n.shop-name {\r\n font-size: 18px;\r\n font-weight: bold;\r\n color: #333;\r\n margin-bottom: 6px;\r\n line-height: 1.2;\r\n}\r\n\r\n.shop-stats {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.stat-item {\r\n font-size: 11px;\r\n color: #666;\r\n margin-right: 8px;\r\n background-color: #f5f5f5;\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n}\r\n\r\n.shop-actions {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n padding-top: 40px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.action-btn {\r\n border-radius: 17px;\r\n margin-left: 8px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 4px 12px;\r\n cursor: pointer;\r\n}\r\n\r\n.action-text {\r\n font-size: 12px;\r\n}\r\n\r\n.chat-btn {\r\n background-color: #ffffff;\r\n border: 1px solid #ddd;\r\n}\r\n\r\n.chat-btn .action-text {\r\n color: #333;\r\n}\r\n\r\n.follow-btn {\r\n background-color: #ff5000;\r\n border: 1px solid #ff5000;\r\n min-width: 60px;\r\n}\r\n\r\n.follow-btn .action-text {\r\n color: #ffffff;\r\n}\r\n\r\n.follow-btn .followed {\r\n opacity: 0.9;\r\n}\r\n\r\n.shop-desc {\r\n color: #999;\r\n padding: 10px 15px 0;\r\n line-height: 1.5;\r\n width: 100%;\r\n box-sizing: border-box;\r\n font-size: 13px;\r\n}\r\n\r\n/* PC 端响应式覆盖 */\r\n@media screen and (min-width: 1025px) {\r\n .shop-header {\r\n align-items: center;\r\n }\r\n \r\n .shop-banner {\r\n height: 300px;\r\n max-width: 1200px;\r\n }\r\n \r\n .shop-info-card {\r\n max-width: 1200px;\r\n margin-top: -40px;\r\n }\r\n \r\n .shop-logo {\r\n width: 100px;\r\n height: 100px;\r\n margin-right: 20px;\r\n }\r\n \r\n .shop-basic-info {\r\n padding-top: 45px;\r\n }\r\n \r\n .shop-name {\r\n font-size: 24px;\r\n margin-bottom: 12px;\r\n }\r\n \r\n .shop-stats .stat-item {\r\n font-size: 14px;\r\n padding: 6px 15px;\r\n margin-right: 15px;\r\n }\r\n \r\n .shop-actions {\r\n padding-top: 50px;\r\n }\r\n \r\n .action-btn {\r\n padding: 8px 24px;\r\n margin-left: 15px;\r\n border-radius: 20px;\r\n }\r\n \r\n .action-text {\r\n font-size: 14px;\r\n }\r\n \r\n .shop-desc {\r\n max-width: 1200px;\r\n font-size: 14px;\r\n padding: 15px 15px;\r\n }\r\n}\r\n\r\n/* Coupon Styles */\r\n.shop-coupons {\r\n margin-top: 20px;\r\n padding: 0 15px;\r\n width: 100%;\r\n max-width: 1200px;\r\n box-sizing: border-box;\r\n}\r\n.coupon-scroll {\r\n width: 100%;\r\n white-space: nowrap;\r\n flex-direction: row;\r\n}\r\n.coupon-wrapper {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: nowrap;\r\n align-items: center;\r\n}\r\n.coupon-card {\r\n display: flex;\r\n flex-direction: row;\r\n background-color: #fff5f5;\r\n border: 1px solid #ffccc7;\r\n border-radius: 4px;\r\n margin-right: 15px;\r\n width: 180px; /* PC 端优惠券加宽 */\r\n height: 70px;\r\n overflow: hidden;\r\n flex-shrink: 0;\r\n cursor: pointer;\r\n}\r\n.coupon-left {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n border-right: 1px dashed #ffccc7;\r\n padding: 0 10px;\r\n}\r\n.coupon-amount {\r\n color: #ff5000;\r\n font-weight: bold;\r\n font-size: 20px;\r\n}\r\n.coupon-cond {\r\n color: #999;\r\n font-size: 12px;\r\n}\r\n.coupon-right {\r\n width: 50px;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background-color: #ff5000;\r\n flex-direction: column;\r\n}\r\n.coupon-btn-label {\r\n color: #fff;\r\n font-size: 14px;\r\n text-align: center;\r\n line-height: 1.2;\r\n}\r\n\r\n.product-section {\r\n padding: 20px 0;\r\n width: 100%;\r\n max-width: 1200px;\r\n margin: 0 auto;\r\n box-sizing: border-box;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: flex-start;\r\n}\r\n\r\n.results-header {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 10px 12px;\r\n\tbackground-color: #fff;\r\n\tmargin-bottom: 2px;\r\n width: 100%;\r\n box-sizing: border-box;\r\n}\r\n\r\n.results-title {\r\n\tfont-size: 15px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n padding-left: 10px;\r\n border-left: 5px solid #ff5000;\r\n}\r\n\r\n.filter-tabs {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: flex-start;\r\n}\r\n\r\n.filter-tab {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tpadding: 8px 12px;\r\n\tborder-radius: 20px;\r\n\tborder: 1px solid #e0e0e0;\r\n\ttransition: all 0.2s ease;\r\n\twhite-space: nowrap;\r\n\ttext-align: center;\r\n\tdisplay: flex;\r\n\tjustify-content: center;\r\n\talign-items: center;\r\n\tmargin-left: 8px;\r\n}\r\n\r\n.filter-tab.active {\r\n\tbackground: #ff5000;\r\n\tcolor: white;\r\n\tborder-color: #ff5000;\r\n}\r\n\r\n.results-list {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: flex-start;\r\n\tpadding: 10px;\r\n\twidth: 100%;\r\n\tbox-sizing: border-box;\r\n margin-top: 5px;\r\n background-color: #fff;\r\n}\r\n\r\n.result-item {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tbackground: #fff;\r\n\tborder-radius: 8px;\r\n\toverflow: hidden;\r\n width: 48%;\r\n\tmargin-bottom: 12px;\r\n margin-right: 2%;\r\n border: 1px solid #f0f0f0;\r\n box-sizing: border-box;\r\n}\r\n\r\n.result-item:nth-child(2n) {\r\n margin-right: 0;\r\n}\r\n\r\n.product-image {\r\n\twidth: 100%;\r\n height: 170px;\r\n\tobject-fit: cover;\r\n\tbackground-color: #f5f5f5;\r\n border-radius: 8px;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.product-name {\r\n\tfont-size: 13px;\r\n\tcolor: #333;\r\n\tmargin-bottom: 5px;\r\n\tline-height: 1.4;\r\n\theight: 36px;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\tpadding: 0 8px;\r\n}\r\n\r\n.product-bottom {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 0 8px 8px;\r\n}\r\n\r\n.product-price {\r\n font-size: 15px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.product-add-btn {\r\n width: 24px;\r\n height: 24px;\r\n background-color: #ff5000;\r\n border-radius: 12px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.add-icon {\r\n color: #fff;\r\n font-size: 16px;\r\n font-weight: bold;\r\n}\r\n\r\n/* 电脑端响应式覆盖 */\r\n@media screen and (min-width: 1025px) {\r\n .product-section {\r\n max-width: 95%; \r\n width: 1200px;\r\n margin: 0 auto;\r\n }\r\n\r\n .result-item {\r\n width: 23%;\r\n margin-right: 2%;\r\n }\r\n \r\n .result-item:nth-child(2n) {\r\n margin-right: 2%;\r\n }\r\n \r\n .result-item:nth-child(4n) {\r\n margin-right: 0;\r\n }\r\n}\r\n\r\n/* 大桌面端 (1400px以上) */\r\n@media screen and (min-width: 1400px) {\r\n\t.result-item {\r\n\t\twidth: 23%;\r\n\t}\r\n}\r\n\r\n.shop-banner {\r\n width: 100%;\r\n height: 200px;\r\n background-color: #f5f5f5;\r\n}\r\n\r\n@media screen and (min-width: 1025px) {\r\n .shop-banner {\r\n height: 300px; /* 大屏加宽 Banner */\r\n border-radius: 0 0 20px 20px;\r\n }\r\n}\r\n</style>\r\n\r\n","<template>\r\n <view class=\"coupons-page\">\r\n <view class=\"coupon-list\">\r\n <view v-if=\"coupons.length === 0\" class=\"empty-state\">\r\n <text class=\"empty-icon\">🎫</text>\r\n <text class=\"empty-text\">暂无优惠券</text>\r\n </view>\r\n \r\n <view v-else v-for=\"(coupon, index) in coupons\" :key=\"index\" class=\"coupon-item\">\r\n <view class=\"coupon-left\">\r\n <text class=\"coupon-amount\">{{ coupon.amount }}</text>\r\n <text class=\"coupon-type\">优惠券</text>\r\n </view>\r\n <view class=\"coupon-right\">\r\n <text class=\"coupon-title\">{{ coupon.title }}</text>\r\n <text class=\"coupon-expiry\">有效期至: {{ coupon.expiry }}</text>\r\n <button class=\"use-btn\" @click=\"useCoupon(coupon)\">去使用</button>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted } from 'vue'\r\nimport supabaseService from '@/utils/supabaseService.uts'\r\nimport type { UserCoupon } from '@/utils/supabaseService.uts'\r\n\r\ntype Coupon = {\r\n title: string\r\n amount: string\r\n expiry: string\r\n id: string\r\n}\r\n\r\nconst coupons = ref<Coupon[]>([])\r\n\r\nconst loadCoupons = async () => {\r\n uni.showLoading({ title: '加载中...' })\r\n try {\r\n const userCoupons = await supabaseService.getUserCoupons(1)\r\n const couponList: Coupon[] = []\r\n for (let i = 0; i < userCoupons.length; i++) {\r\n const item = userCoupons[i]\r\n const amountVal = item.amount ?? 0\r\n const expiryVal = (item.expire_at != null && item.expire_at !== '') \r\n ? item.expire_at.substring(0, 10) \r\n : '长期有效'\r\n const coupon: Coupon = {\r\n id: item.id,\r\n title: (item.template_name != null && item.template_name !== '') ? item.template_name : '优惠券',\r\n amount: `¥${amountVal}`,\r\n expiry: expiryVal\r\n } as Coupon\r\n couponList.push(coupon)\r\n }\r\n coupons.value = couponList\r\n } catch (e) {\r\n console.error('加载优惠券失败', e)\r\n coupons.value = []\r\n } finally {\r\n uni.hideLoading()\r\n }\r\n}\r\n\r\nonMounted(() => {\r\n loadCoupons()\r\n})\r\n\r\nconst useCoupon = (coupon: Coupon) => {\r\n uni.switchTab({\r\n url: '/pages/main/index'\r\n })\r\n}\r\n</script>\r\n\r\n<style>\r\n.coupons-page {\r\n padding: 15px;\r\n background-color: #f5f5f5;\r\n flex: 1;\r\n}\r\n\r\n.empty-state {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding-top: 100px;\r\n}\r\n\r\n.empty-icon {\r\n font-size: 60px;\r\n margin-bottom: 20px;\r\n}\r\n\r\n.empty-text {\r\n font-size: 16px;\r\n color: #999;\r\n}\r\n\r\n.coupon-item {\r\n display: flex;\r\n background-color: white;\r\n border-radius: 8px;\r\n margin-bottom: 15px;\r\n overflow: hidden;\r\n box-shadow: 0 2px 8px rgba(0,0,0,0.05);\r\n}\r\n\r\n.coupon-left {\r\n width: 100px;\r\n background: linear-gradient(135deg, #FF9800, #FF5722);\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n color: white;\r\n padding: 15px;\r\n}\r\n\r\n.coupon-amount {\r\n font-size: 24px;\r\n font-weight: bold;\r\n}\r\n\r\n.coupon-type {\r\n font-size: 12px;\r\n margin-top: 5px;\r\n}\r\n\r\n.coupon-right {\r\n flex: 1;\r\n padding: 15px;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n}\r\n\r\n.coupon-title {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.coupon-expiry {\r\n font-size: 12px;\r\n color: #999;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.use-btn {\r\n align-self: flex-end;\r\n font-size: 12px;\r\n background-color: #FF5722;\r\n color: white;\r\n padding: 4px 12px;\r\n border-radius: 15px;\r\n line-height: 1.5;\r\n}\r\n</style>\r\n","<!-- 收藏页面 -->\r\n<template>\r\n\t<view class=\"favorites-page\">\r\n\t\t<view class=\"favorites-header\">\r\n\t\t\t<view v-if=\"favorites.length > 0\" class=\"header-actions\">\r\n\t\t\t\t<text class=\"action-btn\" @click=\"toggleEditMode\">{{ isEditMode ? '完成' : '编辑' }}</text>\r\n\t\t\t\t<text class=\"action-btn\" @click=\"clearAll\">清空</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<scroll-view class=\"favorites-content\" :scroll-y=\"true\">\r\n\t\t\t<view v-if=\"favorites.length === 0 && !isLoading\" class=\"empty-favorites\">\r\n\t\t\t\t<text class=\"empty-icon\">❤️</text>\r\n\t\t\t\t<text class=\"empty-text\">暂无收藏商品</text>\r\n\t\t\t\t<text class=\"empty-subtext\">快去收藏喜欢的商品吧</text>\r\n\t\t\t\t<button class=\"go-shopping-btn\" @click=\"goShopping\">去逛逛</button>\r\n\t\t\t</view>\r\n\r\n\t\t\t<view class=\"product-group\">\r\n\t\t\t\t<view class=\"group-items\">\r\n\t\t\t\t\t<view v-for=\"(product, index) in favorites\" :key=\"index\" class=\"product-item\">\r\n\t\t\t\t\t\t<view v-if=\"isEditMode\" class=\"item-selector\" @click=\"toggleSelect(product)\">\r\n\t\t\t\t\t\t\t<view :class=\"['select-icon', { selected: product.selected === true }]\">\r\n\t\t\t\t\t\t\t\t<text v-if=\"product.selected === true\" class=\"icon-text\">✓</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"item-content\" @click=\"viewProduct(product)\">\r\n\t\t\t\t\t\t\t<image class=\"product-image\" :src=\"product.main_image_url\" mode=\"aspectFill\" />\r\n\t\t\t\t\t\t\t<text class=\"product-name\" :lines=\"2\">{{ product.name }}</text>\r\n\t\t\t\t\t\t\t<view class=\"product-bottom\">\r\n\t\t\t\t\t\t\t\t<text class=\"product-price\">¥{{ product.price }}</text>\r\n\t\t\t\t\t\t\t\t<view v-if=\"!isEditMode\" class=\"product-add-btn\" @click.stop=\"addToCart(product)\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"add-icon\">+</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<view v-if=\"isLoading\" class=\"loading-more\">\r\n\t\t\t\t<text class=\"loading-text\">加载中...</text>\r\n\t\t\t</view>\r\n\t\t\t<view v-if=\"!isLoading && favorites.length > 0\" class=\"no-more\">\r\n\t\t\t\t<text class=\"no-more-text\">没有更多了</text>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\r\n\t\t<view v-if=\"isEditMode && favorites.length > 0\" class=\"edit-bar\">\r\n\t\t\t<view class=\"select-all\" @click=\"toggleSelectAll\">\r\n\t\t\t\t<view :class=\"['all-select-icon', { selected: isAllSelected }]\">\r\n\t\t\t\t\t<text v-if=\"isAllSelected\" class=\"icon-text\">✓</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<text class=\"select-all-text\">全选</text>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"delete-btn\" @click=\"deleteSelected\">\r\n\t\t\t\t<text class=\"delete-text\">删除({{ selectedCount }})</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted, computed } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype FavoriteType = {\r\n\tid: string\r\n\tname: string\r\n\tprice: number\r\n\tmain_image_url: string\r\n\tmerchant_id: string\r\n\tselected: boolean\r\n}\r\n\r\nconst favorites = ref<FavoriteType[]>([])\r\nconst isEditMode = ref<boolean>(false)\r\nconst isLoading = ref<boolean>(false)\r\n\r\nconst selectedCount = computed((): number => {\r\n\treturn favorites.value.filter((item): Boolean => item.selected === true).length\r\n})\r\n\r\nconst isAllSelected = computed((): boolean => {\r\n\treturn favorites.value.length > 0 && favorites.value.every((item): Boolean => item.selected === true)\r\n})\r\n\r\nconst loadFavorites = async () => {\r\n\tisLoading.value = true\r\n\ttry {\r\n\t\tconst res = await supabaseService.getFavorites()\r\n\t\tconsole.log('收藏数据加载完成,数量:', res.length)\r\n\t\t\r\n\t\tconst productList: FavoriteType[] = []\r\n\t\tfor (let i = 0; i < res.length; i++) {\r\n\t\t\tconst item = res[i]\r\n\t\t\tlet prod: any | null = null\r\n\t\t\tlet itemObj: UTSJSONObject | null = null\r\n\t\t\t\r\n\t\t\tif (item instanceof UTSJSONObject) {\r\n\t\t\t\titemObj = item as UTSJSONObject\r\n\t\t\t\tprod = itemObj.get('ml_products')\r\n\t\t\t} else {\r\n\t\t\t\titemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n\t\t\t\tprod = itemObj.get('ml_products')\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tlet image = '/static/default-product.png'\r\n\t\t\tlet id = ''\r\n\t\t\tlet name = '未知商品'\r\n\t\t\tlet price = 0\r\n\t\t\tlet merchantId = ''\r\n\t\t\t\r\n\t\t\tif (prod != null) {\r\n\t\t\t\tlet prodObj: UTSJSONObject\r\n\t\t\t\tif (prod instanceof UTSJSONObject) {\r\n\t\t\t\t\tprodObj = prod as UTSJSONObject\r\n\t\t\t\t} else {\r\n\t\t\t\t\tprodObj = JSON.parse(JSON.stringify(prod)) as UTSJSONObject\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tid = prodObj.getString('id') ?? ''\r\n\t\t\t\tname = prodObj.getString('name') ?? '未知商品'\r\n\t\t\t\tprice = prodObj.getNumber('base_price') ?? 0\r\n\t\t\t\timage = prodObj.getString('main_image_url') ?? image\r\n\t\t\t\tmerchantId = prodObj.getString('merchant_id') ?? ''\r\n\t\t\t\t\r\n\t\t\t\tif (image === '/static/default-product.png') {\r\n\t\t\t\t\tconst imgUrls = prodObj.getString('image_urls')\r\n\t\t\t\t\tif (imgUrls != null) {\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\tconst arr = JSON.parse(imgUrls)\r\n\t\t\t\t\t\t\tif (Array.isArray(arr) && arr.length > 0) {\r\n\t\t\t\t\t\t\t\timage = arr[0] as string\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} catch(e) {}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif (itemObj != null) {\r\n\t\t\t\t\tid = itemObj.getString('target_id') ?? ''\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tconst product: FavoriteType = {\r\n\t\t\t\tid: id,\r\n\t\t\t\tname: name,\r\n\t\t\t\tprice: price,\r\n\t\t\t\tmain_image_url: image,\r\n\t\t\t\tmerchant_id: merchantId,\r\n\t\t\t\tselected: false\r\n\t\t\t}\r\n\t\t\tproductList.push(product)\r\n\t\t}\r\n\t\tfavorites.value = productList\r\n\t\tconsole.log('收藏列表更新完成,数量:', favorites.value.length)\r\n\t} catch (e) {\r\n\t\tconsole.error('加载收藏失败', e)\r\n\t} finally {\r\n\t\tisLoading.value = false\r\n\t}\r\n}\r\n\r\nconst toggleEditMode = () => {\r\n\tisEditMode.value = !isEditMode.value\r\n\tfor (let i = 0; i < favorites.value.length; i++) {\r\n\t\tfavorites.value[i].selected = false\r\n\t}\r\n}\r\n\r\nconst clearAll = () => {\r\n\tif (favorites.value.length === 0) return\r\n\r\n\tuni.showModal({\r\n\t\ttitle: '清空收藏',\r\n\t\tcontent: '确定要清空所有收藏商品吗?',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tuni.showLoading({ title: '清空中...' })\r\n\t\t\t\t\r\n\t\t\t\tconst productIds: string[] = []\r\n\t\t\t\tfor (let i = 0; i < favorites.value.length; i++) {\r\n\t\t\t\t\tproductIds.push(favorites.value[i].id)\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tlet completed = 0\r\n\t\t\t\t\r\n\t\t\t\tfor (let i = 0; i < productIds.length; i++) {\r\n\t\t\t\t\tsupabaseService.toggleFavorite(productIds[i]).then(() => {\r\n\t\t\t\t\t\tcompleted++\r\n\t\t\t\t\t\tif (completed === productIds.length) {\r\n\t\t\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\t\t\tfavorites.value = []\r\n\t\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\t\ttitle: '已清空',\r\n\t\t\t\t\t\t\t\ticon: 'success'\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}).catch(() => {\r\n\t\t\t\t\t\tcompleted++\r\n\t\t\t\t\t\tif (completed === productIds.length) {\r\n\t\t\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\t\t\tloadFavorites()\r\n\t\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\t\ttitle: '部分清空失败',\r\n\t\t\t\t\t\t\t\ticon: 'none'\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\nconst toggleSelect = (item: FavoriteType) => {\r\n\titem.selected = !(item.selected === true)\r\n\tfavorites.value = [...favorites.value]\r\n}\r\n\r\nconst toggleSelectAll = () => {\r\n\tconst newSelectedState = !isAllSelected.value\r\n\tfor (let i = 0; i < favorites.value.length; i++) {\r\n\t\tfavorites.value[i].selected = newSelectedState\r\n\t}\r\n\tfavorites.value = [...favorites.value]\r\n}\r\n\r\nconst deleteSelected = () => {\r\n\tconst selectedItems = favorites.value.filter((item): Boolean => item.selected === true)\r\n\tif (selectedItems.length === 0) {\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '请选择要删除的商品',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\treturn\r\n\t}\r\n\r\n\tuni.showModal({\r\n\t\ttitle: '删除收藏',\r\n\t\tcontent: `确定要删除选中的 ${selectedItems.length} 个商品吗?`,\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tuni.showLoading({ title: '删除中...' })\r\n\t\t\t\t\r\n\t\t\t\tlet completed = 0\r\n\t\t\t\tconst total = selectedItems.length\r\n\t\t\t\t\r\n\t\t\t\tfor (let i = 0; i < selectedItems.length; i++) {\r\n\t\t\t\t\tsupabaseService.toggleFavorite(selectedItems[i].id).then(() => {\r\n\t\t\t\t\t\tcompleted++\r\n\t\t\t\t\t\tif (completed === total) {\r\n\t\t\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\t\t\tloadFavorites()\r\n\t\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\t\ttitle: '已删除',\r\n\t\t\t\t\t\t\t\ticon: 'success'\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}).catch(() => {\r\n\t\t\t\t\t\tcompleted++\r\n\t\t\t\t\t\tif (completed === total) {\r\n\t\t\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\t\t\tloadFavorites()\r\n\t\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\t\ttitle: '部分删除失败',\r\n\t\t\t\t\t\t\t\ticon: 'none'\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\nconst viewProduct = (product: FavoriteType) => {\r\n\tif (isEditMode.value) {\r\n\t\ttoggleSelect(product)\r\n\t\treturn\r\n\t}\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/product-detail?id=${product.id}`\r\n\t})\r\n}\r\n\r\nconst addToCart = async (product: FavoriteType) => {\r\n\tuni.showLoading({ title: '检查商品...' })\r\n\ttry {\r\n\t\tconst merchantId = product.merchant_id ?? ''\r\n\t\t\r\n\t\tconst skus = await supabaseService.getProductSkus(product.id)\r\n\t\tuni.hideLoading()\r\n\t\t\r\n\t\tif (skus.length > 0) {\r\n\t\t\tuni.showToast({ title: '请选择规格', icon: 'none' })\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tuni.navigateTo({\r\n\t\t\t\t\turl: '/pages/mall/consumer/product-detail?id=' + product.id\r\n\t\t\t\t})\r\n\t\t\t}, 500)\r\n\t\t} else {\r\n\t\t\tuni.showLoading({ title: '添加中...' })\r\n\t\t\tconst success = await supabaseService.addToCart(product.id, 1, '', merchantId)\r\n\t\t\tuni.hideLoading()\r\n\t\t\tif (success) {\r\n\t\t\t\tuni.showToast({ title: '已添加到购物车', icon: 'success' })\r\n\t\t\t} else {\r\n\t\t\t\tuni.showToast({ title: '添加失败', icon: 'none' })\r\n\t\t\t}\r\n\t\t}\r\n\t} catch (e) {\r\n\t\tconsole.error('添加到购物车异常', e)\r\n\t\tuni.hideLoading()\r\n\t\tuni.showToast({ title: '操作失败', icon: 'none' })\r\n\t}\r\n}\r\n\r\nconst goShopping = () => {\r\n\tuni.switchTab({\r\n\t\turl: '/pages/main/index'\r\n\t})\r\n}\r\n\r\nonMounted(() => {\r\n\tloadFavorites()\r\n})\r\n</script>\r\n\r\n<style scoped>\r\n.favorites-page {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tflex: 1;\r\n}\r\n\r\n.favorites-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tborder-bottom: 1px solid #e5e5e5;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.header-actions {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex: 1;\r\n\tjustify-content: flex-end;\r\n\talign-items: center;\r\n\tpadding-right: 0;\r\n}\r\n\r\n.action-btn {\r\n\tcolor: #007aff;\r\n\tfont-size: 14px;\r\n\tpadding: 5px;\r\n\tmargin-left: 20px;\r\n}\r\n\r\n.favorites-content {\r\n\tflex: 1;\r\n\theight: 0px;\r\n}\r\n\r\n.empty-favorites {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpadding: 80px 20px;\r\n\tbackground-color: #ffffff;\r\n}\r\n\r\n.empty-icon {\r\n\tfont-size: 80px;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.empty-text {\r\n\tfont-size: 16px;\r\n\tcolor: #666666;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.empty-subtext {\r\n\tfont-size: 14px;\r\n\tcolor: #999999;\r\n\tmargin-bottom: 30px;\r\n}\r\n\r\n.go-shopping-btn {\r\n\tbackground-color: #007aff;\r\n\tcolor: #ffffff;\r\n\tpadding: 10px 40px;\r\n\tborder-radius: 25px;\r\n\tfont-size: 14px;\r\n\tborder: none;\r\n}\r\n\r\n.product-group {\r\n\tbackground-color: #ffffff;\r\n\tmargin-bottom: 10px;\r\n\tpadding: 0 10px;\r\n}\r\n\r\n.group-items {\r\n\tpadding: 0;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.product-item {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tbackground: #fff;\r\n\tborder-radius: 8px;\r\n\toverflow: hidden;\r\n\twidth: 48%;\r\n\tmargin-bottom: 12px;\r\n\tposition: relative;\r\n}\r\n\r\n.item-selector {\r\n\tposition: absolute;\r\n\ttop: 5px;\r\n\tright: 5px;\r\n\tz-index: 10;\r\n\twidth: 30px;\r\n\theight: 30px;\r\n\tborder-radius: 15px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.select-icon {\r\n\twidth: 20px;\r\n\theight: 20px;\r\n\tborder: 1px solid #cccccc;\r\n\tborder-radius: 10px;\r\n\tbackground-color: rgba(255,255,255,0.5);\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.select-icon.selected {\r\n\tbackground-color: #007aff;\r\n\tborder-color: #007aff;\r\n}\r\n\r\n.icon-text {\r\n\tcolor: #ffffff;\r\n\tfont-size: 12px;\r\n}\r\n\r\n.item-content {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.product-image {\r\n\twidth: 100%;\r\n\theight: 170px;\r\n\tborder-radius: 8px;\r\n\tmargin-bottom: 8px;\r\n\tbackground: #f5f5f5;\r\n}\r\n\r\n.product-name {\r\n\tfont-size: 13px;\r\n\tcolor: #333;\r\n\tmargin-bottom: 5px;\r\n\tline-height: 1.4;\r\n\theight: 36px;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\tpadding: 0 8px;\r\n}\r\n\r\n.product-bottom {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 0 8px 8px;\r\n}\r\n\r\n.product-price {\r\n\tfont-size: 15px;\r\n\tcolor: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.product-add-btn {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tbackground-color: #ff5000;\r\n\tborder-radius: 12px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.add-icon {\r\n\tcolor: #fff;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n@media (min-width: 768px) {\r\n\t.product-item {\r\n\t\twidth: 32% !important;\r\n\t}\r\n}\r\n\r\n@media (min-width: 1024px) {\r\n\t.product-item {\r\n\t\twidth: 16% !important;\r\n\t}\r\n\t\r\n\t.favorites-content, .favorites-header {\r\n\t\tmax-width: 1200px;\r\n\t\tmargin: 0 auto;\r\n\t}\r\n}\r\n\r\n.loading-more,\r\n.no-more {\r\n\tpadding: 20px;\r\n\ttext-align: center;\r\n\tbackground-color: #ffffff;\r\n}\r\n\r\n.loading-text,\r\n.no-more-text {\r\n\tcolor: #999999;\r\n\tfont-size: 14px;\r\n}\r\n\r\n.edit-bar {\r\n\tbackground-color: #ffffff;\r\n\tborder-top: 1px solid #e5e5e5;\r\n\tpadding: 15px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.select-all {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n}\r\n\r\n.all-select-icon {\r\n\twidth: 20px;\r\n\theight: 20px;\r\n\tborder: 1px solid #cccccc;\r\n\tborder-radius: 10px;\r\n\tmargin-right: 10px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.all-select-icon.selected {\r\n\tbackground-color: #007aff;\r\n\tborder-color: #007aff;\r\n}\r\n\r\n.select-all-text {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n}\r\n\r\n.delete-btn {\r\n\tbackground-color: #ff4757;\r\n\tpadding: 10px 20px;\r\n\tborder-radius: 15px;\r\n}\r\n\r\n.delete-text {\r\n\tcolor: #ffffff;\r\n\tfont-size: 14px;\r\n\tfont-weight: bold;\r\n}\r\n</style>\r\n","<!-- 足迹页面 -->\r\n<template>\r\n\t<view class=\"footprint-page\">\r\n\t\t<view class=\"footprint-header\">\r\n\t\t\t<view v-if=\"footprints.length > 0\" class=\"header-actions\">\r\n\t\t\t\t<text class=\"action-btn\" @click=\"toggleEditMode\">{{ isEditMode ? '完成' : '编辑' }}</text>\r\n\t\t\t\t<text class=\"action-btn\" @click=\"clearAll\">清空</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<scroll-view class=\"footprint-content\" :scroll-y=\"true\" @scrolltolower=\"loadMore\">\r\n\t\t\t<view v-if=\"footprints.length === 0 && !isLoading\" class=\"empty-footprints\">\r\n\t\t\t\t<text class=\"empty-icon\">👣</text>\r\n\t\t\t\t<text class=\"empty-text\">暂无浏览记录</text>\r\n\t\t\t\t<text class=\"empty-subtext\">快去浏览喜欢的商品吧</text>\r\n\t\t\t\t<button class=\"go-shopping-btn\" @click=\"goShopping\">去逛逛</button>\r\n\t\t\t</view>\r\n\r\n\t\t\t<view v-for=\"(group, index) in groupedFootprints\" :key=\"index\" class=\"date-group\">\r\n\t\t\t\t<view class=\"group-header\">\r\n\t\t\t\t\t<text class=\"group-date\">{{ group.dateLabel }}</text>\r\n\t\t\t\t\t<text v-if=\"isEditMode\" class=\"group-select\" @click=\"toggleGroupSelect(index)\">\r\n\t\t\t\t\t\t{{ isGroupSelected(index) ? '取消全选' : '全选' }}\r\n\t\t\t\t\t</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<view class=\"group-items\">\r\n\t\t\t\t\t<view v-for=\"item in group.items\" :key=\"item.id\" class=\"footprint-item\">\r\n\t\t\t\t\t\t<view v-if=\"isEditMode\" class=\"item-selector\" @click=\"toggleSelect(item)\">\r\n\t\t\t\t\t\t\t<view :class=\"['select-icon', { selected: item.selected === true }]\">\r\n\t\t\t\t\t\t\t\t<text v-if=\"item.selected === true\" class=\"icon-text\">✓</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"item-content\" @click=\"viewProduct(item)\">\r\n\t\t\t\t\t\t\t<image class=\"product-image\" :src=\"item.image\" mode=\"aspectFill\" />\r\n\t\t\t\t\t\t\t<text class=\"product-name\" :lines=\"2\">{{ item.name }}</text>\r\n\t\t\t\t\t\t\t<view class=\"product-bottom\">\r\n\t\t\t\t\t\t\t\t<text class=\"product-price\">¥{{ item.price }}</text>\r\n\t\t\t\t\t\t\t\t<view class=\"product-add-btn\" @click.stop=\"addToCart(item)\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"add-icon\">+</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<view v-if=\"isLoading\" class=\"loading-more\">\r\n\t\t\t\t<text class=\"loading-text\">加载中...</text>\r\n\t\t\t</view>\r\n\t\t\t<view v-if=\"!hasMore && footprints.length > 0\" class=\"no-more\">\r\n\t\t\t\t<text class=\"no-more-text\">没有更多了</text>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\r\n\t\t<view v-if=\"isEditMode && footprints.length > 0\" class=\"edit-bar\">\r\n\t\t\t<view class=\"select-all\" @click=\"toggleSelectAll\">\r\n\t\t\t\t<view :class=\"['all-select-icon', { selected: isAllSelected }]\">\r\n\t\t\t\t\t<text v-if=\"isAllSelected\" class=\"icon-text\">✓</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<text class=\"select-all-text\">全选</text>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"delete-btn\" @click=\"deleteSelected\">\r\n\t\t\t\t<text class=\"delete-text\">删除({{ selectedCount }})</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted, computed } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype FootprintType = {\r\n\tid: string\r\n\tname: string\r\n\tprice: number\r\n\toriginal_price: number\r\n\timage: string\r\n\tsales: number\r\n\tshopId: string\r\n\tshopName: string\r\n\tviewTime: number\r\n\tselected: boolean\r\n\tmerchant_id: string\r\n}\r\n\r\ntype FootprintGroup = {\r\n\tdateLabel: string\r\n\tdateKey: string\r\n\titems: FootprintType[]\r\n}\r\n\r\ntype FootprintSaveType = {\r\n\tid: string\r\n\tname: string\r\n\tprice: number\r\n\toriginal_price: number\r\n\timage: string\r\n\tsales: number\r\n\tshopId: string\r\n\tshopName: string\r\n\tviewTime: number\r\n}\r\n\r\nconst footprints = ref<FootprintType[]>([])\r\nconst isEditMode = ref<boolean>(false)\r\nconst isLoading = ref<boolean>(false)\r\nconst hasMore = ref<boolean>(false)\r\n\r\nconst selectedCount = computed((): number => {\r\n\treturn footprints.value.filter((item): Boolean => item.selected === true).length\r\n})\r\n\r\nconst isAllSelected = computed((): boolean => {\r\n\treturn footprints.value.length > 0 && footprints.value.every((item): Boolean => item.selected === true)\r\n})\r\n\r\nconst formatGroupDate = (dateStr: string): string => {\r\n\tconst date = new Date(dateStr)\r\n\tconst today = new Date()\r\n\tconst yesterday = new Date(today)\r\n\tyesterday.setDate(yesterday.getDate() - 1)\r\n\t\r\n\tif (date.toDateString() === today.toDateString()) {\r\n\t\treturn '今天'\r\n\t} else if (date.toDateString() === yesterday.toDateString()) {\r\n\t\treturn '昨天'\r\n\t} else {\r\n\t\tconst month = date.getMonth() + 1\r\n\t\tconst day = date.getDate()\r\n\t\treturn `${month}月${day}日`\r\n\t}\r\n}\r\n\r\nconst groupedFootprints = computed((): FootprintGroup[] => {\r\n\tconst result: FootprintGroup[] = []\r\n\t\r\n\tfor (let i = 0; i < footprints.value.length; i++) {\r\n\t\tconst item = footprints.value[i]\r\n\t\tconst dateKey = new Date(item.viewTime).toDateString()\r\n\t\t\r\n\t\tlet foundGroup: FootprintGroup | null = null\r\n\t\tfor (let j = 0; j < result.length; j++) {\r\n\t\t\tif (result[j].dateKey === dateKey) {\r\n\t\t\t\tfoundGroup = result[j]\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (foundGroup != null) {\r\n\t\t\tfoundGroup.items.push(item)\r\n\t\t} else {\r\n\t\t\tconst newGroup: FootprintGroup = {\r\n\t\t\t\tdateLabel: formatGroupDate(dateKey),\r\n\t\t\t\tdateKey: dateKey,\r\n\t\t\t\titems: [item]\r\n\t\t\t} as FootprintGroup\r\n\t\t\tresult.push(newGroup)\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result\r\n})\r\n\r\nconst toggleEditMode = () => {\r\n\tisEditMode.value = !isEditMode.value\r\n\tfor (let i = 0; i < footprints.value.length; i++) {\r\n\t\tfootprints.value[i].selected = false\r\n\t}\r\n}\r\n\r\nconst clearAll = () => {\r\n\tif (footprints.value.length === 0) return\r\n\r\n\tuni.showModal({\r\n\t\ttitle: '清空足迹',\r\n\t\tcontent: '确定要清空所有浏览记录吗?',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tuni.showLoading({ title: '清空中...' })\r\n\t\t\t\t\r\n\t\t\t\tsupabaseService.clearFootprints().then((success) => {\r\n\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (success) {\r\n\t\t\t\t\t\tfootprints.value = []\r\n\t\t\t\t\t\tuni.removeStorageSync('footprints')\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\ttitle: '已清空',\r\n\t\t\t\t\t\t\ticon: 'success'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\ttitle: '清空失败',\r\n\t\t\t\t\t\t\ticon: 'none'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\nconst toggleSelect = (item: FootprintType) => {\r\n\titem.selected = !(item.selected === true)\r\n\tfootprints.value = [...footprints.value]\r\n}\r\n\r\nconst toggleGroupSelect = (groupIndex: number) => {\r\n\tconst group = groupedFootprints.value[groupIndex]\r\n\tif (group == null) return\r\n\t\r\n\tconst allSelected = group.items.every((item): Boolean => item.selected === true)\r\n\tconst newSelectedState = !allSelected\r\n\t\r\n\tfor (let i = 0; i < group.items.length; i++) {\r\n\t\tgroup.items[i].selected = newSelectedState\r\n\t}\r\n\t\r\n\tfootprints.value = [...footprints.value]\r\n}\r\n\r\nconst isGroupSelected = (groupIndex: number): boolean => {\r\n\tconst group = groupedFootprints.value[groupIndex]\r\n\tif (group == null || group.items.length === 0) return false\r\n\treturn group.items.every((item): Boolean => item.selected === true)\r\n}\r\n\r\nconst toggleSelectAll = () => {\r\n\tconst newSelectedState = !isAllSelected.value\r\n\tfor (let i = 0; i < footprints.value.length; i++) {\r\n\t\tfootprints.value[i].selected = newSelectedState\r\n\t}\r\n\tfootprints.value = [...footprints.value]\r\n}\r\n\r\nconst deleteSelected = () => {\r\n\tconst selectedItems = footprints.value.filter((item): Boolean => item.selected === true)\r\n\tif (selectedItems.length === 0) {\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '请选择要删除的记录',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\treturn\r\n\t}\r\n\r\n\tuni.showModal({\r\n\t\ttitle: '确认删除',\r\n\t\tcontent: `确定要删除选中的${selectedItems.length}条记录吗?`,\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tuni.showLoading({ title: '删除中...' })\r\n\t\t\t\t\r\n\t\t\t\t// 收集要删除的商品ID\r\n\t\t\t\tconst productIds: string[] = []\r\n\t\t\t\tfor (let i = 0; i < selectedItems.length; i++) {\r\n\t\t\t\t\tproductIds.push(selectedItems[i].id)\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// 调用服务层批量删除\r\n\t\t\t\tsupabaseService.deleteFootprints(productIds).then((success) => {\r\n\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (success) {\r\n\t\t\t\t\t\t// 从本地列表中移除\r\n\t\t\t\t\t\tfootprints.value = footprints.value.filter((item): Boolean => item.selected !== true)\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// 更新本地缓存\r\n\t\t\t\t\t\tconst dataToSave: FootprintSaveType[] = []\r\n\t\t\t\t\t\tfor (let i = 0; i < footprints.value.length; i++) {\r\n\t\t\t\t\t\t\tconst item = footprints.value[i]\r\n\t\t\t\t\t\t\tdataToSave.push({\r\n\t\t\t\t\t\t\t\tid: item.id,\r\n\t\t\t\t\t\t\t\tname: item.name,\r\n\t\t\t\t\t\t\t\tprice: item.price,\r\n\t\t\t\t\t\t\t\toriginal_price: item.original_price,\r\n\t\t\t\t\t\t\t\timage: item.image,\r\n\t\t\t\t\t\t\t\tsales: item.sales,\r\n\t\t\t\t\t\t\t\tshopId: item.shopId,\r\n\t\t\t\t\t\t\t\tshopName: item.shopName,\r\n\t\t\t\t\t\t\t\tviewTime: item.viewTime\r\n\t\t\t\t\t\t\t} as FootprintSaveType)\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tuni.setStorageSync('footprints', JSON.stringify(dataToSave))\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\ttitle: '删除成功',\r\n\t\t\t\t\t\t\ticon: 'success'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (footprints.value.length === 0) {\r\n\t\t\t\t\t\t\tisEditMode.value = false\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\t\ttitle: '删除失败',\r\n\t\t\t\t\t\t\ticon: 'none'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\nconst addToCart = async (item: FootprintType) => {\r\n\tuni.showLoading({ title: '检查商品...' })\r\n\ttry {\r\n\t\tconst productId = item.id\r\n\t\tconst merchantId = item.merchant_id ?? item.shopId ?? ''\r\n\t\t\r\n\t\t// 检查商品是否有SKU\r\n\t\tconst skus = await supabaseService.getProductSkus(productId)\r\n\t\tuni.hideLoading()\r\n\t\t\r\n\t\tif (skus.length > 0) {\r\n\t\t\t// 有规格,提示并跳转到商品详情页选择规格\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '请选择规格',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tuni.navigateTo({\r\n\t\t\t\t\turl: '/pages/mall/consumer/product-detail?id=' + productId\r\n\t\t\t\t})\r\n\t\t\t}, 500)\r\n\t\t} else {\r\n\t\t\t// 无规格,直接加入购物车\r\n\t\t\tuni.showLoading({ title: '添加中...' })\r\n\t\t\tconst success = await supabaseService.addToCart(productId, 1, '', merchantId)\r\n\t\t\tuni.hideLoading()\r\n\t\t\tif (success) {\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '已添加到购物车',\r\n\t\t\t\t\ticon: 'success'\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '添加失败',\r\n\t\t\t\t\ticon: 'none'\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t} catch (e) {\r\n\t\tconsole.error('添加到购物车异常', e)\r\n\t\tuni.hideLoading()\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '操作失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t}\r\n}\r\n\r\nconst viewProduct = (item: FootprintType) => {\r\n\tif (isEditMode.value) return\r\n\t\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/product-detail?productId=${item.id}&price=${item.price}&originalPrice=${item.original_price}`\r\n\t})\r\n}\r\n\r\nconst loadMore = () => {\r\n}\r\n\r\nconst goShopping = () => {\r\n\tuni.switchTab({\r\n\t\turl: '/pages/main/index'\r\n\t})\r\n}\r\n\r\nconst parseFootprintItem = (item: any): FootprintType => {\r\n\tlet itemObj: UTSJSONObject\r\n\tif (item instanceof UTSJSONObject) {\r\n\t\titemObj = item as UTSJSONObject\r\n\t} else {\r\n\t\titemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n\t}\r\n\t\r\n\treturn {\r\n\t\tid: itemObj.getString('id') ?? '',\r\n\t\tname: itemObj.getString('name') ?? '',\r\n\t\tprice: itemObj.getNumber('price') ?? 0,\r\n\t\toriginal_price: itemObj.getNumber('original_price') ?? 0,\r\n\t\timage: itemObj.getString('image') ?? '',\r\n\t\tsales: itemObj.getNumber('sales') ?? 0,\r\n\t\tshopId: itemObj.getString('shopId') ?? '',\r\n\t\tshopName: itemObj.getString('shopName') ?? '',\r\n\t\tviewTime: itemObj.getNumber('viewTime') ?? 0,\r\n\t\tselected: false,\r\n\t\tmerchant_id: itemObj.getString('merchant_id') ?? ''\r\n\t} as FootprintType\r\n}\r\n\r\nconst loadFootprints = async () => {\r\n\tisLoading.value = true\r\n\t\r\n\ttry {\r\n\t\tconst remoteData = await supabaseService.getFootprints()\r\n\t\t\r\n\t\tif (remoteData.length > 0) {\r\n\t\t\tconsole.log('获取到远程足迹数据:', remoteData.length)\r\n\t\t\tconst newFootprints: FootprintType[] = []\r\n\t\t\tfor (let i = 0; i < remoteData.length; i++) {\r\n\t\t\t\tnewFootprints.push(parseFootprintItem(remoteData[i]))\r\n\t\t\t}\r\n\t\t\tfootprints.value = newFootprints\r\n\t\t\t\r\n\t\t\tconst dataToSave: FootprintSaveType[] = []\r\n\t\t\tfor (let i = 0; i < footprints.value.length; i++) {\r\n\t\t\t\tconst item = footprints.value[i]\r\n\t\t\t\tdataToSave.push({\r\n\t\t\t\t\tid: item.id,\r\n\t\t\t\t\tname: item.name,\r\n\t\t\t\t\tprice: item.price,\r\n\t\t\t\t\toriginal_price: item.original_price,\r\n\t\t\t\t\timage: item.image,\r\n\t\t\t\t\tsales: item.sales,\r\n\t\t\t\t\tshopId: item.shopId,\r\n\t\t\t\t\tshopName: item.shopName,\r\n\t\t\t\t\tviewTime: item.viewTime\r\n\t\t\t\t} as FootprintSaveType)\r\n\t\t\t}\r\n\t\t\tuni.setStorageSync('footprints', JSON.stringify(dataToSave))\r\n\t\t} else {\r\n\t\t\tconst storedFootprints = uni.getStorageSync('footprints')\r\n\t\t\tif (storedFootprints != null) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst data = JSON.parse(storedFootprints as string) as any[]\r\n\t\t\t\t\tconst newFootprints: FootprintType[] = []\r\n\t\t\t\t\tfor (let i = 0; i < data.length; i++) {\r\n\t\t\t\t\t\tnewFootprints.push(parseFootprintItem(data[i]))\r\n\t\t\t\t\t}\r\n\t\t\t\t\tfootprints.value = newFootprints\r\n\t\t\t\t} catch (e) {\r\n\t\t\t\t\tconsole.error('Failed to parse footprints', e)\r\n\t\t\t\t\tfootprints.value = []\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tfootprints.value = []\r\n\t\t\t}\r\n\t\t}\r\n\t} catch (e) {\r\n\t\tconsole.error('加载足迹失败', e)\r\n\t\tconst storedFootprints = uni.getStorageSync('footprints')\r\n\t\tif (storedFootprints != null) {\r\n\t\t\ttry {\r\n\t\t\t\tconst data = JSON.parse(storedFootprints as string) as any[]\r\n\t\t\t\tconst newFootprints: FootprintType[] = []\r\n\t\t\t\tfor (let i = 0; i < data.length; i++) {\r\n\t\t\t\t\tnewFootprints.push(parseFootprintItem(data[i]))\r\n\t\t\t\t}\r\n\t\t\t\tfootprints.value = newFootprints\r\n\t\t\t} catch (err) {\r\n\t\t\t\tfootprints.value = []\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tisLoading.value = false\r\n\thasMore.value = false\r\n}\r\n\r\nonMounted(() => {\r\n\tloadFootprints()\r\n})\r\n</script>\r\n\r\n<style scoped>\r\n.footprint-page {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tflex: 1;\r\n}\r\n\r\n.footprint-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tborder-bottom: 1px solid #e5e5e5;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.header-actions {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex: 1;\r\n\tjustify-content: flex-end;\r\n\talign-items: center;\r\n\tpadding-right: 0;\r\n}\r\n\r\n.action-btn {\r\n\tcolor: #007aff;\r\n\tfont-size: 14px;\r\n\tpadding: 5px;\r\n\tmargin-left: 20px;\r\n}\r\n\r\n.footprint-content {\r\n\tflex: 1;\r\n\theight: 0px;\r\n}\r\n\r\n.empty-footprints {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpadding: 80px 20px;\r\n\tbackground-color: #ffffff;\r\n}\r\n\r\n.empty-icon {\r\n\tfont-size: 80px;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.empty-text {\r\n\tfont-size: 16px;\r\n\tcolor: #666666;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.empty-subtext {\r\n\tfont-size: 14px;\r\n\tcolor: #999999;\r\n\tmargin-bottom: 30px;\r\n}\r\n\r\n.go-shopping-btn {\r\n\tbackground-color: #007aff;\r\n\tcolor: #ffffff;\r\n\tpadding: 10px 40px;\r\n\tborder-radius: 25px;\r\n\tfont-size: 14px;\r\n\tborder: none;\r\n}\r\n\r\n.date-group {\r\n\tbackground-color: #ffffff;\r\n\tmargin-bottom: 10px;\r\n\tpadding: 0 10px;\r\n}\r\n\r\n.group-header {\r\n\tpadding: 15px 5px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.group-date {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.group-select {\r\n\tcolor: #007aff;\r\n\tfont-size: 14px;\r\n}\r\n\r\n.group-items {\r\n\tpadding: 0;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.footprint-item {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tbackground: #fff;\r\n\tborder-radius: 8px;\r\n\toverflow: hidden;\r\n\twidth: 48%;\r\n\tmargin-bottom: 12px;\r\n\tposition: relative;\r\n}\r\n\r\n.item-selector {\r\n\tposition: absolute;\r\n\ttop: 5px;\r\n\tright: 5px;\r\n\tz-index: 10;\r\n\twidth: 30px;\r\n\theight: 30px;\r\n\tborder-radius: 15px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.select-icon {\r\n\twidth: 20px;\r\n\theight: 20px;\r\n\tborder: 1px solid #cccccc;\r\n\tborder-radius: 10px;\r\n\tbackground-color: rgba(255,255,255,0.5);\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.select-icon.selected {\r\n\tbackground-color: #007aff;\r\n\tborder-color: #007aff;\r\n}\r\n\r\n.icon-text {\r\n\tcolor: #ffffff;\r\n\tfont-size: 12px;\r\n}\r\n\r\n.item-content {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.product-image {\r\n\twidth: 100%;\r\n\theight: 170px;\r\n\tborder-radius: 8px;\r\n\tmargin-bottom: 8px;\r\n\tbackground: #f5f5f5;\r\n}\r\n\r\n.product-name {\r\n\tfont-size: 13px;\r\n\tcolor: #333;\r\n\tmargin-bottom: 5px;\r\n\tline-height: 1.4;\r\n\theight: 36px;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\tpadding: 0 8px;\r\n}\r\n\r\n.product-bottom {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 0 8px 8px;\r\n}\r\n\r\n.product-price {\r\n\tfont-size: 15px;\r\n\tcolor: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.product-add-btn {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tbackground-color: #ff5000;\r\n\tborder-radius: 12px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.add-icon {\r\n\tcolor: #fff;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n@media (min-width: 768px) {\r\n\t.footprint-item {\r\n\t\twidth: 32% !important;\r\n\t}\r\n}\r\n\r\n@media (min-width: 1024px) {\r\n\t.footprint-item {\r\n\t\twidth: 16% !important;\r\n\t}\r\n\t\r\n\t.footprint-content, .footprint-header {\r\n\t\tmax-width: 1200px;\r\n\t\tmargin: 0 auto;\r\n\t}\r\n}\r\n\r\n.loading-more,\r\n.no-more {\r\n\tpadding: 20px;\r\n\ttext-align: center;\r\n\tbackground-color: #ffffff;\r\n}\r\n\r\n.loading-text,\r\n.no-more-text {\r\n\tcolor: #999999;\r\n\tfont-size: 14px;\r\n}\r\n\r\n.edit-bar {\r\n\tbackground-color: #ffffff;\r\n\tborder-top: 1px solid #e5e5e5;\r\n\tpadding: 15px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.select-all {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n}\r\n\r\n.all-select-icon {\r\n\twidth: 20px;\r\n\theight: 20px;\r\n\tborder: 1px solid #cccccc;\r\n\tborder-radius: 10px;\r\n\tmargin-right: 10px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.all-select-icon.selected {\r\n\tbackground-color: #007aff;\r\n\tborder-color: #007aff;\r\n}\r\n\r\n.select-all-text {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n}\r\n\r\n.delete-btn {\r\n\tbackground-color: #ff4757;\r\n\tpadding: 10px 20px;\r\n\tborder-radius: 15px;\r\n}\r\n\r\n.delete-text {\r\n\tcolor: #ffffff;\r\n\tfont-size: 14px;\r\n\tfont-weight: bold;\r\n}\r\n</style>\r\n\r\n","<template>\r\n <view class=\"address-list-page\">\r\n <view class=\"address-list\">\r\n <view v-if=\"addresses.length === 0\" class=\"empty-state\">\r\n <text class=\"empty-icon\">📍</text>\r\n <text class=\"empty-text\">暂无收货地址</text>\r\n </view>\r\n \r\n <view v-else v-for=\"(item, index) in addresses\" :key=\"item.id\" class=\"address-item\" @click=\"selectAddress(item)\">\r\n <view class=\"item-content\">\r\n <view class=\"item-header\">\r\n <text class=\"user-name\">{{ item.name }}</text>\r\n <text class=\"user-phone\">{{ item.phone }}</text>\r\n <text v-if=\"item.isDefault\" class=\"default-tag\">默认</text>\r\n <text v-if=\"item.label\" class=\"label-tag\">{{ item.label }}</text>\r\n </view>\r\n <text class=\"address-text\">{{ getFullAddress(item) }}</text>\r\n </view>\r\n <view class=\"item-actions\">\r\n <view class=\"action-item\" @click.stop=\"editAddress(item.id)\">\r\n <text class=\"action-icon\">📝</text>\r\n </view>\r\n <view class=\"action-item\" @click.stop=\"deleteAddress(item.id)\">\r\n <text class=\"action-icon\">🗑️</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <view class=\"footer-btn\">\r\n <button class=\"add-btn\" @click=\"addAddress\">新建收货地址</button>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted, getCurrentInstance } from 'vue'\r\nimport { onShow, onLoad } from '@dcloudio/uni-app'\r\nimport { supabaseService, type UserAddress as SupabaseUserAddress } from '@/utils/supabaseService.uts'\r\n\r\ntype Address = {\r\n id: string\r\n name: string\r\n phone: string\r\n province: string\r\n city: string\r\n district: string\r\n detail: string\r\n isDefault: boolean\r\n label?: string\r\n}\r\n\r\nconst addresses = ref<Address[]>([])\r\nconst selectionMode = ref<boolean>(false)\r\n\r\nconst loadAddresses = async () => {\r\n try {\r\n // 从Supabase加载地址数据\r\n const supabaseAddresses = await supabaseService.getAddresses()\r\n \r\n // 转换数据格式以匹配前端界面\r\n const transformedAddresses: Address[] = []\r\n for (let i = 0; i < supabaseAddresses.length; i++) {\r\n const item = supabaseAddresses[i]\r\n const addr: Address = {\r\n id: item.id,\r\n name: item.recipient_name,\r\n phone: item.phone,\r\n province: item.province,\r\n city: item.city,\r\n district: item.district,\r\n detail: item.detail_address,\r\n isDefault: item.is_default,\r\n label: ''\r\n } as Address\r\n transformedAddresses.push(addr)\r\n }\r\n \r\n addresses.value = transformedAddresses\r\n \r\n // 同时更新本地存储作为缓存\r\n uni.setStorageSync('addresses', JSON.stringify(addresses.value))\r\n } catch (error) {\r\n console.error('加载地址数据失败:', error)\r\n // 如果API调用失败尝试从本地存储加载\r\n const storedAddresses = uni.getStorageSync('addresses')\r\n if (storedAddresses != null) {\r\n try {\r\n addresses.value = JSON.parse(storedAddresses as string) as Address[]\r\n } catch (e) {\r\n console.error('解析地址数据失败', e)\r\n addresses.value = []\r\n }\r\n } else {\r\n addresses.value = []\r\n }\r\n }\r\n}\r\n\r\nonLoad((options) => {\r\n if (options['selectMode'] == 'true') {\r\n selectionMode.value = true\r\n }\r\n})\r\n\r\nonShow(() => {\r\n loadAddresses()\r\n})\r\n\r\n// onMounted logic for EventChannel removed as it is not fully supported in UTS Android\r\n// Using uni.$emit for global event communication instead\r\n\r\nconst getFullAddress = (item: Address): string => {\r\n return `${item.province}${item.city}${item.district} ${item.detail}`\r\n}\r\n\r\nconst addAddress = () => {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/address-edit'\r\n })\r\n}\r\n\r\n// 删除地址\r\nconst deleteAddress = (id: string) => {\r\n uni.showModal({\r\n title: '提示',\r\n content: '确定要删除该地址吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n // 调用Supabase服务删除地址\r\n supabaseService.deleteAddress(id).then((success) => {\r\n if (success) {\r\n // 从本地列表移除\r\n const index = addresses.value.findIndex(addr => addr.id === id)\r\n if (index !== -1) {\r\n addresses.value.splice(index, 1)\r\n // 更新本地存储缓存\r\n uni.setStorageSync('addresses', JSON.stringify(addresses.value))\r\n uni.showToast({\r\n title: '删除成功',\r\n icon: 'success'\r\n })\r\n }\r\n } else {\r\n console.error('删除地址失败')\r\n uni.showToast({\r\n title: '删除失败',\r\n icon: 'none'\r\n })\r\n }\r\n })\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst editAddress = (id: string) => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/address-edit?id=${id}`\r\n })\r\n}\r\n\r\nconst selectAddress = (item: Address) => {\r\n if (selectionMode.value) {\r\n uni.$emit('addressSelected', {\r\n id: item.id,\r\n recipient_name: item.name,\r\n phone: item.phone,\r\n province: item.province,\r\n city: item.city,\r\n district: item.district,\r\n detail: item.detail,\r\n is_default: item.isDefault\r\n })\r\n uni.navigateBack()\r\n } else {\r\n editAddress(item.id)\r\n }\r\n}\r\n</script>\r\n\r\n<style>\r\n.address-list-page {\r\n background-color: #f8f8f8;\r\n padding: 12px;\r\n padding-bottom: 100px;\r\n}\r\n\r\n.address-list {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.address-item {\r\n background-color: #ffffff;\r\n border-radius: 12px;\r\n padding: 16px;\r\n margin-bottom: 12px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);\r\n}\r\n\r\n.item-content {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n margin-right: 12px;\r\n}\r\n\r\n.item-header {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n margin-bottom: 8px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.user-name {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n margin-right: 12px;\r\n}\r\n\r\n.user-phone {\r\n font-size: 14px;\r\n color: #666;\r\n margin-right: 12px;\r\n}\r\n\r\n.default-tag {\r\n background-color: #fff1eb;\r\n color: #ff5000;\r\n font-size: 10px;\r\n padding: 2px 6px;\r\n border-radius: 4px;\r\n margin-right: 6px;\r\n border: 1px solid #ff5000;\r\n}\r\n\r\n.label-tag {\r\n background-color: #eef5ff;\r\n color: #007aff;\r\n font-size: 10px;\r\n padding: 2px 6px;\r\n border-radius: 4px;\r\n border: 1px solid #007aff;\r\n}\r\n\r\n.address-text {\r\n font-size: 14px;\r\n color: #333;\r\n line-height: 1.5;\r\n lines: 2;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.item-actions {\r\n padding-left: 16px;\r\n border-left: 1px solid #f0f0f0;\r\n display: flex;\r\n flex-direction: row; /* 改为横向排列图标更符合习惯 */\r\n align-items: center;\r\n}\r\n\r\n.action-item {\r\n padding: 8px;\r\n}\r\n\r\n.empty-state {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding-top: 100px;\r\n}\r\n\r\n.empty-icon {\r\n font-size: 64px;\r\n margin-bottom: 16px;\r\n opacity: 0.5;\r\n}\r\n\r\n.empty-text {\r\n font-size: 14px;\r\n color: #999;\r\n}\r\n\r\n.footer-btn {\r\n position: fixed;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n background-color: white;\r\n padding: 10px 15px;\r\n padding-bottom: 30px;\r\n box-shadow: 0 -2px 10px rgba(0,0,0,0.05);\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 100;\r\n}\r\n\r\n.add-btn {\r\n background-color: #ff5000;\r\n color: white;\r\n border-radius: 25px;\r\n font-size: 16px;\r\n height: 44px;\r\n line-height: 44px;\r\n border: none;\r\n width: 100%; /* 默认占满 */\r\n}\r\n\r\n/* 响应式布局优化 */\r\n@media screen and (min-width: 768px) {\r\n .address-list {\r\n max-width: 800px;\r\n margin: 0 auto;\r\n }\r\n \r\n .address-list-page {\r\n background-color: #f5f5f5;\r\n }\r\n \r\n .footer-btn {\r\n max-width: 800px;\r\n margin: 0 auto;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n box-shadow: 0 -2px 10px rgba(0,0,0,0.05);\r\n border-radius: 12px 12px 0 0;\r\n }\r\n \r\n .add-btn {\r\n width: 300px; /* 桌面端限制宽度 */\r\n }\r\n}\r\n</style>\r\n\r\n","<template>\r\n <view class=\"page-container\">\r\n <scroll-view class=\"address-edit-scroll\" scroll-y=\"true\">\r\n <view class=\"address-edit-content\">\r\n <!-- 基础信息组 -->\r\n <view class=\"form-group\">\r\n <view class=\"form-item\">\r\n <text class=\"label\">收货人</text>\r\n <input class=\"input\" v-model=\"formData.name\" placeholder=\"请填写收货人姓名\" placeholder-class=\"placeholder\" />\r\n </view>\r\n <view class=\"form-item\">\r\n <text class=\"label\">手机号码</text>\r\n <input class=\"input\" v-model=\"formData.phone\" type=\"number\" maxlength=\"11\" placeholder=\"请填写手机号码\" placeholder-class=\"placeholder\" />\r\n </view>\r\n <view class=\"form-item\">\r\n <text class=\"label\">所在地区</text>\r\n <input class=\"input\" v-model=\"regionString\" placeholder=\"省市区县、乡镇等\" placeholder-class=\"placeholder\" />\r\n <text class=\"arrow-icon\"></text>\r\n </view>\r\n <view class=\"form-item detail-item\">\r\n <text class=\"label\">详细地址</text>\r\n <textarea class=\"textarea\" v-model=\"formData.detail\" placeholder=\"街道、楼牌号等\" placeholder-class=\"placeholder\" maxlength=\"100\"></textarea>\r\n </view>\r\n </view>\r\n \r\n <!-- 标签与默认设置组 -->\r\n <view class=\"form-group\">\r\n <view class=\"form-item label-section\">\r\n <text class=\"label\">地址标签</text>\r\n <view class=\"tags-container\">\r\n <view \r\n v-for=\"tag in tags\" \r\n :key=\"tag\" \r\n class=\"tag-item\"\r\n :class=\"{ active: formData.label === tag }\"\r\n @click=\"selectTag(tag)\"\r\n >\r\n <text class=\"tag-text\" :class=\"{ 'tag-text-active': formData.label === tag }\">{{ tag }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n <view class=\"form-item switch-item\">\r\n <view class=\"switch-label-group\">\r\n <text class=\"label\">设为默认地址</text>\r\n <text class=\"sub-label\">下单时优先使用该地址</text>\r\n </view>\r\n <switch :checked=\"formData.isDefault\" color=\"#ff5000\" @change=\"onSwitchChange\" />\r\n </view>\r\n </view>\r\n\r\n <!-- 智能识别组 -->\r\n <view class=\"form-group smart-group\">\r\n <view class=\"smart-header\">\r\n <text class=\"smart-title\">智能填写</text>\r\n <text class=\"smart-clear\" v-if=\"smartInput\" @click=\"smartInput = ''\">清空</text>\r\n </view>\r\n <textarea class=\"smart-textarea\" v-model=\"smartInput\" placeholder=\"粘贴整段地址,自动识别姓名、电话、地址\" @input=\"parseSmartInput\" maxlength=\"200\"></textarea>\r\n <view class=\"smart-footer\">\r\n <text class=\"smart-tip\">示例张三13800138000北京市朝阳区...</text>\r\n </view>\r\n </view>\r\n \r\n <!-- 底部操作按钮 -->\r\n <view class=\"footer-actions\">\r\n <button class=\"save-btn\" @click=\"saveAddress\">保存地址</button>\r\n <button v-if=\"isEdit\" class=\"delete-btn\" @click=\"deleteAddress\">删除此地址</button>\r\n </view>\r\n </view>\r\n </scroll-view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, reactive, computed } from 'vue'\r\nimport { onLoad } from '@dcloudio/uni-app'\r\nimport { supabaseService, AddAddressParams, UpdateAddressParams } from '@/utils/supabaseService.uts'\r\n\r\ntype Address = {\r\n id: string\r\n name: string\r\n phone: string\r\n province: string\r\n city: string\r\n district: string\r\n detail: string\r\n isDefault: boolean\r\n label?: string\r\n}\r\n\r\nconst isEdit = ref(false)\r\nconst addressId = ref('')\r\nconst regionString = ref('')\r\nconst tags = ['家', '公司', '学校']\r\nconst smartInput = ref('')\r\n\r\ntype AddressForm = {\r\n name: string\r\n phone: string\r\n detail: string\r\n isDefault: boolean\r\n label: string\r\n}\r\n\r\nconst formData = reactive({\r\n name: '',\r\n phone: '',\r\n detail: '',\r\n isDefault: false,\r\n label: ''\r\n} as AddressForm)\r\n\r\nconst loadAddress = async (id: string) => {\r\n try {\r\n // 从Supabase加载地址详情\r\n const address = await supabaseService.getAddressById(id)\r\n if (address != null) {\r\n formData.name = address.recipient_name\r\n formData.phone = address.phone\r\n formData.detail = address.detail_address\r\n formData.isDefault = address.is_default\r\n formData.label = address.label ?? ''\r\n regionString.value = `${address.province} ${address.city} ${address.district}`.trim()\r\n } else {\r\n // 如果Supabase没有找到尝试从本地存储加载\r\n const storedAddresses = uni.getStorageSync('addresses')\r\n if (storedAddresses != null) {\r\n const addresses = JSON.parse(storedAddresses as string) as Address[]\r\n const localAddress = addresses.find(item => item.id === id)\r\n if (localAddress != null) {\r\n formData.name = localAddress.name\r\n formData.phone = localAddress.phone\r\n formData.detail = localAddress.detail\r\n formData.isDefault = localAddress.isDefault\r\n formData.label = localAddress.label ?? ''\r\n regionString.value = `${localAddress.province} ${localAddress.city} ${localAddress.district}`.trim()\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.error('加载地址详情失败:', error)\r\n // 失败时从本地存储加载\r\n const storedAddresses = uni.getStorageSync('addresses')\r\n if (storedAddresses != null) {\r\n try {\r\n const addresses = JSON.parse(storedAddresses as string) as Address[]\r\n const address = addresses.find(item => item.id === id)\r\n if (address != null) {\r\n formData.name = address.name\r\n formData.phone = address.phone\r\n formData.detail = address.detail\r\n formData.isDefault = address.isDefault\r\n formData.label = address.label ?? ''\r\n regionString.value = `${address.province} ${address.city} ${address.district}`.trim()\r\n }\r\n } catch (e) {\r\n console.error('解析本地地址数据失败', e)\r\n }\r\n }\r\n }\r\n}\r\n\r\nonLoad((options) => {\r\n if (options['id'] != null) {\r\n isEdit.value = true\r\n addressId.value = options['id'] as string\r\n loadAddress(addressId.value)\r\n }\r\n})\r\n\r\nconst selectTag = (tag: string) => {\r\n if (formData.label === tag) {\r\n formData.label = ''\r\n } else {\r\n formData.label = tag\r\n }\r\n}\r\n\r\nconst onSwitchChange = (e: UniSwitchChangeEvent) => {\r\n formData.isDefault = e.detail.value\r\n}\r\n\r\nconst saveAddress = async () => {\r\n if (formData.name == '') {\r\n uni.showToast({ title: '请填写收货人', icon: 'none' })\r\n return\r\n }\r\n if (formData.phone == '') {\r\n uni.showToast({ title: '请填写手机号码', icon: 'none' })\r\n return\r\n }\r\n if (regionString.value == '') {\r\n uni.showToast({ title: '请填写所在地区', icon: 'none' })\r\n return\r\n }\r\n if (formData.detail == '') {\r\n uni.showToast({ title: '请填写详细地址', icon: 'none' })\r\n return\r\n }\r\n \r\n // 简单解析地区(这里简化处理,实际应使用选择器)\r\n const regions = regionString.value.split(' ')\r\n const province = regions[0] ?? ''\r\n const city = regions[1] ?? ''\r\n const district = regions.slice(2).join(' ')\r\n\r\n // 构建地址对象\r\n const addressData = {\r\n recipient_name: formData.name,\r\n phone: formData.phone,\r\n province: province,\r\n city: city,\r\n district: district,\r\n detail_address: formData.detail,\r\n postal_code: '', // 如果需要可以添加邮政编码字段\r\n is_default: formData.isDefault,\r\n label: formData.label\r\n } as AddAddressParams\r\n\r\n let success = false\r\n \r\n if (isEdit.value) {\r\n // 更新地址\r\n const updateData = {\r\n recipient_name: formData.name,\r\n phone: formData.phone,\r\n province: province,\r\n city: city,\r\n district: district,\r\n detail_address: formData.detail,\r\n postal_code: '',\r\n is_default: formData.isDefault,\r\n label: formData.label\r\n } as UpdateAddressParams\r\n success = await supabaseService.updateAddress(addressId.value, updateData)\r\n } else {\r\n // 添加新地址\r\n success = await supabaseService.addAddress(addressData)\r\n }\r\n\r\n if (success) {\r\n // 同时更新本地存储作为缓存\r\n const storedAddresses = uni.getStorageSync('addresses')\r\n let addresses: Address[] = []\r\n if (storedAddresses != null) {\r\n try {\r\n addresses = JSON.parse(storedAddresses as string) as Address[]\r\n } catch (e) {\r\n addresses = []\r\n }\r\n }\r\n \r\n // 如果设为默认,取消其他默认\r\n if (formData.isDefault) {\r\n addresses.forEach(item => {\r\n item.isDefault = false\r\n })\r\n }\r\n \r\n if (isEdit.value) {\r\n const index = addresses.findIndex(item => item.id === addressId.value)\r\n if (index !== -1) {\r\n addresses[index] = {\r\n ...addresses[index],\r\n name: formData.name,\r\n phone: formData.phone,\r\n province: province,\r\n city: city,\r\n district: district,\r\n detail: formData.detail,\r\n isDefault: formData.isDefault,\r\n label: formData.label\r\n }\r\n }\r\n } else {\r\n const newAddress: Address = {\r\n id: `addr_${Date.now()}`, // 临时ID实际由Supabase生成\r\n name: formData.name,\r\n phone: formData.phone,\r\n province: province,\r\n city: city,\r\n district: district,\r\n detail: formData.detail,\r\n isDefault: formData.isDefault,\r\n label: formData.label\r\n }\r\n addresses.push(newAddress)\r\n }\r\n \r\n uni.setStorageSync('addresses', JSON.stringify(addresses))\r\n \r\n uni.showToast({\r\n title: '保存成功',\r\n icon: 'success'\r\n })\r\n \r\n setTimeout(() => {\r\n uni.navigateBack()\r\n }, 1500)\r\n } else {\r\n console.error('保存地址失败')\r\n uni.showToast({\r\n title: '保存失败',\r\n icon: 'none'\r\n })\r\n }\r\n}\r\n\r\nconst parseSmartInput = () => {\r\n const input = smartInput.value.trim()\r\n if (input == '') return\r\n \r\n // 提取手机号\r\n const phoneRegex = /(1[3-9]\\d{9})/\r\n const phoneMatch = input.match(phoneRegex)\r\n if (phoneMatch != null) {\r\n formData.phone = phoneMatch[0] ?? ''\r\n }\r\n \r\n // 提取姓名取第一个2-4位中文\r\n const nameRegex = /([\\u4e00-\\u9fa5]{2,4})/\r\n const nameMatch = input.match(nameRegex)\r\n if (nameMatch != null) {\r\n formData.name = nameMatch[0] ?? ''\r\n }\r\n \r\n // 去掉姓名和电话后剩余作为地址\r\n let addrText = input\r\n if (formData.name != '') addrText = addrText.replace(formData.name, '')\r\n if (formData.phone != '') addrText = addrText.replace(formData.phone, '')\r\n addrText = addrText.replace(/[,;\\s]+/g, ' ').trim()\r\n \r\n // 解析省市区\r\n const pattern1 = /^(.*?省)?(.*?市)?(.*?[区县])?(.*)$/\r\n const m = addrText.match(pattern1)\r\n if (m != null) {\r\n const province = m[1] ?? ''\r\n const city = m[2] ?? ''\r\n const district = m[3] ?? ''\r\n const detail = m[4] ?? ''\r\n regionString.value = `${province.trim()} ${city.trim()} ${district.trim()}`.trim()\r\n formData.detail = detail.trim()\r\n } else {\r\n formData.detail = addrText\r\n }\r\n}\r\nconst deleteAddress = () => {\r\n uni.showModal({\r\n title: '提示',\r\n content: '确定要删除该地址吗?',\r\n success: (res: UniShowModalResult) => {\r\n if (res.confirm) {\r\n // 调用Supabase服务删除地址\r\n supabaseService.deleteAddress(addressId.value).then((success) => {\r\n if (success) {\r\n // 同时从本地存储中移除\r\n const storedAddresses = uni.getStorageSync('addresses')\r\n if (storedAddresses != null) {\r\n try {\r\n let addresses = JSON.parse(storedAddresses as string) as Address[]\r\n addresses = addresses.filter(item => item.id !== addressId.value)\r\n uni.setStorageSync('addresses', JSON.stringify(addresses))\r\n } catch (e) {\r\n console.error('解析本地地址数据失败', e)\r\n }\r\n }\r\n \r\n uni.showToast({\r\n title: '删除成功',\r\n icon: 'success'\r\n })\r\n \r\n setTimeout(() => {\r\n uni.navigateBack()\r\n }, 1500)\r\n } else {\r\n console.error('删除地址失败')\r\n uni.showToast({\r\n title: '删除失败',\r\n icon: 'none'\r\n })\r\n }\r\n })\r\n }\r\n }\r\n })\r\n}\r\n</script>\r\n\r\n<style>\r\n.page-container {\r\n flex: 1;\r\n background-color: #f8f8f8;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.address-edit-scroll {\r\n flex: 1;\r\n}\r\n\r\n.address-edit-content {\r\n padding: 12px;\r\n padding-bottom: 40px;\r\n}\r\n\r\n.form-group {\r\n background-color: #ffffff;\r\n border-radius: 12px;\r\n padding: 16px; /* 给整个组增加内边距 */\r\n margin-bottom: 12px;\r\n box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);\r\n}\r\n\r\n.form-item {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n padding: 0 16px;\r\n min-height: 52px;\r\n background-color: #f8f8f8;\r\n border-radius: 26px; /* 增加大圆角,使其从直角变为圆角 */\r\n margin-bottom: 12px;\r\n border: none;\r\n}\r\n\r\n.form-item:last-child {\r\n margin-bottom: 0;\r\n}\r\n\r\n.detail-item {\r\n align-items: flex-start;\r\n flex-direction: column;\r\n padding: 16px;\r\n border-radius: 16px; /* 详细地址区域也增加圆角 */\r\n}\r\n\r\n.detail-item .label {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.label {\r\n width: 80px;\r\n font-size: 15px;\r\n color: #666;\r\n font-weight: 400;\r\n}\r\n\r\n.input {\r\n flex: 1;\r\n height: 44px; /* 增加高度 */\r\n line-height: 44px;\r\n font-size: 16px;\r\n color: #333;\r\n padding: 0 4px;\r\n background-color: transparent; /* 确保输入框背景透明 */\r\n border: none; /* 强制去除安卓原生边框 */\r\n}\r\n\r\n.textarea {\r\n width: 100%;\r\n height: 80px;\r\n font-size: 15px;\r\n line-height: 1.6;\r\n color: #333;\r\n padding: 4px 0;\r\n background-color: transparent;\r\n border: none; /* 强制去除安卓原生边框 */\r\n}\r\n\r\n.placeholder {\r\n color: #bbb;\r\n font-size: 15px;\r\n}\r\n\r\n.arrow-icon {\r\n font-size: 18px;\r\n color: #ccc;\r\n margin-left: 8px;\r\n}\r\n\r\n/* 标签选择 */\r\n.label-section {\r\n align-items: flex-start;\r\n flex-direction: column;\r\n}\r\n\r\n.label-section .label {\r\n margin-bottom: 16px;\r\n}\r\n\r\n.tags-container {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.tag-item {\r\n padding: 8px 20px; /* 增大点击区域 */\r\n background-color: #f7f7f7;\r\n border-radius: 20px;\r\n margin-right: 12px;\r\n margin-bottom: 8px; /* 增加底部间距 */\r\n border: 1px solid transparent;\r\n}\r\n\r\n.tag-item.active {\r\n background-color: #fff1eb;\r\n border-color: #ff5000;\r\n}\r\n\r\n.tag-text {\r\n font-size: 14px; /* 增大标签文字 */\r\n color: #666;\r\n}\r\n\r\n.tag-text-active {\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n/* 开关项 */\r\n.switch-item {\r\n justify-content: space-between;\r\n min-height: 72px; /* 增加开关项高度 */\r\n}\r\n\r\n.switch-label-group {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.sub-label {\r\n font-size: 13px; /* 增大副标题 */\r\n color: #999;\r\n margin-top: 6px;\r\n}\r\n\r\n/* 智能填写 */\r\n.smart-group {\r\n padding: 16px;\r\n}\r\n\r\n.smart-header {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 12px;\r\n}\r\n\r\n.smart-title {\r\n font-size: 14px;\r\n color: #333;\r\n font-weight: bold;\r\n}\r\n\r\n.smart-clear {\r\n font-size: 12px;\r\n color: #007aff;\r\n}\r\n\r\n.smart-textarea {\r\n width: 100%;\r\n height: 80px;\r\n background-color: #f9f9f9;\r\n border-radius: 8px;\r\n padding: 12px;\r\n font-size: 13px;\r\n line-height: 1.6;\r\n color: #666;\r\n}\r\n\r\n.smart-footer {\r\n margin-top: 8px;\r\n}\r\n\r\n.smart-tip {\r\n font-size: 11px;\r\n color: #999;\r\n}\r\n\r\n/* 底部按钮 */\r\n.footer-actions {\r\n margin-top: 32px;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.save-btn {\r\n background-color: #ff5000;\r\n color: #ffffff;\r\n height: 48px;\r\n line-height: 48px;\r\n font-size: 16px;\r\n font-weight: bold;\r\n border-radius: 24px;\r\n border: none;\r\n margin-bottom: 16px;\r\n box-shadow: 0 8rpx 20rpx rgba(255, 80, 0, 0.2);\r\n}\r\n\r\n.delete-btn {\r\n background-color: #ffffff;\r\n color: #ee0a24;\r\n height: 48px;\r\n line-height: 48px;\r\n font-size: 16px;\r\n border-radius: 24px;\r\n border: 1px solid #f0f0f0;\r\n}\r\n</style>\r\n\r\n","<!-- 结算页面 -->\r\n<template>\r\n\t<view class=\"checkout-page\">\r\n\t\t<scroll-view class=\"checkout-content\" direction=\"vertical\">\r\n\t\t\t<!-- 收货地址 -->\r\n\t\t\t<view class=\"section-card address-section\" @click=\"selectAddress\">\r\n\t\t\t\t<view class=\"address-icon-wrapper\">\r\n\t\t\t\t\t<text class=\"location-icon\">📍</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view v-if=\"selectedAddress\" class=\"address-info\">\r\n\t\t\t\t\t<view class=\"address-header\">\r\n\t\t\t\t\t\t<text class=\"recipient\">{{ selectedAddress!!.recipient_name }}</text>\r\n\t\t\t\t\t\t<text class=\"phone\">{{ selectedAddress!!.phone }}</text>\r\n\t\t\t\t\t\t<view v-if=\"selectedAddress!!.is_default\" class=\"default-tag\">\r\n\t\t\t\t\t\t\t<text class=\"tag-text\">默认</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<text class=\"address-detail\">{{ getFullAddress(selectedAddress!!) }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view v-else class=\"no-address\">\r\n\t\t\t\t\t<text class=\"no-address-text\">请选择收货地址</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"address-arrow-wrapper\">\r\n\t\t\t\t\t<text class=\"address-arrow\"></text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 商品列表 (按店铺分组) -->\r\n\t\t\t<view class=\"section-card products-section\">\r\n\t\t\t\t<view v-if=\"shopGroups.length > 0\">\r\n <view v-for=\"group in shopGroups\" :key=\"group.shopId\" class=\"shop-group\">\r\n <view class=\"shop-header\">\r\n <text class=\"shop-icon\">🏪</text>\r\n <text class=\"shop-name\">{{ group.shopName }}</text>\r\n </view>\r\n \r\n <!-- 商品列表 -->\r\n <view v-for=\"item in group.items\" :key=\"item.id\" class=\"product-item\">\r\n <image class=\"product-image\" :src=\"item.product_image\" mode=\"aspectFill\" />\r\n <view class=\"product-info\">\r\n <view class=\"product-name-row\">\r\n <text class=\"product-name\">{{ item.product_name }}</text>\r\n <text class=\"product-price\">¥{{ item.price }}</text>\r\n </view>\r\n <view class=\"product-spec-row\">\r\n <text v-if=\"item.sku_specifications\" class=\"product-spec\">{{ formatSpecs(item.sku_specifications) }}</text>\r\n <text class=\"product-quantity\">×{{ item.quantity }}</text>\r\n </view>\r\n <!-- 商品小计移至图片右侧 -->\r\n <view class=\"item-subtotal-row\">\r\n <text class=\"item-subtotal-label\">小计:</text>\r\n <text class=\"item-subtotal-price\">¥{{ (item.price * item.quantity).toFixed(2) }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view v-else class=\"no-products\">\r\n\t\t\t\t\t<text class=\"no-products-text\">暂无商品信息</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 配送方式 -->\r\n\t\t\t<view class=\"section-card delivery-section\">\r\n\t\t\t\t<view class=\"delivery-row\">\r\n\t\t\t\t\t<text class=\"section-title\">配送方式</text>\r\n\t\t\t\t\t<view class=\"delivery-selector\">\r\n\t\t\t\t\t\t<view v-for=\"option in deliveryOptions\" \r\n\t\t\t\t\t\t\t\t\t:key=\"option.id\" \r\n\t\t\t\t\t\t\t\t\t:class=\"['delivery-pill', { selected: selectedDelivery === option.id }]\"\r\n\t\t\t\t\t\t\t\t\t@click=\"selectDelivery(option)\">\r\n\t\t\t\t\t\t\t<text class=\"pill-name\">{{ option.name }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"delivery-detail\" v-if=\"selectedDelivery\">\r\n\t\t\t\t\t<text class=\"detail-desc\">{{ deliveryOptions.find(opt => opt.id === selectedDelivery)?.description }}</text>\r\n\t\t\t\t\t<text class=\"detail-price\">费用: ¥{{ deliveryOptions.find(opt => opt.id === selectedDelivery)?.price.toFixed(2) }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 优惠券 -->\r\n\t\t\t<view class=\"section-card coupon-section\" @click=\"selectCoupon\">\r\n\t\t\t\t<view class=\"coupon-row\">\r\n\t\t\t\t\t<text class=\"section-title\">优惠券</text>\r\n\t\t\t\t\t<view class=\"coupon-right-content\">\r\n\t\t\t\t\t\t<text v-if=\"selectedCoupon != null\" class=\"coupon-selected-name\">{{ selectedCoupon.template?.name ?? '已选择优惠券' }}</text>\r\n\t\t\t\t\t\t<text v-else class=\"coupon-placeholder\">暂无可用优惠券</text>\r\n\t\t\t\t\t\t<text class=\"arrow-icon\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 买家留言 -->\r\n\t\t\t<view class=\"section-card remark-section\">\r\n\t\t\t\t<view class=\"remark-row\">\r\n\t\t\t\t\t<text class=\"section-title\">买家留言</text>\r\n\t\t\t\t\t<input class=\"remark-input-compact\" \r\n\t\t\t\t\t\t\t\t v-model=\"remark\" \r\n\t\t\t\t\t\t\t\t placeholder=\"选填,给商家留言\"\r\n\t\t\t\t\t\t\t\t maxlength=\"100\" />\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 价格明细 -->\r\n\t\t\t<view class=\"section-card price-section\">\r\n\t\t\t\t<view class=\"price-grid\">\r\n\t\t\t\t\t<view class=\"price-item-inline\">\r\n\t\t\t\t\t\t<text class=\"price-item-label\">商品</text>\r\n\t\t\t\t\t\t<text class=\"price-item-value\">¥{{ totalAmount.toFixed(2) }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"price-item-inline\">\r\n\t\t\t\t\t\t<text class=\"price-item-label\">运费</text>\r\n\t\t\t\t\t\t<text class=\"price-item-value\">+¥{{ deliveryFee.toFixed(2) }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view v-if=\"discountAmount > 0\" class=\"price-item-inline\">\r\n\t\t\t\t\t\t<text class=\"price-item-label\">优惠</text>\r\n\t\t\t\t\t\t<text class=\"price-item-value discount-text\">-¥{{ discountAmount.toFixed(2) }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t\r\n\t\t\t<view class=\"safe-area-bottom\"></view>\r\n\t\t</scroll-view>\r\n\r\n\t\t<!-- 底部结算栏 -->\r\n\t\t<view class=\"footer-action-bar\">\r\n\t\t\t<view class=\"footer-left\">\r\n\t\t\t\t<text class=\"footer-total-label\">合计:</text>\r\n\t\t\t\t<text class=\"footer-currency\">¥</text>\r\n\t\t\t\t<text class=\"footer-price\">{{ actualAmount.toFixed(2) }}</text>\r\n\t\t\t</view>\r\n\t\t\t<button class=\"footer-submit-btn\" @click=\"submitOrder\">提交订单</button>\r\n\t\t</view>\r\n\r\n\t\t<!-- 地址选择弹窗 -->\r\n\t\t<view v-if=\"showAddressPopup\" class=\"address-popup-mask\" @click=\"showAddressPopup = false\">\r\n\t\t\t<view class=\"address-popup\" @click.stop>\r\n\t\t\t\t<view class=\"popup-header\">\r\n\t\t\t\t\t<text class=\"popup-title\">选择收货地址</text>\r\n\t\t\t\t\t<text class=\"popup-close\" @click=\"showAddressPopup = false\">×</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<scroll-view class=\"address-list-container\" direction=\"vertical\" :scroll-with-animation=\"true\">\r\n\t\t\t\t\t<!-- 登录提示 -->\r\n\t\t\t\t\t<view v-if=\"isLoggedIn == false\" class=\"login-prompt\" @click=\"goToLogin\">\r\n\t\t\t\t\t\t<text class=\"login-prompt-icon\">🔒</text>\r\n\t\t\t\t\t\t<text class=\"login-prompt-text\">您尚未登录,点击登录以同步服务器地址</text>\r\n\t\t\t\t\t\t<text class=\"login-prompt-arrow\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 地址列表 -->\r\n\t\t\t\t\t<view v-if=\"isLoggedIn\">\r\n\t\t\t\t\t\t<view v-if=\"addressList.length > 0\">\r\n\t\t\t\t\t\t\t<view v-for=\"address in addressList\" :key=\"address.id\" \r\n\t\t\t\t\t\t\t\t\t\tclass=\"popup-address-item\" @click=\"handleSelectAddress(address)\">\r\n\t\t\t\t\t\t\t\t<view class=\"popup-address-header\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"popup-address-name\">{{ address.recipient_name }}</text>\r\n\t\t\t\t\t\t\t\t\t<text class=\"popup-address-phone\">{{ address.phone }}</text>\r\n\t\t\t\t\t\t\t\t\t<view v-if=\"address.is_default\" class=\"popup-default-tag\">\r\n\t\t\t\t\t\t\t\t\t\t<text class=\"popup-tag-text\">默认</text>\r\n\t\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t\t<text class=\"popup-address-detail\">{{ getFullAddress(address) }}</text>\r\n\t\t\t\t\t\t\t\t<view v-if=\"selectedAddress !== null && selectedAddress.id === address.id\" class=\"popup-selected-indicator\">\r\n\t\t\t\t\t\t\t\t\t<text>✓</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t<!-- 空状态 -->\r\n\t\t\t\t\t\t<view v-else class=\"popup-empty-address\">\r\n\t\t\t\t\t\t\t<text class=\"popup-empty-icon\">📍</text>\r\n\t\t\t\t\t\t\t<text class=\"popup-empty-text\">暂无收货地址</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 未登录时的本地地址展示 -->\r\n\t\t\t\t\t<view v-if=\"isLoggedIn == false && addressList.length > 0\">\r\n\t\t\t\t\t\t<text class=\"local-address-title\">本地地址(未同步)</text>\r\n\t\t\t\t\t\t<view v-for=\"address in addressList\" :key=\"address.id\" \r\n\t\t\t\t\t\t\t\t\tclass=\"popup-address-item\" @click=\"handleSelectAddress(address)\">\r\n\t\t\t\t\t\t\t<view class=\"popup-address-header\">\r\n\t\t\t\t\t\t\t\t<text class=\"popup-address-name\">{{ address.recipient_name }}</text>\r\n\t\t\t\t\t\t\t\t<text class=\"popup-address-phone\">{{ address.phone }}</text>\r\n\t\t\t\t\t\t\t\t<view v-if=\"address.is_default\" class=\"popup-default-tag\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"popup-tag-text\">默认</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<text class=\"popup-address-detail\">{{ getFullAddress(address) }}</text>\r\n\t\t\t\t\t\t\t<view v-if=\"selectedAddress != null && selectedAddress!.id === address.id\" class=\"popup-selected-indicator\">\r\n\t\t\t\t\t\t\t\t<text>✓</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 完全无地址状态 -->\r\n\t\t\t\t\t<view v-if=\"isLoggedIn && addressList.length === 0\" class=\"popup-empty-address\">\r\n\t\t\t\t\t\t<text class=\"popup-empty-icon\">📍</text>\r\n\t\t\t\t\t\t<text class=\"popup-empty-text\">暂无收货地址</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</scroll-view>\r\n\t\t\t\t\r\n\t\t\t\t<!-- 新建地址按钮 -->\r\n\t\t\t\t<view class=\"popup-add-address-btn\" @click=\"handleAddNewAddress\">\r\n\t\t\t\t\t<text class=\"popup-btn-icon\">+</text>\r\n\t\t\t\t\t<text class=\"popup-btn-text\">新建收货地址</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<!-- 新建地址表单弹窗 -->\r\n\t\t<view v-if=\"showNewAddressForm\" class=\"address-form-mask\" @click=\"cancelNewAddress\">\r\n\t\t\t<view class=\"address-form-popup\" @click.stop>\r\n\t\t\t\t<view class=\"form-header\">\r\n\t\t\t\t\t<text class=\"form-title\">新建收货地址</text>\r\n\t\t\t\t\t<view class=\"form-close-btn\" @click=\"cancelNewAddress\">\r\n\t\t\t\t\t\t<text class=\"form-close-icon\">✕</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<scroll-view class=\"form-content\" direction=\"vertical\">\r\n\t\t\t\t\t<view class=\"form-section\">\r\n\t\t\t\t\t\t<view class=\"form-item\">\r\n\t\t\t\t\t\t\t<text class=\"form-label\">收货人</text>\r\n\t\t\t\t\t\t\t<input class=\"form-input\" v-model=\"newAddress.recipient_name\" \r\n\t\t\t\t\t\t\t\t\t\t placeholder=\"请输入收货人姓名\" />\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t<view class=\"form-item\">\r\n\t\t\t\t\t\t\t<text class=\"form-label\">手机号</text>\r\n\t\t\t\t\t\t\t<input class=\"form-input\" v-model=\"newAddress.phone\" \r\n\t\t\t\t\t\t\t\t\t\t placeholder=\"请输入手机号码\" type=\"number\" />\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<view class=\"form-section\">\r\n\t\t\t\t\t\t<view class=\"form-item\">\r\n\t\t\t\t\t\t\t<view class=\"label-row\">\r\n\t\t\t\t\t\t\t\t<text class=\"form-label\">智能填写</text>\r\n\t\t\t\t\t\t\t\t<text class=\"smart-tag\">识别姓名/电话/地址</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<textarea class=\"form-textarea smart-address-input\" \r\n\t\t\t\t\t\t\t\t\t\t\t\tv-model=\"smartAddressInput\" \r\n\t\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"粘贴收货信息文本,自动拆分字段\"\r\n\t\t\t\t\t\t\t\t\t\t\t\t@input=\"parseSmartAddress\"\r\n\t\t\t\t\t\t\t\t\t\t\t\tmaxlength=\"200\" />\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<view class=\"form-section\">\r\n\t\t\t\t\t\t<view class=\"form-item\">\r\n\t\t\t\t\t\t\t<text class=\"form-label\">所在地区</text>\r\n\t\t\t\t\t\t\t<view class=\"region-inputs\">\r\n\t\t\t\t\t\t\t\t<input class=\"form-input region-input form-input-readonly\" v-model=\"newAddress.province\" \r\n\t\t\t\t\t\t\t\t\t\t\t placeholder=\"省\" readonly />\r\n\t\t\t\t\t\t\t\t<input class=\"form-input region-input form-input-readonly\" v-model=\"newAddress.city\" \r\n\t\t\t\t\t\t\t\t\t\t\t placeholder=\"市\" readonly />\r\n\t\t\t\t\t\t\t\t<input class=\"form-input region-input form-input-readonly\" v-model=\"newAddress.district\" \r\n\t\t\t\t\t\t\t\t\t\t\t placeholder=\"区/县\" readonly />\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t<view class=\"form-item\">\r\n\t\t\t\t\t\t\t<text class=\"form-label\">详细地址</text>\r\n\t\t\t\t\t\t\t<textarea class=\"form-textarea detail-textarea\" v-model=\"newAddress.detail\" \r\n\t\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"如街道、楼栋、门牌号等\" \r\n\t\t\t\t\t\t\t\t\t\t\t\tmaxlength=\"100\" />\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<view class=\"form-item checkbox-item\">\r\n\t\t\t\t\t\t<view class=\"checkbox-wrapper\" @click=\"newAddress.is_default = !newAddress.is_default\">\r\n\t\t\t\t\t\t\t<view :class=\"['checkbox', { checked: newAddress.is_default }]\">\r\n\t\t\t\t\t\t\t\t<text v-if=\"newAddress.is_default\" class=\"checkbox-check\">✓</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<text class=\"checkbox-label\">设为默认地址</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</scroll-view>\r\n\t\t\t\t\r\n\t\t\t\t<view class=\"form-buttons\">\r\n\t\t\t\t\t<button class=\"form-submit-btn\" @click=\"saveNewAddress\">保存并使用</button>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<!-- 确认保存弹窗 -->\r\n\t\t<view v-if=\"showSaveConfirm\" class=\"confirm-popup-mask\">\r\n\t\t\t<view class=\"confirm-popup\">\r\n\t\t\t\t<view class=\"confirm-header\">\r\n\t\t\t\t\t<text class=\"confirm-title\">保存地址</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"confirm-content\">\r\n\t\t\t\t\t<text class=\"confirm-message\">是否保存该地址用于下次使用?</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"confirm-buttons\">\r\n\t\t\t\t\t<button class=\"confirm-btn cancel\" @click=\"handleSaveConfirm(false)\">仅本次</button>\r\n\t\t\t\t\t<button class=\"confirm-btn confirm\" @click=\"handleSaveConfirm(true)\">保存</button>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted, computed, watch, onUnmounted, getCurrentInstance } from 'vue'\r\nimport { onLoad } from '@dcloudio/uni-app'\r\nimport { supabaseService, type UserAddress as SupabaseUserAddress } from '@/utils/supabaseService.uts'\r\n\r\ntype CheckoutItemType = {\r\n\tid: string\r\n\tproduct_id: string\r\n\tsku_id: string\r\n\tproduct_name: string\r\n\tproduct_image: string\r\n\tsku_specifications: any\r\n\tprice: number\r\n\toriginal_price: number // 原价\r\n\tmember_price: number // 会员价\r\n\tquantity: number\r\n shop_id?: string\r\n shop_name?: string\r\n merchant_id?: string\r\n}\r\n\r\ntype DeliveryOptionType = {\r\n\tid: string\r\n\tname: string\r\n\tprice: number\r\n\tdescription: string\r\n}\r\n\r\ntype ShopGroupType = {\r\n\tshopId: string\r\n\tshopName: string\r\n\tmerchant_id: string\r\n\titems: Array<CheckoutItemType>\r\n}\r\n\r\ntype CouponTemplateType = {\r\n\tname: string\r\n\tdiscount_value: number\r\n\tmin_order_amount: number\r\n}\r\n\r\ntype UserCouponType = {\r\n\tid: string\r\n\ttemplate: CouponTemplateType | null\r\n}\r\n\r\ntype AddressItem = {\r\n\tid: string\r\n\trecipient_name: string\r\n\tphone: string\r\n\tprovince: string\r\n\tcity: string\r\n\tdistrict: string\r\n\tdetail: string\r\n\tis_default: boolean\r\n}\r\n\r\ntype NewAddressData = {\r\n id: string\r\n name: string\r\n phone: string\r\n province: string\r\n city: string\r\n district: string\r\n detail: string\r\n isDefault: boolean\r\n}\r\n\r\n// 添加新地址表单类型定义\r\ntype NewAddressForm = {\r\n\trecipient_name: string\r\n\tphone: string\r\n\tprovince: string\r\n\tcity: string\r\n\tdistrict: string\r\n\tdetail: string\r\n\tis_default: boolean\r\n}\r\n\r\ntype MockAddress = {\r\n\tid: string\r\n\tname: string\r\n\tphone: string\r\n\tprovince: string\r\n\tcity: string\r\n\tdistrict: string\r\n\tdetail: string\r\n\tisDefault: boolean\r\n}\r\n\r\n// 添加对象 keys 获取函数\r\nfunction getObjectKeys(obj: object): string[] {\r\n const keys: string[] = []\r\n // UTS 兼容的对象属性获取方式\r\n const tempObj = obj as Record<string, any>\r\n \r\n // 使用 try-catch 安全获取对象属性\r\n try {\r\n // 假设我们知道一些常见的属性名\r\n const commonKeys = ['id', 'name', 'value', 'label', 'key', 'recipient_name', 'phone', 'province', 'city', 'district', 'detail', 'is_default']\r\n for (let i = 0; i < commonKeys.length; i++) {\r\n const key = commonKeys[i]\r\n // 替换 hasOwnProperty 检查\r\n if (tempObj[key] !== null) { // 移除对 undefined 的检查\r\n keys.push(key)\r\n }\r\n }\r\n } catch (e) {\r\n // 捕获异常,避免编译错误\r\n }\r\n \r\n return keys\r\n}\r\n\r\nconst checkoutItems = ref<Array<CheckoutItemType>>([])\r\nconst selectedAddress = ref<AddressItem | null>(null)\r\nconst deliveryOptions = ref<Array<DeliveryOptionType>>([\r\n\t{ id: 'express', name: '物流快递', price: 8.00, description: '普通快递配送' },\r\n\t{ id: 'local', name: '同城配送', price: 15.00, description: '同城极速上门' }\r\n])\r\nconst selectedDelivery = ref<string>('express')\r\nconst selectedCoupon = ref<UserCouponType | null>(null)\r\nconst remark = ref<string>('')\r\nconst showAddressPopup = ref<boolean>(false)\r\nconst addressList = ref<Array<AddressItem>>([])\r\nconst newAddress = ref<NewAddressForm>({\r\n\trecipient_name: '',\r\n\tphone: '',\r\n\tprovince: '',\r\n\tcity: '',\r\n\tdistrict: '',\r\n\tdetail: '',\r\n\tis_default: false\r\n})\r\nconst showNewAddressForm = ref<boolean>(false)\r\nconst showSaveConfirm = ref<boolean>(false)\r\nconst smartAddressInput = ref<string>('')\r\n\r\nconst toUTSJSONObject = (value: any): UTSJSONObject => {\r\n\tif (value instanceof UTSJSONObject) return value as UTSJSONObject\r\n\treturn JSON.parse(JSON.stringify(value ?? {})) as UTSJSONObject\r\n}\r\n\r\n// 计算属性 - 修复价格同步问题\r\n// 按店铺分组商品\r\nconst shopGroups = computed((): Array<ShopGroupType> => {\r\n\tconst groups: Array<ShopGroupType> = []\r\n\tcheckoutItems.value.forEach((item) => {\r\n\t\tconst shopId = item.shop_id ?? 'unknown'\r\n\t\tlet target: ShopGroupType | null = null\r\n\t\tfor (let i = 0; i < groups.length; i++) {\r\n\t\t\tif (groups[i].shopId == shopId) {\r\n\t\t\t\ttarget = groups[i]\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (target == null) {\r\n\t\t\ttarget = {\r\n\t\t\t\tshopId: shopId,\r\n\t\t\t\tshopName: item.shop_name ?? '商城优选',\r\n\t\t\t\tmerchant_id: item.merchant_id ?? item.shop_id ?? '',\r\n\t\t\t\titems: []\r\n\t\t\t}\r\n\t\t\tgroups.push(target)\r\n\t\t}\r\n\t\ttarget.items.push(item)\r\n\t})\r\n\treturn groups\r\n})\r\n\r\nconst getGroupTotal = (group: ShopGroupType): string => {\r\n\tlet sum = 0\r\n\tgroup.items.forEach((item) => {\r\n\t\t// 优先使用会员价,如果没有会员价则使用原价\r\n\t\tlet price = item.price\r\n\t\tif (item.member_price != null && item.member_price > 0 && item.member_price < item.price) {\r\n\t\t\tprice = item.member_price\r\n\t\t}\r\n\t\tconst quantity = item.quantity\r\n\t\tif (isNaN(price) == false && isNaN(quantity) == false) {\r\n\t\t\tsum += (price * quantity)\r\n\t\t}\r\n\t})\r\n\treturn sum.toFixed(2)\r\n}\r\n\r\nconst totalAmount = computed(() => {\r\n\tconsole.log('计算商品总价checkoutItems:', checkoutItems.value)\r\n\tif (checkoutItems.value.length == 0) {\r\n\t\tconsole.log('商品列表为空返回0')\r\n\t\treturn 0\r\n\t}\r\n\t\r\n\t// 确保每个商品的价格和数量都是数字类型,并计算总和\r\n\tconst total = checkoutItems.value.reduce((sum, item) => {\r\n\t\t// 确保item存在且包含必要的属性\r\n\t\tif (item == null) return sum\r\n\t\t\r\n\t\t// 优先使用会员价,如果没有会员价则使用原价\r\n\t\tlet price = item.price\r\n\t\tif (item.member_price != null && item.member_price > 0 && item.member_price < item.price) {\r\n\t\t\tprice = item.member_price\r\n\t\t}\r\n\t\tconst quantity = item.quantity\r\n\t\t\r\n\t\t// 验证转换后的数字是否有效\r\n\t\tif (isNaN(price) || isNaN(quantity) || price <= 0 || quantity <= 0) {\r\n\t\t\tconsole.warn('商品价格或数量无效:', item, 'price:', price, 'quantity:', quantity)\r\n\t\t\treturn sum\r\n\t\t}\r\n\t\t\r\n\t\tconst itemTotal = price * quantity\r\n\t\treturn sum + itemTotal\r\n\t}, 0)\r\n\t\r\n\treturn total\r\n})\r\n\r\nconst deliveryFee = computed(() => {\r\n\tconst option = deliveryOptions.value.find(opt => opt.id === selectedDelivery.value)\r\n\treturn option?.price ?? 0\r\n})\r\n\r\nconst discountAmount = computed(() => {\r\n\tconst coupon = selectedCoupon.value?.template\r\n\tif (coupon == null) return 0\r\n\t// 确保使用计算后的商品总价进行比较 (should be min_order_amount)\r\n\tif (totalAmount.value < coupon.min_order_amount) return 0\r\n\t\r\n\t// 简单处理:假设都是满减券\r\n\treturn coupon.discount_value\r\n})\r\n\r\nconst actualAmount = computed(() => {\r\n\t// 确保所有值都是数字类型\r\n\tconst total = typeof totalAmount.value === 'number' ? totalAmount.value : 0\r\n\tconst delivery = typeof deliveryFee.value === 'number' ? deliveryFee.value : 0\r\n\tconst discount = typeof discountAmount.value === 'number' ? discountAmount.value : 0\r\n\t\r\n\t// 正确计算:商品总价 + 运费 - 优惠减免\r\n\tlet amount = total + delivery - discount\r\n\t\r\n\t// 金额必须大于等于0\r\n\treturn amount > 0 ? amount : 0\r\n})\r\n\r\n// 监听checkoutItems变化 - 调试用\r\nwatch(checkoutItems, (newItems: Array<CheckoutItemType>) => {\r\n\tconsole.log('checkoutItems变化了:', newItems)\r\n\tconsole.log('商品总价计算:', totalAmount.value)\r\n}, { deep: true })\r\n\r\n// 处理商品数据清洗\r\nconst processCheckoutItems = async (items: any[]) => {\r\n\t// 获取会员折扣信息\r\n\tlet memberDiscount = 1.0\r\n\ttry {\r\n\t\tconst memberInfo = await supabaseService.getUserMemberInfo()\r\n\t\tconst discountRaw = memberInfo.get('discount')\r\n\t\tif (discountRaw != null) {\r\n\t\t\tmemberDiscount = discountRaw as number\r\n\t\t}\r\n\t} catch (e) {\r\n\t\tconsole.log('获取会员信息失败,使用默认折扣:', e)\r\n\t}\r\n\t\r\n\t// 数据清洗:确保价格和数量是数字类型\r\n\tconst converted: Array<CheckoutItemType> = []\r\n\tif (items != null && items.length > 0) {\r\n\t\tfor (let i = 0; i < items.length; i++) {\r\n\t\t\tconst obj = toUTSJSONObject(items[i])\r\n\t\t\tconst id = obj.getString('id') ?? ''\r\n\t\t\tconst productId = obj.getString('product_id') ?? obj.getString('productId') ?? id\r\n\t\t\tconst skuId = obj.getString('sku_id') ?? obj.getString('skuId') ?? id\r\n\t\t\tconst productName = obj.getString('product_name') ?? obj.getString('name') ?? ''\r\n\t\t\tconst productImage = obj.getString('product_image') ?? obj.getString('image') ?? ''\r\n\r\n\t\t\tlet specs: any = {}\r\n\t\t\tconst skuSpecsAny = obj.get('sku_specifications')\r\n\t\t\tif (skuSpecsAny != null) {\r\n\t\t\t\tspecs = skuSpecsAny\r\n\t\t\t} else {\r\n\t\t\t\tconst specAny = obj.get('spec')\r\n\t\t\t\tif (specAny != null) specs = ({ spec: specAny } as any)\r\n\t\t\t}\r\n\r\n\t\t\tlet price = 0\r\n\t\t\tconst priceAny = obj.get('price')\r\n\t\t\tif (priceAny != null) {\r\n\t\t\t\tconst parsed = parseFloat(priceAny.toString())\r\n\t\t\t\tif (isNaN(parsed) == false) price = parsed\r\n\t\t\t}\r\n\r\n\t\t\tlet quantity = 1\r\n\t\t\tconst quantityAny = obj.get('quantity')\r\n\t\t\tif (quantityAny != null) {\r\n\t\t\t\tconst parsedQ = parseInt(quantityAny.toString())\r\n\t\t\t\tif (isNaN(parsedQ) == false && parsedQ >= 1) quantity = parsedQ\r\n\t\t\t}\r\n\r\n\t\t\tconst shopId = obj.getString('shop_id') ?? obj.getString('shopId') ?? 'unknown'\r\n\t\t\tconst shopName = obj.getString('shop_name') ?? obj.getString('shopName') ?? ''\r\n\t\t\tconst merchantId = obj.getString('merchant_id') ?? obj.getString('merchantId') ?? ''\r\n\t\t\t\r\n\t\t\t// 计算会员价\r\n\t\t\tlet memberPrice = 0\r\n\t\t\tif (memberDiscount > 0 && memberDiscount < 1 && price > 0) {\r\n\t\t\t\tmemberPrice = Math.round(price * memberDiscount * 100) / 100\r\n\t\t\t}\r\n\r\n\t\t\tconverted.push({\r\n\t\t\t\tid: id,\r\n\t\t\t\tproduct_id: productId,\r\n\t\t\t\tsku_id: skuId,\r\n\t\t\t\tproduct_name: productName,\r\n\t\t\t\tproduct_image: productImage,\r\n\t\t\t\tsku_specifications: specs,\r\n\t\t\t\tprice: parseFloat(price.toFixed(2)),\r\n\t\t\t\toriginal_price: parseFloat(price.toFixed(2)),\r\n\t\t\t\tmember_price: memberPrice,\r\n\t\t\t\tquantity: quantity,\r\n\t\t\t\tshop_id: shopId,\r\n\t\t\t\tshop_name: shopName,\r\n\t\t\t\tmerchant_id: merchantId\r\n\t\t\t} as CheckoutItemType)\r\n\t\t}\r\n\t}\r\n\tcheckoutItems.value = converted\r\n\t// 调试:打印每个商品的价格\r\n\tif (checkoutItems.value.length > 0) {\r\n\t\tconsole.log('清洗后商品价格明细:')\r\n\t\tcheckoutItems.value.forEach((item: CheckoutItemType, index: number) => {\r\n\t\t\tconsole.log(`商品${index}:`, item.product_name, '原价:', item.price, '会员价:', item.member_price, 'shop:', item.shop_id)\r\n\t\t})\r\n\t}\r\n}\r\n\r\n// 获取当前用户ID\r\nfunction getCurrentUserId(): string {\r\n\tconst userId = supabaseService.getCurrentUserId()\r\n\treturn userId ?? ''\r\n}\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n\t// 监听地址更新事件\r\n\tuni.$on('addressUpdated', (updatedAddressList: Array<AddressItem>) => {\r\n\t\taddressList.value = updatedAddressList\r\n\t\t\r\n\t\t// 如果当前没有选中地址,尝试选择默认地址\r\n\t\tif (selectedAddress.value == null && addressList.value.length > 0) {\r\n\t\t\tlet defaultAddress: AddressItem | null = null\r\n\t\t\tfor (let i = 0; i < addressList.value.length; i++) {\r\n\t\t\t\tconst addr = addressList.value[i]\r\n\t\t\t\tif (addr.is_default) {\r\n\t\t\t\t\tdefaultAddress = addr\r\n\t\t\t\t\tbreak\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (defaultAddress != null) selectedAddress.value = defaultAddress\r\n\t\t}\r\n\t})\r\n})\r\n\r\n// 组件卸载时移除事件监听\r\nonUnmounted(() => {\r\n\tuni.$off('addressUpdated')\r\n\tuni.$off('checkoutPageShow')\r\n // 离开页面时清除结算数据,防止下次进入时显示旧数据\r\n uni.removeStorageSync('checkout_type')\r\n uni.removeStorageSync('checkout_items')\r\n})\r\n\r\n// 加载默认地址\r\nasync function loadDefaultAddress(): Promise<void> {\r\n\ttry {\r\n\t\t// 首先检查用户是否登录\r\n\t\tconst currentUserId = getCurrentUserId()\r\n\t\t\r\n\t\t// 如果用户已登录尝试从Supabase加载地址数据\r\n\t\tif (currentUserId != '') {\r\n\t\t\tconst supabaseAddresses = await supabaseService.getAddresses()\r\n\t\t\t\r\n\t\t\tif (supabaseAddresses != null && supabaseAddresses.length > 0) {\r\n\t\t\t\t// 查找默认地址\r\n\t\t\t\tconst defaultAddress = supabaseAddresses.find((addr: SupabaseUserAddress) => addr.is_default === true)\r\n\t\t\t\tif (defaultAddress != null) {\r\n\t\t\t\t\t// 转换地址格式以匹配selectedAddress的结构\r\n\t\t\t\t\tconst addr: AddressItem = {\r\n\t\t\t\t\t\tid: defaultAddress.id,\r\n\t\t\t\t\t\trecipient_name: defaultAddress.recipient_name,\r\n\t\t\t\t\t\tphone: defaultAddress.phone,\r\n\t\t\t\t\t\tprovince: defaultAddress.province,\r\n\t\t\t\t\t\tcity: defaultAddress.city,\r\n\t\t\t\t\t\tdistrict: defaultAddress.district,\r\n\t\t\t\t\t\tdetail: defaultAddress.detail_address,\r\n\t\t\t\t\t\tis_default: defaultAddress.is_default\r\n\t\t\t\t\t}\r\n\t\t\t\t\tselectedAddress.value = addr\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 如果没有默认地址,使用第一个地址\r\n\t\t\t\t\tconst firstAddress = supabaseAddresses[0]\r\n\t\t\t\t\tconst addr: AddressItem = {\r\n\t\t\t\t\t\tid: firstAddress.id,\r\n\t\t\t\t\t\trecipient_name: firstAddress.recipient_name,\r\n\t\t\t\t\t\tphone: firstAddress.phone,\r\n\t\t\t\t\t\tprovince: firstAddress.province,\r\n\t\t\t\t\t\tcity: firstAddress.city,\r\n\t\t\t\t\t\tdistrict: firstAddress.district,\r\n\t\t\t\t\t\tdetail: firstAddress.detail_address,\r\n\t\t\t\t\t\tis_default: firstAddress.is_default\r\n\t\t\t\t\t}\r\n\t\t\t\t\tselectedAddress.value = addr\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// 同时更新本地存储缓存\r\n\t\t\t\tconst localAddresses: any[] = []\r\n\t\t\t\tfor (let i = 0; i < supabaseAddresses.length; i++) {\r\n\t\t\t\t\tconst addr = supabaseAddresses[i]\r\n\t\t\t\t\tlocalAddresses.push({\r\n\t\t\t\t\t\tid: addr.id,\r\n\t\t\t\t\t\tname: addr.recipient_name,\r\n\t\t\t\t\t\tphone: addr.phone,\r\n\t\t\t\t\t\tprovince: addr.province,\r\n\t\t\t\t\t\tcity: addr.city,\r\n\t\t\t\t\t\tdistrict: addr.district,\r\n\t\t\t\t\t\tdetail: addr.detail_address,\r\n\t\t\t\t\t\tisDefault: addr.is_default\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\tuni.setStorageSync('addresses', JSON.stringify(localAddresses))\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// 如果Supabase没有地址数据或用户未登录尝试从本地存储加载\r\n\t\tif (selectedAddress.value == null) {\r\n\t\t\tconst storedAddresses = uni.getStorageSync('addresses')\r\n\t\t\tconst storedAddressesStr = storedAddresses != null ? storedAddresses.toString() : ''\r\n\t\t\tif (storedAddressesStr != '') {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst addresses = JSON.parse(storedAddressesStr) as any[]\r\n\t\t\t\t\tif (addresses != null && addresses.length > 0) {\r\n\t\t\t\t\t\tlet picked: UTSJSONObject | null = null\r\n\t\t\t\t\t\tfor (let i = 0; i < addresses.length; i++) {\r\n\t\t\t\t\t\t\tconst obj = toUTSJSONObject(addresses[i])\r\n\t\t\t\t\t\t\tconst isDef = obj.getBoolean('isDefault') ?? obj.getBoolean('is_default') ?? false\r\n\t\t\t\t\t\t\tif (isDef) {\r\n\t\t\t\t\t\t\t\tpicked = obj\r\n\t\t\t\t\t\t\t\tbreak\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (picked == null) picked = toUTSJSONObject(addresses[0])\r\n\r\n\t\t\t\t\t\tconst addr: AddressItem = {\r\n\t\t\t\t\t\t\tid: picked.getString('id') ?? '',\r\n\t\t\t\t\t\t\trecipient_name: picked.getString('recipient_name') ?? picked.getString('name') ?? '',\r\n\t\t\t\t\t\t\tphone: picked.getString('phone') ?? '',\r\n\t\t\t\t\t\t\tprovince: picked.getString('province') ?? '',\r\n\t\t\t\t\t\t\tcity: picked.getString('city') ?? '',\r\n\t\t\t\t\t\t\tdistrict: picked.getString('district') ?? '',\r\n\t\t\t\t\t\t\tdetail: picked.getString('detail') ?? picked.getString('detail_address') ?? '',\r\n\t\t\t\t\t\t\tis_default: picked.getBoolean('isDefault') ?? picked.getBoolean('is_default') ?? false\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tselectedAddress.value = addr\r\n\t\t\t\t\t}\r\n\t\t\t\t} catch (err) {\r\n\t\t\t\t\tconsole.error('解析本地地址数据失败:', err)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// 如果仍然没有地址,使用模拟地址数据\r\n\t\tif (selectedAddress.value == null) {\r\n\t\t\t// 模拟地址数据\r\n\t\t\tconst mockAddresses: MockAddress[] = [\r\n\t\t\t\t{\r\n\t\t\t\t\tid: 'addr_001',\r\n\t\t\t\t\tname: '张三',\r\n\t\t\t\t\tphone: '13800138001',\r\n\t\t\t\t\tprovince: '北京市',\r\n\t\t\t\t\tcity: '北京市',\r\n\t\t\t\t\tdistrict: '朝阳区',\r\n\t\t\t\t\tdetail: '建国路88号SOHO现代城A座1001',\r\n\t\t\t\t\tisDefault: true\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\tid: 'addr_002',\r\n\t\t\t\t\tname: '李四',\r\n\t\t\t\t\tphone: '13900139001',\r\n\t\t\t\t\tprovince: '上海市',\r\n\t\t\t\t\tcity: '上海市',\r\n\t\t\t\t\tdistrict: '浦东新区',\r\n\t\t\t\t\tdetail: '陆家嘴环路1000号汇亚大厦20层',\r\n\t\t\t\t\tisDefault: false\r\n\t\t\t\t}\r\n\t\t\t]\r\n\t\t\t\r\n\t\t\t// 保存模拟地址到本地存储\r\n\t\t\tuni.setStorageSync('addresses', JSON.stringify(mockAddresses))\r\n\t\t\t\r\n\t\t\t// 使用第一个地址作为默认地址\r\n\t\t\tconst first = mockAddresses[0]\r\n\t\t\tconst addr: AddressItem = {\r\n\t\t\t\tid: first.id,\r\n\t\t\t\trecipient_name: first.name,\r\n\t\t\t\tphone: first.phone,\r\n\t\t\t\tprovince: first.province,\r\n\t\t\t\tcity: first.city,\r\n\t\t\t\tdistrict: first.district,\r\n\t\t\t\tdetail: first.detail,\r\n\t\t\t\tis_default: first.isDefault\r\n\t\t\t}\r\n\t\t\tselectedAddress.value = addr\r\n\t\t}\r\n\t\t\r\n\t} catch (error) {\r\n\t\tconsole.error('加载地址失败:', error)\r\n\t}\r\n}\r\n\r\n// 用户登录状态\r\nconst isLoggedIn = computed((): boolean => {\r\n\tconst userId = getCurrentUserId()\r\n\treturn userId != ''\r\n})\r\n\r\n// 获取完整地址\r\nconst getFullAddress = (address: AddressItem): string => {\r\n\treturn `${address.province}${address.city}${address.district}${address.detail}`\r\n}\r\n\r\n// 加载地址列表\r\nasync function loadAddressList(): Promise<void> {\r\n console.log('[loadAddressList] 开始加载地址列表')\r\n\ttry {\r\n\t\tconst currentUserId = getCurrentUserId()\r\n\t\tconsole.log('[loadAddressList] currentUserId:', currentUserId)\r\n\t\t\r\n\t\tif (currentUserId != '') {\r\n\t\t\tconst supabaseAddresses = await supabaseService.getAddresses()\r\n\t\t\tconsole.log('[loadAddressList] supabaseAddresses 数量:', supabaseAddresses != null ? supabaseAddresses.length : 0)\r\n\t\t\t\r\n\t\t\tif (supabaseAddresses != null && supabaseAddresses.length > 0) {\r\n\t\t\t\tconst list: AddressItem[] = []\r\n\t\t\t\tconst localAddresses: any[] = []\r\n\t\t\t\tfor (let i = 0; i < supabaseAddresses.length; i++) {\r\n\t\t\t\t\tconst addr = supabaseAddresses[i]\r\n\t\t\t\t\tconsole.log('[loadAddressList] 地址', i, ':', addr.recipient_name, addr.phone, addr.detail_address)\r\n\t\t\t\t\tlist.push({\r\n\t\t\t\t\t\tid: addr.id,\r\n\t\t\t\t\t\trecipient_name: addr.recipient_name,\r\n\t\t\t\t\t\tphone: addr.phone,\r\n\t\t\t\t\t\tprovince: addr.province,\r\n\t\t\t\t\t\tcity: addr.city,\r\n\t\t\t\t\t\tdistrict: addr.district,\r\n\t\t\t\t\t\tdetail: addr.detail_address,\r\n\t\t\t\t\t\tis_default: addr.is_default\r\n\t\t\t\t\t})\r\n\t\t\t\t\tlocalAddresses.push({\r\n\t\t\t\t\t\tid: addr.id,\r\n\t\t\t\t\t\tname: addr.recipient_name,\r\n\t\t\t\t\t\tphone: addr.phone,\r\n\t\t\t\t\t\tprovince: addr.province,\r\n\t\t\t\t\t\tcity: addr.city,\r\n\t\t\t\t\t\tdistrict: addr.district,\r\n\t\t\t\t\t\tdetail: addr.detail_address,\r\n\t\t\t\t\t\tisDefault: addr.is_default\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\taddressList.value = list\r\n\t\t\t\tconsole.log('[loadAddressList] addressList.value 设置完成, 数量:', addressList.value.length)\r\n\t\t\t\tuni.setStorageSync('addresses', JSON.stringify(localAddresses))\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (addressList.value.length == 0) {\r\n\t\t\tconst storedAddresses = uni.getStorageSync('addresses')\r\n\t\t\tconst storedAddressesStr = storedAddresses != null ? storedAddresses.toString() : ''\r\n\t\t\tif (storedAddressesStr != '') {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst addresses = JSON.parse(storedAddressesStr) as any[]\r\n\t\t\t\t\tif (addresses != null && addresses.length > 0) {\r\n\t\t\t\t\t\tconst list: AddressItem[] = []\r\n\t\t\t\t\t\tfor (let i = 0; i < addresses.length; i++) {\r\n\t\t\t\t\t\t\tconst obj = toUTSJSONObject(addresses[i])\r\n\t\t\t\t\t\t\tlist.push({\r\n\t\t\t\t\t\t\t\tid: obj.getString('id') ?? '',\r\n\t\t\t\t\t\t\t\trecipient_name: obj.getString('recipient_name') ?? obj.getString('name') ?? '',\r\n\t\t\t\t\t\t\t\tphone: obj.getString('phone') ?? '',\r\n\t\t\t\t\t\t\t\tprovince: obj.getString('province') ?? '',\r\n\t\t\t\t\t\t\t\tcity: obj.getString('city') ?? '',\r\n\t\t\t\t\t\t\t\tdistrict: obj.getString('district') ?? '',\r\n\t\t\t\t\t\t\t\tdetail: obj.getString('detail') ?? obj.getString('detail_address') ?? '',\r\n\t\t\t\t\t\t\t\tis_default: obj.getBoolean('isDefault') ?? obj.getBoolean('is_default') ?? false\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\taddressList.value = list\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\taddressList.value = []\r\n\t\t\t\t\t}\r\n\t\t\t\t} catch (err) {\r\n\t\t\t\t\taddressList.value = []\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\taddressList.value = []\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (addressList.value.length == 0) {\r\n\t\t\tconst mockAddresses: MockAddress[] = [\r\n\t\t\t\t{\r\n\t\t\t\t\tid: 'addr_001',\r\n\t\t\t\t\tname: '张三',\r\n\t\t\t\t\tphone: '13800138001',\r\n\t\t\t\t\tprovince: '北京市',\r\n\t\t\t\t\tcity: '北京市',\r\n\t\t\t\t\tdistrict: '朝阳区',\r\n\t\t\t\t\tdetail: '建国路88号SOHO现代城A座1001',\r\n\t\t\t\t\tisDefault: true\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\tid: 'addr_002',\r\n\t\t\t\t\tname: '李四',\r\n\t\t\t\t\tphone: '13900139001',\r\n\t\t\t\t\tprovince: '上海市',\r\n\t\t\t\t\tcity: '上海市',\r\n\t\t\t\t\tdistrict: '浦东新区',\r\n\t\t\t\t\tdetail: '陆家嘴环路1000号汇亚大厦20层',\r\n\t\t\t\t\tisDefault: false\r\n\t\t\t\t}\r\n\t\t\t]\r\n\t\t\t\r\n\t\t\tuni.setStorageSync('addresses', JSON.stringify(mockAddresses))\r\n\t\t\t\r\n\t\t\tconst list: AddressItem[] = []\r\n\t\t\tfor (let i = 0; i < mockAddresses.length; i++) {\r\n\t\t\t\tconst addr = mockAddresses[i]\r\n\t\t\t\tlist.push({\r\n\t\t\t\t\tid: addr.id,\r\n\t\t\t\t\trecipient_name: addr.name,\r\n\t\t\t\t\tphone: addr.phone,\r\n\t\t\t\t\tprovince: addr.province,\r\n\t\t\t\t\tcity: addr.city,\r\n\t\t\t\t\tdistrict: addr.district,\r\n\t\t\t\t\tdetail: addr.detail,\r\n\t\t\t\t\tis_default: addr.isDefault\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t\taddressList.value = list\r\n\t\t}\r\n\t} catch (error) {\r\n\t\tconsole.error('加载地址列表失败:', error)\r\n\t}\r\n}\r\n\r\n// 从本地存储加载结算数据(例如从购物车进入)\r\nasync function loadFromLocalStorage(): Promise<void> {\r\n\tconst cartData = uni.getStorageSync('cart')\r\n\tconst cartDataStr = cartData != null ? cartData.toString() : ''\r\n\tif (cartDataStr != '') {\r\n\t\ttry {\r\n\t\t\tconst cartItems = JSON.parse(cartDataStr) as any[]\r\n\t\t\tconst selectedCartItems: any[] = []\r\n\t\t\tfor (let i = 0; i < cartItems.length; i++) {\r\n\t\t\t\tconst obj = toUTSJSONObject(cartItems[i])\r\n\t\t\t\tconst selected = obj.getBoolean('selected') ?? false\r\n\t\t\t\tif (selected) selectedCartItems.push(obj)\r\n\t\t\t}\r\n\t\t\tif (selectedCartItems.length > 0) {\r\n\t\t\t\tawait processCheckoutItems(selectedCartItems)\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\tconsole.error('解析购物车数据失败:', e)\r\n\t\t}\r\n\t}\r\n\tloadDefaultAddress()\r\n}\r\n\r\n// 加载结算数据兼容旧版本现在主要在onLoad中处理\r\nfunction loadCheckoutData(): void {\r\n\tloadFromLocalStorage()\r\n}\r\n\r\n// 初始化加载数据\r\nasync function initCheckoutData(): Promise<void> {\r\n let dataLoaded = false\r\n\tconst checkoutTypeAny = uni.getStorageSync('checkout_type')\r\n\tconst checkoutType = checkoutTypeAny != null ? checkoutTypeAny.toString() : ''\r\n\tif (checkoutType == 'buy_now' || checkoutType == 'cart') {\r\n\t\tconsole.log(`检测到结算模式(${checkoutType})从Storage加载数据`)\r\n\t\tconst itemsStrAny = uni.getStorageSync('checkout_items')\r\n\t\tconst itemsStr = itemsStrAny != null ? itemsStrAny.toString() : ''\r\n\t\tif (itemsStr != '') {\r\n\t\t\ttry {\r\n\t\t\t\tconst items = JSON.parse(itemsStr as string)\r\n\t\t\t\tconsole.log('从Storage加载的商品数据:', items)\r\n if (items != null && Array.isArray(items) && items.length > 0) {\r\n\t\t\t\t await processCheckoutItems(items)\r\n dataLoaded = true\r\n }\r\n\t\t\t} catch (e) {\r\n\t\t\t\tconsole.error('解析结算数据失败', e)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (dataLoaded == false) {\r\n console.log('未找到预结算数据,尝试从购物车本地存储加载')\r\n\t await loadFromLocalStorage()\r\n }\r\n \r\n loadDefaultAddress()\r\n loadAddressList()\r\n}\r\n\r\nonLoad((options: any) => {\r\n initCheckoutData()\r\n})\r\n\r\n// 页面显示时触发\r\nfunction onShow(): void {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId != '') {\r\n\t\tloadDefaultAddress()\r\n\t\tloadAddressList()\r\n\t}\r\n}\r\n\r\nuni.$on('checkoutPageShow', onShow)\r\n\r\n// 选择地址\r\nconst handleSelectAddress = (address: AddressItem) => {\r\n\tselectedAddress.value = address\r\n\tshowAddressPopup.value = false\r\n}\r\n\r\n// 新建地址\r\nconst handleAddNewAddress = () => {\r\n\tshowNewAddressForm.value = true\r\n}\r\n\r\n// 保存新地址\r\nconst saveNewAddress = async () => {\r\n\tif (newAddress.value.recipient_name == '' || newAddress.value.phone == '' || newAddress.value.detail == '') {\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '请填写完整信息',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\treturn\r\n\t}\r\n\t// 触发保存确认弹窗\r\n\tshowSaveConfirm.value = true\r\n}\r\n\r\n// 处理保存确认\r\nconst handleSaveConfirm = async (save: boolean) => {\r\n\tshowSaveConfirm.value = false\r\n\t\r\n\tconst newAddressData: NewAddressData = {\r\n\t\tid: `addr_${Date.now()}`,\r\n\t\tname: newAddress.value.recipient_name,\r\n\t\tphone: newAddress.value.phone,\r\n\t\tprovince: newAddress.value.province,\r\n\t\tcity: newAddress.value.city,\r\n\t\tdistrict: newAddress.value.district,\r\n\t\tdetail: newAddress.value.detail,\r\n isDefault: newAddress.value.is_default \r\n\t}\r\n\t\t\r\n\t\tif (save) {\r\n\t\t\tconst storedAddresses = uni.getStorageSync('addresses')\r\n\t\t\tlet addresses: any[] = []\r\n\t\t\tconst storedAddressesStr = storedAddresses != null ? storedAddresses.toString() : ''\r\n\t\t\tif (storedAddressesStr != '') {\r\n\t\t\t\ttry {\r\n\t\t\t\t\taddresses = JSON.parse(storedAddressesStr) as any[]\r\n\t\t\t\t} catch (e) {\r\n\t\t\t\t\taddresses = []\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tconst normalized: any[] = []\r\n\t\t\tfor (let i = 0; i < addresses.length; i++) {\r\n\t\t\t\tconst obj = toUTSJSONObject(addresses[i])\r\n\t\t\t\tconst isDef = obj.getBoolean('isDefault') ?? obj.getBoolean('is_default') ?? false\r\n\t\t\t\tnormalized.push({\r\n\t\t\t\t\tid: obj.getString('id') ?? '',\r\n\t\t\t\t\tname: obj.getString('name') ?? obj.getString('recipient_name') ?? '',\r\n\t\t\t\t\tphone: obj.getString('phone') ?? '',\r\n\t\t\t\t\tprovince: obj.getString('province') ?? '',\r\n\t\t\t\t\tcity: obj.getString('city') ?? '',\r\n\t\t\t\t\tdistrict: obj.getString('district') ?? '',\r\n\t\t\t\t\tdetail: obj.getString('detail') ?? obj.getString('detail_address') ?? '',\r\n\t\t\t\t\tisDefault: newAddressData.isDefault ? false : isDef as boolean,\r\n\t\t\t\t\tlabel: obj.getString('label') ?? ''\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (normalized.length === 0 && newAddressData.isDefault == false) {\r\n\t\t\t\tnewAddressData.isDefault = true\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tnormalized.unshift(newAddressData)\r\n\t\t\tuni.setStorageSync('addresses', JSON.stringify(normalized))\r\n\t\t\t\r\n\t\t\tconst updatedList: AddressItem[] = []\r\n\t\t\tfor (let i = 0; i < normalized.length; i++) {\r\n\t\t\t\tconst obj = toUTSJSONObject(normalized[i])\r\n\t\t\t\tupdatedList.push({\r\n\t\t\t\t\tid: obj.getString('id') ?? '',\r\n\t\t\t\t\trecipient_name: obj.getString('recipient_name') ?? obj.getString('name') ?? '',\r\n\t\t\t\t\tphone: obj.getString('phone') ?? '',\r\n\t\t\t\t\tprovince: obj.getString('province') ?? '',\r\n\t\t\t\t\tcity: obj.getString('city') ?? '',\r\n\t\t\t\t\tdistrict: obj.getString('district') ?? '',\r\n\t\t\t\t\tdetail: obj.getString('detail') ?? obj.getString('detail_address') ?? '',\r\n\t\t\t\t\tis_default: obj.getBoolean('isDefault') ?? obj.getBoolean('is_default') ?? false\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t\tuni.$emit('addressUpdated', updatedList)\r\n\t\t}\r\n\t\t\r\n\t\tconst checkoutFormatAddress: AddressItem = {\r\n\t\tid: newAddressData.id ?? '',\r\n\t\trecipient_name: newAddressData.name ?? '',\r\n\t\tphone: newAddressData.phone ?? '',\r\n\t\tprovince: newAddressData.province,\r\n\t\tcity: newAddressData.city,\r\n\t\tdistrict: newAddressData.district,\r\n\t\tdetail: newAddressData.detail,\r\n\t\tis_default: newAddressData.isDefault\r\n\t}\r\n\t\t\r\n\t\tif (checkoutFormatAddress.is_default) {\r\n\t\t\tfor (let i = 0; i < addressList.value.length; i++) {\r\n\t\t\t\taddressList.value[i].is_default = false\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\taddressList.value.unshift(checkoutFormatAddress)\r\n\t\t\r\n\t\tif (checkoutFormatAddress.is_default || selectedAddress.value == null) {\r\n\t\t\tselectedAddress.value = checkoutFormatAddress\r\n\t\t}\r\n\t\t\r\n\t\tnewAddress.value = {\r\n\t\trecipient_name: '',\r\n\t\tphone: '',\r\n\t\tprovince: '',\r\n\t\tcity: '',\r\n\t\tdistrict: '',\r\n\t\tdetail: '',\r\n\t\tis_default: false\r\n\t} as NewAddressForm;\r\n\t\tsmartAddressInput.value = ''\r\n\t\tshowNewAddressForm.value = false\r\n\t\t\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '地址保存成功',\r\n\t\t\ticon: 'success'\r\n\t\t})\r\n}\r\n\r\n// 解析智能地址\r\nconst parseSmartAddress = () => {\r\n\tconst input = smartAddressInput.value.trim()\r\n\tif (input == '') return\r\n\t\r\n\tnewAddress.value.recipient_name = ''\r\n\tnewAddress.value.phone = ''\r\n\tnewAddress.value.province = ''\r\n\tnewAddress.value.city = ''\r\n\tnewAddress.value.district = ''\r\n\tnewAddress.value.detail = ''\r\n\t\r\n\tconst phoneRegex = /(1[3-9]\\d{9})/g\r\n\tconst phoneMatches = input.match(phoneRegex)\r\n\tif (phoneMatches != null && phoneMatches.length > 0) {\r\n\t\tnewAddress.value.phone = phoneMatches[0] ?? ''\r\n\t}\r\n\t\r\n\tconst nameRegex = /([\\u4e00-\\u9fa5]{2,4})/g\r\n\tconst nameMatches = input.match(nameRegex)\r\n\t\tif (nameMatches != null && nameMatches.length > 0) {\r\n\t\t\tnewAddress.value.recipient_name = nameMatches[0] ?? ''\r\n\t\t}\r\n\t\r\n\tlet addressText = input\r\n\tif (newAddress.value.recipient_name != '') {\r\n\t\taddressText = addressText.replace(newAddress.value.recipient_name, '')\r\n\t}\r\n\tif (newAddress.value.phone != '') {\r\n\t\taddressText = addressText.replace(newAddress.value.phone, '')\r\n\t}\r\n\t\r\n\taddressText = addressText.replace(/[,;\\s]+/g, ' ').trim()\r\n\t\r\n\tconst patterns = [\r\n\t\t/^(.*?省)?(.*?市)?(.*?[区县])?(.*)$/,\r\n\t\t/^(.*?省)?(.*?市)?(.*)$/\r\n\t]\r\n\t\r\n\tfor (const pattern of patterns) {\r\n\t\tconst match = addressText.match(pattern)\r\n\t\tif (match != null) {\r\n\t\t\tconst [, province, city, district, detail] = match\r\n\t\t\t\r\n\t\t\tif (province != null) newAddress.value.province = province.replace('省', '').trim()\r\n\t\t\tif (city != null) newAddress.value.city = city.replace('市', '').trim()\r\n\t\t\tif (district != null) newAddress.value.district = district.trim()\r\n\t\t\tif (detail != null) newAddress.value.detail = detail.trim()\r\n\t\t\t\r\n\t\t\tif (newAddress.value.detail == '' && district != null && detail != null) {\r\n\t\t\t\tnewAddress.value.detail = detail.trim()\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tbreak\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (newAddress.value.province == '' && newAddress.value.city == '' && newAddress.value.district == '') {\r\n\t\tconst parts = addressText.split(/[省市县区]/)\r\n\t\tif (parts.length >= 2) {\r\n\t\t\tnewAddress.value.province = parts[0] ?? ''\r\n\t\t\tnewAddress.value.city = parts[1] ?? ''\r\n\t\t\tnewAddress.value.detail = parts.slice(2).join('').trim()\r\n\t\t\tif (newAddress.value.detail == '') {\r\n\t\t\t newAddress.value.detail = addressText\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tnewAddress.value.detail = addressText\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (newAddress.value.detail == '' && addressText.trim() != '') {\r\n\t\tnewAddress.value.detail = addressText.trim()\r\n\t}\r\n}\r\n\r\n// 取消新建地址\r\nconst cancelNewAddress = () => {\r\n\tshowNewAddressForm.value = false\r\n\tnewAddress.value = {\r\n\t\t\trecipient_name: '',\r\n\t\t\tphone: '',\r\n\t\t\tprovince: '',\r\n\t\t\tcity: '',\r\n\t\t\tdistrict: '',\r\n\t\t\tdetail: '',\r\n\t\t\tis_default: false\r\n\t\t} as NewAddressForm;\r\n\t\tsmartAddressInput.value = ''\r\n}\r\n\r\n// 获取规格文本\r\nfunction formatSpecs(specs: any): string {\r\n if (specs == null) return ''\r\n \r\n try {\r\n const specsStr = JSON.stringify(specs)\r\n if (specsStr == '{}' || specsStr == '[]' || specsStr == '\"\"' || specsStr == '') return ''\r\n \r\n // 使用 Record 类型替代 UTSJSONObject 的迭代器方法\r\n const specsObj = JSON.parse(specsStr) as Record<string, any>\r\n \r\n const parts: string[] = []\r\n // 遍历已知可能的规格键名\r\n const possibleKeys = ['颜色', '尺寸', '规格', '型号', '版本', '材质', '款式', 'color', 'size', 'spec', 'version', 'style']\r\n \r\n // 先尝试已知键名\r\n for (let i = 0; i < possibleKeys.length; i++) {\r\n const key = possibleKeys[i]\r\n const value = specsObj[key]\r\n if (value != null && value.toString() != '') {\r\n parts.push(`${key}: ${value.toString()}`)\r\n }\r\n }\r\n \r\n // 如果已知键名没找到,尝试遍历对象的所有属性\r\n if (parts.length === 0) {\r\n // 使用 JSON.stringify 后正则匹配键值对\r\n const keyValueRegex = /\"([^\"]+)\":\\s*\"([^\"]+)\"/g\r\n let match: RegExpExecArray | null = null\r\n while (true) {\r\n match = keyValueRegex.exec(specsStr)\r\n if (match == null) break\r\n const key = match[1]\r\n const value = match[2]\r\n if (key != null && value != null && value != '') {\r\n parts.push(`${key}: ${value}`)\r\n }\r\n }\r\n }\r\n \r\n if (parts.length === 0) return ''\r\n return parts.join('; ')\r\n } catch (e) {\r\n return ''\r\n }\r\n}\r\n\r\n// 选择配送方式\r\nconst selectDelivery = (option: DeliveryOptionType) => {\r\n\tselectedDelivery.value = option.id\r\n}\r\n\r\n// 选择优惠券\r\nconst selectCoupon = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/coupons',\r\n\t\tsuccess: (res: any) => {\r\n // 移除事件通道相关代码,避免使用不支持的 API\r\n // 注释掉事件通道逻辑,因为当前环境不支持 createEventChannel\r\n // const eventChannel = res.eventChannel || uni.createEventChannel()\r\n // if (eventChannel && eventChannel.emit) {\r\n // eventChannel.emit('setSelectMode', { selectMode: true })\r\n // }\r\n }\r\n\t})\r\n\t\r\n\tuni.$on('couponSelected', (coupon: any) => {\r\n\tselectedCoupon.value = coupon as UserCouponType\r\n\tuni.$off('couponSelected')\r\n\t})\r\n}\r\n\r\n// 提交订单\r\nconst submitOrder = async () => {\r\n if (selectedAddress.value == null) {\r\n uni.showToast({ title: '请选择收货地址', icon: 'none' })\r\n return\r\n }\r\n\r\n if (checkoutItems.value.length === 0) {\r\n uni.showToast({ title: '订单中没有商品', icon: 'none' })\r\n return\r\n }\r\n\r\n uni.showLoading({ title: '提交中...' })\r\n\r\n try {\r\n const userId = supabaseService.getCurrentUserId()\r\n if (userId == null || userId == '') {\r\n uni.hideLoading()\r\n uni.showToast({ title: '请先登录', icon: 'none' })\r\n return\r\n }\r\n \r\n console.log('[submitOrder] 开始创建订单, userId:', userId)\r\n console.log('[submitOrder] shopGroups数量:', shopGroups.value.length)\r\n \r\n const groups: any[] = []\r\n for (let i = 0; i < shopGroups.value.length; i++) {\r\n const group = shopGroups.value[i]\r\n console.log(`[submitOrder] 处理店铺组 ${i}:`, {\r\n shopId: group.shopId,\r\n shopName: group.shopName,\r\n merchant_id: group.merchant_id,\r\n itemsCount: group.items.length\r\n })\r\n const items: any[] = []\r\n for (let j = 0; j < group.items.length; j++) {\r\n const item = group.items[j]\r\n items.push({\r\n id: item.id,\r\n product_id: item.product_id,\r\n sku_id: item.sku_id,\r\n quantity: item.quantity,\r\n price: item.price,\r\n member_price: item.member_price,\r\n product_name: item.product_name,\r\n product_image: item.product_image,\r\n specifications: item.sku_specifications\r\n })\r\n }\r\n const finalMerchantId = (group.merchant_id != null && group.merchant_id != '') ? group.merchant_id : group.shopId\r\n console.log(`[submitOrder] 店铺组 ${i} 最终使用的 merchant_id:`, finalMerchantId)\r\n groups.push({\r\n merchant_id: finalMerchantId,\r\n shopId: group.shopId,\r\n shopName: group.shopName,\r\n items: items\r\n })\r\n }\r\n \r\n console.log('[submitOrder] 准备传递的 groups 数量:', groups.length)\r\n\r\n const result = await supabaseService.createOrdersByShop({\r\n shipping_address: selectedAddress.value !== null ? toUTSJSONObject(selectedAddress.value!) : new UTSJSONObject(),\r\n shopGroups: groups,\r\n deliveryFee: deliveryFee.value,\r\n discountAmount: discountAmount.value\r\n })\r\n \r\n uni.hideLoading()\r\n \r\n console.log('[submitOrder] 创建结果 success:', result.success)\r\n\r\n if (result.success) {\r\n try {\r\n uni.removeStorageSync('checkout_items')\r\n uni.removeStorageSync('checkout_type')\r\n } catch(e) { console.error(e) }\r\n\r\n const orderIds = result.orderIds\r\n if (orderIds.length === 1) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/payment?orderId=${orderIds[0]}&amount=${actualAmount.value}`\r\n })\r\n } else {\r\n uni.showToast({ title: `成功创建${orderIds.length}个订单`, icon: 'success' })\r\n setTimeout(() => {\r\n uni.redirectTo({ url: '/pages/mall/consumer/orders' })\r\n }, 1500)\r\n }\r\n } else {\r\n const errMsg = (result.error != null && result.error !== '') ? result.error : '创建订单失败'\r\n console.error('[submitOrder] 订单创建失败:', errMsg)\r\n uni.showToast({ title: errMsg, icon: 'none' })\r\n }\r\n\r\n } catch (err: any) {\r\n uni.hideLoading()\r\n console.error('[submitOrder] 提交订单错误:', err)\r\n const errMsg = (err.message != null && err.message !== '') ? (err.message as string) : '提交订单失败'\r\n uni.showToast({ title: errMsg, icon: 'none' })\r\n }\r\n}\r\n\r\n// 生成订单号\r\nconst generateOrderNo = (): string => {\r\n\tconst date = new Date()\r\n // ...\r\n\tconst random = Math.random().toString().slice(2, 8)\r\n\treturn `ORD${Date.now()}${random}`\r\n}\r\n\r\n// 返回\r\nconst goBack = () => {\r\n\tuni.navigateBack()\r\n}\r\n\r\n// 选择地址\r\nconst selectAddress = () => {\r\n\tshowAddressPopup.value = true;\r\n}\r\n\r\n// 添加登录跳转方法\r\nconst goToLogin = () => {\r\n uni.navigateTo({\r\n url: '/pages/login/login' // 根据实际登录页面路径调整\r\n })\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.checkout-page {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\tright: 0;\r\n\tbottom: 0;\r\n\tbackground-color: #f8f8f8;\r\n\toverflow: hidden;\r\n\talign-items: center; /* PC端居中显示 */\r\n}\r\n/* 顶部栏 */\r\n.checkout-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tborder-bottom: 1px solid #f0f0f0;\r\n\tflex-shrink: 0;\r\n\twidth: 100%;\r\n}\r\n\r\n.header-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n text-align: center;\r\n}\r\n\r\n.checkout-content {\r\n\tflex: 1;\r\n\twidth: 100%;\r\n\tmax-width: 800px; /* 限制PC端内容宽度 */\r\n\tmin-height: 0;\r\n\tbackground-color: #f8f8f8;\r\n}\r\n\r\n/* 卡片容器 */\r\n.no-products { \r\n\tdisplay: flex; \r\n\tflex-direction: column;\r\n\talign-items: center; \r\n\tjustify-content: center; \r\n\tpadding: 30px 0;\r\n}\r\n.no-products-text { font-size: 14px; color: #999999; }\r\n\r\n.section-card {\r\n background-color: #ffffff;\r\n margin: 12px;\r\n padding: 18px;\r\n border-radius: 12px;\r\n box-shadow: 0 2px 8px rgba(0,0,0,0.02);\r\n}\r\n\r\n/* 自适应适配 */\r\n@media screen and (min-width: 768px) {\r\n .section-card {\r\n margin: 16px 0;\r\n }\r\n .delivery-options-grid {\r\n display: flex;\r\n flex-direction: row !important;\r\n flex-wrap: wrap;\r\n gap: 12px;\r\n }\r\n .delivery-card {\r\n flex: 1;\r\n min-width: 280px;\r\n margin-bottom: 0 !important;\r\n }\r\n /* 底部结算栏在大屏居中并限宽 */\r\n .footer-action-bar {\r\n max-width: 800px;\r\n left: 50% !important;\r\n right: auto !important;\r\n transform: translateX(-50%);\r\n border-radius: 16px 16px 0 0;\r\n box-shadow: 0 -4px 16px rgba(0,0,0,0.05);\r\n }\r\n}\r\n\r\n.address-section {\r\n\tmargin-top: 12px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tpadding: 16px;\r\n\tposition: relative;\r\n\tbackground-color: #ffffff;\r\n}\r\n\r\n.address-icon-wrapper {\r\n\tmargin-right: 12px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n}\r\n\r\n.location-icon {\r\n\tfont-size: 24px;\r\n\tcolor: #ff5000;\r\n}\r\n\r\n.address-info { \r\n\tflex: 1; \r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.address-header { \r\n\tdisplay: flex; \r\n\tflex-direction: row;\r\n\talign-items: center; \r\n\tmargin-bottom: 4px; \r\n}\r\n\r\n.recipient { font-size: 17px; font-weight: bold; color: #333333; margin-right: 12px; }\r\n.phone { font-size: 14px; color: #666666; margin-right: 8px; }\r\n.default-tag { background-color: #fff0eb; border: 0.5px solid #ff5000; padding: 0 6px; border-radius: 4px; }\r\n.tag-text { color: #ff5000; font-size: 11px; }\r\n\r\n.address-detail { \r\n\tfont-size: 13px; \r\n\tcolor: #666; \r\n\tline-height: 1.5; \r\n\tlines: 2;\r\n\ttext-overflow: ellipsis;\r\n\toverflow: hidden;\r\n}\r\n\r\n.address-arrow-wrapper {\r\n\tmargin-left: 8px;\r\n}\r\n\r\n.address-arrow { \r\n\tcolor: #ccc; \r\n\tfont-size: 20px; \r\n}\r\n\r\n.no-address { flex: 1; display: flex; align-items: center; }\r\n.no-address-text { font-size: 16px; color: #999999; }\r\n\r\n.products-section { padding: 0; }\r\n.debug-info { padding: 10px 15px; border-bottom: 1px solid #f5f5f5; margin-bottom: 10px; }\r\n.debug-text { font-size: 12px; color: #999; text-align: center; }\r\n.shop-group { background-color: #fff; padding: 0; }\r\n.shop-header { display: flex; flex-direction: row; align-items: center; padding: 5px 0 12px; }\r\n.shop-icon { font-size: 17px; margin-right: 6px; }\r\n.shop-name { font-size: 15px; font-weight: bold; color: #333; }\r\n.shop-subtotal { display: flex; justify-content: flex-end; align-items: center; padding: 12px 0 5px; margin-top: 5px; border-top: 1px dashed #f0f0f0; }\r\n.subtotal-label { color: #888; margin-right: 8px; font-size: 13px; }\r\n.subtotal-value { color: #666; font-size: 13px; }\r\n.subtotal-text { color: #333; margin-right: 5px; font-size: 14px; }\r\n.subtotal-price { color: #ff5000; font-weight: bold; font-size: 16px; }\r\n\r\n.product-item { display: flex; flex-direction: row; padding: 12px 0; }\r\n.product-image { width: 85px; height: 85px; border-radius: 8px; margin-right: 12px; background-color: #f8f8f8; }\r\n.product-info { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }\r\n.product-name-row { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-start; }\r\n.product-name { flex: 1; font-size: 14px; color: #333333; line-height: 1.4; lines: 2; text-overflow: ellipsis; overflow: hidden; margin-right: 12px; }\r\n.product-price { font-size: 15px; color: #333; font-weight: normal; }\r\n.product-spec-row { display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 4px; }\r\n.product-spec { font-size: 12px; color: #999999; background-color: #f7f7f7; padding: 2px 6px; border-radius: 4px; }\r\n.product-quantity { font-size: 12px; color: #999999; }\r\n\r\n/* 配送方式重构 */\r\n.delivery-row {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.delivery-selector {\r\n display: flex;\r\n flex-direction: row;\r\n gap: 8px;\r\n}\r\n\r\n.delivery-pill {\r\n padding: 4px 12px;\r\n background-color: #f5f5f5;\r\n border-radius: 16px;\r\n border: 1px solid transparent;\r\n}\r\n\r\n.delivery-pill.selected {\r\n background-color: #fff9f6;\r\n border-color: #ff5000;\r\n}\r\n\r\n.pill-name {\r\n font-size: 12px;\r\n color: #666;\r\n}\r\n\r\n.delivery-pill.selected .pill-name {\r\n color: #ff5000;\r\n}\r\n\r\n.delivery-detail {\r\n margin-top: 10px;\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n padding-top: 8px;\r\n border-top: 0.5px solid #f9f9f9;\r\n}\r\n\r\n.detail-desc {\r\n font-size: 11px;\r\n color: #999;\r\n}\r\n\r\n.detail-price {\r\n font-size: 11px;\r\n color: #ff5000;\r\n}\r\n\r\n/* 优惠券重构 */\r\n.coupon-row {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.coupon-right-content {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n/* 留言重构 */\r\n.remark-row {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.remark-input-compact {\r\n flex: 1;\r\n margin-left: 15px;\r\n font-size: 14px;\r\n color: #333;\r\n height: 30px;\r\n}\r\n\r\n/* 价格明细横向排列 */\r\n.price-grid {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n}\r\n\r\n.price-item-inline {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: baseline;\r\n}\r\n\r\n.price-item-label {\r\n font-size: 12px;\r\n color: #999;\r\n margin-right: 4px;\r\n}\r\n\r\n.price-item-value {\r\n font-size: 14px;\r\n color: #333;\r\n font-weight: 500;\r\n}\r\n\r\n.item-subtotal-row {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: flex-end;\r\n\talign-items: center;\r\n\tmargin-top: 4px;\r\n}\r\n.item-subtotal-label {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n\tmargin-right: 4px;\r\n}\r\n.item-subtotal-price {\r\n\tfont-size: 14px;\r\n\tcolor: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n/* 配送方式网格 */\r\n.delivery-options-grid {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.delivery-card {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 15px;\r\n border: 1px solid #f0f0f0;\r\n border-radius: 10px;\r\n margin-bottom: 10px;\r\n background-color: #fafafa;\r\n}\r\n\r\n.delivery-card.selected {\r\n border-color: #ff5000;\r\n background-color: #fff9f6;\r\n}\r\n\r\n.option-main {\r\n flex: 1;\r\n}\r\n\r\n.option-name {\r\n font-size: 15px;\r\n color: #333;\r\n font-weight: 500;\r\n margin-bottom: 4px;\r\n display: block;\r\n}\r\n\r\n.option-desc {\r\n font-size: 12px;\r\n color: #999;\r\n display: block;\r\n}\r\n\r\n.option-side {\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.option-price {\r\n font-size: 14px;\r\n color: #333;\r\n margin-right: 10px;\r\n}\r\n\r\n.select-icon {\r\n width: 18px;\r\n height: 18px;\r\n background-color: #ff5000;\r\n border-radius: 50%;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.check-mark {\r\n color: #ffffff;\r\n font-size: 12px;\r\n margin-top: -1px;\r\n}\r\n\r\n/* 优惠券样式重构 */\r\n.coupon-content {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.coupon-left {\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.coupon-tag {\r\n background-color: #ff5000;\r\n color: #fff;\r\n font-size: 10px;\r\n padding: 1px 4px;\r\n border-radius: 2px;\r\n margin-left: 8px;\r\n}\r\n\r\n.coupon-right {\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.coupon-selected-name {\r\n font-size: 14px;\r\n color: #ff5000;\r\n}\r\n\r\n.coupon-placeholder {\r\n font-size: 14px;\r\n color: #999;\r\n}\r\n\r\n.arrow-icon {\r\n font-size: 18px;\r\n color: #ccc;\r\n margin-left: 5px;\r\n}\r\n\r\n/* 留言输入 */\r\n.remark-input-new {\r\n width: 100%;\r\n background-color: #f9f9f9;\r\n border-radius: 8px;\r\n padding: 12px;\r\n font-size: 14px;\r\n min-height: 48px;\r\n}\r\n\r\n/* 价格明细列表 */\r\n.price-list {\r\n margin-top: 5px;\r\n}\r\n\r\n.price-item {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 6px 0;\r\n}\r\n\r\n.price-item-label {\r\n font-size: 14px;\r\n color: #666;\r\n}\r\n\r\n.price-item-value {\r\n font-size: 14px;\r\n color: #333;\r\n}\r\n\r\n.discount-text {\r\n color: #ff5000;\r\n}\r\n\r\n.section-title { font-size: 15px; font-weight: bold; color: #333333; }\r\n\r\n/* 底部操作栏 */\r\n.footer-action-bar {\r\n position: fixed;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n height: 60px;\r\n background-color: #ffffff;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 0 16px;\r\n padding-bottom: constant(safe-area-inset-bottom);\r\n padding-bottom: env(safe-area-inset-bottom);\r\n border-top: 1px solid #f0f0f0;\r\n z-index: 100;\r\n}\r\n\r\n.footer-left {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: baseline;\r\n}\r\n\r\n.footer-total-label {\r\n font-size: 14px;\r\n color: #333;\r\n margin-right: 4px;\r\n}\r\n\r\n.footer-currency {\r\n font-size: 14px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.footer-price {\r\n font-size: 22px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.footer-submit-btn {\r\n background-color: #ff5000;\r\n color: #ffffff;\r\n padding: 0 28px;\r\n height: 42px;\r\n line-height: 42px;\r\n border-radius: 21px;\r\n font-size: 16px;\r\n font-weight: bold;\r\n border: none;\r\n margin: 0;\r\n}\r\n\r\n.safe-area-bottom {\r\n height: 100px; /* 留出底部操作栏和安全区的空间 */\r\n width: 100%;\r\n}\r\n\r\n/* 弹窗样式 */\r\n.address-popup-mask, .address-form-mask, .confirm-popup-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 9998; }\r\n.address-popup-mask { display: flex; align-items: flex-end; justify-content: center; }\r\n.address-form-mask, .confirm-popup-mask { display: flex; align-items: center; justify-content: center; z-index: 10000; }\r\n.address-popup { \r\n background-color: #ffffff; \r\n width: 100%; \r\n height: 450px;\r\n border-radius: 20px 20px 0 0; \r\n display: flex; \r\n flex-direction: column; \r\n position: relative;\r\n z-index: 9999;\r\n}\r\n.address-form-popup { \r\n background-color: #f8f8f8; \r\n width: 92%; \r\n max-width: 500px; \r\n height: 600px;\r\n border-radius: 16px; \r\n display: flex; \r\n flex-direction: column; \r\n overflow: hidden;\r\n position: relative;\r\n z-index: 10001;\r\n}\r\n\r\n.popup-header { \r\n padding: 16px; \r\n border-bottom: 0.5px solid #eee; \r\n display: flex; \r\n flex-direction: row;\r\n align-items: center; \r\n justify-content: center; \r\n flex-shrink: 0; \r\n position: relative;\r\n}\r\n\r\n.popup-title { font-size: 17px; font-weight: bold; color: #333333; }\r\n.popup-close { position: absolute; right: 16px; font-size: 20px; color: #999999; padding: 4px; }\r\n\r\n.address-list-container {\r\n flex: 1;\r\n width: 100%;\r\n padding: 12px;\r\n box-sizing: border-box;\r\n}\r\n\r\n.popup-address-item { \r\n padding: 16px; \r\n margin-bottom: 12px; \r\n background-color: #fff;\r\n border: 1px solid #f0f0f0; \r\n border-radius: 12px; \r\n position: relative; \r\n}\r\n\r\n.popup-address-header { \r\n display: flex; \r\n flex-direction: row;\r\n align-items: center; \r\n margin-bottom: 8px; \r\n}\r\n\r\n.popup-address-name { font-size: 15px; font-weight: bold; color: #333333; margin-right: 12px; }\r\n.popup-address-phone { font-size: 14px; color: #666666; margin-right: 8px; }\r\n.popup-default-tag { background-color: #fff0eb; padding: 1px 6px; border-radius: 4px; }\r\n.popup-tag-text { color: #ff5000; font-size: 10px; }\r\n.popup-address-detail { font-size: 13px; color: #666; line-height: 1.4; }\r\n\r\n.popup-selected-indicator {\r\n position: absolute;\r\n right: 16px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n color: #ff5000;\r\n font-weight: bold;\r\n font-size: 18px;\r\n}\r\n\r\n.popup-empty-address { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; }\r\n.popup-empty-icon { font-size: 48px; margin-bottom: 12px; opacity: 0.3; }\r\n.popup-empty-text { font-size: 14px; color: #999; }\r\n\r\n.popup-add-address-btn { \r\n background-color: #ff5000; \r\n margin: 12px 16px 30px; \r\n height: 44px;\r\n border-radius: 22px; \r\n display: flex; \r\n flex-direction: row;\r\n align-items: center; \r\n justify-content: center; \r\n flex-shrink: 0; \r\n}\r\n\r\n.popup-btn-icon { color: #ffffff; font-size: 20px; margin-right: 6px; font-weight: normal; }\r\n.popup-btn-text { color: #ffffff; font-size: 15px; font-weight: bold; }\r\n\r\n.address-form-popup { \r\n background-color: #f8f8f8; \r\n width: 92%; \r\n max-width: 500px; \r\n height: 600px; /* 改用具体的像素高度Android 端的 scroll-view 计算更稳健 */\r\n border-radius: 16px; \r\n display: flex; \r\n flex-direction: column; \r\n overflow: hidden;\r\n position: relative;\r\n z-index: 10001;\r\n}\r\n\r\n.form-header { \r\n padding: 16px; \r\n background-color: #ffffff;\r\n display: flex; \r\n flex-direction: row;\r\n align-items: center; \r\n justify-content: center; \r\n position: relative;\r\n border-bottom: 0.5px solid #eee;\r\n flex-shrink: 0;\r\n}\r\n\r\n.form-title { font-size: 17px; font-weight: bold; color: #333333; }\r\n.form-close-btn { position: absolute; right: 12px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; }\r\n.form-close-icon { font-size: 18px; color: #999; }\r\n\r\n.form-content { \r\n flex: 1; \r\n width: 100%;\r\n /* 在 Android 下scroll-view 如果不给明确的高度且处于 flex 容器,必须通过 flex-grow 撑开 */\r\n flex-grow: 1;\r\n flex-shrink: 1;\r\n background-color: #ffffff;\r\n padding: 12px;\r\n}\r\n\r\n.form-section {\r\n background-color: #ffffff;\r\n border-radius: 12px;\r\n padding: 0 12px;\r\n margin-bottom: 12px;\r\n}\r\n\r\n.form-item { \r\n padding: 16px 0;\r\n border-bottom: 0.5px solid #f5f5f5;\r\n}\r\n\r\n.form-item:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.form-label { \r\n font-size: 14px; \r\n color: #333; \r\n margin-bottom: 10px; \r\n display: flex;\r\n}\r\n\r\n.label-row {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.smart-tag {\r\n font-size: 11px;\r\n color: #ff5000;\r\n background-color: #fff0eb;\r\n padding: 2px 6px;\r\n border-radius: 4px;\r\n}\r\n\r\n.form-input { \r\n width: 100%; \r\n height: 32px;\r\n font-size: 15px; \r\n color: #333; \r\n}\r\n\r\n.form-input-readonly { color: #888; }\r\n\r\n.region-inputs { \r\n display: flex; \r\n flex-direction: row;\r\n justify-content: space-between; \r\n}\r\n\r\n.region-input { flex: 1; text-align: center; }\r\n\r\n.form-textarea { \r\n width: 100%; \r\n background-color: #f9f9f9;\r\n border-radius: 8px;\r\n padding: 10px; \r\n font-size: 14px; \r\n color: #333; \r\n box-sizing: border-box; \r\n}\r\n\r\n.smart-address-input { height: 80px; }\r\n.detail-textarea { height: 60px; }\r\n\r\n.checkbox-item { \r\n background-color: #ffffff;\r\n border-radius: 12px;\r\n padding: 16px;\r\n}\r\n\r\n.checkbox-wrapper { display: flex; flex-direction: row; align-items: center; }\r\n.checkbox { width: 18px; height: 18px; border: 1.5px solid #ddd; border-radius: 9px; margin-right: 10px; display: flex; align-items: center; justify-content: center; }\r\n.checkbox.checked { background-color: #ff5000; border-color: #ff5000; }\r\n.checkbox-check { color: #ffffff; font-size: 12px; }\r\n.checkbox-label { font-size: 14px; color: #333; }\r\n\r\n.form-buttons { \r\n padding: 12px 16px 30px; \r\n background-color: #ffffff;\r\n flex-shrink: 0;\r\n}\r\n\r\n.form-submit-btn { \r\n width: 100%;\r\n background-color: #ff5000; \r\n color: #ffffff; \r\n height: 44px;\r\n line-height: 44px;\r\n border-radius: 22px; \r\n font-size: 16px; \r\n font-weight: bold; \r\n border: none; \r\n}\r\n\r\n/* 确认保存弹窗 */\r\n.confirm-popup-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 9998; display: flex; align-items: center; justify-content: center; }\r\n.confirm-popup { background-color: #ffffff; width: 80%; max-width: 320px; border-radius: 12px; overflow: hidden; }\r\n\r\n.confirm-header { padding: 24px 0 12px; text-align: center; }\r\n.confirm-title { font-size: 17px; font-weight: bold; color: #333; }\r\n.confirm-content { padding: 0 24px 24px; text-align: center; }\r\n.confirm-message { font-size: 14px; color: #666; line-height: 1.5; }\r\n.confirm-buttons { display: flex; flex-direction: row; border-top: 0.5px solid #eee; }\r\n.confirm-btn { flex: 1; height: 48px; line-height: 48px; text-align: center; font-size: 16px; background-color: #ffffff; border: none; border-radius: 0; }\r\n.confirm-btn.cancel { color: #666; border-right: 0.5px solid #eee; }\r\n.confirm-btn.confirm { color: #ff5000; font-weight: bold; }\r\n</style>\r\n","<!-- 支付页面 -->\r\n<template>\r\n\t<view class=\"payment-page\">\r\n\t\t<scroll-view class=\"payment-content\" direction=\"vertical\">\r\n\t\t\t<!-- 支付成功样式头部 -->\r\n\t\t\t<view class=\"payment-amount-header\">\r\n\t\t\t\t<text class=\"amount-label\">支付金额</text>\r\n\t\t\t\t<view class=\"amount-value-row\">\r\n\t\t\t\t\t<text class=\"amount-currency\">¥</text>\r\n\t\t\t\t\t<text class=\"amount-number\">{{ amount.toFixed(2) }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<text class=\"order-no-text\">订单号: {{ orderNo }}</text>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 支付方式 -->\r\n\t\t\t<view class=\"methods-section-new\">\r\n\t\t\t\t<view class=\"section-header\">\r\n\t\t\t\t\t<text class=\"section-title\">选择支付方式</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"method-list\">\r\n\t\t\t\t\t<view v-for=\"method in paymentMethods\" \r\n\t\t\t\t\t\t\t\t:key=\"method.id\" \r\n\t\t\t\t\t\t\t\tclass=\"method-item-modern\"\r\n\t\t\t\t\t\t\t\t@click=\"selectMethod(method)\">\r\n\t\t\t\t\t\t<view class=\"method-left\">\r\n\t\t\t\t\t\t\t<image class=\"method-img\" :src=\"getMethodBrandIcon(method.id)\" mode=\"aspectFit\" />\r\n\t\t\t\t\t\t\t<view class=\"method-info\">\r\n\t\t\t\t\t\t\t\t<text class=\"method-name\">{{ method.name }}</text>\r\n\t\t\t\t\t\t\t\t<text class=\"method-desc\" v-if=\"method.id === 'balance'\">当前余额: ¥{{ userBalance.toFixed(2) }}</text>\r\n\t\t\t\t\t\t\t\t<text class=\"method-desc\" v-else>{{ method.description }}</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"method-right\">\r\n\t\t\t\t\t\t\t<view :class=\"['radio-circle', { checked: selectedMethod === method.id }]\">\r\n\t\t\t\t\t\t\t\t<view class=\"radio-inner\" v-if=\"selectedMethod === method.id\"></view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 弹窗式密码输入层 -->\r\n\t\t\t<view v-if=\"showPassword\" class=\"password-popup-mask\" @click=\"closePasswordPopup\">\r\n\t\t\t\t<view class=\"password-popup-content\" @click.stop=\"\">\r\n\t\t\t\t\t<view class=\"popup-header\">\r\n\t\t\t\t\t\t<text class=\"popup-close\" @click=\"closePasswordPopup\">✕</text>\r\n\t\t\t\t\t\t<text class=\"popup-title\">请输入支付密码</text>\r\n\t\t\t\t\t\t<view class=\"popup-placeholder\"></view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<view class=\"popup-amount-info\">\r\n\t\t\t\t\t\t<text class=\"popup-amount-label\">支付金额</text>\r\n\t\t\t\t\t\t<view class=\"popup-amount-row\">\r\n\t\t\t\t\t\t\t<text class=\"popup-currency\">¥</text>\r\n\t\t\t\t\t\t\t<text class=\"popup-value\">{{ amount.toFixed(2) }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\r\n\t\t\t\t\t<view class=\"password-input-row\">\r\n\t\t\t\t\t\t<view v-for=\"(_, index) in 6\" \r\n\t\t\t\t\t\t\t\t\t:key=\"index\" \r\n\t\t\t\t\t\t\t\t\tclass=\"password-box\">\r\n\t\t\t\t\t\t\t<view v-if=\"password.length > index\" class=\"password-dot\"></view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<text class=\"forgot-password-link\" @click=\"forgotPassword\">忘记密码?</text>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 这里的密码键盘会被放在页面底部,但我们可以通过 CSS 控制它 -->\r\n\t\t\t\t\t\r\n\t\t\t\t\t<!-- 移动键盘到弹窗内部 -->\r\n\t\t\t\t\t<view class=\"password-keyboard-popup\">\r\n\t\t\t\t\t\t<view class=\"keyboard-grid\">\r\n\t\t\t\t\t\t\t<view v-for=\"num in 9\" \r\n\t\t\t\t\t\t\t\t\t\t:key=\"num\" \r\n\t\t\t\t\t\t\t\t\t\tclass=\"keyboard-key\"\r\n\t\t\t\t\t\t\t\t\t\t@click=\"inputPassword(num.toString())\">\r\n\t\t\t\t\t\t\t\t<text class=\"key-text\">{{ num }}</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"keyboard-key\"></view>\r\n\t\t\t\t\t\t\t<view class=\"keyboard-key\" @click=\"inputPassword('0')\">\r\n\t\t\t\t\t\t\t\t<text class=\"key-text\">0</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"keyboard-key\" @click=\"deletePassword\">\r\n\t\t\t\t\t\t\t\t<text class=\"key-text\">⌫</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\r\n\t\t<!-- 底部支付按钮 -->\r\n\t\t<view class=\"payment-bottom\" v-if=\"!showPassword\">\r\n\t\t\t<view class=\"price-summary\">\r\n\t\t\t\t<text class=\"summary-label\">需支付:</text>\r\n\t\t\t\t<text class=\"summary-price\">¥{{ amount.toFixed(2) }}</text>\r\n\t\t\t</view>\r\n\t\t\t<button class=\"pay-btn\" \r\n\t\t\t\t\t\t\t:class=\"{ disabled: isPaying || (selectedMethod === 'balance' && userBalance < amount) }\"\r\n\t\t\t\t\t\t\t@click=\"confirmPayment\">\r\n\t\t\t\t<text v-if=\"!isPaying\" class=\"pay-text\">{{ getPayButtonText() }}</text>\r\n\t\t\t\t<text v-else class=\"pay-text\">支付中...</text>\r\n\t\t\t</button>\r\n\t\t</view>\r\n\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted, watch, computed, onUnmounted } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype PaymentMethodType = {\r\n\tid: string\r\n\tname: string\r\n\tdescription: string\r\n\ticon: string\r\n\tenabled: boolean\r\n}\r\n\r\nconst orderId = ref<string>('')\r\nconst orderNo = ref<string>('')\r\nconst amount = ref<number>(0)\r\nconst paymentMethods = ref<Array<PaymentMethodType>>([])\r\nconst selectedMethod = ref<string>('wechat')\r\nconst userBalance = ref<number>(0)\r\nconst isPaying = ref<boolean>(false)\r\nconst showPassword = ref<boolean>(false)\r\nconst password = ref<string>('')\r\n\r\n// 价格相关变量\r\nconst productAmount = ref<number>(0) // 商品总价\r\nconst deliveryFee = ref<number>(0) // 运费\r\nconst discountAmount = ref<number>(0) // 优惠减免\r\n\r\n// 加载支付方式(必须在 onMounted 之前定义)\r\nconst loadPaymentMethods = () => {\r\n\tconst methods: PaymentMethodType[] = [\r\n\t\t{\r\n\t\t\tid: 'wechat',\r\n\t\t\tname: '微信支付',\r\n\t\t\tdescription: '推荐安装微信5.0及以上版本使用',\r\n\t\t\ticon: '💳',\r\n\t\t\tenabled: true\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: 'alipay',\r\n\t\t\tname: '支付宝',\r\n\t\t\tdescription: '推荐安装支付宝10.0及以上版本使用',\r\n\t\t\ticon: '💳',\r\n\t\t\tenabled: true\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: 'balance',\r\n\t\t\tname: '余额支付',\r\n\t\t\tdescription: '使用账户余额支付',\r\n\t\t\ticon: '💰',\r\n\t\t\tenabled: true\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: 'bankcard',\r\n\t\t\tname: '银行卡支付',\r\n\t\t\tdescription: '支持储蓄卡、信用卡',\r\n\t\t\ticon: '💳',\r\n\t\t\tenabled: true\r\n\t\t}\r\n\t]\r\n\tpaymentMethods.value = methods\r\n}\r\n\r\n// 加载用户余额(必须在 onMounted 之前定义)\r\nconst loadUserBalance = async () => {\r\n\ttry {\r\n const balance = await supabaseService.getUserBalanceNumber()\r\n userBalance.value = balance\r\n\t} catch (err) {\r\n\t\tconsole.error('加载用户余额异常:', err)\r\n userBalance.value = 0\r\n\t}\r\n}\r\n\r\n// 计算价格明细(必须在 onMounted 之前定义)\r\nconst calculatePriceDetails = (totalAmount: number) => {\r\n\t// 模拟计算各项费用\r\n\t// 假设商品总价占总金额的80%运费占10%优惠减免占10%\r\n\tproductAmount.value = totalAmount * 0.8\r\n\tdeliveryFee.value = totalAmount * 0.1\r\n\tdiscountAmount.value = totalAmount * 0.1\r\n\t\r\n\t// 确保总和等于应付金额\r\n\tconst calculatedTotal = productAmount.value + deliveryFee.value - discountAmount.value\r\n\tif (Math.abs(calculatedTotal - totalAmount) > 0.01) {\r\n\t\t// 调整商品总价以匹配应付金额\r\n\t\tproductAmount.value = totalAmount + discountAmount.value - deliveryFee.value\r\n\t}\r\n}\r\n\r\n// 更新本地存储中的订单状态(必须在 onMounted 之前定义)\r\nconst updateOrderInStorage = (targetOrderId: string, status: number) => {\r\n\ttry {\r\n // 尝试从 'orders' 读取 (checkout页面写入的key)\r\n\t\tconst ordersStr = uni.getStorageSync('orders')\r\n\t\tlet orders: Record<string, any>[] = []\r\n\t\tif (ordersStr != null && ordersStr !== '') {\r\n\t\t\tconst parsed = JSON.parse(ordersStr as string)\r\n\t\t\tif (Array.isArray(parsed)) {\r\n\t\t\t\tfor (let i = 0; i < parsed.length; i++) {\r\n // 使用 JSON 序列化转换\r\n const itemStr = JSON.stringify(parsed[i])\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed != null) {\r\n orders.push(itemParsed as Record<string, any>)\r\n }\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tlet foundIndex = -1\r\n\t\tfor (let i = 0; i < orders.length; i++) {\r\n\t\t\tconst o = orders[i]\r\n\t\t\tif (o['id'] === targetOrderId) {\r\n\t\t\t\tfoundIndex = i\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (foundIndex !== -1) {\r\n\t\t\torders[foundIndex]['status'] = status\r\n\t\t\torders[foundIndex]['payment_status'] = status === 2 ? 1 : 0 // 2=待发货(已支付), 1=待支付(未支付)\r\n\t\t\torders[foundIndex]['updated_at'] = new Date().toISOString()\r\n // 确保更新的是 'orders' key\r\n\t\t\tuni.setStorageSync('orders', JSON.stringify(orders))\r\n console.log('订单状态已更新到Storage (orders):', targetOrderId, status)\r\n\t\t} else {\r\n // 本地缓存中没有订单数据是正常的,数据在数据库中\r\n console.log('本地缓存中无订单数据,已忽略:', targetOrderId)\r\n }\r\n\t} catch (e) {\r\n\t\tconsole.error('更新订单状态失败', e)\r\n\t}\r\n}\r\n\r\n// 取消支付,更新订单状态(必须在 goBack 之前定义)\r\nconst cancelPayment = async () => {\r\n\ttry {\r\n\t\t// 这里应该调用API更新订单状态为待支付status: 1\r\n\t\t// 模拟更新订单状态\r\n \r\n // 更新本地存储\r\n updateOrderInStorage(orderId.value, 1) // 1: 待支付\r\n\t\t\r\n\t\t// 发布订单更新事件让profile页面可以刷新数据\r\n\t\tuni.$emit('orderUpdated', { orderId: orderId.value, status: 1 })\r\n\t\t\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '已保存到待支付订单',\r\n\t\t\ticon: 'success'\r\n\t\t})\r\n\t\t\r\n\t\t// 延迟返回,让用户看到提示\r\n\t\tsetTimeout(() => {\r\n\t\t\tuni.navigateBack()\r\n\t\t}, 1500)\r\n\t\t\r\n\t} catch (err) {\r\n\t\tconsole.error('取消支付异常:', err)\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '操作失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t}\r\n}\r\n\r\n// 返回(必须在 onBackPress 之前定义)\r\nconst goBack = () => {\r\n\tuni.showModal({\r\n\t\ttitle: '取消支付',\r\n\t\tcontent: '确定要取消支付吗?取消后订单将保存到待支付订单中',\r\n\t\tconfirmText: '取消支付',\r\n\t\tcancelText: '继续支付',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\t// 用户确认取消支付,更新订单状态为待支付\r\n\t\t\t\tcancelPayment()\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 加载订单信息(必须在 onMounted 之前定义)\r\nconst loadOrderInfo = async () => {\r\n\ttry {\r\n if (orderId.value == '') return\r\n\r\n const order = await supabaseService.getOrderDetail(orderId.value)\r\n if (order != null) {\r\n // 使用 JSON 序列化转换对象\r\n const orderStr = JSON.stringify(order)\r\n const orderParsed = JSON.parse(orderStr)\r\n if (orderParsed == null) {\r\n console.error('订单数据解析失败')\r\n return\r\n }\r\n const orderObj = orderParsed as UTSJSONObject\r\n \r\n const orderNoVal = orderObj.getString('order_no')\r\n if (orderNoVal != null) {\r\n orderNo.value = orderNoVal\r\n }\r\n \r\n const totalAmount = orderObj.getNumber('total_amount')\r\n const dbAmount = totalAmount ?? 0\r\n if (dbAmount > 0) {\r\n amount.value = dbAmount\r\n }\r\n const items = orderObj.get('items')\r\n if (items != null && Array.isArray(items) && items.length > 0) {\r\n // Could update product name etc if displayed\r\n }\r\n } else {\r\n // Fallback or error\r\n console.warn('Order not found in DB', orderId.value)\r\n if (orderNo.value == '') orderNo.value = 'ORD_PENDING_' + Date.now()\r\n }\r\n\t} catch (err) {\r\n\t\tconsole.error('加载订单信息异常:', err)\r\n\t}\r\n}\r\n\r\n// 生命周期\r\nonLoad((options) => {\r\n if (options != null) {\r\n const orderIdValue = options['orderId']\r\n if (orderIdValue != null) {\r\n orderId.value = orderIdValue as string\r\n loadOrderInfo()\r\n }\r\n \r\n const amountValue = options['amount']\r\n if (amountValue != null) {\r\n amount.value = parseFloat(amountValue.toString())\r\n }\r\n \r\n // 获取传递的价格详情\r\n const productAmountValue = options['productAmount']\r\n if (productAmountValue != null) {\r\n productAmount.value = parseFloat(productAmountValue.toString())\r\n }\r\n const deliveryFeeValue = options['deliveryFee']\r\n if (deliveryFeeValue != null) {\r\n deliveryFee.value = parseFloat(deliveryFeeValue.toString())\r\n }\r\n const discountAmountValue = options['discountAmount']\r\n if (discountAmountValue != null) {\r\n discountAmount.value = parseFloat(discountAmountValue.toString())\r\n }\r\n \r\n // 如果没有传详情,尝试根据总价估算(兼容旧逻辑,但优先使用传参)\r\n if (productAmountValue == null && amount.value > 0) {\r\n calculatePriceDetails(amount.value)\r\n }\r\n \r\n loadPaymentMethods()\r\n loadUserBalance()\r\n }\r\n})\r\n\r\nonMounted(() => {\r\n // onMounted 中的逻辑已移到 onLoad 中\r\n})\r\n\r\n// 监听返回操作(包含系统返回键和导航栏返回按钮)\r\nonBackPress((options) => {\r\n\t// 如果是通过代码主动调用 navigateBack 返回,则允许\r\n\tif (options.from === 'navigateBack') {\r\n\t\treturn false\r\n\t}\r\n\t\r\n\t// 否则拦截返回,显示确认弹窗\r\n\tgoBack()\r\n\treturn true\r\n})\r\n\r\n// 获取当前用户ID\r\nconst getCurrentUserId = (): string => {\r\n\tconst userStore = uni.getStorageSync('userInfo')\r\n\tif (userStore != null) {\r\n // 使用 JSON 序列化转换\r\n const userStr = JSON.stringify(userStore)\r\n const userParsed = JSON.parse(userStr)\r\n if (userParsed != null) {\r\n const userObj = userParsed as UTSJSONObject\r\n const id = userObj.getString('id')\r\n if (id != null) {\r\n return id\r\n }\r\n }\r\n\t}\r\n\treturn ''\r\n}\r\n\r\n// 获取支付方式图标\r\nconst getMethodIcon = (methodId: string): string => {\r\n\tif (methodId === 'wechat') {\r\n\t\treturn '💳'\r\n\t} else if (methodId === 'alipay') {\r\n\t\treturn '💳'\r\n\t} else if (methodId === 'balance') {\r\n\t\treturn '💰'\r\n\t} else if (methodId === 'bankcard') {\r\n\t\treturn '💳'\r\n\t}\r\n\treturn '💳'\r\n}\r\n\r\n// 获取支付品牌图片\r\nconst getMethodBrandIcon = (methodId: string): string => {\r\n\tif (methodId === 'wechat') {\r\n\t\treturn '/static/logo.png' // 替换为真实的微信支付图标路径\r\n\t} else if (methodId === 'alipay') {\r\n\t\treturn '/static/logo.png' // 替换为真实的支付宝图标路径\r\n\t} else if (methodId === 'balance') {\r\n\t\treturn '/static/logo.png' // 替换为真实的余额支付图标路径\r\n\t} else if (methodId === 'bankcard') {\r\n\t\treturn '/static/logo.png' // 替换为真实的银行卡支付图标路径\r\n\t}\r\n\treturn '/static/logo.png'\r\n}\r\n\r\n// 选择支付方式\r\nconst selectMethod = (method: PaymentMethodType) => {\r\n\tif (!method.enabled) {\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '该支付方式暂不可用',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\treturn\r\n\t}\r\n\t\r\n\tselectedMethod.value = method.id\r\n\t// 切换方式时,除非点击支付,否则不自动弹出密码\r\n\tshowPassword.value = false\r\n\tpassword.value = '' // 清空密码\r\n}\r\n\r\n// 关闭密码弹窗\r\nconst closePasswordPopup = () => {\r\n\tshowPassword.value = false\r\n\tpassword.value = ''\r\n}\r\n\r\n// 获取支付按钮文本\r\nconst getPayButtonText = (): string => {\r\n\tif (selectedMethod.value === 'balance' && userBalance.value < amount.value) {\r\n\t\treturn '余额不足'\r\n\t}\r\n\t\r\n\tif (selectedMethod.value === 'wechat') {\r\n\t\treturn '微信支付'\r\n\t} else if (selectedMethod.value === 'alipay') {\r\n\t\treturn '支付宝支付'\r\n\t} else if (selectedMethod.value === 'balance') {\r\n\t\treturn '余额支付'\r\n\t} else if (selectedMethod.value === 'bankcard') {\r\n\t\treturn '银行卡支付'\r\n\t}\r\n\treturn '确认支付'\r\n}\r\n\r\n// 减少商品库存\r\n// const reduceStock = (orderId: string) => {\r\n // Update should happen on server side during payment processing\r\n// }\r\n\r\n// 确认支付\r\nconst confirmPayment = async () => {\r\n\tif (isPaying.value) return\r\n\t\r\n\t// 余额支付或银行卡支付检查密码\r\n\tif (selectedMethod.value === 'balance' || selectedMethod.value === 'bankcard') {\r\n\t\tif (selectedMethod.value === 'balance' && userBalance.value < amount.value) {\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '余额不足',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t\treturn\r\n\t\t}\r\n\t\t\r\n\t\tif (!showPassword.value) {\r\n\t\t\tshowPassword.value = true\r\n\t\t\tpassword.value = ''\r\n\t\t\treturn\r\n\t\t}\r\n\t\t\r\n\t\tif (password.value.length !== 6) {\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '请输入6位支付密码',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\t\r\n\tisPaying.value = true\r\n\tuni.showLoading({ title: '支付中...' })\r\n\t\r\n\ttry {\r\n console.log('[confirmPayment] 开始支付, orderId:', orderId.value, 'method:', selectedMethod.value)\r\n \r\n const success = await supabaseService.payOrder(orderId.value, selectedMethod.value, amount.value)\r\n console.log('[confirmPayment] 支付结果:', success)\r\n\r\n if (!success) {\r\n console.error('[confirmPayment] payOrder 返回 false')\r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '支付处理失败',\r\n icon: 'none'\r\n })\r\n isPaying.value = false\r\n return\r\n }\r\n\t\t\r\n\t\tuni.hideLoading()\r\n\t\t\r\n\t\tupdateOrderInStorage(orderId.value, 2)\r\n\t\t\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '支付成功',\r\n\t\t\ticon: 'success',\r\n\t\t\tduration: 2000\r\n\t\t})\r\n\t\t\r\n\t\tuni.$emit('orderUpdated', { orderId: orderId.value, status: 2 })\r\n\t\t\r\n\t\tsetTimeout(() => {\r\n\t\t\tuni.redirectTo({\r\n\t\t\t\turl: `/pages/mall/consumer/payment-success?orderId=${orderId.value}`\r\n\t\t\t})\r\n\t\t}, 1500)\r\n\t\t\r\n\t} catch (err) {\r\n\t\tconsole.error('[confirmPayment] 支付异常:', err)\r\n\t\tuni.hideLoading()\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '支付失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\tisPaying.value = false\r\n\t}\r\n}\r\n\r\n// 获取支付方式代码\r\nconst getPaymentMethodCode = (methodId: string): number => {\r\n\tif (methodId === 'wechat') {\r\n\t\treturn 1\r\n\t} else if (methodId === 'alipay') {\r\n\t\treturn 2\r\n\t} else if (methodId === 'balance') {\r\n\t\treturn 3\r\n\t} else if (methodId === 'bankcard') {\r\n\t\treturn 4\r\n\t}\r\n\treturn 0\r\n}\r\n\r\n// 验证密码(必须在 watch 之前定义)\r\nconst verifyPassword = async () => {\r\n\t// 这里应该验证支付密码,这里简单模拟\r\n\tconst userId = getCurrentUserId()\r\n\t\r\n\ttry {\r\n\t\t// 模拟验证\r\n\t\tawait new Promise<void>((resolve: (value: void) => void) => {\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tresolve()\r\n\t\t\t}, 500)\r\n\t\t})\r\n\t\t\r\n\t\t// 假设密码正确\r\n\t\tconst isCorrect = true\r\n\t\t\r\n\t\tif (isCorrect) {\r\n\t\t\t// 密码正确,继续支付\r\n\t\t\tconfirmPayment()\r\n\t\t} else {\r\n\t\t\tpassword.value = ''\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '密码错误',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t}\r\n\t\t\r\n\t} catch (err) {\r\n\t\tconsole.error('验证密码异常:', err)\r\n\t}\r\n}\r\n\r\n// 输入密码\r\nconst inputPassword = (num: string) => {\r\n\tif (password.value.length >= 6) return\r\n\tpassword.value += num\r\n}\r\n\r\n// 删除密码\r\nconst deletePassword = () => {\r\n\tif (password.value.length > 0) {\r\n\t\tpassword.value = password.value.slice(0, -1)\r\n\t}\r\n}\r\n\r\n// 监听密码输入\r\nwatch(password, (newPassword: string) => {\r\n\tif (newPassword.length === 6) {\r\n\t\t// 自动验证密码\r\n\t\tverifyPassword()\r\n\t}\r\n})\r\n\r\n// 忘记密码\r\nconst forgotPassword = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/user/forgot-password'\r\n\t})\r\n}\r\n\r\n// 在组件卸载时移除返回键监听\r\nonUnmounted(() => {\r\n\t// uni.offBackPress() 在uni-app中不需要手动移除\r\n})\r\n</script>\r\n\r\n<style scoped>\r\n.payment-page {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tposition: fixed;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\tright: 0;\r\n\tbottom: 0;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tbackground-color: #f5f5f5;\r\n\toverflow: hidden;\r\n}\r\n\r\n.payment-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tborder-bottom: 1px solid #e5e5e5;\r\n}\r\n\r\n.back-btn {\r\n\tfont-size: 24px;\r\n\tcolor: #333333;\r\n\tpadding: 5px;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.header-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.payment-content {\r\n\tflex: 1;\r\n\t/* overflow-y: auto; */\r\n\tbackground-color: #f8f8f8;\r\n}\r\n\r\n.payment-amount-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 40px 15px;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.amount-label {\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.amount-value-row {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: baseline;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.amount-currency {\r\n font-size: 20px;\r\n font-weight: bold;\r\n color: #333;\r\n margin-right: 4px;\r\n}\r\n\r\n.amount-number {\r\n font-size: 40px;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n\r\n.order-no-text {\r\n font-size: 13px;\r\n color: #999;\r\n}\r\n\r\n.methods-section-new {\r\n background-color: #ffffff;\r\n margin: 0 12px;\r\n border-radius: 12px;\r\n padding: 15px;\r\n}\r\n\r\n.section-header {\r\n margin-bottom: 15px;\r\n}\r\n\r\n.section-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.method-list {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.method-item-modern {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 16px 0;\r\n border-bottom: 0.5px solid #f5f5f5;\r\n}\r\n\r\n.method-item-modern:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.method-left {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.method-img {\r\n width: 28px;\r\n height: 28px;\r\n margin-right: 12px;\r\n}\r\n\r\n.method-info {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.method-name {\r\n\tfont-size: 15px;\r\n\tcolor: #333;\r\n}\r\n\r\n.method-desc {\r\n\tfont-size: 11px;\r\n\tcolor: #999;\r\n\tmargin-top: 2px;\r\n}\r\n\r\n.radio-circle {\r\n width: 20px;\r\n height: 20px;\r\n border-radius: 10px;\r\n border: 1px solid #ddd;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.radio-circle.checked {\r\n border-color: #ff5000;\r\n background-color: #ff5000;\r\n}\r\n\r\n.radio-inner {\r\n width: 10px;\r\n height: 10px;\r\n border-radius: 5px;\r\n background-color: #ffffff;\r\n}\r\n\r\n.balance-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.balance-info {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.balance-label {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n}\r\n\r\n.balance-value {\r\n\tfont-size: 18px;\r\n\tcolor: #ff4757;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.balance-tip {\r\n\tpadding: 10px;\r\n\tbackground-color: #fff0f0;\r\n\tborder-radius: 5px;\r\n}\r\n\r\n.tip-text {\r\n\tfont-size: 12px;\r\n\tcolor: #ff4757;\r\n}\r\n\r\n/* 密码输入弹窗 */\r\n.password-popup-mask {\r\n\tposition: fixed;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\tright: 0;\r\n\tbottom: 0;\r\n\tbackground-color: rgba(0, 0, 0, 0.6);\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tjustify-content: flex-end;\r\n\tz-index: 1000;\r\n}\r\n\r\n.password-popup-content {\r\n\tbackground-color: #ffffff;\r\n\tborder-radius: 16px 16px 0 0;\r\n\tpadding: 20px 0; /* 减少左右内边距,让键盘撑满 */\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tanimation: slideUp 0.3s ease-out;\r\n\twidth: 100%;\r\n}\r\n\r\n@keyframes slideUp {\r\n\tfrom { transform: translateY(100%); }\r\n\tto { transform: translateY(0); }\r\n}\r\n\r\n.popup-header {\r\n\twidth: 100%;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 30px;\r\n\tpadding: 0 20px;\r\n}\r\n\r\n.popup-close {\r\n\tfont-size: 20px;\r\n\tcolor: #999;\r\n\tpadding: 4px;\r\n}\r\n\r\n.popup-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.popup-placeholder {\r\n\twidth: 28px;\r\n}\r\n\r\n.popup-amount-info {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tmargin-bottom: 30px;\r\n}\r\n\r\n.popup-amount-label {\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.popup-amount-row {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: baseline;\r\n}\r\n\r\n.popup-currency {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n\tmargin-right: 2px;\r\n}\r\n\r\n.popup-value {\r\n\tfont-size: 32px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.password-input-row {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: center;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.password-box {\r\n\twidth: 45px;\r\n\theight: 45px;\r\n\tborder: 1px solid #ddd;\r\n\tborder-right: none;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tbackground-color: #f9f9f9;\r\n}\r\n\r\n.password-box:first-child {\r\n\tborder-radius: 4px 0 0 4px;\r\n}\r\n\r\n.password-box:last-child {\r\n\tborder-right: 1px solid #ddd;\r\n\tborder-radius: 0 4px 4px 0;\r\n}\r\n\r\n.password-dot {\r\n\twidth: 10px;\r\n\theight: 10px;\r\n\tborder-radius: 5px;\r\n\tbackground-color: #000;\r\n}\r\n\r\n.forgot-password-link {\r\n\tfont-size: 13px;\r\n\tcolor: #576b95;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.password-section {\r\n /* 移除旧的样式或保持隐藏 */\r\n display: none;\r\n}\r\n\r\n/* 弹窗专用键盘样式 */\r\n.password-keyboard-popup {\r\n\twidth: 100%;\r\n\tbackground-color: #f5f5f5;\r\n\tpadding: 6px;\r\n\tpadding-bottom: env(safe-area-inset-bottom);\r\n}\r\n\r\n/* 键盘样式优化 */\r\n.password-keyboard {\r\n\tdisplay: none; /* 隐藏独立键盘 */\r\n}\r\n\r\n.keyboard-grid {\r\n\tdisplay: flex;\r\n\tflex-wrap: wrap;\r\n\tbackground-color: #f5f5f5;\r\n}\r\n\r\n.keyboard-key {\r\n\twidth: 33.33%;\r\n\tbackground-color: #ffffff;\r\n\theight: 54px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tborder: 3px solid #f5f5f5;\r\n\tbox-sizing: border-box;\r\n\tborder-radius: 8px;\r\n}\r\n\r\n.keyboard-key:active {\r\n\tbackground-color: #e0e0e0;\r\n}\r\n\r\n.key-text {\r\n\tfont-size: 22px;\r\n\tfont-weight: 500;\r\n\tcolor: #333333;\r\n}\r\n\r\n.payment-bottom {\r\n\tbackground-color: #ffffff;\r\n\tborder-top: 1px solid #f0f0f0;\r\n\tpadding: 12px 16px;\r\n padding-bottom: constant(safe-area-inset-bottom);\r\n padding-bottom: env(safe-area-inset-bottom);\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.price-summary {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: baseline;\r\n}\r\n\r\n.summary-label {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tmargin-right: 4px;\r\n}\r\n\r\n.summary-price {\r\n\tfont-size: 24px;\r\n\tcolor: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.pay-btn {\r\n\tbackground-color: #ff5000;\r\n\tcolor: #ffffff;\r\n\tpadding: 0 40px;\r\n\theight: 44px;\r\n\tline-height: 44px;\r\n\tborder-radius: 22px;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tborder: none;\r\n margin: 0;\r\n}\r\n\r\n.pay-btn.disabled {\r\n\tbackground-color: #cccccc;\r\n\topacity: 0.6;\r\n}\r\n\r\n.password-keyboard {\r\n\tbackground-color: #ffffff;\r\n\tborder-top: 1px solid #e5e5e5;\r\n\tpadding: 10px;\r\n}\r\n\r\n.keyboard-grid {\r\n\tdisplay: flex;\r\n\tflex-wrap: wrap;\r\n\t/* grid-template-columns: repeat(3, 1fr); uvue unsupported */\r\n\t/* grid-gap: 1px; uvue unsupported */\r\n\tbackground-color: #e5e5e5;\r\n}\r\n\r\n.keyboard-key {\r\n\twidth: 33.33%;\r\n\tbackground-color: #ffffff;\r\n\theight: 60px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tborder: 1px solid #f5f5f5; /* mimic grid gap */\r\n\tbox-sizing: border-box;\r\n}\r\n\r\n.key-text {\r\n\tfont-size: 24px;\r\n\tcolor: #333333;\r\n}\r\n</style>\r\n\r\n","<!-- pages/mall/consumer/orders.uvue -->\r\n<template>\r\n <view class=\"orders-page\">\r\n <!-- 顶部标题栏 -->\r\n <view class=\"orders-header\" :style=\"{ paddingTop: statusBarHeight + 'px' }\">\r\n <view class=\"header-search full-width\">\r\n <input \r\n class=\"search-input\" \r\n type=\"text\" \r\n placeholder=\"搜索订单号或商品名称\"\r\n :value=\"searchKeyword\"\r\n @input=\"onSearchInput\"\r\n @confirm=\"onSearchConfirm\"\r\n />\r\n <text v-if=\"searchKeyword\" class=\"search-clear\" @click=\"clearSearch\">×</text>\r\n <text v-else class=\"search-icon\">🔍</text>\r\n </view>\r\n </view>\r\n \r\n <!-- 订单状态筛选 -->\r\n <view class=\"order-tabs-fixed-container\">\r\n <view \r\n :class=\"['tab-item-fixed', { active: activeTab === 'all' }]\"\r\n @click=\"switchTab('all')\"\r\n >\r\n <text class=\"tab-name\">全部</text>\r\n <view v-if=\"activeTab === 'all'\" class=\"active-indicator\"></view>\r\n </view>\r\n <scroll-view scroll-x=\"true\" class=\"tab-scroll-mobile\" :show-scrollbar=\"false\" :scroll-with-animation=\"true\">\r\n <view class=\"tab-container-mobile\">\r\n <view \r\n v-for=\"tab in orderTabsMobile\" \r\n :key=\"tab.id\"\r\n :class=\"['tab-item-mobile', { active: activeTab === tab.id }]\"\r\n @click=\"switchTab(tab.id)\"\r\n >\r\n <text class=\"tab-name\">{{ tab.name }}</text>\r\n <text v-if=\"tab.count > 0\" class=\"tab-count\">{{ tab.count }}</text>\r\n <view v-if=\"activeTab === tab.id\" class=\"active-indicator\"></view>\r\n </view>\r\n </view>\r\n </scroll-view>\r\n </view>\r\n \r\n <!-- 订单列表 -->\r\n <scroll-view \r\n direction=\"vertical\"\r\n class=\"orders-content\"\r\n refresher-enabled\r\n :refresher-triggered=\"refreshing\"\r\n @refresherrefresh=\"onRefresh\"\r\n @scrolltolower=\"loadMore\"\r\n >\r\n <!-- 空状态 -->\r\n <view v-if=\"!loading && orders.length === 0\" class=\"empty-orders\">\r\n <text class=\"empty-icon\">📦</text>\r\n <text class=\"empty-title\">暂无订单</text>\r\n <text class=\"empty-desc\">去逛逛,发现心仪的商品</text>\r\n <button class=\"go-shopping-btn\" @click=\"goShopping\">去逛逛</button>\r\n </view>\r\n \r\n <!-- 订单列表 -->\r\n <view v-else class=\"order-list\">\r\n <view \r\n v-for=\"order in orders\" \r\n :key=\"order.id\"\r\n class=\"order-card\"\r\n @click=\"viewOrderDetail(order.id)\"\r\n >\r\n <!-- 订单头部:显示店铺名称 -->\r\n <view class=\"order-card-header\">\r\n <view class=\"shop-info\">\r\n <text class=\"shop-icon\">🏪</text>\r\n <text class=\"shop-name\">{{ order.shop_name != null && order.shop_name != '' ? order.shop_name : '自营店铺' }}</text>\r\n <text class=\"arrow-right\"></text>\r\n </view>\r\n <view class=\"status-row\">\r\n <text :class=\"['order-status', getStatusClass(order.status)]\">\r\n {{ getStatusText(order.status) }}\r\n </text>\r\n <text class=\"more-btn\" @click.stop=\"showOrderMenu(order)\">⋯</text>\r\n </view>\r\n </view>\r\n \r\n <!-- 订单商品 -->\r\n <view class=\"order-products\">\r\n <view \r\n v-for=\"product in order.products\" \r\n :key=\"product.id\"\r\n class=\"order-product\"\r\n >\r\n <image \r\n class=\"product-image\" \r\n :src=\"product.image\" \r\n mode=\"aspectFill\"\r\n @click.stop=\"navigateToProduct(product)\"\r\n />\r\n <view class=\"product-info\">\r\n <view class=\"product-top-info\">\r\n <text class=\"product-name\">{{ product.name }}</text>\r\n <text class=\"product-spec\">{{ product.spec }}</text>\r\n </view>\r\n <view class=\"product-footer\">\r\n <text class=\"product-price\">¥{{ product.price }}</text>\r\n <text class=\"product-quantity\">x{{ product.quantity }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 订单汇总信息 -->\r\n <view class=\"order-summary\">\r\n <text class=\"order-time\">{{ formatDate(order.create_time) }}</text>\r\n <view class=\"summary-right\">\r\n <text class=\"summary-label\">共{{ order.products.length }}件商品 实付:</text>\r\n <text class=\"summary-price\">¥{{ order.total_amount }}</text>\r\n </view>\r\n </view>\r\n \r\n <!-- 分享免单入口 (已付款订单显示: 待发货、待收货、已完成,且商家开启了分享免单) -->\r\n <view v-if=\"order.status >= 2 && order.status <= 4 && isShareFreeEnabled(order.merchant_id)\" class=\"share-free-row\" @click.stop=\"shareForFree(order)\">\r\n <text class=\"share-free-icon\">🎁</text>\r\n <text class=\"share-free-text\">分享免单</text>\r\n <text class=\"share-free-tip\">分享给好友,{{ getRequiredCount(order.merchant_id) }}人购买即可免单</text>\r\n <text class=\"share-free-arrow\"></text>\r\n </view>\r\n \r\n <!-- 订单操作 -->\r\n <view class=\"order-actions\" @click.stop=\"\">\r\n <view v-if=\"order.status === 1\" class=\"action-buttons\">\r\n <button class=\"action-btn cancel\" @click=\"cancelOrder(order.id)\">取消订单</button>\r\n <button class=\"action-btn pay\" @click=\"payOrder(order.id)\">立即支付</button>\r\n </view>\r\n \r\n <view v-if=\"order.status === 2\" class=\"action-buttons\">\r\n <button class=\"action-btn remind\" @click.stop=\"remindShipping(order.id)\">提醒发货</button>\r\n <button class=\"action-btn refund\" @click.stop=\"onApplyRefund(order)\">申请售后</button>\r\n </view>\r\n \r\n <view v-if=\"order.status === 3\" class=\"action-buttons\">\r\n <button class=\"action-btn view\" @click.stop=\"viewLogistics(order.id)\">查看物流</button>\r\n <button class=\"action-btn confirm\" @click.stop=\"confirmReceipt(order.id)\">确认收货</button>\r\n <button class=\"action-btn refund\" @click.stop=\"onApplyRefund(order)\">申请售后</button>\r\n </view>\r\n \r\n <view v-if=\"order.status === 4\" class=\"action-buttons\">\r\n <button class=\"action-btn review\" @click.stop=\"goReview(order)\">评价</button>\r\n <button class=\"action-btn refund\" @click.stop=\"onApplyRefund(order)\">申请售后</button>\r\n <button class=\"action-btn repurchase\" @click.stop=\"repurchase(order)\">再次购买</button>\r\n </view>\r\n \r\n <view v-if=\"order.status === 5\" class=\"action-buttons\">\r\n <button class=\"action-btn view\" @click.stop=\"viewOrderDetail(order.id)\">查看详情</button>\r\n </view>\r\n \r\n <view v-if=\"order.status === 6\" class=\"action-buttons\">\r\n <button class=\"action-btn view\" @click.stop=\"viewOrderDetail(order.id)\">查看详情</button>\r\n <button class=\"action-btn cancel\" @click.stop=\"cancelRefund(order.id)\">取消退款</button>\r\n <button class=\"action-btn refund\" @click.stop=\"viewRefundProgress(order.id)\">退款进度</button>\r\n </view>\r\n \r\n <view v-if=\"order.status === 7\" class=\"action-buttons\">\r\n <button class=\"action-btn view\" @click=\"viewOrderDetail(order.id)\">查看详情</button>\r\n <button class=\"action-btn repurchase\" @click=\"repurchase(order)\">再次购买</button>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 加载更多 -->\r\n <view v-if=\"loadingMore\" class=\"loading-more\">\r\n <view class=\"loading-spinner\"></view>\r\n <text>加载中...</text>\r\n </view>\r\n \r\n <view v-if=\"!hasMore && orders.length > 0\" class=\"no-more\">\r\n <text>没有更多订单了</text>\r\n </view>\r\n \r\n <!-- 安全区域 -->\r\n <view class=\"safe-area\"></view>\r\n </scroll-view>\r\n \r\n <!-- 底部导航 -->\r\n <view class=\"tabbar-placeholder\"></view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, reactive, onMounted, computed } from 'vue'\r\nimport { onShow, onLoad, onBackPress } from '@dcloudio/uni-app'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\n// 定义标签页类型\r\ntype OrderTabItem = {\r\n id: string,\r\n name: string,\r\n count: number\r\n}\r\n\r\n// 定义订单产品类型\r\ntype OrderProduct = {\r\n id: string,\r\n name: string,\r\n price: number,\r\n image: string,\r\n spec: string,\r\n quantity: number\r\n}\r\n\r\n// 定义订单类型\r\ntype OrderItem = {\r\n id: string,\r\n order_no: string,\r\n status: number,\r\n create_time: string,\r\n product_amount: number,\r\n shipping_fee: number,\r\n total_amount: number,\r\n merchant_id: string,\r\n shop_name: string,\r\n products: OrderProduct[]\r\n}\r\n\r\n// 响应式数据\r\nconst orders = ref<OrderItem[]>([])\r\nconst allOrdersList = ref<OrderItem[]>([]) // Store all fetched orders for client-side filtering\r\nconst loading = ref<boolean>(false)\r\nconst loadingMore = ref<boolean>(false)\r\nconst hasMore = ref<boolean>(true)\r\nconst refreshing = ref<boolean>(false)\r\nconst page = ref<number>(1)\r\nconst activeTab = ref<string>('all')\r\nconst statusBarHeight = ref<number>(0)\r\nconst searchKeyword = ref<string>('')\r\n\r\n// 商家推销配置缓存\r\nconst merchantShareFreeEnabled = ref<UTSJSONObject>(new UTSJSONObject())\r\nconst merchantRequiredCount = ref<UTSJSONObject>(new UTSJSONObject())\r\n\r\n// 订单标签页 - 使用 ref 以便整体替换\r\nconst orderTabs = ref<OrderTabItem[]>([\r\n { id: 'all', name: '全部', count: 0 },\r\n { id: 'pending', name: '待付款', count: 0 },\r\n { id: 'shipping', name: '待发货', count: 0 },\r\n { id: 'delivering', name: '待收货', count: 0 },\r\n { id: 'completed', name: '已完成', count: 0 },\r\n { id: 'aftersale', name: '售后', count: 0 },\r\n { id: 'cancelled', name: '已取消', count: 0 }\r\n])\r\n\r\n// 模拟状态筛选(除去\"全部\"后的其余标签)\r\nconst orderTabsMobile = computed((): OrderTabItem[] => {\r\n return orderTabs.value.filter((tab: OrderTabItem) => tab.id !== 'all')\r\n})\r\n\r\n\r\n// 辅助函数:获取状态码\r\nconst getStatusByTab = (tabId: string): number => {\r\n if (tabId == 'pending') return 1\r\n if (tabId == 'shipping') return 2\r\n if (tabId == 'delivering') return 3\r\n if (tabId == 'completed') return 4\r\n if (tabId == 'cancelled') return 5\r\n if (tabId == 'aftersale') return 6\r\n return 0\r\n}\r\n\r\n// 格式化规格对象为友好的文本 - 必须在 parseSpecText 之前定义\r\nfunction formatSpecObj(obj: any): string {\r\n if (obj == null) return ''\r\n if (typeof obj !== 'object') {\r\n // 非对象类型直接返回字符串形式\r\n if (typeof obj === 'string') return obj\r\n if (typeof obj === 'number') return obj.toString()\r\n return ''\r\n }\r\n \r\n try {\r\n const objStr = JSON.stringify(obj)\r\n const objParsed = JSON.parse(objStr)\r\n if (objParsed == null) return ''\r\n \r\n const specObj = objParsed as UTSJSONObject\r\n \r\n // 使用 JSON.stringify 获取所有键\r\n const specObjStr = JSON.stringify(specObj)\r\n const specObjForKeys = JSON.parse(specObjStr) as UTSJSONObject\r\n \r\n // 手动提取键值对\r\n const parts: string[] = []\r\n \r\n // 尝试获取已知字段\r\n const colorVal = specObjForKeys.getString('Color')\r\n if (colorVal != null && colorVal != '') {\r\n parts.push('Color: ' + colorVal)\r\n }\r\n \r\n const sizeVal = specObjForKeys.getString('Size')\r\n if (sizeVal != null && sizeVal != '') {\r\n parts.push('Size: ' + sizeVal)\r\n }\r\n \r\n const defaultVal = specObjForKeys.getString('默认')\r\n if (defaultVal != null && defaultVal != '') {\r\n parts.push('默认: ' + defaultVal)\r\n }\r\n \r\n // 如果没有匹配到已知字段,尝试直接显示 JSON\r\n if (parts.length === 0) {\r\n // 尝试遍历对象\r\n const objAny = specObjForKeys as any\r\n if (objAny != null) {\r\n return specObjStr.replace(/[{}\"]/g, '').replace(/:/g, ': ').replace(/,/g, ' | ')\r\n }\r\n }\r\n \r\n return parts.join(' | ')\r\n } catch (e) {\r\n return ''\r\n }\r\n}\r\n\r\n// 辅助函数:解析规格文本\r\nfunction parseSpecText(specs: any): string {\r\n if (specs == null) return ''\r\n if (typeof specs === 'string') {\r\n // 如果是 JSON 字符串,尝试解析\r\n if (specs.startsWith('{') || specs.startsWith('[')) {\r\n try {\r\n const parsed = JSON.parse(specs)\r\n if (parsed == null) return specs\r\n return formatSpecObj(parsed)\r\n } catch (e) {\r\n return specs\r\n }\r\n }\r\n return specs\r\n }\r\n // 对于对象类型,格式化显示\r\n return formatSpecObj(specs)\r\n}\r\n\r\n// 辅助函数:更新标签计数\r\nconst updateTabsCounts = (allOrders: OrderItem[]) => {\r\n const countAll = allOrders.length\r\n const countPending = allOrders.filter((o: OrderItem) => o.status === 1).length\r\n const countShipping = allOrders.filter((o: OrderItem) => o.status === 2).length\r\n const countDelivering = allOrders.filter((o: OrderItem) => o.status === 3).length\r\n const countCompleted = allOrders.filter((o: OrderItem) => o.status === 4).length\r\n const countCancelled = allOrders.filter((o: OrderItem) => o.status === 5).length\r\n const countAftersale = allOrders.filter((o: OrderItem) => o.status === 6 || o.status === 7).length\r\n \r\n orderTabs.value[0].count = countAll\r\n orderTabs.value[1].count = countPending\r\n orderTabs.value[2].count = countShipping\r\n orderTabs.value[3].count = countDelivering\r\n orderTabs.value[4].count = countCompleted\r\n orderTabs.value[5].count = countAftersale\r\n orderTabs.value[6].count = countCancelled\r\n}\r\n\r\n// 辅助函数:按标签筛选订单\r\nconst filterOrdersByTab = () => {\r\n if (activeTab.value === 'all') {\r\n orders.value = allOrdersList.value\r\n } else if (activeTab.value === 'aftersale') {\r\n orders.value = allOrdersList.value.filter((o: OrderItem) => {\r\n return o.status === 6 || o.status === 7\r\n })\r\n } else {\r\n const targetStatus = getStatusByTab(activeTab.value)\r\n orders.value = allOrdersList.value.filter((o: OrderItem) => {\r\n return o.status === targetStatus\r\n })\r\n }\r\n}\r\n\r\n// 检查商家是否开启分享免单\r\nconst isShareFreeEnabled = (merchantId: string): boolean => {\r\n const val = merchantShareFreeEnabled.value.get(merchantId)\r\n return val === true\r\n}\r\n\r\n// 获取商家要求的购买人数\r\nconst getRequiredCount = (merchantId: string): number => {\r\n const val = merchantRequiredCount.value.get(merchantId)\r\n if (val != null && typeof val === 'number') {\r\n return val as number\r\n }\r\n return 4\r\n}\r\n\r\n// 加载商家推销配置\r\nconst loadMerchantPromotionConfigs = async (orderList: OrderItem[]) => {\r\n // 收集所有唯一的商家ID\r\n const merchantIds = new Set<string>()\r\n for (let i = 0; i < orderList.length; i++) {\r\n const merchantId = orderList[i].merchant_id\r\n const existingVal = merchantShareFreeEnabled.value.get(merchantId)\r\n if (merchantId != null && merchantId !== '' && existingVal == null) {\r\n merchantIds.add(merchantId)\r\n }\r\n }\r\n \r\n // 批量加载商家配置\r\n const merchantIdArray = Array.from(merchantIds)\r\n for (let i = 0; i < merchantIdArray.length; i++) {\r\n const merchantIdRaw = merchantIdArray[i]\r\n const merchantId = merchantIdRaw as string\r\n try {\r\n const config = await supabaseService.getMerchantPromotionConfig(merchantId)\r\n const promotionEnabled = config.get('promotion_enabled')\r\n const shareFreeEnabled = config.get('share_free_enabled')\r\n const requiredCount = config.get('required_count')\r\n \r\n const isEnabled: any = \r\n (promotionEnabled === true || promotionEnabled === 'true') && \r\n (shareFreeEnabled === true || shareFreeEnabled === 'true')\r\n merchantShareFreeEnabled.value.set(merchantId, isEnabled)\r\n \r\n if (requiredCount != null) {\r\n merchantRequiredCount.value.set(merchantId, requiredCount)\r\n } else {\r\n merchantRequiredCount.value.set(merchantId, 4 as any)\r\n }\r\n } catch (e) {\r\n console.error('加载商家推销配置失败:', merchantId, e)\r\n merchantShareFreeEnabled.value.set(merchantId, false as any)\r\n merchantRequiredCount.value.set(merchantId, 4 as any)\r\n }\r\n }\r\n}\r\n\r\n// 加载订单数据\r\nconst loadOrders = async () => {\r\n loading.value = true\r\n \r\n try {\r\n // Fetch all orders from Supabase (status=0)\r\n const fetchedOrders = await supabaseService.getOrders(0)\r\n console.log('[loadOrders] 获取到订单数量:', fetchedOrders.length)\r\n \r\n // Map to View Model\r\n const mappedOrders: OrderItem[] = []\r\n for (let i = 0; i < fetchedOrders.length; i++) {\r\n const order = fetchedOrders[i]\r\n // 使用 JSON 序列化转换\r\n const orderStr = JSON.stringify(order)\r\n const orderParsed = JSON.parse(orderStr)\r\n if (orderParsed == null) continue\r\n const orderObj = orderParsed as UTSJSONObject\r\n \r\n const itemsRaw = orderObj.get('ml_order_items')\r\n const productsList: OrderProduct[] = []\r\n \r\n console.log('[loadOrders] 订单商品数据:', itemsRaw)\r\n \r\n if (itemsRaw != null) {\r\n // 先检查是否为数组\r\n if (Array.isArray(itemsRaw)) {\r\n const items = itemsRaw as any[]\r\n console.log('[loadOrders] 商品数量:', items.length)\r\n for (let j = 0; j < items.length; j++) {\r\n const item = items[j]\r\n const itemStr = JSON.stringify(item)\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed == null) continue\r\n const itemObj = itemParsed as UTSJSONObject\r\n \r\n const specRaw = itemObj.get('specifications')\r\n const specText = specRaw != null ? parseSpecText(specRaw) : ''\r\n \r\n const productId = itemObj.getString('product_id')\r\n const productName = itemObj.getString('product_name')\r\n const price = itemObj.getNumber('price')\r\n const imageUrl = itemObj.getString('image_url')\r\n const quantity = itemObj.getNumber('quantity')\r\n \r\n console.log('[loadOrders] 商品:', productName, '图片:', imageUrl, '规格:', specText)\r\n \r\n const productItem: OrderProduct = {\r\n id: productId ?? '',\r\n name: productName ?? '未知商品',\r\n price: price ?? 0,\r\n image: imageUrl ?? '/static/default-product.png',\r\n spec: specText,\r\n quantity: quantity ?? 1\r\n }\r\n productsList.push(productItem)\r\n }\r\n }\r\n }\r\n \r\n const orderId = orderObj.getString('id')\r\n const orderNo = orderObj.getString('order_no')\r\n const orderStatus = orderObj.getNumber('order_status')\r\n const createdAt = orderObj.getString('created_at')\r\n const productAmount = orderObj.getNumber('product_amount')\r\n const shippingFee = orderObj.getNumber('shipping_fee')\r\n const totalAmount = orderObj.getNumber('total_amount')\r\n const paidAmount = orderObj.getNumber('paid_amount')\r\n const merchantId = orderObj.getString('merchant_id')\r\n \r\n // 从关联查询的 ml_shops 表获取店铺名称\r\n let shopName = '自营店铺'\r\n const shopsRaw = orderObj.get('ml_shops')\r\n if (shopsRaw != null) {\r\n const shopStr = JSON.stringify(shopsRaw)\r\n const shopParsed = JSON.parse(shopStr)\r\n if (shopParsed != null) {\r\n const shopObj = shopParsed as UTSJSONObject\r\n const shopNameFromDb = shopObj.getString('shop_name')\r\n if (shopNameFromDb != null && shopNameFromDb != '') {\r\n shopName = shopNameFromDb\r\n }\r\n }\r\n } else if (merchantId != null && merchantId != '') {\r\n shopName = '商家店铺'\r\n }\r\n \r\n console.log('[loadOrders] 订单号:', orderNo, '店铺:', shopName, '商品数:', productsList.length)\r\n \r\n // 如果没有商品数据,添加一个占位商品\r\n if (productsList.length === 0) {\r\n const placeholderProduct: OrderProduct = {\r\n id: 'placeholder',\r\n name: '订单商品',\r\n price: totalAmount ?? paidAmount ?? 0,\r\n image: '/static/default-product.png',\r\n spec: '',\r\n quantity: 1\r\n }\r\n productsList.push(placeholderProduct)\r\n }\r\n \r\n const mappedOrder: OrderItem = {\r\n id: orderId ?? '',\r\n order_no: orderNo ?? '',\r\n status: orderStatus ?? 1,\r\n create_time: createdAt ?? '',\r\n product_amount: productAmount ?? 0,\r\n shipping_fee: shippingFee ?? 0,\r\n total_amount: totalAmount ?? paidAmount ?? 0,\r\n merchant_id: merchantId ?? '',\r\n shop_name: shopName,\r\n products: productsList\r\n }\r\n mappedOrders.push(mappedOrder)\r\n }\r\n\r\n // Sort by created_at desc - 直接使用 OrderItem 类型访问属性\r\n mappedOrders.sort((a: OrderItem, b: OrderItem) => {\r\n const timeA = new Date(a.create_time).getTime()\r\n const timeB = new Date(b.create_time).getTime()\r\n return timeB - timeA\r\n })\r\n\r\n allOrdersList.value = mappedOrders\r\n \r\n // Update tab counts\r\n updateTabsCounts(mappedOrders)\r\n \r\n // Apply current tab filter\r\n filterOrdersByTab()\r\n \r\n // 加载商家推销配置\r\n loadMerchantPromotionConfigs(mappedOrders)\r\n \r\n } catch (err) {\r\n console.error('加载订单异常:', err)\r\n uni.showToast({ title: '加载订单失败', icon: 'none' })\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n\r\n// 生命周期\r\nonLoad((options) => {\r\n // 初始化状态栏高度\r\n const systemInfo = uni.getSystemInfoSync()\r\n statusBarHeight.value = systemInfo.statusBarHeight ?? 0\r\n \r\n if (options == null) return\r\n const statusVal = options['status']\r\n if (statusVal != null) {\r\n const status = statusVal as string\r\n if (['all', 'pending', 'shipping', 'delivering', 'completed', 'aftersale', 'cancelled'].includes(status)) {\r\n activeTab.value = status\r\n }\r\n }\r\n const typeVal = options['type']\r\n if (typeVal != null) {\r\n const type = typeVal as string\r\n if (type === 'pending') activeTab.value = 'pending'\r\n else if (type === 'shipped') activeTab.value = 'delivering' // 映射到待收货\r\n else if (type === 'review') activeTab.value = 'completed' // 映射到已完成\r\n else if (type === 'refund') activeTab.value = 'aftersale' // 退款/售后跳转到售后标签页\r\n }\r\n})\r\n\r\nonShow(() => {\r\n loadOrders()\r\n})\r\n\r\nconst formatDate = (isoString: string): string => {\r\n if (isoString == '') return ''\r\n const date = new Date(isoString)\r\n return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`\r\n}\r\n\r\n// 辅助函数:获取当前订单数据(必须在 performSearch 之前定义)\r\nfunction getCurrentOrderData(): OrderItem[] {\r\n return allOrdersList.value\r\n}\r\n\r\n// 搜索执行函数(必须在 onSearchInput 等之前定义)\r\nconst performSearch = () => {\r\n const keyword = searchKeyword.value.trim().toLowerCase()\r\n if (keyword == '') {\r\n loadOrders()\r\n return\r\n }\r\n \r\n // 在当前订单数据中搜索\r\n const allOrders = getCurrentOrderData()\r\n const filtered = allOrders.filter((order: any) => {\r\n const orderObj = order as Record<string, any>\r\n // 搜索订单号\r\n const orderNo = orderObj['order_no'] as string\r\n if (orderNo != null && orderNo.toLowerCase().includes(keyword)) {\r\n return true\r\n }\r\n \r\n // 搜索商品名称\r\n const products = orderObj['products']\r\n if (products != null && Array.isArray(products)) {\r\n return products.some((product: any) => {\r\n const productObj = product as Record<string, any>\r\n const name = productObj['name'] as string\r\n return name != null && name.toLowerCase().includes(keyword)\r\n })\r\n }\r\n \r\n return false\r\n })\r\n \r\n orders.value = filtered\r\n}\r\n\r\n// 搜索相关函数\r\nconst onSearchInput = (e: any) => {\r\n const eObj = e as Record<string, any>\r\n const detail = eObj['detail'] as Record<string, any>\r\n searchKeyword.value = detail['value'] as string\r\n performSearch()\r\n}\r\n\r\nconst onSearchConfirm = () => {\r\n performSearch()\r\n}\r\n\r\nconst clearSearch = () => {\r\n searchKeyword.value = ''\r\n performSearch()\r\n}\r\n\r\nconst formatSpec = (specs: any): string => {\r\n if (specs == null) return ''\r\n if (typeof specs === 'string') return specs\r\n if (typeof specs === 'object') {\r\n return JSON.stringify(specs)\r\n }\r\n return ''\r\n}\r\n\r\n// 切换标签\r\nconst switchTab = (tabId: string) => {\r\n activeTab.value = tabId\r\n filterOrdersByTab()\r\n}\r\n\r\n// 获取状态文本\r\nconst getStatusText = (status: number): string => {\r\n if (status == 1) return '待付款'\r\n if (status == 2) return '待发货'\r\n if (status == 3) return '待收货'\r\n if (status == 4) return '已完成'\r\n if (status == 5) return '已取消'\r\n if (status == 6) return '退款中'\r\n if (status == 7) return '已退款'\r\n return '未知状态'\r\n}\r\n\r\n// 获取状态类名\r\nconst getStatusClass = (status: number): string => {\r\n if (status == 1) return 'status-pending'\r\n if (status == 2) return 'status-shipping'\r\n if (status == 3) return 'status-delivering'\r\n if (status == 4) return 'status-completed'\r\n if (status == 5) return 'status-cancelled'\r\n if (status == 6) return 'status-refunding'\r\n if (status == 7) return 'status-refunded'\r\n return 'status-unknown'\r\n}\r\n\r\n// 联系卖家\r\nconst contactSeller = (order: OrderItem) => {\r\n if (order.merchant_id != '') {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/chat?merchantId=${order.merchant_id}`\r\n })\r\n } else {\r\n uni.showToast({\r\n title: '暂无卖家联系方式',\r\n icon: 'none'\r\n })\r\n }\r\n}\r\n\r\n// 删除订单\r\nconst deleteOrder = (orderId: string) => {\r\n uni.showModal({\r\n title: '删除订单',\r\n content: '确定要删除此订单吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.showLoading({ title: '删除中...' })\r\n supabaseService.deleteOrder(orderId).then(() => {\r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '订单已删除',\r\n icon: 'success'\r\n })\r\n loadOrders()\r\n }).catch(() => {\r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '删除失败',\r\n icon: 'none'\r\n })\r\n })\r\n }\r\n }\r\n })\r\n}\r\n\r\n// 下拉刷新\r\nconst onRefresh = () => {\r\n refreshing.value = true\r\n setTimeout(() => {\r\n loadOrders()\r\n refreshing.value = false\r\n uni.showToast({\r\n title: '刷新成功',\r\n icon: 'success'\r\n })\r\n }, 1000)\r\n}\r\n\r\n// 上拉加载更多\r\nconst loadMore = () => {\r\n if (loadingMore.value || !hasMore.value) return\r\n \r\n // 暂未实现分页,直接返回\r\n hasMore.value = false\r\n}\r\n\r\n// 订单操作函数\r\nconst cancelOrder = (orderId: string) => {\r\n uni.showModal({\r\n title: '确认取消',\r\n content: '确定要取消此订单吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.showLoading({ title: '取消中...' })\r\n supabaseService.cancelOrder(orderId).then((success) => {\r\n uni.hideLoading()\r\n if (success) {\r\n uni.showToast({\r\n title: '订单已取消',\r\n icon: 'success'\r\n })\r\n loadOrders()\r\n } else {\r\n uni.showToast({\r\n title: '取消失败',\r\n icon: 'none'\r\n })\r\n }\r\n }).catch(() => {\r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '取消失败',\r\n icon: 'none'\r\n })\r\n })\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst payOrder = (orderId: string) => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/payment?orderId=${orderId}`\r\n })\r\n}\r\n\r\nconst remindShipping = async (orderId: string) => {\r\n // 基础提醒\r\n uni.showLoading({ title: '正在提醒...' })\r\n \r\n try {\r\n // 查找订单中的商家ID\r\n const order = orders.value.find(o => o.id === orderId)\r\n if (order != null) {\r\n const merchantId = order.merchant_id\r\n const orderNo = order.order_no\r\n \r\n if (merchantId != '') {\r\n // 向商家发送自动催单消息\r\n const message = `你好,我的订单[${orderNo}]还没有发货,请尽快安排,谢谢。`\r\n const success = await supabaseService.sendChatMessage(message, merchantId)\r\n \r\n if (success) {\r\n console.log('催单消息发送成功')\r\n } else {\r\n console.warn('催单消息发送失败,可能是由于网络原因')\r\n }\r\n }\r\n }\r\n } catch (e) {\r\n console.error('提醒发货异常:', e)\r\n } finally {\r\n uni.hideLoading()\r\n }\r\n \r\n uni.showToast({\r\n title: '已提醒卖家发货',\r\n icon: 'success'\r\n })\r\n}\r\n\r\nconst viewLogistics = (orderId: string) => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/logistics?orderId=${orderId}`\r\n })\r\n}\r\n\r\n// goReview 必须在 doConfirmReceipt 之前定义,因为 doConfirmReceipt 会调用它\r\nconst goReview = (order: OrderItem) => {\r\n const productIds = order.products.map((p: OrderProduct): string => {\r\n return p.id\r\n }).join(',')\r\n const orderId = order.id\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/review?orderId=${orderId}&productIds=${productIds}`\r\n })\r\n}\r\n\r\nconst doConfirmReceipt = async (orderId: string) => {\r\n uni.showLoading({ title: '处理中...' })\r\n try {\r\n const result = await supabaseService.confirmReceipt(orderId)\r\n uni.hideLoading()\r\n \r\n if (result.success) {\r\n uni.showToast({\r\n title: '收货成功',\r\n icon: 'success'\r\n })\r\n \r\n // 更新 allOrdersList 中的订单状态\r\n const allIndex = allOrdersList.value.findIndex((o: OrderItem): boolean => o.id === orderId)\r\n if (allIndex !== -1) {\r\n allOrdersList.value[allIndex].status = 4\r\n allOrdersList.value = [...allOrdersList.value]\r\n }\r\n \r\n // 更新标签计数\r\n updateTabsCounts(allOrdersList.value)\r\n \r\n // 重新应用当前标签筛选\r\n filterOrdersByTab()\r\n\r\n // 跳转到评价页面\r\n setTimeout(() => {\r\n const order = allOrdersList.value.find((o: OrderItem): boolean => o.id === orderId)\r\n if (order != null) {\r\n goReview(order)\r\n }\r\n }, 1000)\r\n } else {\r\n uni.showToast({\r\n title: result.error ?? '确认收货失败',\r\n icon: 'none'\r\n })\r\n }\r\n } catch (e) {\r\n uni.hideLoading()\r\n uni.showToast({\r\n title: '系统异常',\r\n icon: 'none'\r\n })\r\n }\r\n}\r\n\r\nconst confirmReceipt = (orderId: string) => {\r\n uni.showModal({\r\n title: '确认收货',\r\n content: '请确认您已收到商品,且商品无误',\r\n success: (res) => {\r\n if (res.confirm) {\r\n doConfirmReceipt(orderId)\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst repurchase = (order: OrderItem) => {\r\n const products = order.products\r\n \r\n if (products.length === 0) {\r\n uni.showToast({\r\n title: '订单无商品',\r\n icon: 'none'\r\n })\r\n return\r\n }\r\n \r\n uni.showLoading({ title: '处理中...' })\r\n \r\n let completed = 0\r\n const total = products.length\r\n let successCount = 0\r\n \r\n for (let i = 0; i < products.length; i++) {\r\n const product = products[i]\r\n const productId = product.id\r\n const merchantId = order.merchant_id\r\n \r\n if (productId != null && productId !== '') {\r\n supabaseService.addToCart(productId, 1, '', merchantId ?? '').then((success: boolean) => {\r\n completed++\r\n if (success) successCount++\r\n if (completed === total) {\r\n uni.hideLoading()\r\n if (successCount > 0) {\r\n uni.showToast({\r\n title: `已添加${successCount}件商品`,\r\n icon: 'success'\r\n })\r\n } else {\r\n uni.showToast({\r\n title: '添加失败',\r\n icon: 'none'\r\n })\r\n }\r\n }\r\n }).catch(() => {\r\n completed++\r\n if (completed === total) {\r\n uni.hideLoading()\r\n if (successCount > 0) {\r\n uni.showToast({\r\n title: `已添加${successCount}件商品`,\r\n icon: 'success'\r\n })\r\n } else {\r\n uni.showToast({\r\n title: '添加失败',\r\n icon: 'none'\r\n })\r\n }\r\n }\r\n })\r\n } else {\r\n completed++\r\n if (completed === total) {\r\n uni.hideLoading()\r\n if (successCount > 0) {\r\n uni.showToast({\r\n title: `已添加${successCount}件商品`,\r\n icon: 'success'\r\n })\r\n } else {\r\n uni.showToast({\r\n title: '添加失败',\r\n icon: 'none'\r\n })\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\nconst viewOrderDetail = (orderId: string) => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/order-detail?id=${orderId}`\r\n })\r\n}\r\n\r\nconst onApplyRefund = (order: OrderItem) => {\r\n const orderId = order.id\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/apply-refund?orderId=${orderId}`\r\n })\r\n}\r\n\r\nconst viewRefundProgress = (orderId: string) => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/refund?orderId=${orderId}`\r\n })\r\n}\r\n\r\nconst doCancelRefund = async (orderId: string) => {\r\n uni.showLoading({ title: '处理中...' })\r\n const result = await supabaseService.cancelRefund(orderId)\r\n uni.hideLoading()\r\n \r\n if (result.success) {\r\n uni.showToast({ title: '已取消退款', icon: 'success' })\r\n loadOrders()\r\n } else {\r\n uni.showToast({ title: result.message, icon: 'none' })\r\n }\r\n}\r\n\r\n// 取消退款申请\r\nconst cancelRefund = (orderId: string) => {\r\n uni.showModal({\r\n title: '确认取消',\r\n content: '确定要取消退款申请吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n doCancelRefund(orderId)\r\n }\r\n }\r\n })\r\n}\r\n\r\n// 处理订单操作\r\nconst handleOrderAction = (order: OrderItem, action: string) => {\r\n if (action === '取消订单') {\r\n cancelOrder(order.id)\r\n } else if (action === '联系卖家') {\r\n contactSeller(order)\r\n } else if (action === '提醒发货') {\r\n remindShipping(order.id)\r\n } else if (action === '申请退款' || action === '申请售后') {\r\n onApplyRefund(order)\r\n } else if (action === '查看物流') {\r\n viewLogistics(order.id)\r\n } else if (action === '确认收货') {\r\n confirmReceipt(order.id)\r\n } else if (action === '再次购买') {\r\n repurchase(order)\r\n } else if (action === '删除订单') {\r\n deleteOrder(order.id)\r\n } else if (action === '退款进度') {\r\n viewRefundProgress(order.id)\r\n } else if (action === '取消退款') {\r\n cancelRefund(order.id)\r\n }\r\n}\r\n\r\n// 显示订单操作菜单\r\nconst showOrderMenu = (order: OrderItem) => {\r\n const status = order.status\r\n let actions: string[] = []\r\n \r\n if (status === 1) {\r\n actions = ['取消订单', '联系卖家']\r\n } else if (status === 2) {\r\n actions = ['提醒发货', '申请退款', '联系卖家']\r\n } else if (status === 3) {\r\n actions = ['查看物流', '确认收货', '申请退款', '联系卖家']\r\n } else if (status === 4) {\r\n actions = ['申请售后', '再次购买', '联系卖家']\r\n } else if (status === 5) {\r\n actions = ['删除订单', '再次购买', '联系卖家']\r\n } else if (status === 6) {\r\n actions = ['取消退款', '退款进度', '联系卖家']\r\n } else if (status === 7) {\r\n actions = ['再次购买', '联系卖家']\r\n }\r\n \r\n uni.showActionSheet({\r\n itemList: actions,\r\n success: (res) => {\r\n const action = actions[res.tapIndex]\r\n handleOrderAction(order, action)\r\n }\r\n })\r\n}\r\n\r\n// 导航函数\r\nconst navigateToSearch = () => {\r\n uni.navigateTo({ url: '/pages/mall/consumer/search' })\r\n}\r\n\r\nconst navigateToProduct = (product: OrderProduct) => {\r\n const productId = product.id\r\n uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${productId}` })\r\n}\r\n\r\nconst goShopping = () => {\r\n uni.switchTab({ url: '/pages/main/index' })\r\n}\r\n\r\nconst shareForFree = async (order: OrderItem) => {\r\n if (order.products.length === 0) {\r\n uni.showToast({ title: '没有可分享的商品', icon: 'none' })\r\n return\r\n }\r\n \r\n const firstProduct = order.products[0]\r\n \r\n try {\r\n uni.showLoading({ title: '创建分享...' })\r\n const result = await supabaseService.createShareRecord(\r\n firstProduct.id,\r\n order.id,\r\n '',\r\n firstProduct.name,\r\n firstProduct.image,\r\n firstProduct.price\r\n )\r\n uni.hideLoading()\r\n \r\n const shareIdRaw = result.get('id')\r\n const shareCodeRaw = result.get('share_code')\r\n \r\n if (shareIdRaw != null && shareCodeRaw != null) {\r\n const shareId = shareIdRaw as string\r\n const shareCode = shareCodeRaw as string\r\n \r\n uni.showModal({\r\n title: '分享成功',\r\n content: `您的分享码: ${shareCode}\\n分享给好友当有4人购买后即可免单`,\r\n confirmText: '查看详情',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.navigateTo({ url: `/pages/mall/consumer/share/detail?id=${shareId}` })\r\n }\r\n }\r\n })\r\n } else {\r\n uni.showToast({ title: '分享创建失败', icon: 'none' })\r\n }\r\n } catch (e) {\r\n uni.hideLoading()\r\n console.error('[shareForFree] 创建分享失败:', e)\r\n uni.showToast({ title: '分享失败', icon: 'none' })\r\n }\r\n}\r\n</script>\r\n\r\n<style>\r\n.orders-page {\r\n display: flex;\r\n flex-direction: column;\r\n /* 采用相对于窗口的 100% 方案 */\r\n width: 100%;\r\n height: 100%;\r\n /* App/小程序通过 fixed 解决根容器撑满问题H5/电脑端建议使用 flex 1 */\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n background-color: #f5f5f5;\r\n overflow: hidden;\r\n}\r\n\r\n/* 头部:确保显式可见且不被遮挡 */\r\n.orders-header {\r\n background-color: #ffffff;\r\n padding: 10px 15px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n width: 100%;\r\n border-bottom: 1px solid #eeeeee;\r\n z-index: 999;\r\n position: relative;\r\n box-sizing: border-box;\r\n flex-shrink: 0;\r\n}\r\n\r\n.header-search.full-width {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n position: relative;\r\n width: 100%;\r\n flex: 1;\r\n}\r\n\r\n.search-input {\r\n flex: 1;\r\n height: 36px;\r\n line-height: 36px;\r\n border: 1px solid #dddddd;\r\n border-radius: 18px;\r\n padding: 0 40px 0 16px;\r\n font-size: 14px;\r\n background-color: #f5f5f5;\r\n color: #333333;\r\n width: 100%;\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.search-input::placeholder {\r\n color: #999;\r\n font-size: 12px;\r\n}\r\n.search-input:focus {\r\n border-color: #ff5000;\r\n background-color: white;\r\n}\r\n\r\n.search-icon {\r\n position: absolute;\r\n right: 12px;\r\n font-size: 18px;\r\n color: #999;\r\n}\r\n\r\n.search-clear {\r\n position: absolute;\r\n right: 12px;\r\n font-size: 20px;\r\n color: #999;\r\n width: 20px;\r\n height: 20px;\r\n line-height: 18px;\r\n text-align: center;\r\n border-radius: 10px; /* fixed 50% */\r\n background-color: #ddd;\r\n /* cursor: pointer; removed */\r\n}\r\n\r\n/* 标签页容器:确保在安卓下层级正确且不随内容滚动 */\r\n.order-tabs-fixed-container {\r\n background-color: #ffffff;\r\n border-bottom: 1px solid #f0f0f0;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n width: 100%;\r\n z-index: 100 !important;\r\n flex-shrink: 0;\r\n position: relative;\r\n}\r\n\r\n/* 安卓端滚动修复:关键容器 */\r\n.orders-content {\r\n flex: 1;\r\n width: 100%;\r\n /* 极致方案:不再设置 height: 0改用 flex: 1 填满,并显式设置 scroll-y 的表现 */\r\n min-height: 0;\r\n flex-shrink: 1;\r\n background-color: #f5f5f5;\r\n}\r\n\r\n/* 顶部内容区域必须显式不可伸缩,防止撑占滚动空间 */\r\n.orders-header, .order-tabs-fixed-container {\r\n flex-shrink: 0;\r\n width: 100%;\r\n}\r\n\r\n.tab-item-fixed {\r\n padding: 0 15px;\r\n text-align: center;\r\n position: relative;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n white-space: nowrap;\r\n flex-shrink: 0;\r\n min-width: 60px;\r\n height: 100%;\r\n}\r\n\r\n.tab-item-fixed.active {\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.tab-scroll-mobile {\r\n flex: 1;\r\n white-space: nowrap;\r\n /* 移除 width: 0改用更稳健的安卓适配方式 */\r\n display: flex;\r\n flex-direction: row;\r\n}\r\n\r\n.tab-container-mobile {\r\n display: flex;\r\n flex-direction: row;\r\n /* 加上这个确保容器能够横向撑开 */\r\n flex-wrap: nowrap;\r\n}\r\n\r\n.tab-item-mobile {\r\n padding: 15px 15px;\r\n text-align: center;\r\n position: relative;\r\n display: flex;\r\n flex-direction: row; /* 显式声明 */\r\n justify-content: center;\r\n align-items: center;\r\n white-space: nowrap;\r\n flex-shrink: 0; /* 绝对不能被压缩,否则无法滚动 */\r\n}\r\n\r\n.tab-item-mobile.active {\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.active-indicator {\r\n position: absolute;\r\n bottom: 0px;\r\n left: 10px;\r\n right: 10px;\r\n height: 3px;\r\n background-color: #ff5000;\r\n border-radius: 2px;\r\n}\r\n\r\n.tab-name {\r\n font-size: 14px;\r\n}\r\n\r\n.tab-count {\r\n margin-left: 4px;\r\n background-color: #ff5000;\r\n color: white;\r\n font-size: 10px;\r\n padding: 1px 4px;\r\n border-radius: 8px;\r\n min-width: 12px;\r\n text-align: center;\r\n}\r\n\r\n/* 内容区 */\r\n.orders-content {\r\n flex: 1;\r\n width: 100%;\r\n height: 0; /* 关键:强制让 flex:1 在安卓端生效,防止内容撑开父容器 */\r\n}\r\n\r\n/* 空状态 */\r\n.empty-orders {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 80px 20px;\r\n}\r\n\r\n.empty-icon {\r\n font-size: 80px;\r\n color: #ddd;\r\n margin-bottom: 20px;\r\n}\r\n\r\n.empty-title {\r\n font-size: 18px;\r\n color: #666;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.empty-desc {\r\n font-size: 14px;\r\n color: #999;\r\n margin-bottom: 30px;\r\n}\r\n\r\n.go-shopping-btn {\r\n background-color: #ff5000;\r\n color: white;\r\n border: none;\r\n border-radius: 25px;\r\n padding: 10px 40px;\r\n font-size: 16px;\r\n}\r\n\r\n/* 订单列表 */\r\n.order-list {\r\n padding: 10px;\r\n width: 100%;\r\n box-sizing: border-box;\r\n}\r\n\r\n.order-card {\r\n background-color: white;\r\n border-radius: 10px;\r\n margin-bottom: 10px;\r\n overflow: hidden;\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\r\n flex-shrink: 0;\r\n box-sizing: border-box;\r\n}\r\n\r\n/* 订单头部 */\r\n.order-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 15px;\r\n border-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.order-no {\r\n font-size: 14px;\r\n color: #666;\r\n}\r\n\r\n.order-status {\r\n font-size: 14px;\r\n font-weight: bold;\r\n}\r\n\r\n.status-pending {\r\n color: #ff5000;\r\n}\r\n\r\n.status-shipping {\r\n color: #ff9500;\r\n}\r\n\r\n.status-delivering {\r\n color: #007aff;\r\n}\r\n\r\n.status-completed {\r\n color: #34c759;\r\n}\r\n\r\n.status-cancelled {\r\n color: #999;\r\n}\r\n\r\n.status-refunding {\r\n color: #ff5000;\r\n}\r\n\r\n.status-refunded {\r\n color: #999;\r\n}\r\n\r\n/* 订单商品 */\r\n.order-products {\r\n padding: 15px;\r\n}\r\n\r\n.order-product {\r\n display: flex;\r\n flex-direction: row; /* 显式声明横向排列 */\r\n margin-bottom: 15px;\r\n width: 100%;\r\n}\r\n\r\n.order-product:last-child {\r\n margin-bottom: 0;\r\n}\r\n\r\n.product-image {\r\n width: 80px;\r\n height: 80px;\r\n border-radius: 8px;\r\n margin-right: 12px;\r\n flex-shrink: 0; /* 防止图片被压缩 */\r\n}\r\n\r\n.product-info {\r\n flex: 1; /* 占据右侧剩余所有空间 */\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n height: 80px; \r\n overflow: hidden; /* 防止文字溢出 */\r\n}\r\n\r\n.product-top-info {\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n}\r\n\r\n.product-name {\r\n font-size: 14px;\r\n color: #333;\r\n line-height: 1.4;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n lines: 2;\r\n}\r\n\r\n.product-spec {\r\n font-size: 12px;\r\n color: #999;\r\n margin-top: 2px;\r\n}\r\n\r\n.product-footer {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-top: auto;\r\n}\r\n\r\n.product-price {\r\n font-size: 16px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.product-quantity {\r\n font-size: 13px;\r\n color: #999;\r\n}\r\n\r\n/* 订单信息 */\r\n.order-info {\r\n padding: 15px;\r\n border-top: 1px solid #f5f5f5;\r\n border-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.info-row {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.info-row:last-child {\r\n margin-bottom: 0;\r\n}\r\n\r\n.info-row.total {\r\n margin-top: 8px;\r\n padding-top: 8px;\r\n border-top: 1px solid #f5f5f5;\r\n}\r\n\r\n.info-label {\r\n font-size: 14px;\r\n color: #666;\r\n}\r\n\r\n.info-value {\r\n font-size: 14px;\r\n color: #333;\r\n}\r\n\r\n.total-price {\r\n font-size: 18px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n/* 订单操作 */\r\n.order-actions {\r\n padding: 15px;\r\n}\r\n\r\n.action-buttons {\r\n display: flex;\r\n justify-content: flex-end;\r\n /* gap: 10px; removed */\r\n}\r\n\r\n.action-btn {\r\n padding: 6px 15px;\r\n border-radius: 15px;\r\n font-size: 13px;\r\n border: 1px solid;\r\n background-color: transparent; /* fixed background: none */\r\n margin-left: 10px; /* alternative to gap */\r\n}\r\n\r\n.action-btn.cancel {\r\n color: #666;\r\n border-color: #ccc;\r\n}\r\n\r\n.action-btn.pay {\r\n color: #ff5000;\r\n border-color: #ff5000;\r\n}\r\n\r\n.action-btn.remind {\r\n color: #666;\r\n border-color: #ccc;\r\n}\r\n\r\n.action-btn.view {\r\n color: #666;\r\n border-color: #ccc;\r\n}\r\n\r\n.action-btn.confirm {\r\n color: #34c759;\r\n border-color: #34c759;\r\n}\r\n\r\n.action-btn.refund {\r\n color: #666;\r\n border-color: #ccc;\r\n}\r\n\r\n.action-btn.cancel {\r\n color: #ff6b6b;\r\n border-color: #ff6b6b;\r\n}\r\n\r\n.action-btn.review {\r\n color: #ff9500;\r\n border-color: #ff9500;\r\n}\r\n\r\n.action-btn.repurchase {\r\n color: #ff5000;\r\n border-color: #ff5000;\r\n}\r\n\r\n/* 加载更多 */\r\n.loading-more {\r\n padding: 20px;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.loading-spinner {\r\n width: 24px;\r\n height: 24px;\r\n border: 2px solid #f0f5ff;\r\n border-top-color: #ff5000;\r\n border-radius: 12px;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.no-more {\r\n text-align: center;\r\n color: #999;\r\n font-size: 13px;\r\n padding: 20px 0;\r\n}\r\n\r\n/* 安全区域 */\r\n.safe-area {\r\n height: 20px;\r\n}\r\n\r\n/* 底部导航占位 */\r\n.tabbar-placeholder {\r\n height: 50px;\r\n background-color: #f5f5f5;\r\n}\r\n\r\n/* 响应式适配 */\r\n@media screen and (min-width: 768px) {\r\n .order-list {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n width: 100%;\r\n max-width: 1200px;\r\n margin: 0 auto;\r\n justify-content: flex-start;\r\n align-content: flex-start;\r\n box-sizing: border-box;\r\n }\r\n \r\n .order-card {\r\n width: 48%;\r\n margin: 0 1% 20px 1%;\r\n box-shadow: 0 4px 12px rgba(0,0,0,0.05);\r\n flex: none;\r\n box-sizing: border-box;\r\n }\r\n}\r\n\r\n@media screen and (min-width: 1200px) {\r\n .order-card {\r\n width: 31%;\r\n margin: 0 1% 20px 1%;\r\n flex: none;\r\n box-sizing: border-box;\r\n }\r\n}\r\n\r\n/* 订单卡片新样式 */\r\n.order-card-header {\r\n padding: 12px 15px;\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n border-bottom: 1px solid #f9f9f9;\r\n}\r\n\r\n.status-row {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.more-btn {\r\n font-size: 18px;\r\n color: #999;\r\n margin-left: 8px;\r\n padding: 0 5px;\r\n}\r\n\r\n.shop-info {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.shop-icon {\r\n font-size: 16px;\r\n margin-right: 6px;\r\n}\r\n\r\n.shop-name {\r\n font-size: 14px;\r\n font-weight: bold;\r\n color: #333;\r\n max-width: 150px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n lines: 1;\r\n}\r\n\r\n.arrow-right {\r\n font-size: 14px;\r\n color: #ccc;\r\n margin-left: 4px;\r\n}\r\n\r\n.product-title-row, .product-spec-row {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: flex-start;\r\n}\r\n\r\n.product-title-row {\r\n margin-bottom: 4px;\r\n}\r\n\r\n.product-spec-row {\r\n margin-top: 2px;\r\n}\r\n\r\n.order-summary {\r\n padding: 10px 15px;\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n border-top: 1px solid #f9f9f9;\r\n}\r\n\r\n.order-time {\r\n font-size: 12px;\r\n color: #999;\r\n}\r\n\r\n.summary-right {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.summary-label {\r\n font-size: 12px;\r\n color: #666;\r\n margin-right: 5px;\r\n}\r\n\r\n.summary-price {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n\r\n/* 分享免单入口样式 */\r\n.share-free-row {\r\n margin: 0 15px;\r\n padding: 12px 15px;\r\n background: linear-gradient(135deg, #fff5f0 0%, #ffecd2 100%);\r\n border-radius: 8px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.share-free-icon {\r\n font-size: 20px;\r\n margin-right: 8px;\r\n}\r\n\r\n.share-free-text {\r\n font-size: 14px;\r\n font-weight: bold;\r\n color: #ff6b35;\r\n margin-right: 8px;\r\n}\r\n\r\n.share-free-tip {\r\n flex: 1;\r\n font-size: 12px;\r\n color: #ff8c42;\r\n}\r\n\r\n.share-free-arrow {\r\n font-size: 16px;\r\n color: #ff8c42;\r\n}\r\n\r\n@media screen and (max-width: 320px) {\r\n .tab-item {\r\n padding: 0 10px;\r\n margin-right: 5px;\r\n }\r\n \r\n .action-btn {\r\n padding: 6px 10px;\r\n font-size: 12px;\r\n }\r\n}\r\n\r\n@media screen and (min-width: 415px) {\r\n .order-card {\r\n margin-bottom: 15px;\r\n }\r\n}\r\n</style>\r\n\r\n","<!-- 消费者端 - 订单详情页 -->\r\n<template>\r\n <view class=\"order-detail-page\">\r\n <scroll-view scroll-y=\"true\" class=\"scroll-content\">\r\n <!-- 订单状态 -->\r\n <view class=\"order-status\" :class=\"getStatusClass()\">\r\n <view class=\"status-content\">\r\n <view class=\"status-info\">\r\n <view class=\"status-title-row\">\r\n <text class=\"status-emoji\">{{ getStatusIcon() }}</text>\r\n <text class=\"status-text\">{{ getStatusText() }}</text>\r\n </view>\r\n <text class=\"status-desc\">{{ getStatusDesc() }}</text>\r\n </view>\r\n <!-- 分享免单入口 -->\r\n <view v-if=\"order?.order_status === 4\" class=\"share-free-entry\" @click=\"shareForFree\">\r\n <text class=\"share-free-icon\">🎁</text>\r\n <view class=\"share-free-info\">\r\n <text class=\"share-free-title\">分享免单</text>\r\n <text class=\"share-free-desc\">分享给好友4人购买即可免单</text>\r\n </view>\r\n <text class=\"share-free-arrow\"></text>\r\n </view>\r\n </view>\r\n </view>\r\n\r\n <!-- 配送信息 -->\r\n <view v-if=\"order != null && (order?.order_status ?? 0) >= 2\" class=\"delivery-info card\">\r\n <view class=\"delivery-header\">\r\n <text class=\"section-title\">配送信息</text>\r\n </view>\r\n <view class=\"delivery-info-content\">\r\n <view class=\"delivery-address\">\r\n <view class=\"address-icon\">📍</view>\r\n <view class=\"address-content\">\r\n <view class=\"address-user\">\r\n <text class=\"recipient\">{{ deliveryAddress?.name ?? '' }}</text>\r\n <text class=\"phone\">{{ deliveryAddress?.phone ?? '' }}</text>\r\n </view>\r\n <text class=\"address-detail\">{{ getFullAddress(deliveryAddress as any) }}</text>\r\n </view>\r\n </view>\r\n <!-- 如果有物流信息显示 -->\r\n <view v-if=\"deliveryInfo != null && deliveryInfo?.tracking_no != ''\" class=\"courier-info\">\r\n <view class=\"courier-icon\">🚚</view>\r\n <view class=\"courier-content\">\r\n <text class=\"courier-label\">物流信息</text>\r\n <view class=\"tracking-row\">\r\n <text class=\"carrier-name\">{{ deliveryInfo?.carrier_name ?? '快递运单' }}</text>\r\n <text class=\"tracking-no\">{{ deliveryInfo?.tracking_no ?? '' }}</text>\r\n <view class=\"copy-tag\" @click=\"copyText(deliveryInfo?.tracking_no ?? '')\">\r\n <text class=\"copy-tag-text\">复制</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n\r\n <!-- 商品信息 -->\r\n <view class=\"order-products card\">\r\n <view class=\"shop-header\" @click=\"goToShop\">\r\n <text class=\"shop-icon\">🏪</text>\r\n <text class=\"shop-name\">{{ shopName }}</text>\r\n <text class=\"arrow-right\"></text>\r\n </view>\r\n <view v-for=\"item in orderItems\" :key=\"item.id\" class=\"product-item\" @click=\"goToProduct(item.product_id)\">\r\n <image :src=\"item.image_url != null && item.image_url != '' ? item.image_url : '/static/default-product.png'\" class=\"product-image\" mode=\"aspectFill\"/>\r\n <view class=\"product-info\">\r\n <text class=\"product-name\">{{ item.product_name }}</text>\r\n <text v-if=\"item.specifications\" class=\"product-spec\">{{ getSpecText(item.specifications) }}</text>\r\n <view class=\"price-quantity\">\r\n <text class=\"product-price\">¥{{ item.price }}</text>\r\n <text class=\"product-quantity\">×{{ item.quantity }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n\r\n <!-- 订单信息 -->\r\n <view class=\"order-info card\" v-if=\"order != null\">\r\n <view class=\"info-row\">\r\n <text class=\"info-label\">订单编号</text>\r\n <text class=\"info-value copyable\" @click=\"copyText(order?.order_no ?? '')\">{{ order?.order_no ?? '' }} <text class=\"copy-icon\">📄</text></text>\r\n </view>\r\n <view class=\"info-row\">\r\n <text class=\"info-label\">下单时间</text>\r\n <text class=\"info-value\">{{ formatTime(order?.created_at ?? '') }}</text>\r\n </view>\r\n <view class=\"info-row\" v-if=\"order?.payment_method != null && order?.payment_method != ''\">\r\n <text class=\"info-label\">支付方式</text>\r\n <text class=\"info-value\">{{ getPaymentMethodText(order?.payment_method as any) }}</text>\r\n </view>\r\n <view class=\"info-row\" v-if=\"order?.paid_at != null && order?.paid_at != ''\">\r\n <text class=\"info-label\">支付时间</text>\r\n <text class=\"info-value\">{{ formatTime(order?.paid_at ?? '') }}</text>\r\n </view>\r\n <view class=\"info-row\" v-if=\"order?.shipped_at != null && order?.shipped_at != ''\">\r\n <text class=\"info-label\">发货时间</text>\r\n <text class=\"info-value\">{{ formatTime(order?.shipped_at ?? '') }}</text>\r\n </view>\r\n <view class=\"info-row\" v-if=\"order?.completed_at != null && order?.completed_at != ''\">\r\n <text class=\"info-label\">完成时间</text>\r\n <text class=\"info-value\">{{ formatTime(order?.completed_at ?? '') }}</text>\r\n </view>\r\n </view>\r\n\r\n <!-- 费用明细 -->\r\n <view class=\"cost-detail card\" v-if=\"order != null\">\r\n <view class=\"cost-row\">\r\n <text class=\"cost-label\">商品总额</text>\r\n <text class=\"cost-value\">¥{{ order?.product_amount ?? 0 }}</text>\r\n </view>\r\n <view class=\"cost-row\">\r\n <text class=\"cost-label\">运费</text>\r\n <text class=\"cost-value\">+¥{{ order?.shipping_fee != null ? order?.shipping_fee : 0 }}</text>\r\n </view>\r\n <view class=\"cost-row\" v-if=\"(order?.discount_amount ?? 0) > 0\">\r\n <text class=\"cost-label\">优惠金额</text>\r\n <text class=\"cost-value\">-¥{{ order?.discount_amount ?? 0 }}</text>\r\n </view>\r\n <view class=\"cost-row total\">\r\n <text class=\"cost-label\">实付金额</text>\r\n <text class=\"cost-value price\">¥{{ order?.total_amount ?? 0 }}</text>\r\n </view>\r\n </view>\r\n </scroll-view>\r\n\r\n <view class=\"bottom-actions\" v-if=\"order != null\">\r\n <view class=\"action-bar-wrapper\">\r\n <view class=\"action-left\" @click=\"contactService\">\r\n <view class=\"service-item\">\r\n <text class=\"service-icon\">🎧</text>\r\n <text class=\"service-label\">客服</text>\r\n </view>\r\n </view>\r\n <view class=\"action-right\">\r\n <view v-if=\"order?.order_status === 1\" class=\"btn-group\">\r\n <button class=\"btn\" @click=\"cancelOrder\">取消订单</button>\r\n <button class=\"btn primary\" @click=\"payOrder\">立即支付</button>\r\n </view>\r\n \r\n <view v-if=\"order?.order_status === 2\" class=\"btn-group\">\r\n <button class=\"btn\" @click=\"applyRefund\">申请退款</button>\r\n <button class=\"btn primary\" @click=\"remindDelivery\">提醒发货</button>\r\n </view>\r\n \r\n <view v-if=\"order?.order_status === 3\" class=\"btn-group\">\r\n <button class=\"btn\" @click=\"viewLogistics\">查看物流</button>\r\n <button class=\"btn primary\" @click=\"confirmReceive\">确认收货</button>\r\n </view>\r\n \r\n <view v-if=\"order?.order_status === 4\" class=\"btn-group\">\r\n <button class=\"btn\" @click=\"applyAfterSales\">申请售后</button>\r\n <button class=\"btn share-free\" @click=\"shareForFree\">分享免单</button>\r\n <button class=\"btn\" @click=\"rePurchase\">再次购买</button>\r\n <button class=\"btn primary\" @click=\"goToReview\">评价订单</button>\r\n </view>\r\n \r\n <view v-if=\"order?.order_status === 5\" class=\"btn-group\">\r\n <button class=\"btn primary\" @click=\"rePurchase\">重新购买</button>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted, computed } from 'vue'\r\nimport { onLoad, onBackPress } from '@dcloudio/uni-app'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\nimport supa from '@/components/supadb/aksupainstance.uts'\r\n\r\n// 定义订单类型\r\ntype OrderType = {\r\n\torder_no: string,\r\n\torder_status: number,\r\n\ttotal_amount: number,\r\n\tproduct_amount: number,\r\n\tshipping_fee: number,\r\n\tdiscount_amount: number,\r\n\tpayment_method: string,\r\n\tcreated_at: string,\r\n\tpaid_at: string,\r\n\tshipped_at: string,\r\n\tcompleted_at: string,\r\n\tmerchant_id: string,\r\n\tshipping_address: any | null\r\n}\r\n\r\ntype OrderItemType = {\r\n\tid: string,\r\n\tproduct_id: string,\r\n\tproduct_name: string,\r\n\timage_url: string,\r\n\tprice: number,\r\n\tquantity: number,\r\n\tspecifications: any\r\n}\r\n\r\ntype AddressType = {\r\n\tname: string,\r\n\tphone: string,\r\n\tprovince: string,\r\n\tcity: string,\r\n\tdistrict: string,\r\n\tdetail: string,\r\n\taddress: string\r\n}\r\n\r\ntype DeliveryInfoType = {\r\n\ttracking_no: string,\r\n\tcarrier_name: string\r\n}\r\n\r\nconst orderId = ref('')\r\nconst order = ref<OrderType | null>(null)\r\nconst orderItems = ref<OrderItemType[]>([])\r\nconst shopName = ref('店铺名称')\r\nconst deliveryAddress = ref<AddressType | null>(null)\r\nconst deliveryInfo = ref<DeliveryInfoType | null>(null)\r\n\r\n// 辅助函数 - 必须在调用前定义\r\nconst getStatusText = (): string => {\r\n const status = order.value?.order_status ?? 0\r\n if (status == 1) return '待付款'\r\n if (status == 2) return '待发货'\r\n if (status == 3) return '待收货'\r\n if (status == 4) return '已完成'\r\n if (status == 5) return '已取消'\r\n if (status == 6) return '退款中'\r\n if (status == 7) return '已退款'\r\n return '未知状态'\r\n}\r\n\r\nconst getStatusDesc = (): string => {\r\n const status = order.value?.order_status ?? 0\r\n if (status == 1) return '请尽快完成支付'\r\n if (status == 2) return '商家正在打包商品'\r\n if (status == 3) return '商品正在赶往您的地址'\r\n if (status == 4) return '订单已完成,感谢支持'\r\n if (status == 5) return '订单已取消'\r\n if (status == 6) return '售后处理中'\r\n if (status == 7) return '钱款已退回'\r\n return ''\r\n}\r\n\r\nconst getStatusIcon = (): string => {\r\n const status = order.value?.order_status ?? 0\r\n if (status === 1) return '💳'\r\n if (status === 2) return '📦'\r\n if (status === 3) return '🚚'\r\n if (status === 4) return '✅'\r\n return '📝'\r\n}\r\n\r\nconst getStatusClass = (): string => {\r\n const status = order.value?.order_status ?? 0\r\n return `status-${status}`\r\n}\r\n\r\nconst getFullAddress = (addr: any): string => {\r\n if (addr == null) return ''\r\n if (typeof addr === 'string') return addr\r\n \r\n try {\r\n const addrObj = JSON.parse(JSON.stringify(addr)) as UTSJSONObject\r\n const addressField = addrObj.getString('address')\r\n if (addressField != null && addressField != '') return addressField\r\n \r\n const province = addrObj.getString('province') ?? ''\r\n const city = addrObj.getString('city') ?? ''\r\n const district = addrObj.getString('district') ?? ''\r\n const detail = addrObj.getString('detail') ?? addrObj.getString('address_detail') ?? ''\r\n return province + city + district + detail\r\n } catch (e) {\r\n console.error('[getFullAddress] 解析地址失败:', e)\r\n return ''\r\n }\r\n}\r\n\r\nfunction formatSpecs(specs: any): string {\r\n if (specs == null) return ''\r\n if (typeof specs === 'string') {\r\n if (specs == '') return ''\r\n try {\r\n const parsed = JSON.parse(specs as string)\r\n if (parsed != null) {\r\n return formatSpecs(parsed)\r\n }\r\n return specs as string\r\n } catch (e) {\r\n return specs as string\r\n }\r\n }\r\n \r\n try {\r\n const specStr = JSON.stringify(specs)\r\n const specObj = JSON.parse(specStr) as UTSJSONObject\r\n \r\n // 定义常见的键名\r\n const keys = ['Color', 'Size', '颜色', '尺寸', '规格', '默认', 'spec', 'color', 'size']\r\n const parts : string[] = []\r\n \r\n // 尝试提取键值\r\n for (let i = 0; i < keys.length; i++) {\r\n const key = keys[i]\r\n const val = specObj.get(key)\r\n if (val != null && val != '') {\r\n parts.push(val.toString())\r\n }\r\n }\r\n \r\n // 如果提取到了内容\r\n if (parts.length > 0) {\r\n return parts.join(' | ')\r\n }\r\n \r\n // 如果没有提取到已知键,则进行通用清理\r\n return specStr.replace(/[{}\"]/g, '').replace(/:/g, ': ').replace(/,/g, ' | ')\r\n } catch (e) {\r\n return ''\r\n }\r\n}\r\n\r\nconst getSpecText = (specs: any): string => {\r\n return formatSpecs(specs)\r\n}\r\n\r\nconst formatTime = (iso: string): string => {\r\n if (iso == '') return ''\r\n const d = new Date(iso)\r\n return `${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()} ${d.getHours()}:${d.getMinutes()}`\r\n}\r\n\r\nconst getPaymentMethodText = (method: any): string => {\r\n return '在线支付'\r\n}\r\n\r\nconst copyText = (text: string) => {\r\n if(text == '') return\r\n uni.setClipboardData({\r\n data: text,\r\n success: () => uni.showToast({ title: '已复制' })\r\n })\r\n}\r\n\r\nconst loadShopInfo = async (merchantId: string) => {\r\n try {\r\n const result = await supa\r\n .from('ml_shops')\r\n .select('shop_name')\r\n .eq('merchant_id', merchantId)\r\n .limit(1)\r\n .execute()\r\n \r\n if (result.error != null) {\r\n console.error('[loadShopInfo] 获取店铺信息失败:', result.error)\r\n return\r\n }\r\n \r\n const rawData = result.data\r\n if (rawData == null) return\r\n \r\n const rawList = rawData as any[]\r\n if (rawList.length == 0) return\r\n \r\n const shopData = rawList[0]\r\n const shopObj = JSON.parse(JSON.stringify(shopData)) as UTSJSONObject\r\n shopName.value = (shopObj.getString('shop_name') ?? '店铺') as string\r\n } catch (e) {\r\n console.error('[loadShopInfo] 获取店铺信息异常:', e)\r\n }\r\n}\r\n\r\nconst loadOrderDetail = async () => {\r\n uni.showLoading({ title: '加载中' })\r\n try {\r\n const data = await supabaseService.getOrderDetail(orderId.value)\r\n console.log('[loadOrderDetail] 获取到的数据:', JSON.stringify(data))\r\n \r\n if (data != null) {\r\n const dataObj = data as UTSJSONObject\r\n \r\n order.value = {\r\n order_no: (dataObj.get('order_no') ?? '') as string,\r\n order_status: (dataObj.get('order_status') ?? 1) as number,\r\n total_amount: (dataObj.get('total_amount') ?? 0) as number,\r\n product_amount: (dataObj.get('product_amount') ?? 0) as number,\r\n shipping_fee: (dataObj.get('shipping_fee') ?? 0) as number,\r\n discount_amount: (dataObj.get('discount_amount') ?? 0) as number,\r\n payment_method: (dataObj.get('payment_method') ?? '') as string,\r\n created_at: (dataObj.get('created_at') ?? '') as string,\r\n paid_at: (dataObj.get('paid_at') ?? '') as string,\r\n shipped_at: (dataObj.get('shipped_at') ?? '') as string,\r\n completed_at: (dataObj.get('completed_at') ?? '') as string,\r\n merchant_id: (dataObj.get('merchant_id') ?? '') as string,\r\n shipping_address: (dataObj.get('shipping_address') ?? null) as any\r\n } as OrderType\r\n \r\n const itemsRaw = dataObj.get('ml_order_items')\r\n console.log('[loadOrderDetail] 订单商品数据:', itemsRaw)\r\n \r\n if (itemsRaw != null && Array.isArray(itemsRaw)) {\r\n const items = itemsRaw as any[]\r\n orderItems.value = []\r\n for (let i = 0; i < items.length; i++) {\r\n const item = items[i]\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n \r\n const orderItem: OrderItemType = {\r\n id: (itemObj.get('id') ?? '') as string,\r\n product_id: (itemObj.get('product_id') ?? '') as string,\r\n product_name: (itemObj.get('product_name') ?? '未知商品') as string,\r\n price: (itemObj.get('price') ?? 0) as number,\r\n quantity: (itemObj.get('quantity') ?? 1) as number,\r\n image_url: (itemObj.get('image_url') ?? '') as string,\r\n specifications: (itemObj.get('specifications') ?? '') as any\r\n }\r\n orderItems.value.push(orderItem)\r\n }\r\n }\r\n \r\n const addressRaw = dataObj.get('shipping_address')\r\n console.log('[loadOrderDetail] 收货地址数据:', addressRaw)\r\n \r\n if (addressRaw != null) {\r\n let addressObj: UTSJSONObject\r\n if (addressRaw instanceof UTSJSONObject) {\r\n addressObj = addressRaw as UTSJSONObject\r\n } else if (typeof addressRaw === 'string') {\r\n addressObj = JSON.parse(addressRaw as string) as UTSJSONObject\r\n } else {\r\n addressObj = JSON.parse(JSON.stringify(addressRaw)) as UTSJSONObject\r\n }\r\n \r\n const province = (addressObj.get('province') ?? '') as string\r\n const city = (addressObj.get('city') ?? '') as string\r\n const district = (addressObj.get('district') ?? '') as string\r\n const detail = (addressObj.get('detail') ?? (addressObj.get('address_detail') ?? '')) as string\r\n \r\n deliveryAddress.value = {\r\n name: (addressObj.get('name') ?? (addressObj.get('recipient_name') ?? (addressObj.get('receiver_name') ?? ''))) as string,\r\n phone: (addressObj.get('phone') ?? (addressObj.get('recipient_phone') ?? (addressObj.get('receiver_phone') ?? ''))) as string,\r\n province: province,\r\n city: city,\r\n district: district,\r\n detail: detail,\r\n address: province + city + district + detail\r\n } as AddressType\r\n }\r\n \r\n const merchantId = (dataObj.get('merchant_id') ?? '') as string\r\n if (merchantId != '') {\r\n loadShopInfo(merchantId)\r\n }\r\n \r\n // 加载物流信息\r\n const trackingNoVal = dataObj.getString('tracking_no')\r\n const carrierNameVal = dataObj.getString('carrier_name')\r\n if (trackingNoVal != null && trackingNoVal != '') {\r\n deliveryInfo.value = {\r\n tracking_no: trackingNoVal,\r\n carrier_name: carrierNameVal ?? ''\r\n } as DeliveryInfoType\r\n }\r\n \r\n console.log('[loadOrderDetail] 订单详情加载成功,商品数量:', orderItems.value.length)\r\n } else {\r\n uni.showToast({ title: '订单不存在', icon: 'none' })\r\n }\r\n } catch (e) {\r\n console.error('[loadOrderDetail] 加载订单详情失败:', e)\r\n uni.showToast({ title: '加载失败', icon: 'none' })\r\n } finally {\r\n uni.hideLoading()\r\n }\r\n}\r\n\r\n// 动作函数\r\nconst contactService = () => {\r\n if (order.value != null && order.value?.merchant_id != '') {\r\n // 跳转到商家的聊天窗口\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/chat?merchantId=${order.value?.merchant_id}&merchantName=${encodeURIComponent(shopName.value)}`\r\n })\r\n } else {\r\n uni.showActionSheet({\r\n itemList: ['在线客服', '拨打电话'],\r\n success: (res) => {\r\n if (res.tapIndex === 1) {\r\n // 模拟拨打电话\r\n uni.makePhoneCall({ phoneNumber: '400-123-4567' })\r\n } else {\r\n uni.showToast({ title: '连接到了系统客服' })\r\n }\r\n }\r\n })\r\n }\r\n}\r\n\r\nconst payOrder = () => {\r\n const totalAmount = order.value?.total_amount ?? 0\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`\r\n })\r\n}\r\n\r\nconst doCancelOrder = async () => {\r\n try {\r\n const updatePayload = new UTSJSONObject()\r\n updatePayload.set('order_status', 5)\r\n updatePayload.set('updated_at', new Date().toISOString())\r\n \r\n const result = await supa\r\n .from('ml_orders')\r\n .update(updatePayload)\r\n .eq('id', orderId.value)\r\n .execute()\r\n \r\n if (result.error == null) {\r\n if (order.value != null) {\r\n order.value.order_status = 5\r\n }\r\n uni.showToast({ title: '订单已取消' })\r\n } else {\r\n console.error('[doCancelOrder] 取消订单失败:', result.error)\r\n uni.showToast({ title: '取消失败', icon: 'none' })\r\n }\r\n } catch (e) {\r\n console.error('[doCancelOrder] 取消订单异常:', e)\r\n uni.showToast({ title: '取消失败', icon: 'none' })\r\n }\r\n}\r\n\r\nconst cancelOrder = () => {\r\n uni.showModal({\r\n title: '提示',\r\n content: '确定要取消订单吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n doCancelOrder()\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst remindDelivery = async () => {\r\n const merchantId = order.value?.merchant_id\r\n if (merchantId == null || merchantId == '') {\r\n uni.showToast({ title: '商家信息不存在', icon: 'none' })\r\n return\r\n }\r\n \r\n const orderNo = order.value?.order_no ?? ''\r\n const message = `您好,订单 ${orderNo} 已付款,请尽快安排发货,谢谢!`\r\n \r\n uni.showLoading({ title: '发送中...' })\r\n const success = await supabaseService.sendChatMessage(message, merchantId, 'text')\r\n uni.hideLoading()\r\n \r\n if (success) {\r\n uni.showToast({ title: '已提醒商家尽快发货' })\r\n } else {\r\n uni.showToast({ title: '发送失败,请稍后重试', icon: 'none' })\r\n }\r\n}\r\n\r\nconst viewLogistics = () => {\r\n uni.navigateTo({ url: `/pages/mall/consumer/logistics?orderId=${orderId.value}` })\r\n}\r\n\r\nconst goToReview = () => {\r\n uni.navigateTo({ url: `/pages/mall/consumer/review?orderId=${orderId.value}` })\r\n}\r\n\r\nconst doConfirmReceive = async () => {\r\n try {\r\n const result = await supabaseService.confirmReceipt(orderId.value)\r\n if (result.success) {\r\n if (order.value != null) {\r\n order.value.order_status = 4\r\n }\r\n uni.showToast({ title: '收货成功' })\r\n setTimeout(() => goToReview(), 1500)\r\n } else {\r\n uni.showToast({ title: result.error ?? '失败', icon: 'none' })\r\n }\r\n } catch (e) {\r\n console.error('[doConfirmReceive] 确认收货异常:', e)\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n }\r\n}\r\n\r\nconst confirmReceive = () => {\r\n uni.showModal({\r\n title: '确认收货',\r\n content: '确保您已收到货物',\r\n success: (res) => {\r\n if (res.confirm) {\r\n doConfirmReceive()\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst rePurchase = async () => {\r\n uni.showLoading({ title: '处理中' })\r\n try {\r\n const items = orderItems.value\r\n if (items.length == 0) {\r\n uni.hideLoading()\r\n uni.showToast({ title: '没有可购买的商品', icon: 'none' })\r\n return\r\n }\r\n \r\n let successCount = 0\r\n for (let i = 0; i < items.length; i++) {\r\n const item = items[i]\r\n const result = await supabaseService.addToCart(\r\n item.product_id,\r\n item.quantity,\r\n '',\r\n order.value?.merchant_id ?? ''\r\n )\r\n if (result) successCount++\r\n }\r\n \r\n uni.hideLoading()\r\n \r\n if (successCount > 0) {\r\n uni.showToast({ title: '已加入购物车' })\r\n setTimeout(() => {\r\n uni.switchTab({ url: '/pages/main/cart' })\r\n }, 1000)\r\n } else {\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n }\r\n } catch (e) {\r\n uni.hideLoading()\r\n console.error('[rePurchase] 再次购买异常:', e)\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n }\r\n}\r\n\r\nconst doApplyRefund = async (reason: string) => {\r\n try {\r\n const success = await supabaseService.applyRefund(orderId.value, reason)\r\n if (success) {\r\n if (order.value != null) {\r\n order.value.order_status = 6\r\n }\r\n uni.showToast({ title: '申请已提交' })\r\n } else {\r\n uni.showToast({ title: '提交失败', icon: 'none' })\r\n }\r\n } catch (e) {\r\n console.error('[doApplyRefund] 申请退款异常:', e)\r\n uni.showToast({ title: '提交失败', icon: 'none' })\r\n }\r\n}\r\n\r\nconst applyRefund = () => {\r\n uni.showModal({\r\n title: '申请退款',\r\n editable: true,\r\n placeholderText: '请输入退款原因',\r\n success: (res) => {\r\n if (res.confirm) {\r\n const reason = res.content ?? '用户主动申请'\r\n doApplyRefund(reason)\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst applyAfterSales = () => {\r\n // 售后逻辑类似退款,或者是跳转到专门的售后单页面\r\n applyRefund()\r\n}\r\n\r\nconst goToShop = () => {\r\n // 跳转到店铺详情\r\n const merchantId = order.value?.merchant_id ?? ''\r\n if (merchantId != '') {\r\n uni.navigateTo({ \r\n url: `/pages/mall/consumer/shop-detail?id=${merchantId}` \r\n })\r\n } else {\r\n uni.showToast({ title: '商家信息不存在', icon: 'none' })\r\n }\r\n}\r\n\r\nconst goToProduct = (pid: string) => {\r\n uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${pid}` })\r\n}\r\n\r\nconst shareForFree = async () => {\r\n if (orderItems.value.length === 0) {\r\n uni.showToast({ title: '没有可分享的商品', icon: 'none' })\r\n return\r\n }\r\n \r\n const firstItem = orderItems.value[0]\r\n \r\n try {\r\n uni.showLoading({ title: '创建分享...' })\r\n const result = await supabaseService.createShareRecord(\r\n firstItem.product_id,\r\n orderId.value,\r\n firstItem.id,\r\n firstItem.product_name,\r\n firstItem.image_url,\r\n firstItem.price\r\n )\r\n uni.hideLoading()\r\n \r\n const shareIdRaw = result.get('id')\r\n const shareCodeRaw = result.get('share_code')\r\n \r\n if (shareIdRaw != null && shareCodeRaw != null) {\r\n const shareId = shareIdRaw as string\r\n const shareCode = shareCodeRaw as string\r\n \r\n uni.showModal({\r\n title: '分享成功',\r\n content: `您的分享码: ${shareCode}\\n分享给好友当有4人购买后即可免单`,\r\n confirmText: '查看详情',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.navigateTo({ url: `/pages/mall/consumer/share/detail?id=${shareId}` })\r\n }\r\n }\r\n })\r\n } else {\r\n uni.showToast({ title: '分享创建失败', icon: 'none' })\r\n }\r\n } catch (e) {\r\n uni.hideLoading()\r\n console.error('[shareForFree] 创建分享失败:', e)\r\n uni.showToast({ title: '分享失败', icon: 'none' })\r\n }\r\n}\r\n\r\n// 使用 onBackPress 拦截物理返回键和系统导航栏返回\r\nonBackPress((_): boolean => {\r\n const pages = getCurrentPages()\r\n console.log('[order-detail onBackPress] pages count:', pages.length)\r\n \r\n if (pages.length > 1) {\r\n // 正常返回上一页\r\n return false\r\n }\r\n \r\n // 如果只有当前页面,跳转到 orders\r\n uni.redirectTo({ url: '/pages/mall/consumer/orders' })\r\n return true\r\n})\r\n\r\n// 生命周期 - 在所有函数定义之后\r\nonLoad((options) => {\r\n const id = options['id']\r\n const orderIdParam = options['orderId']\r\n if (id != null && id != '') {\r\n orderId.value = id as string\r\n loadOrderDetail()\r\n } else if (orderIdParam != null && orderIdParam != '') {\r\n orderId.value = orderIdParam as string\r\n loadOrderDetail()\r\n }\r\n})\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.order-detail-page {\r\n display: flex;\r\n flex-direction: column;\r\n flex: 1;\r\n background-color: #f5f5f5;\r\n}\r\n\r\n.scroll-content {\r\n flex: 1;\r\n padding-bottom: 20px;\r\n}\r\n\r\n.card {\r\n background-color: #ffffff;\r\n margin: 10px;\r\n padding: 15px;\r\n border-radius: 10px;\r\n}\r\n\r\n/* 状态栏 */\r\n.order-status {\r\n background: linear-gradient(135deg, #ff9000, #ff5000);\r\n padding: 30px 20px;\r\n color: white;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center; /* 手机端默认居中 */\r\n}\r\n\r\n.status-content {\r\n max-width: 1200px;\r\n margin: 0 auto;\r\n width: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center; /* 确保内容居中 */\r\n text-align: center; /* 文字居中 */\r\n}\r\n\r\n.status-info {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n}\r\n\r\n.status-title-row {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: center; /* 标题行居中 */\r\n margin-bottom: 8px;\r\n}\r\n\r\n.status-emoji {\r\n font-size: 28px;\r\n margin-right: 12px;\r\n}\r\n\r\n.status-text {\r\n font-size: 20px;\r\n font-weight: bold;\r\n letter-spacing: 1px;\r\n}\r\n\r\n.status-desc {\r\n font-size: 14px;\r\n opacity: 0.95;\r\n text-align: center;\r\n}\r\n\r\n/* 分享免单入口 */\r\n.share-free-entry {\r\n margin-top: 20px;\r\n background-color: rgba(255, 255, 255, 0.2);\r\n border-radius: 12px;\r\n padding: 14px 16px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n width: 100%;\r\n max-width: 400px;\r\n}\r\n\r\n.share-free-icon {\r\n font-size: 28px;\r\n margin-right: 12px;\r\n}\r\n\r\n.share-free-info {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: flex-start;\r\n}\r\n\r\n.share-free-title {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: white;\r\n margin-bottom: 4px;\r\n}\r\n\r\n.share-free-desc {\r\n font-size: 12px;\r\n color: rgba(255, 255, 255, 0.85);\r\n}\r\n\r\n.share-free-arrow {\r\n font-size: 20px;\r\n color: rgba(255, 255, 255, 0.8);\r\n}\r\n\r\n/* 配送信息 */\r\n.section-title {\r\n font-weight: bold;\r\n font-size: 16px;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.delivery-address {\r\n display: flex;\r\n align-items: flex-start;\r\n}\r\n\r\n.address-icon {\r\n font-size: 20px;\r\n margin-right: 10px;\r\n color: #666;\r\n}\r\n\r\n.address-user {\r\n margin-bottom: 5px;\r\n font-weight: bold;\r\n font-size: 14px;\r\n}\r\n\r\n.phone {\r\n margin-left: 10px;\r\n color: #666;\r\n font-weight: normal;\r\n}\r\n\r\n.address-detail {\r\n font-size: 13px;\r\n color: #333;\r\n line-height: 1.4;\r\n}\r\n\r\n.courier-info {\r\n margin-top: 15px;\r\n padding-top: 15px;\r\n border-top: 1px solid #f5f5f5;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: flex-start;\r\n}\r\n\r\n.courier-icon {\r\n font-size: 20px;\r\n margin-right: 10px;\r\n color: #666;\r\n}\r\n\r\n.courier-content {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.courier-label {\r\n font-size: 14px;\r\n color: #333;\r\n font-weight: bold;\r\n margin-bottom: 5px;\r\n}\r\n\r\n.tracking-row {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.carrier-name {\r\n font-size: 12px;\r\n color: #999;\r\n margin-right: 8px;\r\n}\r\n\r\n.tracking-no {\r\n font-size: 13px;\r\n color: #666;\r\n margin-right: 10px;\r\n font-family: monospace; /* 适合单号显示 */\r\n}\r\n\r\n.copy-tag {\r\n background-color: #fff2f0;\r\n border: 1px solid #ffccc7;\r\n border-radius: 4px;\r\n padding: 1px 8px;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.copy-tag-text {\r\n color: #ff4d4f;\r\n font-size: 11px;\r\n}\r\n\r\n/* 店铺与商品 */\r\n.shop-header {\r\n display: flex;\r\n flex-direction: row; /* 显式声明横向 */\r\n align-items: center;\r\n margin-bottom: 15px;\r\n padding-bottom: 10px;\r\n border-bottom: 1px solid #f5f5f5;\r\n width: 100%; /* 占满容器 */\r\n}\r\n\r\n.shop-icon {\r\n margin-right: 8px;\r\n font-size: 16px;\r\n}\r\n\r\n.shop-name {\r\n font-size: 14px;\r\n font-weight: bold;\r\n flex: 1; /* 自适应占据空间 */\r\n color: #333;\r\n}\r\n\r\n.arrow-right {\r\n color: #999;\r\n font-size: 14px;\r\n margin-left: auto; /* 确保在最后端 */\r\n}\r\n\r\n.product-item {\r\n display: flex;\r\n flex-direction: row;\r\n margin-bottom: 15px;\r\n align-items: flex-start;\r\n}\r\n\r\n.product-item:last-child {\r\n margin-bottom: 0;\r\n}\r\n\r\n.product-image {\r\n width: 90px;\r\n height: 90px;\r\n border-radius: 8px;\r\n margin-right: 12px;\r\n background-color: #f9f9f9;\r\n flex-shrink: 0;\r\n}\r\n\r\n.product-info {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n min-height: 90px;\r\n}\r\n\r\n.product-name {\r\n font-size: 14px;\r\n line-height: 1.4;\r\n color: #333;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n lines: 2;\r\n margin-bottom: 4px;\r\n}\r\n\r\n.product-spec {\r\n font-size: 12px;\r\n color: #999;\r\n background-color: #f5f5f5;\r\n padding: 2px 5px;\r\n border-radius: 4px;\r\n align-self: flex-start;\r\n margin-top: 5px;\r\n}\r\n\r\n.price-quantity {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-top: 5px;\r\n width: 100%;\r\n}\r\n\r\n.product-price {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n\r\n.product-quantity {\r\n color: #999;\r\n font-size: 12px;\r\n}\r\n\r\n/* 订单详情 */\r\n.info-row {\r\n display: flex;\r\n justify-content: space-between;\r\n margin-bottom: 10px;\r\n font-size: 13px;\r\n}\r\n\r\n.info-label {\r\n color: #999;\r\n}\r\n\r\n.info-value {\r\n color: #333;\r\n}\r\n\r\n.copy-icon {\r\n font-size: 12px;\r\n}\r\n\r\n/* 费用明细 */\r\n.cost-detail {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center; /* 居中显示 */\r\n padding: 20px 15px;\r\n}\r\n\r\n.cost-row {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n margin-bottom: 12px;\r\n font-size: 14px;\r\n width: 100%;\r\n max-width: 400px; /* 控制明细区域宽度,并保持居中感 */\r\n}\r\n\r\n.cost-row.total {\r\n margin-top: 15px;\r\n padding-top: 15px;\r\n border-top: 1px solid #f5f5f5;\r\n align-items: center;\r\n}\r\n\r\n.cost-value.price {\r\n color: #ff5000;\r\n font-size: 20px;\r\n font-weight: bold;\r\n}\r\n\r\n/* 底部按钮 */\r\n.bottom-actions {\r\n background-color: #ffffff;\r\n padding: 12px 15px;\r\n padding-bottom: 30px;\r\n box-shadow: 0 -2px 15px rgba(0,0,0,0.08);\r\n}\r\n\r\n.action-bar-wrapper {\r\n max-width: 1200px;\r\n margin: 0 auto;\r\n width: 100%;\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.action-left {\r\n padding: 0;\r\n margin-right: 0;\r\n}\r\n\r\n.service-item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n min-width: 50px;\r\n}\r\n\r\n.service-icon {\r\n font-size: 20px;\r\n margin-bottom: 2px;\r\n}\r\n\r\n.service-label {\r\n font-size: 11px;\r\n color: #666;\r\n}\r\n\r\n.action-right {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.btn-group {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: flex-end;\r\n}\r\n\r\n.btn {\r\n margin: 0;\r\n margin-left: 12px;\r\n padding: 0 18px;\r\n height: 36px;\r\n line-height: 36px;\r\n font-size: 14px;\r\n border-radius: 18px;\r\n background: #ffffff;\r\n border: 1px solid #ddd;\r\n color: #444;\r\n}\r\n\r\n.btn.primary {\r\n background: linear-gradient(to right, #ff9000, #ff5000);\r\n color: #ffffff;\r\n border: none;\r\n font-weight: bold;\r\n box-shadow: 0 4px 8px rgba(255, 80, 0, 0.2);\r\n}\r\n\r\n.btn.share-free {\r\n background: linear-gradient(to right, #52c41a, #73d13d);\r\n color: #ffffff;\r\n border: none;\r\n font-weight: bold;\r\n}\r\n\r\n/* 响应式适配 */\r\n@media screen and (min-width: 768px) {\r\n .card {\r\n width: 1200px;\r\n max-width: 1080px;\r\n margin: 15px auto;\r\n padding: 25px;\r\n box-sizing: border-box;\r\n }\r\n \r\n .status-content, .action-bar-wrapper {\r\n width: 1200px;\r\n max-width: 1080px;\r\n margin: 0 auto;\r\n }\r\n\r\n /* 优化店铺头部在大屏下的自适应布局 */\r\n .shop-header {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n padding: 15px 0;\r\n margin-bottom: 20px;\r\n border-bottom: 1px solid #f0f0f0;\r\n width: 100%;\r\n }\r\n \r\n .shop-icon {\r\n font-size: 20px;\r\n margin-right: 12px;\r\n }\r\n \r\n .shop-name {\r\n font-size: 16px;\r\n color: #333;\r\n font-weight: bold;\r\n flex: 1;\r\n }\r\n \r\n .arrow-right {\r\n font-size: 18px;\r\n color: #ccc;\r\n margin-left: auto; /* 确保箭头始终在最右侧 */\r\n }\r\n\r\n /* 费用明细在大屏下的居中对齐 */\r\n .cost-detail {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center; /* 整体板块内容在大屏下也保持水平居中 */\r\n }\r\n \r\n .cost-row {\r\n width: 100%;\r\n max-width: 500px;\r\n margin-bottom: 12px;\r\n font-size: 15px;\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: center;\r\n }\r\n \r\n .cost-row.total {\r\n width: 100%;\r\n max-width: 500px;\r\n padding-top: 20px;\r\n margin-top: 10px;\r\n border-top: 1px solid #eee;\r\n }\r\n\r\n .cost-label {\r\n font-size: 15px;\r\n color: #666;\r\n }\r\n\r\n .cost-value.price {\r\n font-size: 28px;\r\n }\r\n\r\n /* 配送信息平铺优化 */\r\n .delivery-info-content {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n align-items: flex-start;\r\n }\r\n \r\n .delivery-address {\r\n flex: 2;\r\n }\r\n \r\n .courier-info {\r\n flex: 1;\r\n border-top: none;\r\n margin-top: 0;\r\n padding-top: 0;\r\n justify-content: flex-end;\r\n }\r\n\r\n /* 订单信息在大屏下对齐展示 */\r\n .order-info {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n }\r\n .order-info .info-row {\r\n width: 30%;\r\n margin: 0 1.5% 10px 1.5%;\r\n border-bottom: 1px solid #f9f9f9;\r\n padding-bottom: 8px;\r\n }\r\n \r\n .status-text {\r\n font-size: 26px;\r\n }\r\n \r\n .btn {\r\n height: 42px;\r\n line-height: 42px;\r\n padding: 0 30px;\r\n font-size: 15px;\r\n }\r\n}\r\n\r\n/* 状态样式 */\r\n.status-4 .status-text { /* Completed */ }\r\n</style>\r\n","<template>\r\n <view class=\"logistics-page\">\r\n <view class=\"logistics-header\">\r\n <view class=\"product-info\">\r\n <image class=\"product-image\" :src=\"productImage\" mode=\"aspectFill\"></image>\r\n <view class=\"info-right\">\r\n <text class=\"status-text\">{{ logisticsStatus }}</text>\r\n <text class=\"courier-name\">{{ courierName }}: {{ trackingNo }}</text>\r\n <text class=\"phone-text\">官方电话: {{ courierPhone }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n\r\n <view class=\"logistics-body\">\r\n <view class=\"track-list\">\r\n <view \r\n v-for=\"(item, index) in trackList\" \r\n :key=\"index\" \r\n class=\"track-item\"\r\n :class=\"{ first: index === 0 }\"\r\n >\r\n <view class=\"node-icon\">\r\n <view class=\"dot\"></view>\r\n <view class=\"line\" v-if=\"index !== trackList.length - 1\"></view>\r\n </view>\r\n <view class=\"node-content\">\r\n <text class=\"track-desc\">{{ item.desc }}</text>\r\n <text class=\"track-time\">{{ item.time }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted } from 'vue'\r\nimport { onLoad } from '@dcloudio/uni-app'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\nconst orderId = ref('')\r\nconst productImage = ref('/static/product1.jpg')\r\nconst logisticsStatus = ref('暂无物流信息')\r\nconst courierName = ref('')\r\nconst courierPhone = ref('')\r\nconst trackingNo = ref('')\r\n\r\ntype TrackItem = {\r\n desc: string,\r\n time: string\r\n}\r\n\r\nconst trackList = ref<TrackItem[]>([])\r\n\r\n// 加载物流信息函数 - 必须在 onLoad 之前定义\r\nconst loadLogisticsInfo = async () => {\r\n if (orderId.value == '') return\r\n \r\n try {\r\n console.log('[logistics] 开始加载物流信息, orderId:', orderId.value)\r\n const order = await supabaseService.getOrderDetail(orderId.value)\r\n console.log('[logistics] 获取订单结果:', order != null ? '成功' : '失败')\r\n \r\n if (order != null) {\r\n const orderStr = JSON.stringify(order)\r\n console.log('[logistics] 订单JSON:', orderStr)\r\n const orderParsed = JSON.parse(orderStr)\r\n if (orderParsed == null) {\r\n console.error('[logistics] 解析订单数据失败')\r\n return\r\n }\r\n const orderObj = orderParsed as UTSJSONObject\r\n \r\n // 获取物流信息\r\n const trackingNoVal = orderObj.getString('tracking_no')\r\n const carrierNameVal = orderObj.getString('carrier_name')\r\n const shippingStatus = orderObj.getNumber('shipping_status')\r\n \r\n console.log('[logistics] tracking_no:', trackingNoVal)\r\n console.log('[logistics] carrier_name:', carrierNameVal)\r\n console.log('[logistics] shipping_status:', shippingStatus)\r\n \r\n if (trackingNoVal != null && trackingNoVal != '') {\r\n trackingNo.value = trackingNoVal\r\n } else {\r\n console.log('[logistics] 物流单号为空,订单可能未发货')\r\n // 物流单号为空时显示提示\r\n trackingNo.value = '暂无物流单号'\r\n logisticsStatus.value = '商家未填写物流信息'\r\n }\r\n \r\n if (carrierNameVal != null && carrierNameVal != '') {\r\n courierName.value = carrierNameVal\r\n // 根据快递公司设置电话\r\n if (carrierNameVal.includes('顺丰')) {\r\n courierPhone.value = '95338'\r\n } else if (carrierNameVal.includes('中通')) {\r\n courierPhone.value = '95311'\r\n } else if (carrierNameVal.includes('圆通')) {\r\n courierPhone.value = '95554'\r\n } else if (carrierNameVal.includes('韵达')) {\r\n courierPhone.value = '95546'\r\n } else if (carrierNameVal.includes('申通')) {\r\n courierPhone.value = '95543'\r\n } else {\r\n courierPhone.value = ''\r\n }\r\n }\r\n \r\n // 根据发货状态设置物流状态\r\n if (shippingStatus == 2) {\r\n logisticsStatus.value = '已签收'\r\n } else if (shippingStatus == 1) {\r\n logisticsStatus.value = '运输中'\r\n } else {\r\n logisticsStatus.value = '待发货'\r\n }\r\n \r\n // 获取商品图片\r\n const itemsRaw = orderObj.get('ml_order_items')\r\n if (itemsRaw != null && Array.isArray(itemsRaw)) {\r\n const items = itemsRaw as any[]\r\n if (items.length > 0) {\r\n const firstItem = items[0]\r\n const itemStr = JSON.stringify(firstItem)\r\n const itemParsed = JSON.parse(itemStr)\r\n if (itemParsed != null) {\r\n const itemObj = itemParsed as UTSJSONObject\r\n const imgUrl = itemObj.getString('image_url')\r\n if (imgUrl != null && imgUrl != '') {\r\n productImage.value = imgUrl\r\n }\r\n }\r\n }\r\n }\r\n \r\n // 构建物流轨迹(如果有发货时间)\r\n const shippedAt = orderObj.getString('shipped_at')\r\n if (shippedAt != null && shippedAt != '') {\r\n const trackItem: TrackItem = {\r\n desc: '商家已发货,等待快递揽收',\r\n time: shippedAt\r\n }\r\n trackList.value.push(trackItem)\r\n }\r\n \r\n // 如果已签收,添加签收信息\r\n const deliveredAt = orderObj.getString('delivered_at')\r\n if (deliveredAt != null && deliveredAt != '') {\r\n const trackItem: TrackItem = {\r\n desc: '快件已签收',\r\n time: deliveredAt\r\n }\r\n trackList.value.unshift(trackItem)\r\n logisticsStatus.value = '已签收'\r\n }\r\n }\r\n } catch (e) {\r\n console.error('加载物流信息失败:', e)\r\n }\r\n}\r\n\r\nonLoad((options) => {\r\n if (options == null) return\r\n const orderIdValue = options['orderId']\r\n if (orderIdValue != null) {\r\n orderId.value = orderIdValue as string\r\n loadLogisticsInfo()\r\n }\r\n})\r\n\r\nonMounted(() => {\r\n // 逻辑已移到 onLoad\r\n})\r\n</script>\r\n\r\n<style scoped>\r\n.logistics-page {\r\n /* min-height: 100vh; */\r\n flex: 1;\r\n background-color: #f5f5f5;\r\n padding-bottom: 20px;\r\n}\r\n\r\n.logistics-header {\r\n background-color: #fff;\r\n padding: 15px;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.product-info {\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.product-image {\r\n width: 60px;\r\n height: 60px;\r\n border-radius: 4px;\r\n margin-right: 15px;\r\n background-color: #eee;\r\n}\r\n\r\n.info-right {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.status-text {\r\n font-size: 16px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n margin-bottom: 5px;\r\n}\r\n\r\n.courier-name {\r\n font-size: 14px;\r\n color: #333;\r\n margin-bottom: 2px;\r\n}\r\n\r\n.phone-text {\r\n font-size: 12px;\r\n color: #999;\r\n}\r\n\r\n.logistics-body {\r\n background-color: #fff;\r\n padding: 20px 15px;\r\n}\r\n\r\n.track-list {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.track-item {\r\n display: flex;\r\n position: relative;\r\n padding-bottom: 25px;\r\n}\r\n\r\n.track-item:last-child {\r\n padding-bottom: 0;\r\n}\r\n\r\n.node-icon {\r\n width: 20px;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n margin-right: 15px;\r\n}\r\n\r\n.dot {\r\n width: 8px;\r\n height: 8px;\r\n border-radius: 4px;\r\n}\r\n\r\n.first .dot {\r\n background-color: #ff5000;\r\n width: 12px;\r\n height: 12px;\r\n margin-top: 4px;\r\n box-shadow: 0 0 0 4px rgba(255, 80, 0, 0.2);\r\n}\r\n\r\n.line {\r\n width: 1px;\r\n background-color: #eee;\r\n flex: 1;\r\n margin-top: 2px;\r\n}\r\n\r\n.node-content {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.track-desc {\r\n font-size: 14px;\r\n color: #333;\r\n line-height: 1.5;\r\n margin-bottom: 5px;\r\n}\r\n\r\n.first .track-desc {\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.track-time {\r\n font-size: 12px;\r\n color: #999;\r\n}\r\n</style>\r\n\r\n","<!-- 评价页面 -->\r\n<template>\r\n\t<view class=\"review-page\">\r\n\t\t<!-- 顶部栏:已移除“发表评价”文字 -->\r\n\t\t<view class=\"review-header\">\r\n\t\t\t<view class=\"header-back\" @click=\"goBack\">\r\n\t\t\t\t<image class=\"back-icon\" src=\"/static/icons/back.png\" mode=\"aspectFit\"></image>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"header-title-placeholder\"></view>\r\n\t\t\t<view class=\"header-right\"></view>\r\n\t\t</view>\r\n\r\n\t\t<scroll-view class=\"review-content\" direction=\"vertical\">\r\n\t\t\t<!-- 订单信息 -->\r\n\t\t\t<view class=\"order-section\">\r\n\t\t\t\t<text class=\"order-no\">订单号: {{ order != null ? order.order_no : '' }}</text>\r\n\t\t\t\t<text class=\"order-time\">下单时间: {{ formatTime(order != null ? order.created_at : '') }}</text>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 商品评价 -->\r\n\t\t\t<view class=\"products-section\">\r\n\t\t\t\t<view v-for=\"(item, index) in orderItems\" :key=\"item.id\" class=\"product-review\">\r\n\t\t\t\t\t<view class=\"product-header\">\r\n\t\t\t\t\t\t<image class=\"product-image\" :src=\"item.product_image ?? '/static/default-product.png'\" />\r\n\t\t\t\t\t\t<view class=\"product-info\">\r\n\t\t\t\t\t\t\t<text class=\"product-name\">{{ item.product_name }}</text>\r\n\t\t\t\t\t\t\t<text v-if=\"item.sku_specifications != null\" class=\"product-spec\">{{ getSpecText(item.sku_specifications) }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\r\n\t\t\t\t\t<!-- 评分 -->\r\n\t\t\t\t\t<view class=\"rating-section\">\r\n\t\t\t\t\t\t<text class=\"rating-label\">评分</text>\r\n\t\t\t\t\t\t<view class=\"rating-stars\">\r\n\t\t\t\t\t\t\t<text v-for=\"star in 5\" \r\n\t\t\t\t\t\t\t\t\t\t:key=\"star\" \r\n\t\t\t\t\t\t\t\t\t\tclass=\"star-icon\"\r\n\t\t\t\t\t\t\t\t\t\t:class=\"{ active: star <= ratings[index] }\"\r\n\t\t\t\t\t\t\t\t\t\t@click=\"setRating(index, star)\">\r\n\t\t\t\t\t\t\t\t⭐\r\n\t\t\t\t\t\t\t</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"rating-text\">{{ getRatingText(ratings[index]) }}</text>\r\n\t\t\t\t\t</view>\r\n\r\n\t\t\t\t\t<!-- 评价内容 -->\r\n\t\t\t\t\t<view class=\"content-section\">\r\n\t\t\t\t\t\t<textarea class=\"review-textarea\" \r\n\t\t\t\t\t\t\t\t\t\t\tv-model=\"contents[index]\" \r\n\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"请写下您的使用感受,分享给其他小伙伴吧\"\r\n\t\t\t\t\t\t\t\t\t\t\tmaxlength=\"500\" />\r\n\t\t\t\t\t\t<text class=\"word-count\">{{ contents[index]?.length ?? 0 }}/500</text>\r\n\t\t\t\t\t</view>\r\n\r\n\t\t\t\t\t<!-- 图片上传 -->\r\n\t\t\t\t\t<view class=\"images-section\">\r\n\t\t\t\t\t\t<text class=\"images-label\">上传图片(可选)</text>\r\n\t\t\t\t\t\t<view class=\"images-grid\">\r\n\t\t\t\t\t\t\t<view v-for=\"(image, imgIndex) in images[index]\" \r\n\t\t\t\t\t\t\t\t\t\t:key=\"imgIndex\" \r\n\t\t\t\t\t\t\t\t\t\tclass=\"image-item\">\r\n\t\t\t\t\t\t\t\t<image class=\"uploaded-image\" :src=\"image\" />\r\n\t\t\t\t\t\t\t\t<text class=\"delete-image\" @click=\"deleteImage(index, imgIndex)\">×</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view v-if=\"images[index].length < 9\" \r\n\t\t\t\t\t\t\t\t\t\tclass=\"upload-btn\" \r\n\t\t\t\t\t\t\t\t\t\t@click=\"uploadImage(index)\">\r\n\t\t\t\t\t\t\t\t<text class=\"upload-icon\">+</text>\r\n\t\t\t\t\t\t\t\t<text class=\"upload-text\">添加图片</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\r\n\t\t\t\t\t<!-- 匿名评价 -->\r\n\t\t\t\t\t<view class=\"anonymous-section\">\r\n\t\t\t\t\t\t<view class=\"anonymous-switch\">\r\n\t\t\t\t\t\t\t<text class=\"switch-label\">匿名评价</text>\r\n\t\t\t\t\t\t\t<switch :checked=\"anonymous\" @change=\"toggleAnonymous\" />\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"anonymous-tip\">评价内容对其他用户不可见</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 店铺评价 -->\r\n\t\t\t<view v-if=\"merchant\" class=\"merchant-section\">\r\n\t\t\t\t<text class=\"section-title\">店铺评价</text>\r\n\t\t\t\t<view class=\"merchant-rating\">\r\n\t\t\t\t\t<text class=\"rating-item\">商品描述相符</text>\r\n\t\t\t\t\t<view class=\"rating-stars small\">\r\n\t\t\t\t\t\t<text v-for=\"star in 5\" \r\n\t\t\t\t\t\t\t\t\t:key=\"star\" \r\n\t\t\t\t\t\t\t\t\tclass=\"star-icon\"\r\n\t\t\t\t\t\t\t\t\t:class=\"{ active: star <= merchantRating.description }\"\r\n\t\t\t\t\t\t\t\t\t@click=\"setMerchantRating('description', star)\">\r\n\t\t\t\t\t\t\t⭐\r\n\t\t\t\t\t\t</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"merchant-rating\">\r\n\t\t\t\t\t<text class=\"rating-item\">物流服务</text>\r\n\t\t\t\t\t<view class=\"rating-stars small\">\r\n\t\t\t\t\t\t<text v-for=\"star in 5\" \r\n\t\t\t\t\t\t\t\t\t:key=\"star\" \r\n\t\t\t\t\t\t\t\t\tclass=\"star-icon\"\r\n\t\t\t\t\t\t\t\t\t:class=\"{ active: star <= merchantRating.logistics }\"\r\n\t\t\t\t\t\t\t\t\t@click=\"setMerchantRating('logistics', star)\">\r\n\t\t\t\t\t\t\t⭐\r\n\t\t\t\t\t\t</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"merchant-rating\">\r\n\t\t\t\t\t<text class=\"rating-item\">服务态度</text>\r\n\t\t\t\t\t<view class=\"rating-stars small\">\r\n\t\t\t\t\t\t<text v-for=\"star in 5\" \r\n\t\t\t\t\t\t\t\t\t:key=\"star\" \r\n\t\t\t\t\t\t\t\t\tclass=\"star-icon\"\r\n\t\t\t\t\t\t\t\t\t:class=\"{ active: star <= merchantRating.service }\"\r\n\t\t\t\t\t\t\t\t\t@click=\"setMerchantRating('service', star)\">\r\n\t\t\t\t\t\t\t⭐\r\n\t\t\t\t\t\t</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 评价提示 -->\r\n\t\t\t<view class=\"tips-section\">\r\n\t\t\t\t<text class=\"tip-title\">评价须知</text>\r\n\t\t\t\t<text class=\"tip-item\">1. 评价后不可修改,请谨慎评价</text>\r\n\t\t\t\t<text class=\"tip-item\">2. 上传图片需为真实商品照片</text>\r\n\t\t\t\t<text class=\"tip-item\">3. 恶意评价将被删除并限制评价功能</text>\r\n\t\t\t\t<text class=\"tip-item\">4. 优质评价可获得积分奖励</text>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\r\n\t\t<!-- 提交按钮 -->\r\n\t\t<view class=\"submit-section\">\r\n\t\t\t<button class=\"submit-btn\" \r\n\t\t\t\t\t\t\t:class=\"{ disabled: canSubmit === false || isSubmitting }\"\r\n\t\t\t\t\t\t\t@click=\"submitReview\">\r\n\t\t\t\t<text v-if=\"isSubmitting === false\" class=\"submit-text\">提交评价</text>\r\n\t\t\t\t<text v-else class=\"submit-text\">提交中...</text>\r\n\t\t\t</button>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, computed } from 'vue'\r\nimport { onLoad } from '@dcloudio/uni-app'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype OrderType = {\r\n\tid: string\r\n\torder_no: string\r\n\tcreated_at: string\r\n\tmerchant_id: string\r\n}\r\n\r\ntype OrderItemType = {\r\n\tid: number\r\n\torder_id: number\r\n\tproduct_id: number\r\n\tproduct_name: string\r\n\tproduct_image: string\r\n\tsku_specifications: any | null\r\n\tprice: number\r\n\tquantity: number\r\n}\r\n\r\ntype MerchantRatingType = {\r\n\tdescription: number\r\n\tlogistics: number\r\n\tservice: number\r\n}\r\n\r\ntype MerchantType = {\r\n\tid: string\r\n\tshop_name: string\r\n\trating: number\r\n}\r\n\r\nconst orderId = ref<string>('')\r\nconst order = ref<OrderType | null>(null)\r\nconst orderItems = ref<Array<OrderItemType>>([])\r\nconst merchant = ref<MerchantType | null>(null)\r\nconst ratings = ref<Array<number>>([])\r\nconst contents = ref<Array<string>>([])\r\nconst images = ref<Array<Array<string>>>([])\r\nconst anonymous = ref<boolean>(false)\r\nconst merchantRating = ref<MerchantRatingType>({\r\n\tdescription: 5,\r\n\tlogistics: 5,\r\n\tservice: 5\r\n} as MerchantRatingType)\r\nconst isSubmitting = ref<boolean>(false)\r\n\r\nconst loadOrderData = async (): Promise<void> => {\r\n\ttry {\r\n\t\tconsole.log('[loadOrderData] 开始加载订单数据, orderId:', orderId.value)\r\n\t\t\r\n\t\t// 使用 supabaseService 获取订单详情\r\n\t\tconst orderDetailRaw = await supabaseService.getOrderDetail(orderId.value)\r\n\t\tconsole.log('[loadOrderData] orderDetailRaw:', JSON.stringify(orderDetailRaw))\r\n\t\t\r\n\t\tif (orderDetailRaw == null) {\r\n\t\t\tconsole.error('加载订单失败: 未找到订单')\r\n\t\t\tuni.showToast({ title: '订单不存在', icon: 'none' })\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// 转换为 UTSJSONObject\r\n\t\tconst orderDetail = JSON.parse(JSON.stringify(orderDetailRaw)) as UTSJSONObject\r\n\r\n\t\t// 解析订单基本信息\r\n\t\torder.value = {\r\n\t\t\tid: orderDetail.getString('id') ?? '',\r\n\t\t\torder_no: orderDetail.getString('order_no') ?? '',\r\n\t\t\tcreated_at: orderDetail.getString('created_at') ?? '',\r\n\t\t\tmerchant_id: orderDetail.getString('merchant_id') ?? ''\r\n\t\t} as OrderType\r\n\r\n\t\t// 解析订单商品\r\n\t\tconst itemsRaw = orderDetail.get('ml_order_items')\r\n\t\tconsole.log('[loadOrderData] itemsRaw:', JSON.stringify(itemsRaw))\r\n\t\t\r\n\t\tif (itemsRaw != null) {\r\n\t\t\tconst itemsList = itemsRaw as any[]\r\n\t\t\tconst processedItems: Array<OrderItemType> = []\r\n\t\t\t\r\n\t\t\tfor (let i: number = 0; i < itemsList.length; i++) {\r\n\t\t\t\tconst itemStr = JSON.stringify(itemsList[i])\r\n\t\t\t\tconst item = JSON.parse(itemStr) as UTSJSONObject\r\n\t\t\t\tconst skuSpec = item.get('sku_specifications')\r\n\t\t\t\t\r\n\t\t\t\tprocessedItems.push({\r\n\t\t\t\t\tid: (item.getNumber('id') ?? 0) as number,\r\n\t\t\t\t\torder_id: (item.getNumber('order_id') ?? 0) as number,\r\n\t\t\t\t\tproduct_id: (item.getNumber('product_id') ?? 0) as number,\r\n\t\t\t\t\tproduct_name: item.getString('product_name') ?? '',\r\n\t\t\t\t\tprice: (item.getNumber('price') ?? 0) as number,\r\n\t\t\t\t\tquantity: (item.getNumber('quantity') ?? 1) as number,\r\n\t\t\t\t\tsku_specifications: skuSpec,\r\n\t\t\t\t\tproduct_image: item.getString('product_image') ?? item.getString('image_url') ?? '/static/default-product.png'\r\n\t\t\t\t} as OrderItemType)\r\n\t\t\t}\r\n\t\t\torderItems.value = processedItems\r\n\t\t\tconsole.log('[loadOrderData] processedItems count:', processedItems.length)\r\n\t\t}\r\n\r\n\t\t// 初始化评价数据\r\n\t\tconst count = orderItems.value.length\r\n\t\tconst newRatings: Array<number> = []\r\n\t\tconst newContents: Array<string> = []\r\n\t\tconst newImages: Array<Array<string>> = []\r\n\t\tfor (let i: number = 0; i < count; i++) {\r\n\t\t\tnewRatings.push(5)\r\n\t\t\tnewContents.push('')\r\n\t\t\tnewImages.push([])\r\n\t\t}\r\n\t\tratings.value = newRatings\r\n\t\tcontents.value = newContents\r\n\t\timages.value = newImages\r\n\r\n\t\t// 获取商家信息\r\n\t\tconst orderObj = order.value\r\n\t\tif (orderObj != null) {\r\n\t\t\tconst merchantId = orderObj.merchant_id\r\n\t\t\tif (merchantId != '') {\r\n\t\t\t\tconst shopInfo = await supabaseService.getShopByMerchantId(merchantId)\r\n\t\t\t\tif (shopInfo != null) {\r\n\t\t\t\t\tmerchant.value = {\r\n\t\t\t\t\t\tid: shopInfo.id,\r\n\t\t\t\t\t\tshop_name: shopInfo.shop_name,\r\n\t\t\t\t\t\trating: shopInfo.rating_avg ?? 5\r\n\t\t\t\t\t} as MerchantType\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t} catch (err) {\r\n\t\tconsole.error('加载订单数据异常:', err)\r\n\t\tuni.showToast({ title: '加载失败', icon: 'none' })\r\n\t}\r\n}\r\n\r\nconst canSubmit = computed((): boolean => {\r\n\tif (ratings.value.length === 0) return false\r\n\tfor (let i: number = 0; i < ratings.value.length; i++) {\r\n\t\tif (ratings.value[i] <= 0) return false\r\n\t}\r\n\treturn true\r\n})\r\n\r\nonLoad((options: any) => {\r\n\tif (options != null) {\r\n\t\tconst optObj = options as UTSJSONObject\r\n\t\torderId.value = optObj.getString('orderId') ?? ''\r\n\t\tif (orderId.value != '') loadOrderData()\r\n\t}\r\n})\r\n\r\n// 格式化时间\r\nconst formatTime = (timeStr?: string): string => {\r\n\tif (timeStr == null) return ''\r\n\tconst date = new Date(timeStr)\r\n\tconst year = date.getFullYear()\r\n\tconst month = (date.getMonth() + 1).toString().padStart(2, '0')\r\n\tconst day = date.getDate().toString().padStart(2, '0')\r\n\treturn `${year}-${month}-${day}`\r\n}\r\n\r\nconst getSpecText = (specs: any | null): string => {\r\n\tif (specs == null) return ''\r\n\tif (typeof specs === 'string') return specs as string\r\n\t\r\n\ttry {\r\n\t\tconst specObj = JSON.parse(JSON.stringify(specs)) as UTSJSONObject\r\n\t\tconst jsonStr = JSON.stringify(specObj)\r\n\t\tif (jsonStr == '{}' || jsonStr == 'null') return ''\r\n\t\t\r\n\t\t// 简单解析:直接返回 JSON 字符串(去除大括号)\r\n\t\tconst cleanStr = jsonStr.replace(/^\\{|\\}$/g, '').replace(/\"/g, '').replace(/:/g, ': ').replace(/,/g, '; ')\r\n\t\treturn cleanStr\r\n\t} catch (e) {\r\n\t\treturn ''\r\n\t}\r\n}\r\n\r\n// 获取评分文本\r\nconst getRatingText = (rating: number): string => {\r\n\tif (rating === 1) return '非常差'\r\n\tif (rating === 2) return '差'\r\n\tif (rating === 3) return '一般'\r\n\tif (rating === 4) return '好'\r\n\tif (rating === 5) return '非常好'\r\n\treturn '未评价'\r\n}\r\n\r\n// 设置商品评分\r\nconst setRating = (index: number, rating: number) => {\r\n\tratings.value[index] = rating\r\n\t// 触发响应式更新\r\n\tconst newRatings: number[] = []\r\n\tfor (let i: number = 0; i < ratings.value.length; i++) {\r\n\t\tnewRatings.push(ratings.value[i])\r\n\t}\r\n\tratings.value = newRatings\r\n}\r\n\r\nconst setMerchantRating = (type: string, rating: number) => {\r\n\tif (type === 'description') {\r\n\t\tmerchantRating.value.description = rating\r\n\t} else if (type === 'logistics') {\r\n\t\tmerchantRating.value.logistics = rating\r\n\t} else if (type === 'service') {\r\n\t\tmerchantRating.value.service = rating\r\n\t}\r\n}\r\n\r\n// 切换匿名\r\nconst toggleAnonymous = (event: any) => {\r\n\tconst eventObj = event as UTSJSONObject\r\n\tconst detailRaw = eventObj.get('detail')\r\n\tconst detail = detailRaw != null ? (detailRaw as UTSJSONObject) : (new UTSJSONObject())\r\n\tconst valueRaw = detail.get('value')\r\n\tanonymous.value = valueRaw != null ? (valueRaw as boolean) : false\r\n}\r\n\r\n// 上传图片\r\nconst uploadImage = async (index: number) => {\r\n\t// 检查图片数量限制\r\n\tif (images.value[index].length >= 9) {\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '最多上传9张图片',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t\treturn\r\n\t}\r\n\r\n\t// 使用uni.chooseImage选择图片\r\n\tuni.chooseImage({\r\n\t\tcount: 9 - images.value[index].length,\r\n\t\tsizeType: ['compressed'],\r\n\t\tsourceType: ['album', 'camera'],\r\n\t\tsuccess: (res) => {\r\n\t\t\tconst resObj = res as UTSJSONObject\r\n\t\t\tconst tempFilesRaw = resObj.get('tempFilePaths')\r\n\t\t\tconst tempFiles = tempFilesRaw != null ? (tempFilesRaw as Array<string>) : []\r\n\t\t\t\r\n\t\t\tuni.showLoading({\r\n\t\t\t\ttitle: '上传中...'\r\n\t\t\t})\r\n\t\t\t\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\tfor (let i: number = 0; i < tempFiles.length; i++) {\r\n\t\t\t\t\timages.value[index].push(tempFiles[i])\r\n\t\t\t\t}\r\n\t\t\t\tconst newImages: Array<Array<string>> = []\r\n\t\t\t\tfor (let i: number = 0; i < images.value.length; i++) {\r\n\t\t\t\t\tconst innerArray: Array<string> = []\r\n\t\t\t\t\tfor (let j: number = 0; j < images.value[i].length; j++) {\r\n\t\t\t\t\t\tinnerArray.push(images.value[i][j])\r\n\t\t\t\t\t}\r\n\t\t\t\t\tnewImages.push(innerArray)\r\n\t\t\t\t}\r\n\t\t\t\timages.value = newImages\r\n\t\t\t\t\r\n\t\t\t\tuni.hideLoading()\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '上传成功',\r\n\t\t\t\t\ticon: 'success'\r\n\t\t\t\t})\r\n\t\t\t}, 1000)\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 删除图片\r\nconst deleteImage = (index: number, imgIndex: number) => {\r\n\timages.value[index].splice(imgIndex, 1)\r\n\t// 触发响应式更新\r\n\tconst newImages: string[][] = []\r\n\tfor (let i: number = 0; i < images.value.length; i++) {\r\n\t\tconst innerArray: string[] = []\r\n\t\tfor (let j: number = 0; j < images.value[i].length; j++) {\r\n\t\t\tinnerArray.push(images.value[i][j])\r\n\t\t}\r\n\t\tnewImages.push(innerArray)\r\n\t}\r\n\timages.value = newImages\r\n}\r\n\r\n// 获取当前用户ID\r\nconst getCurrentUserId = (): string => {\r\n\tconst userStore = uni.getStorageSync('userInfo')\r\n\tif (userStore == null) return ''\r\n\tconst userInfo = userStore as UTSJSONObject\r\n\treturn userInfo.getString('id') ?? ''\r\n}\r\n\r\nconst submitReview = async (): Promise<void> => {\r\n\tif (canSubmit.value === false || isSubmitting.value) return\r\n\r\n\tisSubmitting.value = true\r\n\r\n\ttry {\r\n\t\tconst userId = getCurrentUserId()\r\n\t\tif (userId == '') {\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '用户信息错误',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\ttype ProductReviewType = {\r\n\t\t\tuser_id: string,\r\n\t\t\tproduct_id: number,\r\n\t\t\torder_id: string,\r\n\t\t\trating: number,\r\n\t\t\tcontent: string,\r\n\t\t\timages: Array<string>,\r\n\t\t\tis_anonymous: boolean\r\n\t\t}\r\n\t\tconst productReviews: Array<UTSJSONObject> = []\r\n\t\tfor (let index: number = 0; index < orderItems.value.length; index++) {\r\n\t\t\tconst item = orderItems.value[index]\r\n\t\t\tconst reviewObj: UTSJSONObject = new UTSJSONObject()\r\n\t\t\treviewObj.set('user_id', userId)\r\n\t\t\treviewObj.set('product_id', item.product_id)\r\n\t\t\treviewObj.set('order_id', orderId.value)\r\n\t\t\treviewObj.set('rating', ratings.value[index])\r\n\t\t\treviewObj.set('content', contents.value[index] != '' ? contents.value[index] : '')\r\n\t\t\treviewObj.set('images', images.value[index])\r\n\t\t\treviewObj.set('is_anonymous', anonymous.value)\r\n\t\t\tproductReviews.push(reviewObj)\r\n\t\t}\r\n\r\n\t\tconst reviewsSuccess = await supabaseService.submitProductReviews(productReviews)\r\n\t\tif (reviewsSuccess == false) {\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '提交失败',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t\tisSubmitting.value = false\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif (merchant.value != null) {\r\n\t\t\ttype MerchantReviewType = {\r\n\t\t\t\tuser_id: string,\r\n\t\t\t\tshop_id: string,\r\n\t\t\t\torder_id: string,\r\n\t\t\t\tdescription_rating: number,\r\n\t\t\t\tlogistics_rating: number,\r\n\t\t\t\tservice_rating: number\r\n\t\t\t}\r\n\t\t\tconst merchantReviewObj: UTSJSONObject = new UTSJSONObject()\r\n\t\t\tmerchantReviewObj.set('user_id', userId)\r\n\t\t\tmerchantReviewObj.set('shop_id', merchant.value.id)\r\n\t\t\tmerchantReviewObj.set('order_id', orderId.value)\r\n\t\t\tmerchantReviewObj.set('description_rating', merchantRating.value.description)\r\n\t\t\tmerchantReviewObj.set('logistics_rating', merchantRating.value.logistics)\r\n\t\t\tmerchantReviewObj.set('service_rating', merchantRating.value.service)\r\n\r\n\t\t\tawait supabaseService.submitShopReview(merchantReviewObj)\r\n\t\t}\r\n\r\n\t\tawait supabaseService.updateOrderStatus(orderId.value, 4)\r\n\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '评价成功',\r\n\t\t\ticon: 'success',\r\n\t\t\tduration: 2000\r\n\t\t})\r\n\r\n\t\t// 跳转到评价成功页面\r\n\t\tsetTimeout(() => {\r\n\t\t\tuni.navigateBack()\r\n\t\t}, 1500)\r\n\r\n\t} catch (err) {\r\n\t\tconsole.error('提交评价失败:', err)\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '提交失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t} finally {\r\n\t\tisSubmitting.value = false\r\n\t}\r\n}\r\n\r\n// 返回\r\nconst goBack = (): void => {\r\n\tuni.navigateBack()\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.review-page {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tflex: 1;\r\n\tbackground-color: #f5f5f5;\r\n}\r\n\r\n.review-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 10px 15px;\r\n\theight: 44px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n\tborder-bottom: 1px solid #f0f0f0;\r\n\tposition: fixed;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\tright: 0;\r\n\tz-index: 100;\r\n}\r\n\r\n.header-back {\r\n\twidth: 44px;\r\n\theight: 44px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: flex-start;\r\n}\r\n\r\n.back-icon {\r\n\twidth: 20px;\r\n\theight: 20px;\r\n}\r\n\r\n.header-title-placeholder {\r\n\tflex: 1;\r\n}\r\n\r\n.header-right {\r\n\twidth: 44px;\r\n}\r\n\r\n.review-content {\r\n\tflex: 1;\r\n}\r\n\r\n.order-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tmargin-bottom: 10px;\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n}\r\n\r\n.order-no {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n}\r\n\r\n.order-time {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n}\r\n\r\n.products-section {\r\n\tbackground-color: #ffffff;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.product-review {\r\n\tpadding: 15px;\r\n\tborder-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.product-header {\r\n\tdisplay: flex;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.product-image {\r\n\twidth: 60px;\r\n\theight: 60px;\r\n\tborder-radius: 5px;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.product-info {\r\n\tflex: 1;\r\n}\r\n\r\n.product-name {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tline-height: 1.4;\r\n\tmargin-bottom: 5px;\r\n\t/* display: block; removed */\r\n}\r\n\r\n.product-spec {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n\t/* display: block; removed */\r\n}\r\n\r\n.rating-section {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tmargin-bottom: 20px;\r\n\tpadding: 5px 0;\r\n}\r\n\r\n.rating-label {\r\n\tfont-size: 15px;\r\n\tcolor: #333333;\r\n\tfont-weight: bold;\r\n\tmargin-right: 20px;\r\n}\r\n\r\n.rating-stars {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.star-icon {\r\n\tfont-size: 26px;\r\n\tmargin-right: 8px;\r\n\tcolor: #e0e0e0;\r\n\ttransition: transform 0.1s ease;\r\n}\r\n\r\n.star-icon.active {\r\n\tcolor: #ff5000;\r\n\ttransform: scale(1.1);\r\n}\r\n\r\n.rating-text {\r\n\tfont-size: 14px;\r\n\tcolor: #999999;\r\n\tmargin-left: 5px;\r\n}\r\n\r\n.rating-stars.small {\r\n\t/* gap: 5px; removed */\r\n}\r\n\r\n.content-section {\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.review-textarea {\r\n\twidth: 100%;\r\n\tmin-height: 80px;\r\n\tpadding: 10px;\r\n\tborder: 1px solid #e5e5e5;\r\n\tborder-radius: 8px;\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tline-height: 1.4;\r\n}\r\n\r\n.word-count {\r\n\t/* display: block; removed */\r\n\ttext-align: right;\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n\tmargin-top: 5px;\r\n}\r\n\r\n.images-section {\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.images-label {\r\n\t/* display: block; removed */\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.images-grid {\r\n\tdisplay: flex;\r\n\tflex-wrap: wrap;\r\n\t/* gap: 10px; removed */\r\n}\r\n\r\n.image-item {\r\n\tmargin-right: 10px;\r\n\tmargin-bottom: 10px;\r\n\twidth: 70px;\r\n\theight: 70px;\r\n\tborder-radius: 5px;\r\n\toverflow: hidden;\r\n\tposition: relative;\r\n}\r\n\r\n.uploaded-image {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n}\r\n\r\n.delete-image {\r\n\tposition: absolute;\r\n\ttop: 2px;\r\n\tright: 2px;\r\n\twidth: 16px;\r\n\theight: 16px;\r\n\tborder-radius: 8px;\r\n\tbackground-color: rgba(0, 0, 0, 0.5);\r\n\tcolor: #ffffff;\r\n\tfont-size: 12px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.upload-btn {\r\n\twidth: 70px;\r\n\theight: 70px;\r\n\tborder: 1px dashed #cccccc;\r\n\tborder-radius: 5px;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.upload-icon {\r\n\tfont-size: 24px;\r\n\tcolor: #999999;\r\n\tmargin-bottom: 5px;\r\n}\r\n\r\n.upload-text {\r\n\tfont-size: 10px;\r\n\tcolor: #999999;\r\n}\r\n\r\n.anonymous-section {\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.anonymous-switch {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 5px;\r\n}\r\n\r\n.switch-label {\r\n\tfont-size: 14px;\r\n\t/* display: block; removed */\r\n}\r\n\r\n.anonymous-tip {\r\n\t/* display: block; removed */\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n}\r\n\r\n.merchant-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 20px 15px;\r\n\tmargin-top: 15px;\r\n\tborder-radius: 12px;\r\n}\r\n\r\n.section-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n\tmargin-bottom: 20px;\r\n\tpadding-left: 10px;\r\n\tborder-left: 4px solid #ff5000;\r\n}\r\n\r\n.merchant-rating {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 20px;\r\n\tpadding: 0 5px;\r\n}\r\n\r\n.rating-item {\r\n\tfont-size: 14px;\r\n\tcolor: #666666;\r\n\tflex: 1;\r\n}\r\n\r\n.merchant-rating .rating-stars.small {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n}\r\n\r\n.merchant-rating .rating-stars.small .star-icon {\r\n\tfont-size: 20px;\r\n\tmargin-right: 5px;\r\n\tcolor: #e0e0e0;\r\n}\r\n\r\n.merchant-rating .rating-stars.small .star-icon.active {\r\n\tcolor: #ff5000;\r\n}\r\n\r\n.tips-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.tip-title {\r\n\t/* display: block; removed */\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.tip-item {\r\n\t/* display: block; removed */\r\n\tfont-size: 12px;\r\n\tcolor: #666666;\r\n\tline-height: 1.6;\r\n\tmargin-bottom: 5px;\r\n}\r\n\r\n.submit-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tborder-top: 1px solid #e5e5e5;\r\n}\r\n\r\n.submit-btn {\r\n\tbackground-color: #007aff;\r\n\tcolor: #ffffff;\r\n\theight: 50px;\r\n\tborder-radius: 25px;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tborder: none;\r\n}\r\n\r\n.submit-btn.disabled {\r\n\tbackground-color: #cccccc;\r\n\topacity: 0.6;\r\n}\r\n</style>\r\n\r\n","<!-- 退款页面 -->\r\n<template>\r\n\t<view class=\"refund-page\">\r\n\t\t<!-- 顶部栏 -->\r\n\t\t<view class=\"refund-header\">\r\n\t\t\t<text class=\"back-btn\" @click=\"goBack\"></text>\r\n\t\t\t<text class=\"header-title\">退款/售后</text>\r\n\t\t</view>\r\n\r\n\t\t<!-- 标签页 -->\r\n\t\t<view class=\"refund-tabs\">\r\n\t\t\t<view :class=\"['refund-tab', { active: activeTab === 'all' }]\" @click=\"changeTab('all')\">\r\n\t\t\t\t<text class=\"tab-text\">全部</text>\r\n\t\t\t</view>\r\n\t\t\t<view :class=\"['refund-tab', { active: activeTab === 'processing' }]\" @click=\"changeTab('processing')\">\r\n\t\t\t\t<text class=\"tab-text\">处理中</text>\r\n\t\t\t\t<text v-if=\"tabCounts.processing > 0\" class=\"tab-badge\">{{ tabCounts.processing }}</text>\r\n\t\t\t</view>\r\n\t\t\t<view :class=\"['refund-tab', { active: activeTab === 'completed' }]\" @click=\"changeTab('completed')\">\r\n\t\t\t\t<text class=\"tab-text\">已完成</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<!-- 售后列表 -->\r\n\t\t<scroll-view class=\"refund-content\" direction=\"vertical\" @scrolltolower=\"loadMore\">\r\n\t\t\t<!-- 空状态 -->\r\n\t\t\t<view v-if=\"refunds.length === 0 && !isLoading\" class=\"empty-refunds\">\r\n\t\t\t\t<text class=\"empty-icon\">🔄</text>\r\n\t\t\t\t<text class=\"empty-text\">暂无售后记录</text>\r\n\t\t\t\t<text class=\"empty-subtext\">您可以在订单详情中申请售后</text>\r\n\t\t\t\t<button class=\"go-orders-btn\" @click=\"goToOrders\">查看订单</button>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 售后项 -->\r\n\t\t\t<view v-for=\"refund in refunds\" :key=\"refund.id\" class=\"refund-item\">\r\n\t\t\t\t<view class=\"refund-header\">\r\n\t\t\t\t\t<text class=\"refund-no\">售后单号: {{ refund.refund_no }}</text>\r\n\t\t\t\t\t<text :class=\"['refund-status', getStatusClass(refund.status)]\">\r\n\t\t\t\t\t\t{{ getStatusText(refund.status) }}\r\n\t\t\t\t\t</text>\r\n\t\t\t\t</view>\r\n\r\n\t\t\t\t<view class=\"order-info\">\r\n\t\t\t\t\t<text class=\"order-no\">订单号: {{ refund.order?.order_no }}</text>\r\n\t\t\t\t\t<text class=\"order-time\">{{ formatTime(refund.order?.created_at) }}</text>\r\n\t\t\t\t</view>\r\n\r\n\t\t\t\t<view class=\"product-info\" @click=\"viewOrder(refund.order_id)\">\r\n\t\t\t\t\t<image class=\"product-image\" :src=\"getProductImage(refund)\" />\r\n\t\t\t\t\t<view class=\"product-details\">\r\n\t\t\t\t\t\t<text class=\"product-name\">{{ getProductName(refund) }}</text>\r\n\t\t\t\t\t\t<text v-if=\"refund.refund_reason\" class=\"refund-reason\">原因: {{ refund.refund_reason }}</text>\r\n\t\t\t\t\t\t<view class=\"refund-amount\">\r\n\t\t\t\t\t\t\t<text class=\"amount-label\">退款金额:</text>\r\n\t\t\t\t\t\t\t<text class=\"amount-value\">¥{{ refund.refund_amount }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\r\n\t\t\t\t<!-- 进度时间线 -->\r\n\t\t\t\t<view v-if=\"refund.status_history != null && refund.status_history.length > 0\" class=\"timeline\">\r\n\t\t\t\t\t<view v-for=\"(step, index) in getTimelineSteps(refund)\" \r\n\t\t\t\t\t\t\t\t:key=\"index\" \r\n\t\t\t\t\t\t\t\tclass=\"timeline-step\">\r\n\t\t\t\t\t\t<view class=\"step-dot\" :class=\"{ active: step.active, completed: step.completed }\"></view>\r\n\t\t\t\t\t\t<view class=\"step-info\">\r\n\t\t\t\t\t\t\t<text class=\"step-title\">{{ step.title }}</text>\r\n\t\t\t\t\t\t\t<text class=\"step-time\">{{ step.time }}</text>\r\n\t\t\t\t\t\t\t<text v-if=\"step.desc\" class=\"step-desc\">{{ step.desc }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\r\n\t\t\t\t<!-- 操作按钮 -->\r\n\t\t\t\t<view v-if=\"refund.status === 1\" class=\"refund-actions\">\r\n\t\t\t\t\t<button class=\"action-btn cancel\" @click=\"cancelRefund(refund)\">取消申请</button>\r\n\t\t\t\t\t<button class=\"action-btn contact\" @click=\"contactService(refund)\">联系客服</button>\r\n\t\t\t\t</view>\r\n\r\n\t\t\t\t<view v-if=\"refund.status === 3\" class=\"refund-actions\">\r\n\t\t\t\t\t<button class=\"action-btn review\" @click=\"reviewRefund(refund)\">评价服务</button>\r\n\t\t\t\t\t<button class=\"action-btn delete\" @click=\"deleteRefund(refund)\">删除记录</button>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 加载更多 -->\r\n\t\t\t<view v-if=\"isLoading\" class=\"loading-more\">\r\n\t\t\t\t<text class=\"loading-text\">加载中...</text>\r\n\t\t\t</view>\r\n\t\t\t<view v-if=\"!hasMore && refunds.length > 0\" class=\"no-more\">\r\n\t\t\t\t<text class=\"no-more-text\">没有更多了</text>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\r\n\t\t<!-- 申请售后按钮 -->\r\n\t\t<view class=\"apply-btn-container\">\r\n\t\t\t<button class=\"apply-btn\" @click=\"applyRefund\">申请售后</button>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted, watch } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype RefundStatusHistoryItem = {\r\n\tstatus: number\r\n\tremark: string\r\n\tcreated_at: string\r\n}\r\n\r\ntype RefundProductInfo = {\r\n\timages: string[]\r\n}\r\n\r\ntype RefundOrderItem = {\r\n\tid: string\r\n\tproduct_name: string\r\n\tsku_specifications: any | null\r\n\tprice: number\r\n\tquantity: number\r\n\tproduct?: RefundProductInfo\r\n}\r\n\r\ntype RefundOrderInfo = {\r\n\tid: string\r\n\torder_no: string\r\n\tcreated_at: string\r\n\torder_items: RefundOrderItem[]\r\n}\r\n\r\ntype RefundType = {\r\n\tid: string\r\n\tuser_id: string\r\n\torder_id: string\r\n\trefund_no: string\r\n\trefund_type: number // 1:仅退款 2:退货退款\r\n\trefund_reason: string\r\n\trefund_amount: number\r\n\tstatus: number // 1:待处理 2:处理中 3:已完成 4:已取消 5:已拒绝\r\n\tstatus_history: RefundStatusHistoryItem[] | null\r\n\tcreated_at: string\r\n\torder?: RefundOrderInfo\r\n}\r\n\r\ntype TabCountsType = {\r\n\tprocessing: number\r\n}\r\n\r\nconst activeTab = ref<string>('all')\r\nconst refunds = ref<Array<RefundType>>([])\r\nconst tabCounts = ref<TabCountsType>({\r\n\tprocessing: 0\r\n})\r\nconst isLoading = ref<boolean>(false)\r\nconst currentPage = ref<number>(1)\r\nconst pageSize = ref<number>(15)\r\nconst hasMore = ref<boolean>(true)\r\n\r\nconst getCurrentUserId = (): string => {\r\n\treturn supabaseService.getCurrentUserId() ?? ''\r\n}\r\n\r\nconst resetData = () => {\r\n\trefunds.value = []\r\n\tcurrentPage.value = 1\r\n\thasMore.value = true\r\n}\r\n\r\nconst loadRefunds = async (loadMore: boolean): Promise<void> => {\r\n\tif (isLoading.value || (!hasMore.value && loadMore)) {\r\n\t\treturn\r\n\t}\r\n\r\n\tisLoading.value = true\r\n\r\n\ttry {\r\n\t\tconst userId = getCurrentUserId()\r\n\t\tif (userId == '') {\r\n\t\t\tuni.navigateTo({\r\n\t\t\t\turl: '/pages/user/login'\r\n\t\t\t})\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tconst page = loadMore ? currentPage.value + 1 : 1\r\n\t\t\r\n let statusList: number[] = []\r\n\t\tif (activeTab.value === 'processing') {\r\n\t\t\tstatusList = [1, 2]\r\n\t\t} else if (activeTab.value === 'completed') {\r\n\t\t\tstatusList = [3, 4, 5]\r\n\t\t}\r\n\t\t\r\n const rawData = await supabaseService.getRefunds(statusList, page, pageSize.value)\r\n \r\n const newRefunds: Array<RefundType> = []\r\n for (let i: number = 0; i < rawData.length; i++) {\r\n const item = rawData[i] as UTSJSONObject\r\n const orderObjRaw = item.get('order')\r\n const orderObj = (orderObjRaw != null) ? (orderObjRaw as UTSJSONObject) : (new UTSJSONObject())\r\n const dbItemsRaw = orderObj.get('ml_order_items')\r\n const dbItems = (dbItemsRaw != null) ? (dbItemsRaw as any[]) : []\r\n \r\n const uiItems: Array<RefundOrderItem> = []\r\n for (let j: number = 0; j < dbItems.length; j++) {\r\n const di = dbItems[j] as UTSJSONObject\r\n const imgRaw = di.get('image_url')\r\n const imgUrl = (imgRaw != null) ? (imgRaw as string) : '/static/default-product.png'\r\n const productInfo: RefundProductInfo = {\r\n images: [imgUrl]\r\n } as RefundProductInfo\r\n \r\n const specRaw = di.get('specifications')\r\n const specifications = (specRaw != null) ? (specRaw as any) : null\r\n const orderItem: RefundOrderItem = {\r\n id: di.getString('id') ?? '',\r\n product_name: di.getString('product_name') ?? '',\r\n sku_specifications: specifications,\r\n price: 0,\r\n quantity: di.getNumber('quantity') ?? 1,\r\n product: productInfo\r\n } as RefundOrderItem\r\n uiItems.push(orderItem)\r\n }\r\n \r\n const statusHistoryRaw = item.get('status_history')\r\n const statusHistory = (statusHistoryRaw != null) ? (statusHistoryRaw as RefundStatusHistoryItem[]) : []\r\n \r\n const refundItem: RefundType = {\r\n id: item.getString('id') ?? '',\r\n user_id: item.getString('user_id') ?? '',\r\n order_id: item.getString('order_id') ?? '',\r\n refund_no: item.getString('refund_no') ?? '',\r\n refund_type: item.getNumber('refund_type') ?? 1,\r\n refund_reason: item.getString('refund_reason') ?? '',\r\n refund_amount: item.getNumber('refund_amount') ?? 0,\r\n status: item.getNumber('status') ?? 1,\r\n status_history: statusHistory,\r\n created_at: item.getString('created_at') ?? '',\r\n order: {\r\n id: item.getString('order_id') ?? '',\r\n order_no: orderObj.getString('order_no') ?? '',\r\n created_at: orderObj.getString('created_at') ?? '',\r\n order_items: uiItems\r\n } as RefundOrderInfo\r\n } as RefundType\r\n newRefunds.push(refundItem)\r\n }\r\n\r\n\t\tif (loadMore) {\r\n\t\t\trefunds.value.push(...newRefunds)\r\n\t\t\tcurrentPage.value = page\r\n\t\t} else {\r\n\t\t\trefunds.value = newRefunds\r\n\t\t\tcurrentPage.value = 1\r\n\t\t}\r\n\r\n\t\thasMore.value = newRefunds.length === pageSize.value\r\n\t} catch (err) {\r\n\t\tconsole.error('加载售后记录异常:', err)\r\n\t} finally {\r\n\t\tisLoading.value = false\r\n\t}\r\n}\r\n\r\nconst loadTabCounts = async () => {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId == '') return\r\n\r\n\ttry {\r\n\t\tconst processingRefunds = await supabaseService.getRefunds([1, 2], 1, 100)\r\n\t\ttabCounts.value.processing = processingRefunds.length\r\n\t} catch (err) {\r\n\t\tconsole.error('加载计数异常:', err)\r\n\t}\r\n}\r\n\r\nwatch(activeTab, () => {\r\n\tresetData()\r\n\tloadRefunds(false)\r\n})\r\n\r\nonMounted(() => {\r\n\tloadRefunds(false)\r\n\tloadTabCounts()\r\n})\r\n\r\nconst getStatusText = (status: number): string => {\r\n\tif (status === 1) return '待处理'\r\n\tif (status === 2) return '处理中'\r\n\tif (status === 3) return '已完成'\r\n\tif (status === 4) return '已取消'\r\n\tif (status === 5) return '已拒绝'\r\n\treturn '未知状态'\r\n}\r\n\r\nconst getStatusClass = (status: number): string => {\r\n\tif (status === 1) return 'status-pending'\r\n\tif (status === 2) return 'status-processing'\r\n\tif (status === 3) return 'status-completed'\r\n\tif (status === 4) return 'status-cancelled'\r\n\tif (status === 5) return 'status-rejected'\r\n\treturn 'status-unknown'\r\n}\r\n\r\n// 获取商品图片\r\nconst getProductImage = (refund: RefundType): string => {\r\n\tconst firstItem = refund.order?.order_items?.[0]\r\n\tif (firstItem?.product?.images == null || firstItem?.product?.images.length == 0) {\r\n\t\treturn '/static/default-product.png'\r\n\t}\r\n\treturn firstItem.product!.images[0]\r\n}\r\n\r\n// 获取商品名称\r\nconst getProductName = (refund: RefundType): string => {\r\n\tconst items = refund.order?.order_items ?? []\r\n\tif (items.length === 0) return '未知商品'\r\n\t\r\n\tif (items.length === 1) {\r\n\t\treturn items[0].product_name\r\n\t} else {\r\n\t\treturn `${items[0].product_name}等${items.length}件商品`\r\n\t}\r\n}\r\n\r\n// 格式化时间\r\nconst formatTime = (timeStr?: string): string => {\r\n\tif (timeStr == null || timeStr == '') return ''\r\n\tconst date = new Date(timeStr)\r\n\tconst month = (date.getMonth() + 1).toString().padStart(2, '0')\r\n\tconst day = date.getDate().toString().padStart(2, '0')\r\n\treturn `${month}-${day}`\r\n}\r\n\r\nconst getCurrentStepIndex = (status: number): number => {\r\n\tif (status === 1) return 0\r\n\tif (status === 2) return 1\r\n\tif (status === 3) return 2\r\n\tif (status === 4) return 0\r\n\tif (status === 5) return 1\r\n\treturn 0\r\n}\r\n\r\ntype TimelineStepType = {\r\n\tstatus: number,\r\n\ttitle: string,\r\n\ttime: string,\r\n\tactive: boolean,\r\n\tcompleted: boolean,\r\n\tdesc: string\r\n}\r\n\r\nconst getTimelineSteps = (refund: RefundType): Array<TimelineStepType> => {\r\n\tconst steps: Array<TimelineStepType> = [\r\n\t\t{ status: 0, title: '提交申请', time: refund.created_at, active: false, completed: false, desc: '' },\r\n\t\t{ status: 1, title: '商家处理', time: '', active: false, completed: false, desc: '' },\r\n\t\t{ status: 3, title: '退款完成', time: '', active: false, completed: false, desc: '' }\r\n\t]\r\n\t\r\n\tif (refund.status_history != null) {\r\n\t\tfor (let i: number = 0; i < refund.status_history.length; i++) {\r\n\t\t\tconst history = refund.status_history[i]\r\n\t\t\tif (history.status === 1 || history.status === 2) {\r\n\t\t\t\tsteps[1].time = history.created_at ?? ''\r\n\t\t\t\tsteps[1].desc = history.remark ?? ''\r\n\t\t\t} else if (history.status === 3) {\r\n\t\t\t\tsteps[2].time = history.created_at ?? ''\r\n\t\t\t\tsteps[2].desc = history.remark ?? ''\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tconst currentStepIndex = getCurrentStepIndex(refund.status)\r\n\tconst result: Array<TimelineStepType> = []\r\n\tfor (let i: number = 0; i < steps.length; i++) {\r\n\t\tconst step = steps[i]\r\n\t\tresult.push({\r\n\t\t\tstatus: step.status,\r\n\t\t\ttitle: step.title,\r\n\t\t\ttime: step.time,\r\n\t\t\tdesc: step.desc,\r\n\t\t\tactive: i === currentStepIndex,\r\n\t\t\tcompleted: i < currentStepIndex\r\n\t\t})\r\n\t}\r\n\treturn result\r\n}\r\n\r\n// 切换标签页\r\nconst changeTab = (tab: string) => {\r\n\tactiveTab.value = tab\r\n}\r\n\r\n// 加载更多\r\nconst loadMore = () => {\r\n\tif (hasMore.value && !isLoading.value) {\r\n\t\tloadRefunds(true)\r\n\t}\r\n}\r\n\r\n// 查看订单\r\nconst viewOrder = (orderId: string) => {\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/order-detail?id=${orderId}`\r\n\t})\r\n}\r\n\r\nconst doCancelRefund = async (refund: RefundType) => {\r\n\ttry {\r\n\t\tconst result = await supabaseService.createRefund({\r\n\t\t\tid: refund.id,\r\n\t\t\tstatus: 4\r\n\t\t} as any)\r\n\t\t\r\n\t\tif (result.success) {\r\n\t\t\trefund.status = 4\r\n\t\t\tloadTabCounts()\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '已取消',\r\n\t\t\t\ticon: 'success'\r\n\t\t\t})\r\n\t\t} else {\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '取消失败',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t}\r\n\t} catch (err) {\r\n\t\tconsole.error('取消退款失败:', err)\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '取消失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t}\r\n}\r\n\r\nconst cancelRefund = (refund: RefundType) => {\r\n\tuni.showModal({\r\n\t\ttitle: '取消申请',\r\n\t\tcontent: '确定要取消这个退款申请吗?',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tdoCancelRefund(refund)\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 联系客服\r\nconst contactService = (refund: RefundType) => {\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/service/chat?refundId=${refund.id}`\r\n\t})\r\n}\r\n\r\n// 评价服务\r\nconst reviewRefund = (refund: RefundType) => {\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/refund-review?id=${refund.id}`\r\n\t})\r\n}\r\n\r\nconst doDeleteRefund = async (refund: RefundType) => {\r\n\ttry {\r\n\t\tconst result = await supabaseService.deleteRefund(refund.id)\r\n\t\t\r\n\t\tif (result) {\r\n\t\t\tconst newRefunds: Array<RefundType> = []\r\n\t\t\tfor (let i: number = 0; i < refunds.value.length; i++) {\r\n\t\t\t\tif (refunds.value[i].id !== refund.id) {\r\n\t\t\t\t\tnewRefunds.push(refunds.value[i])\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\trefunds.value = newRefunds\r\n\t\t\t\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '删除成功',\r\n\t\t\t\ticon: 'success'\r\n\t\t\t})\r\n\t\t} else {\r\n\t\t\tuni.showToast({\r\n\t\t\t\ttitle: '删除失败',\r\n\t\t\t\ticon: 'none'\r\n\t\t\t})\r\n\t\t}\r\n\t} catch (err) {\r\n\t\tconsole.error('删除记录失败:', err)\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '删除失败',\r\n\t\t\ticon: 'none'\r\n\t\t})\r\n\t}\r\n}\r\n\r\nconst deleteRefund = (refund: RefundType) => {\r\n\tuni.showModal({\r\n\t\ttitle: '删除记录',\r\n\t\tcontent: '确定要删除这个售后记录吗?',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\tdoDeleteRefund(refund)\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 申请售后\r\nconst applyRefund = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/mall/consumer/apply-refund'\r\n\t})\r\n}\r\n\r\n// 查看订单\r\nconst goToOrders = () => {\r\n\tuni.switchTab({\r\n\t\turl: '/pages/mall/consumer/orders'\r\n\t})\r\n}\r\n\r\n// 返回\r\nconst goBack = () => {\r\n\tuni.navigateBack()\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.refund-page {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tflex: 1;\r\n\tbackground-color: #f5f5f5;\r\n}\r\n\r\n.refund-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tborder-bottom: 1px solid #e5e5e5;\r\n}\r\n\r\n.back-btn {\r\n\tfont-size: 20px;\r\n\tcolor: #333333;\r\n\tpadding: 5px;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.header-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n}\r\n\r\n.refund-tabs {\r\n\tbackground-color: #ffffff;\r\n\tdisplay: flex;\r\n\tborder-bottom: 1px solid #e5e5e5;\r\n}\r\n\r\n.refund-tab {\r\n\tflex: 1;\r\n\tpadding: 15px;\r\n\ttext-align: center;\r\n\tposition: relative;\r\n}\r\n\r\n.refund-tab.active {\r\n\tcolor: #007aff;\r\n\tborder-bottom: 2px solid #007aff;\r\n}\r\n\r\n.tab-text {\r\n\tfont-size: 16px;\r\n\tcolor: #666666;\r\n}\r\n\r\n.refund-tab.active .tab-text {\r\n\tcolor: #007aff;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.tab-badge {\r\n\tposition: absolute;\r\n\ttop: 10px;\r\n\tright: 20px;\r\n\tbackground-color: #ff4757;\r\n\tcolor: #ffffff;\r\n\tfont-size: 10px;\r\n\tpadding: 2px 5px;\r\n\tborder-radius: 8px;\r\n\tmin-width: 16px;\r\n\ttext-align: center;\r\n}\r\n\r\n.refund-content {\r\n\tflex: 1;\r\n}\r\n\r\n.empty-refunds {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpadding: 80px 20px;\r\n\tbackground-color: #ffffff;\r\n}\r\n\r\n.empty-icon {\r\n\tfont-size: 80px;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.empty-text {\r\n\tfont-size: 16px;\r\n\tcolor: #666666;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.empty-subtext {\r\n\tfont-size: 14px;\r\n\tcolor: #999999;\r\n\tmargin-bottom: 30px;\r\n}\r\n\r\n.go-orders-btn {\r\n\tbackground-color: #007aff;\r\n\tcolor: #ffffff;\r\n\tpadding: 10px 40px;\r\n\tborder-radius: 25px;\r\n\tfont-size: 14px;\r\n\tborder: none;\r\n}\r\n\r\n.refund-item {\r\n\tbackground-color: #ffffff;\r\n\tmargin-bottom: 10px;\r\n\tpadding: 15px;\r\n}\r\n\r\n.refund-header {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 10px;\r\n\tpadding-bottom: 10px;\r\n\tborder-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.refund-no {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n}\r\n\r\n.refund-status {\r\n\tfont-size: 14px;\r\n\tpadding: 4px 10px;\r\n\tborder-radius: 12px;\r\n\tcolor: #ffffff;\r\n}\r\n\r\n.status-pending {\r\n\tbackground-color: #ff5000;\r\n}\r\n\r\n.status-processing {\r\n\tbackground-color: #2196f3;\r\n}\r\n\r\n.status-completed {\r\n\tbackground-color: #4caf50;\r\n}\r\n\r\n.status-cancelled {\r\n\tbackground-color: #9e9e9e;\r\n}\r\n\r\n.status-rejected {\r\n\tbackground-color: #f44336;\r\n}\r\n\r\n.order-info {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 15px;\r\n\tpadding-bottom: 10px;\r\n\tborder-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.order-no {\r\n\tfont-size: 13px;\r\n\tcolor: #666666;\r\n}\r\n\r\n.order-time {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n}\r\n\r\n.product-info {\r\n\tdisplay: flex;\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.product-image {\r\n\twidth: 60px;\r\n\theight: 60px;\r\n\tborder-radius: 5px;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.product-details {\r\n\tflex: 1;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.product-name {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tline-height: 1.4;\r\n\tmargin-bottom: 5px;\r\n}\r\n\r\n.refund-reason {\r\n\tfont-size: 12px;\r\n\tcolor: #666666;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.refund-amount {\r\n\tdisplay: flex;\r\n\talign-items: flex-end;\r\n}\r\n\r\n.amount-label {\r\n\tfont-size: 13px;\r\n\tcolor: #666666;\r\n\tmargin-right: 5px;\r\n}\r\n\r\n.amount-value {\r\n\tfont-size: 16px;\r\n\tcolor: #ff4757;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.timeline {\r\n\tpadding: 15px 0;\r\n\tborder-top: 1px solid #f5f5f5;\r\n}\r\n\r\n.timeline-step {\r\n\tdisplay: flex;\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.timeline-step:last-child {\r\n\tmargin-bottom: 0;\r\n}\r\n\r\n.step-dot {\r\n\twidth: 12px;\r\n\theight: 12px;\r\n\tborder-radius: 6px;\r\n\tborder: 2px solid #e5e5e5;\r\n\tmargin-right: 15px;\r\n\tposition: relative;\r\n\ttop: 3px;\r\n}\r\n\r\n.step-dot.active {\r\n\tborder-color: #007aff;\r\n\tbackground-color: #007aff;\r\n}\r\n\r\n.step-dot.completed {\r\n\tborder-color: #4caf50;\r\n\tbackground-color: #4caf50;\r\n}\r\n\r\n.step-info {\r\n\tflex: 1;\r\n}\r\n\r\n.step-title {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tfont-weight: bold;\r\n\tmargin-bottom: 3px;\r\n}\r\n\r\n.step-time {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n\tmargin-bottom: 3px;\r\n}\r\n\r\n.step-desc {\r\n\tfont-size: 12px;\r\n\tcolor: #666666;\r\n}\r\n\r\n.refund-actions {\r\n\tdisplay: flex;\r\n\tjustify-content: flex-end;\r\n\t/* gap: 10px; removed for uni-app-x */\r\n\tpadding-top: 15px;\r\n\tborder-top: 1px solid #f5f5f5;\r\n}\r\n\r\n.action-btn {\r\n\tmargin-left: 10px;\r\n\tpadding: 6px 15px;\r\n\tborder-radius: 15px;\r\n\tfont-size: 12px;\r\n\tborder: 1px solid;\r\n\tbackground-color: #ffffff;\r\n}\r\n\r\n.action-btn.cancel {\r\n\tborder-color: #666666;\r\n\tcolor: #666666;\r\n}\r\n\r\n.action-btn.contact {\r\n\tborder-color: #007aff;\r\n\tcolor: #007aff;\r\n}\r\n\r\n.action-btn.review {\r\n\tborder-color: #ff5000;\r\n\tcolor: #ff5000;\r\n}\r\n\r\n.action-btn.delete {\r\n\tborder-color: #f44336;\r\n\tcolor: #f44336;\r\n}\r\n\r\n.loading-more,\r\n.no-more {\r\n\tpadding: 20px;\r\n\ttext-align: center;\r\n\tbackground-color: #ffffff;\r\n}\r\n\r\n.loading-text,\r\n.no-more-text {\r\n\tcolor: #999999;\r\n\tfont-size: 14px;\r\n}\r\n\r\n.apply-btn-container {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tborder-top: 1px solid #e5e5e5;\r\n}\r\n\r\n.apply-btn {\r\n\tbackground-color: #007aff;\r\n\tcolor: #ffffff;\r\n\theight: 50px;\r\n\tborder-radius: 25px;\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tborder: none;\r\n}\r\n</style>\r\n","<!-- pages/mall/consumer/chat.uvue -->\r\n<template>\r\n <view class=\"chat-page\">\r\n <!-- 聊天头部 -->\r\n <view class=\"chat-header\" :style=\"{ paddingTop: navPaddingTop }\">\r\n <view class=\"header-back\" @click=\"goBack\">\r\n <text class=\"back-icon\"></text>\r\n </view>\r\n <view class=\"header-info\">\r\n <view class=\"header-info-text-wrapper\">\r\n <text class=\"chat-title\">{{ headerTitle }}</text>\r\n <text class=\"chat-status\">在线</text>\r\n </view>\r\n </view>\r\n <view class=\"header-actions\">\r\n <view class=\"action-icon\" @click=\"showMoreActions\">\r\n <text class=\"action-icon-text\">⋯</text>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 聊天内容 -->\r\n <scroll-view \r\n scroll-y=\"true\"\r\n class=\"chat-content\"\r\n :scroll-into-view=\"scrollToView\"\r\n :scroll-with-animation=\"true\"\r\n :show-scrollbar=\"false\"\r\n upper-threshold=\"100\"\r\n @scrolltoupper=\"onScrollToUpper\"\r\n >\r\n <!-- 聊天消息列表 -->\r\n <view class=\"chat-messages\">\r\n <!-- 系统消息 -->\r\n <view class=\"message-item system\">\r\n <text class=\"system-text\">客服 小美 已接入,请描述您的问题</text>\r\n </view>\r\n \r\n <!-- 时间分割线 -->\r\n <view class=\"time-divider\">\r\n <text class=\"time-text\">今天 14:30</text>\r\n </view>\r\n \r\n <!-- 消息项 -->\r\n <view \r\n v-for=\"message in messages\" \r\n :key=\"message.id\"\r\n :class=\"['message-item', message.type]\"\r\n :id=\"message.viewId\"\r\n >\r\n <!-- 对方消息 -->\r\n <view v-if=\"message.type === 'received'\" class=\"message-wrapper\">\r\n <image \r\n class=\"avatar\" \r\n :src=\"merchantAvatar\" \r\n mode=\"aspectFill\"\r\n />\r\n <view class=\"message-content-wrapper\">\r\n <text class=\"sender-name\">{{ headerTitle }}</text>\r\n <view class=\"message-bubble received-bubble\">\r\n <!-- 图片消息 -->\r\n <image \r\n v-if=\"message.msgType == 'image'\" \r\n class=\"message-image\" \r\n :src=\"message.content\" \r\n mode=\"widthFix\"\r\n @click=\"previewImage(message.content)\"\r\n />\r\n <!-- 文本消息 -->\r\n <text v-if=\"message.msgType != 'image'\" class=\"message-text\">{{ message.content }}</text>\r\n <text class=\"message-time\">{{ message.time }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 我的消息 -->\r\n <view v-else class=\"message-wrapper me\">\r\n <view class=\"message-content-wrapper\">\r\n <view class=\"message-bubble me\">\r\n <!-- 图片消息 -->\r\n <image \r\n v-if=\"message.msgType == 'image'\" \r\n class=\"message-image\" \r\n :src=\"message.content\" \r\n mode=\"widthFix\"\r\n @click=\"previewImage(message.content)\"\r\n />\r\n <!-- 文本消息 -->\r\n <text v-if=\"message.msgType != 'image'\" class=\"message-text\">{{ message.content }}</text>\r\n <text class=\"message-time\">{{ message.time }}</text>\r\n </view>\r\n </view>\r\n <image \r\n class=\"avatar me\" \r\n src=\"/static/images/default-product.png\" \r\n mode=\"aspectFill\"\r\n />\r\n </view>\r\n </view>\r\n </view>\r\n </scroll-view>\r\n \r\n <!-- 聊天输入区 -->\r\n <view class=\"chat-input\">\r\n <view class=\"input-tools\">\r\n <text class=\"tool-icon\" @click=\"showEmojiPicker\">😊</text>\r\n <text class=\"tool-icon\" @click=\"showImagePicker\">📷</text>\r\n <text class=\"tool-icon\" @click=\"showMoreTools\"></text>\r\n </view>\r\n \r\n <view class=\"input-wrapper\">\r\n <input \r\n class=\"message-input\" \r\n v-model=\"inputMessage\"\r\n placeholder=\"请输入消息...\"\r\n :focus=\"inputFocus\"\r\n @confirm=\"sendMessage\"\r\n confirm-type=\"send\"\r\n />\r\n <button \r\n class=\"send-button\" \r\n :class=\"{ active: inputMessage.trim() }\" \r\n @click=\"sendMessage\"\r\n >\r\n 发送\r\n </button>\r\n </view>\r\n </view>\r\n \r\n <!-- 表情选择器 -->\r\n <scroll-view v-if=\"showEmoji\" class=\"emoji-picker\" direction=\"vertical\">\r\n <view class=\"emoji-category\">\r\n <text \r\n v-for=\"emoji in emojiList\" \r\n :key=\"emoji\"\r\n class=\"emoji-item\"\r\n @click=\"insertEmoji(emoji)\"\r\n >\r\n {{ emoji }}\r\n </text>\r\n </view>\r\n </scroll-view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'\r\nimport { supabaseService, type ChatMessage } from '@/utils/supabaseService.uts'\r\nimport supa from '@/components/supadb/aksupainstance.uts'\r\nimport type { AkSupaRealtimeChannel } from '@/components/supadb/aksupa.uts'\r\nimport { getCurrentUser } from '@/utils/store.uts'\r\n\r\ntype UiChatMessage = {\r\n\tid: string\r\n\tviewId: string\r\n\ttype: string\r\n\tcontent: string\r\n\ttime: string\r\n\tmsgType: string // 'text' | 'image'\r\n}\r\n\r\n// 响应式数据\r\nconst messages = ref<UiChatMessage[]>([])\r\nconst inputMessage = ref<string>('')\r\nconst inputFocus = ref<boolean>(false)\r\nconst showEmoji = ref<boolean>(false)\r\nconst scrollToView = ref<string>('')\r\nconst currentUserId = ref<string>('')\r\nconst merchantId = ref<string>('') // 商家ID\r\nconst headerTitle = ref<string>('在线客服')\r\nconst merchantAvatar = ref<string>('/static/default-shop.png') // 商家头像\r\nconst navPaddingTop = ref<string>('30px') // 默认值,包含状态栏高度+原有内边距\r\nconst isInitialLoading = ref<boolean>(true)\r\nlet realtimeChannel: AkSupaRealtimeChannel | null = null\r\n\r\n// 模拟表情列表\r\nconst emojiList = ['😊', '😂', '🤣', '😍', '😘', '🥰', '😭', '😡', '👍', '👏', '🙏', '🎉', '❤️', '🔥', '⭐']\r\n\r\nfunction scrollToBottom() : void {\r\n if (messages.value.length === 0) return\r\n\r\n // 获取最后一条消息的 ID\r\n const lastMsg = messages.value[messages.value.length - 1]\r\n const targetId = lastMsg.viewId\r\n\r\n // 关键点:在 UVue 安卓端,直接连续赋值可能被合并。\r\n // 我们先清空 ID然后在下一帧赋值确保 scroll-view 监听到变化。\r\n scrollToView.value = ''\r\n\r\n // 延迟更久一点,确保安卓端列表排版彻底完成\r\n setTimeout(() => {\r\n scrollToView.value = targetId\r\n console.log('[scrollToBottom] 发起滚动定位:', targetId)\r\n \r\n // 分级校准:针对长消息或渲染抖动导致的高度变化\r\n setTimeout(() => {\r\n scrollToView.value = ''\r\n setTimeout(() => {\r\n scrollToView.value = targetId\r\n console.log('[scrollToBottom] 第一阶段校准:', targetId)\r\n }, 50)\r\n }, 500)\r\n\r\n // 最终深度校准(针对首屏数据较多时)\r\n setTimeout(() => {\r\n scrollToView.value = ''\r\n setTimeout(() => {\r\n scrollToView.value = targetId\r\n console.log('[scrollToBottom] 最终校准:', targetId)\r\n }, 50)\r\n }, 1200)\r\n }, 300)\r\n}\r\n\r\nfunction getCurrentTime(): string {\r\n const now = new Date()\r\n const hours = now.getHours().toString().padStart(2, '0')\r\n const minutes = now.getMinutes().toString().padStart(2, '0')\r\n return `${hours}:${minutes}`\r\n}\r\n\r\nfunction setupRealtimeSubscription(): void {\r\n\tconsole.log('开始建立聊天实时订阅...')\r\n\tconsole.log('当前用户ID:', currentUserId.value, '商家ID:', merchantId.value)\r\n\t\r\n\trealtimeChannel = supa.channel('chat-messages-' + Date.now().toString())\r\n\t\t.on('postgres_changes', {\r\n\t\t\tevent: 'INSERT',\r\n\t\t\tschema: 'public',\r\n\t\t\ttable: 'ml_chat_messages'\r\n\t\t}, (payload: any) => {\r\n\t\t\tconsole.log('=== 收到实时订阅回调 ===')\r\n\t\t\tconst payloadObj = (payload instanceof UTSJSONObject) ? (payload as UTSJSONObject) : (JSON.parse(JSON.stringify(payload ?? {})) as UTSJSONObject)\r\n\t\t\tconst newMsgAny = payloadObj.get('new')\r\n\t\t\tif (newMsgAny == null) {\r\n\t\t\t\tconsole.log('newMsgAny 为空,跳过')\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tconst newMsg = (newMsgAny instanceof UTSJSONObject) ? (newMsgAny as UTSJSONObject) : (JSON.parse(JSON.stringify(newMsgAny)) as UTSJSONObject)\r\n\t\t\tconsole.log('收到新消息:', newMsg)\r\n\r\n\t\t\tconst senderId = newMsg.getString('sender_id') ?? ''\r\n\t\t\tconst receiverId = newMsg.getString('receiver_id') ?? ''\r\n\t\t\tconst msgId = newMsg.getString('id') ?? ''\r\n\t\t\tconst content = newMsg.getString('content') ?? ''\r\n\t\t\tconst msgType = newMsg.getString('msg_type') ?? 'text'\r\n\t\t\t\r\n\t\t\tconsole.log('=== 消息详情 ===')\r\n\t\t\tconsole.log('消息ID:', msgId)\r\n\t\t\tconsole.log('发送者ID:', senderId)\r\n\t\t\tconsole.log('接收者ID:', receiverId)\r\n\t\t\tconsole.log('当前用户ID:', currentUserId.value)\r\n\t\t\tconsole.log('商家ID:', merchantId.value)\r\n\t\t\tconsole.log('消息内容:', content)\r\n\t\t\tconsole.log('消息类型 msgType:', msgType)\r\n\r\n\t\t\t// 检查消息是否已经在列表中(避免重复)\r\n\t\t\tfor (let i = 0; i < messages.value.length; i++) {\r\n\t\t\t\tif (messages.value[i].id == msgId) {\r\n\t\t\t\t\tconsole.log('消息已存在,跳过')\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// 判断消息类型\r\n\t\t\tconst isMyMessage = (senderId == currentUserId.value)\r\n\t\t\tconst isForMe = (receiverId == currentUserId.value)\r\n\t\t\tconst isRelatedToCurrentChat = (senderId == merchantId.value || receiverId == merchantId.value)\r\n\t\t\t\r\n\t\t\tconsole.log('=== 条件判断 ===')\r\n\t\t\tconsole.log('isMyMessage:', isMyMessage)\r\n\t\t\tconsole.log('isForMe:', isForMe)\r\n\t\t\tconsole.log('isRelatedToCurrentChat:', isRelatedToCurrentChat)\r\n\r\n\t\t\t// 如果消息与当前聊天无关,跳过\r\n\t\t\tif (!isRelatedToCurrentChat) {\r\n\t\t\t\tconsole.log('消息与当前聊天无关,跳过')\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\t// 如果是自己发送的消息,或者是发给自己的消息,都显示\r\n\t\t\tif (isMyMessage || isForMe) {\r\n\t\t\t\tconst createdAt = newMsg.getString('created_at') ?? new Date().toISOString()\r\n\t\t\t\tconst date = new Date(createdAt)\r\n\t\t\t\tconst timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`\r\n\r\n\t\t\t\t// 生成安全的 viewId\r\n\t\t\t\tconst safeViewId = 'msg_' + msgId.replace(/[^a-zA-Z0-9]/g, '_')\r\n\r\n\t\t\t\tconst incomingMsg: UiChatMessage = {\r\n\t\t\t\t\tid: msgId,\r\n\t\t\t\t\tviewId: safeViewId,\r\n\t\t\t\t\ttype: isMyMessage ? 'sent' : 'received',\r\n\t\t\t\t\tcontent: content,\r\n\t\t\t\t\ttime: timeStr,\r\n\t\t\t\t\tmsgType: msgType\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconsole.log('=== 添加新消息到列表 ===')\r\n\t\t\t\tconsole.log('消息类型:', incomingMsg.type)\r\n\t\t\t\tconsole.log('消息内容:', incomingMsg.content)\r\n\t\t\t\tmessages.value.push(incomingMsg)\r\n\t\t\t\tscrollToBottom()\r\n\t\t\t} else {\r\n\t\t\t\tconsole.log('条件不满足,不添加消息')\r\n\t\t\t}\r\n\t\t})\r\n\t\t.subscribe((status: string, err: any | null) => {\r\n\t\t\tconsole.log('订阅状态:', status)\r\n\t\t\tif (err != null) {\r\n\t\t\t\tconsole.log('订阅错误:', err)\r\n\t\t\t}\r\n\t\t})\r\n}\r\n\r\nasync function loadChatHistory(): Promise<void> {\r\n let rawMsgs : ChatMessage[] = []\r\n\r\n if (merchantId.value != '') {\r\n rawMsgs = await supabaseService.getChatMessages(merchantId.value)\r\n } else {\r\n console.warn(\"No merchant ID provided for chat\")\r\n return\r\n }\r\n\r\n // 确保时间顺序是升序(旧的在前,新的在后)\r\n // Supabase 返回的消息如果是降序,我们需要 reverse 过来显示\r\n const sortedRawMsgs = rawMsgs.sort((a, b) => {\r\n const timeA = new Date(a.created_at ?? '').getTime()\r\n const timeB = new Date(b.created_at ?? '').getTime()\r\n return timeA - timeB\r\n })\r\n\r\n const uiMessages : UiChatMessage[] = []\r\n for (let i = 0; i < sortedRawMsgs.length; i++) {\r\n const m = sortedRawMsgs[i]\r\n const date = new Date(m.created_at ?? new Date().toISOString())\r\n const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`\r\n\r\n const sender = m.sender_id ?? ''\r\n const msgType = (currentUserId.value != '' && sender == currentUserId.value) ? 'sent' : 'received'\r\n const rawId = (m.id ?? '').toString()\r\n const msgId = rawId != '' ? rawId : Date.now().toString() + i.toString()\r\n const safeViewId = 'msg_' + msgId.replace(/[^a-zA-Z0-9]/g, '_')\r\n \r\n const uiMsg : UiChatMessage = {\r\n id: msgId,\r\n viewId: safeViewId,\r\n type: msgType,\r\n content: m.content ?? '',\r\n time: timeStr,\r\n msgType: m.msg_type ?? 'text'\r\n }\r\n uiMessages.push(uiMsg)\r\n }\r\n messages.value = uiMessages\r\n \r\n if (isInitialLoading.value) {\r\n // 增加一点初始化延迟,等待 scroll-view 渲染就绪\r\n setTimeout(() => {\r\n scrollToBottom()\r\n isInitialLoading.value = false\r\n }, 500)\r\n }\r\n}\r\n\r\nfunction onScrollToUpper(e: any): void {\r\n console.log('[onScrollToUpper] 触发加载历史记录')\r\n}\r\n\r\nasync function loadMerchantInfo(): Promise<void> {\r\n\tif (merchantId.value == '') return\r\n\t\r\n\ttry {\r\n\t\tconst response = await supa\r\n\t\t\t.from('ml_shops')\r\n\t\t\t.select('shop_logo, shop_name')\r\n\t\t\t.eq('merchant_id', merchantId.value)\r\n\t\t\t.limit(1)\r\n\t\t\t.execute()\r\n\t\t\r\n\t\tif (response.error != null) {\r\n\t\t\tconsole.error('[loadMerchantInfo] 获取商家信息失败:', response.error)\r\n\t\t\treturn\r\n\t\t}\r\n\t\t\r\n\t\tconst rawData = response.data\r\n\t\tif (rawData == null) return\r\n\t\t\r\n\t\tconst rawList = rawData as any[]\r\n\t\tif (rawList.length == 0) return\r\n\t\t\r\n\t\tconst shopData = rawList[0]\r\n\t\tconst shopObj = JSON.parse(JSON.stringify(shopData)) as UTSJSONObject\r\n\t\t\r\n\t\tconst logo = shopObj.getString('shop_logo')\r\n\t\tif (logo != null && logo != '') {\r\n\t\t\tmerchantAvatar.value = logo\r\n\t\t}\r\n\t\t\r\n\t\tconst name = shopObj.getString('shop_name')\r\n\t\tif (name != null && name != '' && headerTitle.value == '在线客服') {\r\n\t\t\theaderTitle.value = name\r\n\t\t}\r\n\t} catch (e) {\r\n\t\tconsole.error('[loadMerchantInfo] 获取商家信息异常:', e)\r\n\t}\r\n}\r\n\r\n// 生命周期\r\nonLoad((options: any) => {\r\n // 动态获取状态栏高度\r\n const sysInfo = uni.getSystemInfoSync()\r\n const statusBarH = sysInfo.statusBarHeight\r\n // 状态栏高度 + 10px 原有顶部内边距\r\n navPaddingTop.value = (statusBarH + 10) + 'px'\r\n\r\n\tconst optObj = (options instanceof UTSJSONObject) ? (options as UTSJSONObject) : (JSON.parse(JSON.stringify(options ?? {})) as UTSJSONObject)\r\n\tconst mid = optObj.getString('merchantId') ?? ''\r\n\tif (mid !== '') {\r\n\t\tmerchantId.value = mid\r\n\t}\r\n\tconst mname = optObj.getString('merchantName') ?? ''\r\n\tif (mname !== '') {\r\n\t\theaderTitle.value = mname\r\n\t}\r\n})\r\n\r\nonMounted(() => {\r\n\tsupabaseService.ensureSession().then((uid) => {\r\n\t\tif (uid != null) {\r\n\t\t\tcurrentUserId.value = uid\r\n\t\t} else {\r\n\t\t\tgetCurrentUser().then((user) => {\r\n\t\t\t\tif (user != null) {\r\n\t\t\t\t\tcurrentUserId.value = user.id ?? ''\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\r\n\t\tloadMerchantInfo()\r\n\t\tloadChatHistory()\r\n\t\tsetupRealtimeSubscription()\r\n\t})\r\n})\r\n\r\nonUnmounted(() => {\r\n if (realtimeChannel != null) {\r\n supa.removeChannel(realtimeChannel!!)\r\n }\r\n})\r\n\r\nconst sendMessage = async () => {\r\n const content = inputMessage.value.trim()\r\n if (content == '') return\r\n \r\n // 清空输入框\r\n inputMessage.value = ''\r\n // 发送消息时确保收起表情面板\r\n showEmoji.value = false\r\n \r\n // 发送到 Supabase\r\n if (merchantId.value != '') {\r\n console.log('[sendMessage] 开始发送消息到:', merchantId.value)\r\n const success = await supabaseService.sendMessage(merchantId.value, content)\r\n console.log('[sendMessage] 发送结果:', success)\r\n if (!success) {\r\n uni.showToast({\r\n title: '发送失败',\r\n icon: 'none'\r\n })\r\n }\r\n // 不需要手动添加消息,等待实时订阅推送\r\n }\r\n}\r\n\r\n// 模拟客服回复 (已禁用,改用 Realtime)\r\n/*\r\nconst simulateCustomerReply = async () => {\r\n // ...\r\n}\r\n*/\r\n\r\n/* 移除不再使用的 simulateCustomerReply 和 addReceivedMessage */\r\n\r\n// 插入表情\r\nfunction insertEmoji(emoji: string): void {\r\n inputMessage.value += emoji\r\n showEmoji.value = false // 选中表情后收起表情列表\r\n inputFocus.value = true\r\n}\r\n\r\n// 显示表情选择器\r\nfunction showEmojiPicker(): void {\r\n showEmoji.value = !showEmoji.value\r\n if (showEmoji.value) {\r\n // 如果打开表情,通常需要收起键盘\r\n uni.hideKeyboard()\r\n }\r\n}\r\n\r\n// 执行图片上传\r\nasync function doUploadImage(filePath: string): Promise<void> {\r\n console.log('[doUploadImage] 开始上传图片:', filePath)\r\n \r\n // 显示加载提示\r\n uni.showLoading({\r\n title: '发送中...',\r\n mask: true\r\n })\r\n \r\n try {\r\n // 上传图片\r\n const imageUrl = await supabaseService.uploadChatImage(filePath)\r\n \r\n uni.hideLoading()\r\n \r\n if (imageUrl == '') {\r\n uni.showToast({\r\n title: '图片上传失败',\r\n icon: 'none'\r\n })\r\n return\r\n }\r\n \r\n console.log('[doUploadImage] 图片上传成功:', imageUrl)\r\n \r\n // 发送图片消息\r\n const success = await supabaseService.sendMessage(merchantId.value, imageUrl, 'image')\r\n if (!success) {\r\n uni.showToast({\r\n title: '发送失败',\r\n icon: 'none'\r\n })\r\n }\r\n } catch (e) {\r\n uni.hideLoading()\r\n console.error('[doUploadImage] 上传异常:', e)\r\n uni.showToast({\r\n title: '上传失败',\r\n icon: 'none'\r\n })\r\n }\r\n}\r\n\r\n// 显示图片选择器\r\nfunction showImagePicker(): void {\r\n uni.chooseImage({\r\n count: 1,\r\n success: (res) => {\r\n console.log('选择图片成功:', JSON.stringify(res))\r\n \r\n // 处理 tempFilePaths兼容不同平台\r\n let filePath: string = ''\r\n const tempFilePaths = res.tempFilePaths\r\n if (tempFilePaths != null) {\r\n if (Array.isArray(tempFilePaths)) {\r\n const arr = tempFilePaths as string[]\r\n if (arr.length > 0) {\r\n filePath = arr[0]\r\n }\r\n } else if (tempFilePaths instanceof UTSJSONObject) {\r\n const keys = UTSJSONObject.keys(tempFilePaths as UTSJSONObject)\r\n if (keys.length > 0) {\r\n filePath = (tempFilePaths as UTSJSONObject).getString(keys[0]) ?? ''\r\n }\r\n } else if (typeof tempFilePaths === 'string') {\r\n filePath = tempFilePaths as string\r\n }\r\n }\r\n \r\n // 尝试从 tempFiles 获取\r\n if (filePath == '' && res.tempFiles != null) {\r\n const tempFiles = res.tempFiles\r\n if (Array.isArray(tempFiles)) {\r\n const files = tempFiles as any[]\r\n if (files.length > 0) {\r\n const firstFile = files[0]\r\n if (firstFile instanceof UTSJSONObject) {\r\n filePath = firstFile.getString('path') ?? ''\r\n } else if (typeof firstFile === 'object' && firstFile != null) {\r\n const fileObj = JSON.parse(JSON.stringify(firstFile)) as UTSJSONObject\r\n filePath = fileObj.getString('path') ?? ''\r\n }\r\n }\r\n }\r\n }\r\n \r\n console.log('[showImagePicker] 文件路径:', filePath)\r\n \r\n if (filePath == '') {\r\n uni.showToast({\r\n title: '获取图片路径失败',\r\n icon: 'none'\r\n })\r\n return\r\n }\r\n \r\n // 执行上传\r\n doUploadImage(filePath)\r\n },\r\n fail: (err) => {\r\n console.log('选择图片失败:', err)\r\n uni.showToast({\r\n title: '选择图片失败',\r\n icon: 'none'\r\n })\r\n }\r\n })\r\n}\r\n\r\n// 预览图片\r\nfunction previewImage(url: string): void {\r\n uni.previewImage({\r\n urls: [url],\r\n current: url\r\n })\r\n}\r\n\r\n// 显示更多工具\r\nfunction showMoreTools(): void {\r\n uni.showActionSheet({\r\n itemList: ['发送位置', '发送文件', '发送语音'],\r\n success: (res) => {\r\n console.log('选择工具:', res.tapIndex)\r\n }\r\n })\r\n}\r\n\r\n// 显示更多操作\r\nfunction showMoreActions(): void {\r\n uni.showActionSheet({\r\n itemList: ['投诉客服', '结束对话', '清除记录'],\r\n success: (res) => {\r\n switch (res.tapIndex) {\r\n case 0:\r\n uni.navigateTo({ url: '/pages/mall/consumer/complaint' })\r\n break\r\n case 1:\r\n uni.showModal({\r\n title: '确认结束',\r\n content: '确定要结束本次对话吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.navigateBack()\r\n }\r\n }\r\n })\r\n break\r\n case 2:\r\n uni.showModal({\r\n title: '确认清除',\r\n content: '确定要清除聊天记录吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n messages.value = []\r\n }\r\n }\r\n })\r\n break\r\n }\r\n }\r\n })\r\n}\r\n\r\n// 返回\r\nconst goBack = () => {\r\n uni.navigateBack()\r\n}\r\n</script>\r\n\r\n<style>\r\n.chat-page {\r\n width: 100%;\r\n flex: 1;\r\n background-color: #f5f5f5;\r\n display: flex;\r\n flex-direction: column;\r\n overflow: hidden;\r\n}\r\n\r\n/* 聊天头部 */\r\n.chat-header {\r\n background-color: white;\r\n padding: 10px 15px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: space-between;\r\n border-bottom: 1px solid #eee;\r\n flex-shrink: 0;\r\n}\r\n\r\n.header-back {\r\n width: 40px;\r\n}\r\n\r\n.back-icon {\r\n font-size: 20px;\r\n color: #333;\r\n}\r\n\r\n.header-info {\r\n flex: 1;\r\n}\r\n\r\n.header-info-text-wrapper {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n}\r\n\r\n.chat-title {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n margin-bottom: 2px;\r\n}\r\n\r\n.chat-status {\r\n font-size: 12px;\r\n color: #34c759;\r\n}\r\n\r\n.header-actions .action-icon {\r\n font-size: 20px;\r\n color: #333;\r\n width: 40px;\r\n}\r\n\r\n.action-icon-text {\r\n text-align: right;\r\n width: 100%;\r\n}\r\n\r\n/* 聊天内容区 */\r\n.chat-content {\r\n flex: 1;\r\n height: 0;\r\n padding: 10px;\r\n padding-bottom: 20px;\r\n box-sizing: border-box;\r\n}\r\n\r\n.chat-messages {\r\n display: flex;\r\n flex-direction: column;\r\n padding-bottom: 80px;\r\n}\r\n\r\n/* 系统消息 */\r\n.message-item.system {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: center;\r\n margin-bottom: 20px;\r\n}\r\n\r\n.system-text {\r\n font-size: 12px;\r\n color: #999;\r\n background-color: #f0f0f0;\r\n padding: 5px 15px;\r\n border-radius: 15px;\r\n text-align: center;\r\n}\r\n\r\n/* 时间分割线 */\r\n.time-divider {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: center;\r\n margin: 20px 0;\r\n}\r\n\r\n.time-text {\r\n font-size: 12px;\r\n color: #999;\r\n background-color: #f0f0f0;\r\n padding: 3px 10px;\r\n border-radius: 10px;\r\n text-align: center;\r\n}\r\n\r\n/* 消息项 */\r\n.message-wrapper {\r\n display: flex;\r\n flex-direction: row;\r\n margin-bottom: 15px;\r\n}\r\n\r\n.message-wrapper.me {\r\n justify-content: flex-end;\r\n}\r\n\r\n.avatar {\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 20px;\r\n margin-right: 10px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.avatar.me {\r\n margin-right: 0;\r\n margin-left: 10px;\r\n /* order: 2; removed for uni-app-x */\r\n}\r\n\r\n.message-content-wrapper {\r\n width: 260px;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.message-bubble {\r\n background-color: white;\r\n padding: 10px 15px;\r\n border-radius: 12px;\r\n position: relative;\r\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.received-bubble {\r\n align-self: flex-start;\r\n border-top-left-radius: 2px;\r\n}\r\n\r\n.message-bubble.me {\r\n background-color: #95ec69;\r\n align-self: flex-end; /* 关键:靠右对齐且宽度自适应 */\r\n border-top-right-radius: 2px;\r\n}\r\n\r\n.sender-name {\r\n font-size: 11px;\r\n color: #999;\r\n margin-bottom: 2px;\r\n align-self: flex-start;\r\n}\r\n\r\n.message-text {\r\n font-size: 15px;\r\n color: #333;\r\n line-height: 1.4;\r\n margin-bottom: 5px;\r\n white-space: pre-wrap;\r\n}\r\n\r\n.message-image {\r\n max-width: 200px;\r\n min-width: 100px;\r\n border-radius: 8px;\r\n margin-bottom: 5px;\r\n}\r\n\r\n.message-time {\r\n font-size: 11px;\r\n color: #999;\r\n text-align: right;\r\n}\r\n\r\n/* 聊天输入区 */\r\n.chat-input {\r\n background-color: white;\r\n border-top: 1px solid #eee;\r\n padding: 10px 15px;\r\n padding-bottom: 20px;\r\n position: fixed;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n flex-shrink: 0;\r\n}\r\n\r\n.input-tools {\r\n display: flex;\r\n flex-direction: row;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.tool-icon {\r\n font-size: 20px;\r\n margin-right: 15px;\r\n color: #666;\r\n}\r\n\r\n.input-wrapper {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.message-input {\r\n flex: 1;\r\n background-color: #f5f5f5;\r\n border-radius: 20px;\r\n padding: 10px 15px;\r\n font-size: 15px;\r\n margin-right: 10px;\r\n min-height: 40px;\r\n max-height: 100px;\r\n}\r\n\r\n.send-button {\r\n background-color: #ccc;\r\n color: white;\r\n border: none;\r\n border-radius: 20px;\r\n padding: 8px 20px;\r\n font-size: 14px;\r\n min-width: 60px;\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.send-button.active {\r\n background-color: #ff5000;\r\n}\r\n\r\n/* 表情选择器 */\r\n.emoji-picker {\r\n background-color: white;\r\n border-top: 1px solid #eee;\r\n padding: 10px;\r\n height: 200px;\r\n position: fixed;\r\n bottom: 80px;\r\n left: 0;\r\n right: 0;\r\n z-index: 99;\r\n}\r\n\r\n.emoji-category {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.emoji-item {\r\n font-size: 24px;\r\n padding: 8px;\r\n width: 45px;\r\n height: 45px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n/* 响应式适配 removed for strict uv-app-x compliance */\r\n</style>\r\n\r\n","<template>\r\n <view class=\"followed-shops-page\">\r\n <view class=\"header\">\r\n <text class=\"header-title\">我关注的店铺</text>\r\n </view>\r\n\r\n <view class=\"shop-list\" v-if=\"shops.length > 0\">\r\n <view class=\"shop-item\" v-for=\"shop in shops\" :key=\"shop.id\" @click=\"goToShop(shop)\">\r\n <image :src=\"shop.shop_logo != null ? shop.shop_logo : '/static/default-shop.png'\" class=\"shop-logo\" mode=\"aspectFill\" />\r\n <view class=\"shop-info\">\r\n <text class=\"shop-name\">{{ shop.shop_name }}</text>\r\n <text class=\"shop-desc\">{{ shop.description != null ? shop.description : '暂无介绍' }}</text>\r\n <view class=\"shop-meta\">\r\n <text class=\"rating shop-meta-text\">⭐ {{ shop.rating_avg }}</text>\r\n <text class=\"sales shop-meta-text\">销量: {{ shop.total_sales }}</text>\r\n </view>\r\n </view>\r\n <button class=\"unfollow-btn\" @click.stop=\"unfollow(shop)\">已关注</button>\r\n </view>\r\n </view>\r\n \r\n <view v-else-if=\"loading == false\" class=\"empty-state\">\r\n <text class=\"empty-text\">暂无关注的店铺</text>\r\n <button class=\"go-shop-btn\" @click=\"goHome\">去逛逛</button>\r\n </view>\r\n \r\n <view v-if=\"loading\" class=\"loading-state\">\r\n <text>加载中...</text>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype FollowedShop = {\r\n id: string\r\n merchant_id: string\r\n shop_name: string\r\n shop_logo: string | null\r\n description: string | null\r\n rating_avg: number\r\n total_sales: number\r\n}\r\n\r\nconst shops = ref<Array<FollowedShop>>([])\r\nconst loading = ref<boolean>(true)\r\n\r\nconst loadFollowedShops = async (): Promise<void> => {\r\n loading.value = true\r\n const userId = supabaseService.getCurrentUserId()\r\n if (userId == null || userId == '') {\r\n uni.navigateTo({ url: '/pages/auth/login' })\r\n return\r\n }\r\n\r\n const res = await supabaseService.getFollowedShops(userId)\r\n \r\n const list: Array<FollowedShop> = []\r\n if (res != null && Array.isArray(res)) {\r\n for (let i: number = 0; i < res.length; i++) {\r\n const item = res[i] as UTSJSONObject\r\n const shopDataRaw = item.get('ml_shops')\r\n if (shopDataRaw != null) {\r\n const shopData = shopDataRaw as UTSJSONObject\r\n const shop: FollowedShop = {\r\n id: shopData.getString('id') ?? '',\r\n merchant_id: shopData.getString('merchant_id') ?? '',\r\n shop_name: shopData.getString('shop_name') ?? '',\r\n shop_logo: shopData.getString('shop_logo'),\r\n description: shopData.getString('description'),\r\n rating_avg: shopData.getNumber('rating_avg') ?? 5.0,\r\n total_sales: shopData.getNumber('total_sales') ?? 0\r\n } as FollowedShop\r\n list.push(shop)\r\n }\r\n }\r\n }\r\n \r\n shops.value = list\r\n loading.value = false\r\n}\r\n\r\nconst doUnfollow = async (shopId: string, userId: string): Promise<void> => {\r\n const success = await supabaseService.unfollowShop(shopId, userId)\r\n if (success) {\r\n uni.showToast({ title: '已取消', icon: 'none' })\r\n loadFollowedShops()\r\n } else {\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n }\r\n}\r\n\r\nconst unfollow = async (shop: FollowedShop): Promise<void> => {\r\n const userId = supabaseService.getCurrentUserId()\r\n if (userId == null || userId == '') return\r\n\r\n uni.showModal({\r\n title: '提示',\r\n content: '确定取消关注该店铺吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n doUnfollow(shop.id, userId)\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst goToShop = (shop: FollowedShop): void => {\r\n const targetId = shop.merchant_id != '' ? shop.merchant_id : shop.id\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/shop-detail?merchantId=${targetId}`\r\n })\r\n}\r\n\r\nconst goHome = (): void => {\r\n uni.switchTab({ url: '/pages/main/index' })\r\n}\r\n\r\nonMounted(() => {\r\n loadFollowedShops()\r\n})\r\n</script>\r\n\r\n<style>\r\n.followed-shops-page {\r\n padding: 15px;\r\n background-color: #f5f5f5;\r\n flex: 1;\r\n}\r\n.header {\r\n margin-bottom: 15px;\r\n}\r\n.header-title {\r\n font-size: 18px;\r\n font-weight: bold;\r\n}\r\n.shop-list {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n.shop-item {\r\n background-color: #fff;\r\n border-radius: 8px;\r\n padding: 12px;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n margin-bottom: 10px;\r\n}\r\n.shop-item:last-child {\r\n margin-bottom: 0;\r\n}\r\n.shop-logo {\r\n width: 50px;\r\n height: 50px;\r\n border-radius: 4px;\r\n background-color: #eee;\r\n margin-right: 12px;\r\n}\r\n.shop-info {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n.shop-name {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n.shop-desc {\r\n font-size: 12px;\r\n color: #999;\r\n margin-top: 4px;\r\n max-width: 200px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n.shop-meta {\r\n font-size: 10px;\r\n color: #999;\r\n margin-top: 4px;\r\n display: flex;\r\n}\r\n.shop-meta-text {\r\n margin-right: 8px;\r\n}\r\n.unfollow-btn {\r\n font-size: 12px;\r\n padding: 4px 12px;\r\n background-color: #eee;\r\n color: #666;\r\n border-radius: 20px;\r\n margin-left: 10px;\r\n}\r\n.empty-state {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding-top: 100px;\r\n}\r\n.empty-text {\r\n color: #999;\r\n margin-bottom: 20px;\r\n}\r\n.go-shop-btn {\r\n background-color: #ff4444;\r\n color: white;\r\n padding: 8px 24px;\r\n border-radius: 20px;\r\n font-size: 14px;\r\n}\r\n.loading-state {\r\n text-align: center;\r\n padding-top: 50px;\r\n color: #999;\r\n}\r\n</style>\r\n","<template>\n <scroll-view class=\"points-page\" direction=\"vertical\">\n <view class=\"points-header\">\n <view class=\"points-info\">\n <text class=\"points-label\">当前积分</text>\n <text class=\"points-value\">{{ totalPoints }}</text>\n </view>\n <view class=\"points-actions\">\n <button class=\"exchange-btn\" @click=\"handleExchange\">积分兑换</button>\n </view>\n </view>\n\n <view class=\"quick-actions\">\n <view class=\"action-item\" @click=\"goToSignin\">\n <view class=\"action-icon signin-icon\">📅</view>\n <text class=\"action-text\">每日签到</text>\n <view class=\"action-badge\" v-if=\"!signedToday\">\n <text class=\"badge-text\">+5</text>\n </view>\n <view class=\"signed-badge\" v-else>\n <text class=\"signed-text\">已签</text>\n </view>\n </view>\n <view class=\"action-item\" @click=\"handleExchange\">\n <view class=\"action-icon exchange-icon\">🎁</view>\n <text class=\"action-text\">积分兑换</text>\n </view>\n <view class=\"action-item\" @click=\"goToMyReviews\">\n <view class=\"action-icon review-icon\">⭐</view>\n <text class=\"action-text\">我的评价</text>\n </view>\n </view>\n\n <view class=\"signin-card\" v-if=\"!signedToday\">\n <view class=\"signin-info\">\n <text class=\"signin-title\">今日未签到</text>\n <text class=\"signin-desc\">连续签到可获得额外奖励</text>\n </view>\n <button class=\"signin-btn\" @click=\"goToSignin\">去签到</button>\n </view>\n\n <view class=\"signin-card signed\" v-else>\n <view class=\"signin-info\">\n <text class=\"signin-title\">今日已签到</text>\n <text class=\"signin-desc\">已连续签到 {{ continuousDays }} 天</text>\n </view>\n <text class=\"signed-icon\">✓</text>\n </view>\n\n <view class=\"expiring-card\" v-if=\"expiringPoints > 0\" @click=\"showExpiringDetails\">\n <view class=\"expiring-icon\">⚠️</view>\n <view class=\"expiring-info\">\n <text class=\"expiring-title\">{{ expiringPoints }} 积分即将过期</text>\n <text class=\"expiring-date\">过期日期:{{ expiringDate }}</text>\n </view>\n <text class=\"expiring-arrow\"></text>\n </view>\n\n <view class=\"records-section\">\n <text class=\"section-title\">积分明细</text>\n \n <view v-if=\"loading\" class=\"loading-state\">\n <text>加载中...</text>\n </view>\n \n <view v-else-if=\"records.length === 0\" class=\"empty-state\">\n <text class=\"empty-text\">暂无积分记录</text>\n </view>\n \n <view v-else class=\"record-list\">\n <view v-for=\"item in records\" :key=\"item.id\" class=\"record-item\">\n <view class=\"record-left\">\n <text class=\"record-title\">{{ item.description ?? getTypeText(item.type) }}</text>\n <text class=\"record-time\">{{ formatTime(item.created_at) }}</text>\n </view>\n <view class=\"record-right\">\n <text class=\"record-amount\" :class=\"{ positive: item.points > 0, negative: item.points < 0 }\">\n {{ item.points > 0 ? '+' : '' }}{{ item.points }}\n </text>\n </view>\n </view>\n </view>\n </view>\n\n <view class=\"expiring-popup\" v-if=\"showExpiringPopup\" @click=\"closeExpiringPopup\">\n <view class=\"popup-content\" @click.stop>\n <view class=\"popup-header\">\n <text class=\"popup-title\">即将过期积分</text>\n <text class=\"popup-close\" @click=\"closeExpiringPopup\">×</text>\n </view>\n <view class=\"popup-list\">\n <view class=\"popup-item\" v-for=\"(detail, index) in expiringDetails\" :key=\"index\">\n <view class=\"popup-item-info\">\n <text class=\"popup-item-points\">+{{ detail.points }} 积分</text>\n <text class=\"popup-item-desc\">{{ detail.description ?? '积分获取' }}</text>\n </view>\n <view class=\"popup-item-expire\">\n <text class=\"popup-item-date\">{{ formatDate(detail.expires_at) }}</text>\n <text class=\"popup-item-label\">过期</text>\n </view>\n </view>\n </view>\n <view class=\"popup-tip\">\n <text class=\"tip-text\">积分有效期为获取后365天请及时使用避免过期</text>\n </view>\n </view>\n </view>\n </scroll-view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted } from 'vue'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype PointRecord = {\n id: string\n user_id: string\n points: number\n type: string\n description: string\n created_at: string\n}\n\ntype ExpiringDetail = {\n points: number\n description: string | null\n expires_at: string\n created_at: string\n}\n\nconst totalPoints = ref<number>(0)\nconst records = ref<PointRecord[]>([])\nconst loading = ref<boolean>(true)\nconst signedToday = ref<boolean>(false)\nconst continuousDays = ref<number>(0)\nconst expiringPoints = ref<number>(0)\nconst expiringDate = ref<string>('')\nconst expiringDetails = ref<ExpiringDetail[]>([])\nconst showExpiringPopup = ref<boolean>(false)\n\nconst loadPoints = async (): Promise<void> => {\n try {\n const points = await supabaseService.getUserPoints()\n totalPoints.value = points\n } catch (e) {\n console.error('获取积分失败', e)\n }\n}\n\nconst loadRecords = async (): Promise<void> => {\n try {\n const list = await supabaseService.getPointRecords()\n records.value = list as PointRecord[]\n } catch (e) {\n console.error('获取积分记录失败', e)\n }\n}\n\nconst loadSigninStatus = async (): Promise<void> => {\n try {\n const status = await supabaseService.getTodaySigninStatus()\n signedToday.value = status.getBoolean('signed') ?? false\n continuousDays.value = status.getNumber('continuous_days') ?? 0\n } catch (e) {\n console.error('获取签到状态失败', e)\n }\n}\n\nconst loadExpiringPoints = async (): Promise<void> => {\n try {\n const result = await supabaseService.getExpiringPoints()\n expiringPoints.value = result.getNumber('expiring_points') ?? 0\n expiringDate.value = result.getString('expiring_date') ?? ''\n \n const detailsRaw = result.get('details')\n if (detailsRaw != null && Array.isArray(detailsRaw)) {\n const details: ExpiringDetail[] = []\n const arr = detailsRaw as any[]\n for (let i = 0; i < arr.length; i++) {\n const item = arr[i]\n let itemObj: UTSJSONObject\n if (item instanceof UTSJSONObject) {\n itemObj = item\n } else {\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n details.push({\n points: itemObj.getNumber('points') ?? 0,\n description: itemObj.getString('description'),\n expires_at: itemObj.getString('expires_at') ?? '',\n created_at: itemObj.getString('created_at') ?? ''\n })\n }\n expiringDetails.value = details\n }\n } catch (e) {\n console.error('获取即将过期积分失败', e)\n }\n}\n\nconst loadData = async (): Promise<void> => {\n loading.value = true\n await Promise.all([\n loadPoints(),\n loadRecords(),\n loadSigninStatus(),\n loadExpiringPoints()\n ])\n loading.value = false\n}\n\nonMounted(() => {\n loadData()\n})\n\nconst handleExchange = (): void => {\n uni.navigateTo({\n url: '/pages/mall/consumer/points/exchange'\n })\n}\n\nconst goToSignin = (): void => {\n uni.navigateTo({\n url: '/pages/mall/consumer/points/signin'\n })\n}\n\nconst goToMyReviews = (): void => {\n uni.navigateTo({\n url: '/pages/mall/consumer/my-reviews'\n })\n}\n\nconst showExpiringDetails = (): void => {\n showExpiringPopup.value = true\n}\n\nconst closeExpiringPopup = (): void => {\n showExpiringPopup.value = false\n}\n\nconst getTypeText = (type: string): string => {\n if (type == 'signin') {\n return '每日签到'\n } else if (type == 'shopping') {\n return '购物奖励'\n } else if (type == 'redeem') {\n return '积分兑换'\n } else if (type == 'admin') {\n return '系统调整'\n } else if (type == 'register') {\n return '注册赠送'\n } else if (type == 'expire') {\n return '积分过期'\n } else {\n return '积分变动'\n }\n}\n\nconst formatTime = (timeStr: string): string => {\n if (timeStr == '') return ''\n const date = new Date(timeStr)\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n const hh = date.getHours().toString().padStart(2, '0')\n const mm = date.getMinutes().toString().padStart(2, '0')\n return `${y}-${m}-${d} ${hh}:${mm}`\n}\n\nconst formatDate = (dateStr: string): string => {\n if (dateStr == '') return ''\n const date = new Date(dateStr)\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n return `${y}-${m}-${d}`\n}\n</script>\n\n<style>\n.points-page {\n flex: 1;\n height: 100%;\n background-color: #f5f5f5;\n}\n\n.points-header {\n background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);\n padding: 30px 20px;\n color: white;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.points-info {\n display: flex;\n flex-direction: column;\n}\n\n.points-label {\n font-size: 14px;\n opacity: 0.9;\n margin-bottom: 8px;\n}\n\n.points-value {\n font-size: 36px;\n font-weight: bold;\n}\n\n.exchange-btn {\n background-color: rgba(255,255,255,0.2);\n color: white;\n border: 1px solid rgba(255,255,255,0.4);\n font-size: 14px;\n border-radius: 20px;\n padding: 0 15px;\n height: 32px;\n line-height: 32px;\n}\n\n.quick-actions {\n display: flex;\n flex-direction: row;\n background-color: white;\n padding: 16px 0;\n margin-bottom: 8px;\n}\n\n.action-item {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n position: relative;\n}\n\n.action-icon {\n width: 44px;\n height: 44px;\n border-radius: 22px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n margin-bottom: 6px;\n}\n\n.signin-icon {\n background-color: #fff5f0;\n}\n\n.exchange-icon {\n background-color: #f0f5ff;\n}\n\n.review-icon {\n background-color: #fff5f0;\n}\n\n.action-text {\n font-size: 12px;\n color: #666;\n}\n\n.action-badge {\n position: absolute;\n top: 0;\n right: 20px;\n background-color: #ff6b35;\n border-radius: 8px;\n padding: 2px 6px;\n}\n\n.badge-text {\n font-size: 10px;\n color: white;\n}\n\n.signed-badge {\n position: absolute;\n top: 0;\n right: 20px;\n background-color: #52c41a;\n border-radius: 8px;\n padding: 2px 6px;\n}\n\n.signed-text {\n font-size: 10px;\n color: white;\n}\n\n.signin-card {\n background-color: white;\n margin: 0 12px 8px;\n border-radius: 12px;\n padding: 16px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n}\n\n.signin-card.signed {\n background: linear-gradient(135deg, #f6ffed 0%, #e6fffb 100%);\n}\n\n.signin-info {\n display: flex;\n flex-direction: column;\n}\n\n.signin-title {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n}\n\n.signin-desc {\n font-size: 12px;\n color: #999;\n margin-top: 4px;\n}\n\n.signin-btn {\n background-color: #ff6b35;\n color: white;\n font-size: 14px;\n border-radius: 16px;\n padding: 0 20px;\n height: 32px;\n line-height: 32px;\n}\n\n.signed-icon {\n width: 32px;\n height: 32px;\n background-color: #52c41a;\n border-radius: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n color: white;\n}\n\n.expiring-card {\n background: linear-gradient(135deg, #fff7e6 0%, #ffe7ba 100%);\n margin: 0 12px 8px;\n border-radius: 12px;\n padding: 14px 16px;\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.expiring-icon {\n font-size: 24px;\n margin-right: 12px;\n}\n\n.expiring-info {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.expiring-title {\n font-size: 14px;\n font-weight: bold;\n color: #d48806;\n}\n\n.expiring-date {\n font-size: 12px;\n color: #ad8b00;\n margin-top: 2px;\n}\n\n.expiring-arrow {\n font-size: 20px;\n color: #d48806;\n}\n\n.records-section {\n background-color: white;\n padding: 0 16px;\n min-height: 300px;\n}\n\n.section-title {\n font-size: 16px;\n font-weight: bold;\n padding: 16px 0;\n border-bottom: 1px solid #f0f0f0;\n display: flex;\n}\n\n.record-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 0;\n border-bottom: 1px solid #f9f9f9;\n}\n\n.record-left {\n display: flex;\n flex-direction: column;\n}\n\n.record-title {\n margin-bottom: 4px;\n font-size: 15px;\n color: #333;\n}\n\n.record-time {\n font-size: 12px;\n color: #999;\n}\n\n.record-amount {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n}\n\n.record-amount.positive {\n color: #ff6b35;\n}\n\n.record-amount.negative {\n color: #333;\n}\n\n.empty-state {\n padding: 40px 0;\n display: flex;\n justify-content: center;\n}\n\n.empty-text {\n color: #999;\n font-size: 14px;\n}\n\n.loading-state {\n padding: 40px 0;\n display: flex;\n justify-content: center;\n}\n\n.expiring-popup {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n}\n\n.popup-content {\n background-color: white;\n border-radius: 16px 16px 0 0;\n width: 100%;\n max-height: 400px;\n padding: 16px;\n}\n\n.popup-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.popup-title {\n font-size: 18px;\n font-weight: bold;\n color: #333;\n}\n\n.popup-close {\n font-size: 24px;\n color: #999;\n}\n\n.popup-list {\n max-height: 300px;\n}\n\n.popup-item {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding: 12px 0;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.popup-item-info {\n display: flex;\n flex-direction: column;\n}\n\n.popup-item-points {\n font-size: 14px;\n font-weight: bold;\n color: #ff6b35;\n}\n\n.popup-item-desc {\n font-size: 12px;\n color: #999;\n margin-top: 2px;\n}\n\n.popup-item-expire {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n}\n\n.popup-item-date {\n font-size: 12px;\n color: #d48806;\n}\n\n.popup-item-label {\n font-size: 10px;\n color: #999;\n}\n\n.popup-tip {\n margin-top: 16px;\n padding: 12px;\n background-color: #f9f9f9;\n border-radius: 8px;\n}\n\n.tip-text {\n font-size: 12px;\n color: #666;\n}\n</style>\n","<template>\n <view class=\"signin-page\">\n <view class=\"header\">\n <view class=\"header-content\">\n <text class=\"title\">每日签到</text>\n <text class=\"subtitle\">连续签到可获得额外奖励</text>\n </view>\n <view class=\"points-display\">\n <text class=\"points-label\">当前积分</text>\n <text class=\"points-value\">{{ totalPoints }}</text>\n </view>\n </view>\n\n <view class=\"calendar-section\">\n <view class=\"calendar-header\">\n <view class=\"month-nav\">\n <text class=\"nav-btn\" @click=\"prevMonth\">&lt;</text>\n <text class=\"current-month\">{{ currentYear }}年{{ currentMonth }}月</text>\n <text class=\"nav-btn\" @click=\"nextMonth\">&gt;</text>\n </view>\n <view class=\"continuous-info\">\n <text class=\"continuous-label\">已连续签到</text>\n <text class=\"continuous-value\">{{ continuousDays }}天</text>\n </view>\n </view>\n\n <view class=\"calendar-weekdays\">\n <text class=\"weekday\" v-for=\"day in weekdays\" :key=\"day\">{{ day }}</text>\n </view>\n\n <view class=\"calendar-days\">\n <view \n v-for=\"(day, index) in calendarDays\" \n :key=\"index\" \n class=\"day-cell\"\n :class=\"{ \n 'empty': day.day === 0,\n 'signed': day.signed,\n 'today': day.isToday\n }\"\n >\n <text v-if=\"day.day > 0\" class=\"day-number\">{{ day.day }}</text>\n <view v-if=\"day.signed\" class=\"signed-mark\">\n <text class=\"check-icon\">✓</text>\n </view>\n </view>\n </view>\n </view>\n\n <view class=\"signin-btn-section\">\n <button \n class=\"signin-btn\" \n :class=\"{ 'signed-today': signedToday }\"\n :disabled=\"signedToday\"\n @click=\"doSignin\"\n >\n {{ signedToday ? '今日已签到' : '立即签到' }}\n </button>\n </view>\n\n <view class=\"rules-section\">\n <text class=\"section-title\">签到规则</text>\n <view class=\"rule-list\">\n <view class=\"rule-item\">\n <text class=\"rule-icon\">📅</text>\n <text class=\"rule-text\">每日签到可获得5积分</text>\n </view>\n <view class=\"rule-item\">\n <text class=\"rule-icon\">🔥</text>\n <text class=\"rule-text\">连续签到7天额外奖励20积分</text>\n </view>\n <view class=\"rule-item\">\n <text class=\"rule-icon\">🏆</text>\n <text class=\"rule-text\">连续签到30天额外奖励100积分</text>\n </view>\n <view class=\"rule-item\">\n <text class=\"rule-icon\">⚠️</text>\n <text class=\"rule-text\">中断签到后连续天数将重置</text>\n </view>\n </view>\n </view>\n\n <view class=\"signin-popup\" v-if=\"showPopup\" @click=\"closePopup\">\n <view class=\"popup-content\" @click.stop>\n <view class=\"popup-icon\">🎉</view>\n <text class=\"popup-title\">签到成功</text>\n <view class=\"popup-points\">\n <text class=\"popup-points-label\">获得积分</text>\n <text class=\"popup-points-value\">+{{ popupPoints }}</text>\n </view>\n <view class=\"popup-bonus\" v-if=\"popupBonus > 0\">\n <text class=\"popup-bonus-label\">连续签到奖励</text>\n <text class=\"popup-bonus-value\">+{{ popupBonus }}</text>\n </view>\n <view class=\"popup-continuous\">\n <text>已连续签到 {{ popupContinuousDays }} 天</text>\n </view>\n <button class=\"popup-btn\" @click=\"closePopup\">确定</button>\n </view>\n </view>\n </view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted, computed } from 'vue'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype CalendarDay = {\n day: number\n signed: boolean\n isToday: boolean\n}\n\nconst totalPoints = ref<number>(0)\nconst continuousDays = ref<number>(0)\nconst signedToday = ref<boolean>(false)\nconst currentYear = ref<number>(new Date().getFullYear())\nconst currentMonth = ref<number>(new Date().getMonth() + 1)\nconst signinRecords = ref<string[]>([])\nconst showPopup = ref<boolean>(false)\nconst popupPoints = ref<number>(0)\nconst popupBonus = ref<number>(0)\nconst popupContinuousDays = ref<number>(0)\n\nconst weekdays: string[] = ['日', '一', '二', '三', '四', '五', '六']\n\nconst calendarDays = computed((): CalendarDay[] => {\n const days: CalendarDay[] = []\n const year = currentYear.value\n const month = currentMonth.value\n \n const firstDay = new Date(year, month - 1, 1).getDay()\n const daysInMonth = new Date(year, month, 0).getDate()\n \n const today = new Date()\n const todayStr = today.toISOString().split('T')[0]\n \n for (let i = 0; i < firstDay; i++) {\n days.push({ day: 0, signed: false, isToday: false })\n }\n \n for (let i = 1; i <= daysInMonth; i++) {\n const dateStr = `${year}-${month.toString().padStart(2, '0')}-${i.toString().padStart(2, '0')}`\n const isToday = dateStr === todayStr\n const signed = signinRecords.value.includes(dateStr)\n days.push({ day: i, signed, isToday })\n }\n \n return days\n})\n\nconst loadSigninData = async (): Promise<void> => {\n uni.showLoading({ title: '加载中...' })\n \n try {\n const points = await supabaseService.getUserPoints()\n totalPoints.value = points\n \n const status = await supabaseService.getTodaySigninStatus()\n signedToday.value = status.getBoolean('signed') ?? false\n continuousDays.value = status.getNumber('continuous_days') ?? 0\n \n const records = await supabaseService.getSigninRecords(currentYear.value, currentMonth.value)\n const dates: string[] = []\n for (let i = 0; i < records.length; i++) {\n const record = records[i]\n let dateStr = ''\n if (record instanceof UTSJSONObject) {\n dateStr = record.getString('signin_date') ?? ''\n } else {\n const rObj = JSON.parse(JSON.stringify(record)) as UTSJSONObject\n dateStr = rObj.getString('signin_date') ?? ''\n }\n if (dateStr !== '') {\n dates.push(dateStr)\n }\n }\n signinRecords.value = dates\n } catch (e) {\n console.error('加载签到数据失败:', e)\n } finally {\n uni.hideLoading()\n }\n}\n\nconst doSignin = async (): Promise<void> => {\n if (signedToday.value) return\n \n uni.showLoading({ title: '签到中...' })\n \n try {\n const result = await supabaseService.signin()\n \n if (result.getBoolean('success') === true) {\n popupPoints.value = result.getNumber('points') ?? 0\n popupBonus.value = result.getNumber('bonus_points') ?? 0\n popupContinuousDays.value = result.getNumber('continuous_days') ?? 0\n totalPoints.value = result.getNumber('total_points') ?? 0\n continuousDays.value = popupContinuousDays.value\n signedToday.value = true\n \n const today = new Date().toISOString().split('T')[0]\n signinRecords.value.push(today)\n \n showPopup.value = true\n } else {\n const message = result.getString('message') ?? '签到失败'\n uni.showToast({ title: message, icon: 'none' })\n }\n } catch (e) {\n console.error('签到异常:', e)\n uni.showToast({ title: '签到异常', icon: 'none' })\n } finally {\n uni.hideLoading()\n }\n}\n\nconst closePopup = (): void => {\n showPopup.value = false\n}\n\nconst prevMonth = (): void => {\n if (currentMonth.value === 1) {\n currentYear.value--\n currentMonth.value = 12\n } else {\n currentMonth.value--\n }\n loadSigninData()\n}\n\nconst nextMonth = (): void => {\n if (currentMonth.value === 12) {\n currentYear.value++\n currentMonth.value = 1\n } else {\n currentMonth.value++\n }\n loadSigninData()\n}\n\nonMounted(() => {\n loadSigninData()\n})\n</script>\n\n<style>\n.signin-page {\n flex: 1;\n background-color: #f5f5f5;\n}\n\n.header {\n background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);\n padding: 20px 16px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n}\n\n.header-content {\n display: flex;\n flex-direction: column;\n}\n\n.title {\n font-size: 24px;\n font-weight: bold;\n color: white;\n}\n\n.subtitle {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n margin-top: 4px;\n}\n\n.points-display {\n background-color: rgba(255, 255, 255, 0.2);\n border-radius: 12px;\n padding: 10px 16px;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.points-label {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n.points-value {\n font-size: 24px;\n font-weight: bold;\n color: white;\n}\n\n.calendar-section {\n background-color: white;\n margin: 12px;\n border-radius: 12px;\n padding: 16px;\n}\n\n.calendar-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.month-nav {\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.nav-btn {\n font-size: 18px;\n color: #666;\n padding: 4px 12px;\n}\n\n.current-month {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n margin: 0 8px;\n}\n\n.continuous-info {\n display: flex;\n flex-direction: row;\n align-items: center;\n background-color: #fff5f0;\n padding: 4px 12px;\n border-radius: 16px;\n}\n\n.continuous-label {\n font-size: 12px;\n color: #ff6b35;\n}\n\n.continuous-value {\n font-size: 14px;\n font-weight: bold;\n color: #ff6b35;\n margin-left: 4px;\n}\n\n.calendar-weekdays {\n display: flex;\n flex-direction: row;\n margin-bottom: 8px;\n}\n\n.weekday {\n flex: 1;\n text-align: center;\n font-size: 12px;\n color: #999;\n padding: 8px 0;\n}\n\n.calendar-days {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n.day-cell {\n width: 14.28%;\n height: 45px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n position: relative;\n}\n\n.day-cell.empty {\n background-color: transparent;\n}\n\n.day-number {\n font-size: 14px;\n color: #333;\n}\n\n.day-cell.today .day-number {\n color: #ff6b35;\n font-weight: bold;\n}\n\n.day-cell.signed {\n background-color: #fff5f0;\n border-radius: 8px;\n}\n\n.signed-mark {\n position: absolute;\n bottom: 2px;\n width: 16px;\n height: 16px;\n background-color: #ff6b35;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.check-icon {\n font-size: 10px;\n color: white;\n}\n\n.signin-btn-section {\n padding: 16px;\n}\n\n.signin-btn {\n background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);\n color: white;\n font-size: 18px;\n font-weight: bold;\n border-radius: 24px;\n height: 48px;\n line-height: 48px;\n}\n\n.signin-btn.signed-today {\n background: #ccc;\n}\n\n.rules-section {\n background-color: white;\n margin: 12px;\n border-radius: 12px;\n padding: 16px;\n}\n\n.section-title {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n margin-bottom: 12px;\n}\n\n.rule-list {\n display: flex;\n flex-direction: column;\n}\n\n.rule-item {\n display: flex;\n flex-direction: row;\n align-items: center;\n padding: 8px 0;\n}\n\n.rule-icon {\n font-size: 16px;\n margin-right: 8px;\n}\n\n.rule-text {\n font-size: 14px;\n color: #666;\n}\n\n.signin-popup {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n\n.popup-content {\n background-color: white;\n border-radius: 16px;\n padding: 24px;\n width: 280px;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.popup-icon {\n font-size: 48px;\n margin-bottom: 12px;\n}\n\n.popup-title {\n font-size: 20px;\n font-weight: bold;\n color: #333;\n margin-bottom: 16px;\n}\n\n.popup-points {\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.popup-points-label {\n font-size: 14px;\n color: #666;\n}\n\n.popup-points-value {\n font-size: 24px;\n font-weight: bold;\n color: #ff6b35;\n margin-left: 8px;\n}\n\n.popup-bonus {\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.popup-bonus-label {\n font-size: 14px;\n color: #666;\n}\n\n.popup-bonus-value {\n font-size: 20px;\n font-weight: bold;\n color: #ff6b35;\n margin-left: 8px;\n}\n\n.popup-continuous {\n font-size: 14px;\n color: #999;\n margin-bottom: 16px;\n}\n\n.popup-btn {\n background-color: #ff6b35;\n color: white;\n font-size: 16px;\n border-radius: 20px;\n width: 100%;\n height: 40px;\n line-height: 40px;\n}\n</style>\n","<template>\n <scroll-view class=\"exchange-page\" direction=\"vertical\">\n <view class=\"header\">\n <view class=\"points-info\">\n <text class=\"points-label\">可用积分</text>\n <text class=\"points-value\">{{ totalPoints }}</text>\n </view>\n <view class=\"header-actions\">\n <text class=\"records-link\" @click=\"goToRecords\">兑换记录</text>\n </view>\n </view>\n\n <view class=\"tabs\">\n <view \n class=\"tab-item\" \n :class=\"activeTab === 'all' ? 'active' : ''\"\n @click=\"switchTab('all')\"\n >\n <text class=\"tab-text\">全部</text>\n </view>\n <view \n class=\"tab-item\" \n :class=\"activeTab === 'coupon' ? 'active' : ''\"\n @click=\"switchTab('coupon')\"\n >\n <text class=\"tab-text\">优惠券</text>\n </view>\n <view \n class=\"tab-item\" \n :class=\"activeTab === 'physical' ? 'active' : ''\"\n @click=\"switchTab('physical')\"\n >\n <text class=\"tab-text\">实物</text>\n </view>\n <view \n class=\"tab-item\" \n :class=\"activeTab === 'virtual' ? 'active' : ''\"\n @click=\"switchTab('virtual')\"\n >\n <text class=\"tab-text\">虚拟</text>\n </view>\n </view>\n\n <view class=\"product-list\" v-if=\"!loading\">\n <view \n class=\"product-card\" \n v-for=\"product in filteredProducts\" \n :key=\"product.id\"\n @click=\"showExchangePopup(product)\"\n >\n <image \n class=\"product-image\" \n :src=\"product.image_url != null && product.image_url.length > 0 ? product.image_url : defaultImage\" \n mode=\"aspectFill\"\n />\n <view class=\"product-info\">\n <text class=\"product-name\">{{ product.name }}</text>\n <text class=\"product-desc\" v-if=\"product.description\">{{ product.description }}</text>\n <view class=\"product-bottom\">\n <view class=\"product-points\">\n <text class=\"points-num\">{{ product.points_required }}</text>\n <text class=\"points-unit\">积分</text>\n </view>\n <text class=\"product-stock\">库存{{ product.stock }}件</text>\n <text class=\"product-original\" v-if=\"product.original_price\">¥{{ product.original_price }}</text>\n </view>\n </view>\n </view>\n </view>\n\n <view class=\"empty-state\" v-if=\"!loading && filteredProducts.length === 0\">\n <text class=\"empty-text\">暂无可兑换商品</text>\n </view>\n\n <view class=\"loading-state\" v-if=\"loading\">\n <text class=\"loading-text\">加载中...</text>\n </view>\n\n <view class=\"exchange-popup\" v-if=\"showPopup\" @click=\"closePopup\">\n <view class=\"popup-content\" @click.stop>\n <view class=\"popup-header\">\n <text class=\"popup-title\">确认兑换</text>\n <text class=\"popup-close\" @click=\"closePopup\">×</text>\n </view>\n \n <view class=\"popup-product\" v-if=\"selectedProduct != null\">\n <image \n class=\"popup-product-image\" \n :src=\"selectedProduct.image_url != null && selectedProduct.image_url.length > 0 ? selectedProduct.image_url : defaultImage\" \n mode=\"aspectFill\"\n />\n <view class=\"popup-product-info\">\n <text class=\"popup-product-name\">{{ selectedProduct.name }}</text>\n <view class=\"popup-product-points\">\n <text class=\"popup-points-num\">{{ selectedProduct.points_required }}</text>\n <text class=\"popup-points-unit\">积分</text>\n </view>\n </view>\n </view>\n\n <view class=\"popup-quantity\">\n <text class=\"quantity-label\">兑换数量</text>\n <view class=\"quantity-control\">\n <text class=\"quantity-btn\" @click=\"decreaseQuantity\">-</text>\n <text class=\"quantity-value\">{{ exchangeQuantity }}</text>\n <text class=\"quantity-btn\" @click=\"increaseQuantity\">+</text>\n </view>\n </view>\n\n <view class=\"popup-summary\">\n <view class=\"summary-row\">\n <text class=\"summary-label\">消耗积分</text>\n <text class=\"summary-value\">{{ totalPointsCost }}</text>\n </view>\n <view class=\"summary-row\">\n <text class=\"summary-label\">当前积分</text>\n <text class=\"summary-value\">{{ totalPoints }}</text>\n </view>\n <view class=\"summary-row\" v-if=\"totalPoints < totalPointsCost\">\n <text class=\"summary-label insufficient\">积分不足</text>\n <text class=\"summary-value insufficient\">差{{ totalPointsCost - totalPoints }}</text>\n </view>\n </view>\n\n <button \n class=\"popup-btn\" \n :class=\"{ disabled: totalPoints < totalPointsCost }\"\n :disabled=\"totalPoints < totalPointsCost || exchanging\"\n @click=\"confirmExchange\"\n >\n {{ exchanging ? '兑换中...' : '确认兑换' }}\n </button>\n </view>\n </view>\n\n <view class=\"success-popup\" v-if=\"showSuccess\" @click=\"closeSuccess\">\n <view class=\"success-content\" @click.stop>\n <view class=\"success-icon\">✓</view>\n <text class=\"success-title\">兑换成功</text>\n <text class=\"success-desc\">消耗 {{ totalPointsCost }} 积分</text>\n <button class=\"success-btn\" @click=\"closeSuccess\">确定</button>\n </view>\n </view>\n </scroll-view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, computed, onMounted } from 'vue'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype PointProduct = {\n id: string\n name: string\n description: string | null\n image_url: string | null\n product_type: string\n points_required: number\n original_price: number | null\n stock: number\n status: number\n}\n\nconst totalPoints = ref<number>(0)\nconst products = ref<PointProduct[]>([])\nconst loading = ref<boolean>(true)\nconst activeTab = ref<string>('all')\nconst showPopup = ref<boolean>(false)\nconst showSuccess = ref<boolean>(false)\nconst selectedProduct = ref<PointProduct | null>(null)\nconst exchangeQuantity = ref<number>(1)\nconst exchanging = ref<boolean>(false)\n\nconst defaultImage: string = '/static/images/default-product.png'\n\nconst filteredProducts = computed((): PointProduct[] => {\n if (activeTab.value === 'all') {\n return products.value\n }\n const filtered: PointProduct[] = []\n for (let i = 0; i < products.value.length; i++) {\n if (products.value[i].product_type === activeTab.value) {\n filtered.push(products.value[i])\n }\n }\n return filtered\n})\n\nconst totalPointsCost = computed((): number => {\n if (selectedProduct.value == null) return 0\n return selectedProduct.value.points_required * exchangeQuantity.value\n})\n\nconst loadProducts = async (): Promise<void> => {\n loading.value = true\n try {\n const points = await supabaseService.getUserPoints()\n totalPoints.value = points\n\n const productList = await supabaseService.getPointProducts()\n const parsed: PointProduct[] = []\n for (let i = 0; i < productList.length; i++) {\n const item = productList[i]\n \n let id = ''\n let name = ''\n let description: string | null = null\n let image_url: string | null = null\n let product_type = 'coupon'\n let points_required = 0\n let original_price: number | null = null\n let stock = 0\n let status = 1\n \n let itemObj: UTSJSONObject | null = null\n if (item instanceof UTSJSONObject) {\n itemObj = item\n } else {\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n \n id = itemObj.getString('id') ?? ''\n name = itemObj.getString('name') ?? ''\n description = itemObj.getString('description')\n image_url = itemObj.getString('image_url')\n product_type = itemObj.getString('product_type') ?? 'coupon'\n points_required = itemObj.getNumber('points_required') ?? 0\n original_price = itemObj.getNumber('original_price')\n stock = itemObj.getNumber('stock') ?? 0\n status = itemObj.getNumber('status') ?? 1\n \n const product: PointProduct = {\n id,\n name,\n description,\n image_url,\n product_type,\n points_required,\n original_price,\n stock,\n status\n }\n parsed.push(product)\n }\n products.value = parsed\n } catch (e) {\n console.error('加载商品失败:', e)\n } finally {\n loading.value = false\n }\n}\n\nconst switchTab = (tab: string): void => {\n activeTab.value = tab\n}\n\nconst showExchangePopup = (product: PointProduct): void => {\n selectedProduct.value = product\n exchangeQuantity.value = 1\n showPopup.value = true\n}\n\nconst closePopup = (): void => {\n showPopup.value = false\n selectedProduct.value = null\n}\n\nconst increaseQuantity = (): void => {\n if (selectedProduct.value != null && exchangeQuantity.value < selectedProduct.value.stock) {\n exchangeQuantity.value++\n }\n}\n\nconst decreaseQuantity = (): void => {\n if (exchangeQuantity.value > 1) {\n exchangeQuantity.value--\n }\n}\n\nconst confirmExchange = async (): Promise<void> => {\n if (selectedProduct.value == null) return\n if (totalPoints.value < totalPointsCost.value) return\n \n exchanging.value = true\n \n try {\n const result = await supabaseService.exchangeProduct(\n selectedProduct.value.id,\n exchangeQuantity.value,\n null\n )\n \n if (result.getBoolean('success') === true) {\n showPopup.value = false\n totalPoints.value -= totalPointsCost.value\n showSuccess.value = true\n loadProducts()\n } else {\n const message = result.getString('message') ?? '兑换失败'\n uni.showToast({ title: message, icon: 'none' })\n }\n } catch (e) {\n console.error('兑换异常:', e)\n uni.showToast({ title: '兑换异常', icon: 'none' })\n } finally {\n exchanging.value = false\n }\n}\n\nconst closeSuccess = (): void => {\n showSuccess.value = false\n}\n\nconst goToRecords = (): void => {\n uni.navigateTo({\n url: '/pages/mall/consumer/points/exchange-records'\n })\n}\n\nonMounted(() => {\n loadProducts()\n})\n</script>\n\n<style>\n.exchange-page {\n flex: 1;\n height: 100%;\n background-color: #f5f5f5;\n}\n\n.header {\n background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);\n padding: 16px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n}\n\n.points-info {\n display: flex;\n flex-direction: column;\n}\n\n.points-label {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n.points-value {\n font-size: 28px;\n font-weight: bold;\n color: white;\n}\n\n.header-actions {\n display: flex;\n flex-direction: row;\n}\n\n.records-link {\n font-size: 14px;\n color: white;\n padding: 6px 12px;\n background-color: rgba(255, 255, 255, 0.2);\n border-radius: 16px;\n}\n\n.tabs {\n display: flex;\n flex-direction: row;\n background-color: white;\n padding: 0 16px;\n}\n\n.tab-item {\n flex: 1;\n padding: 12px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-bottom: 2px solid transparent;\n}\n\n.tab-item.active {\n border-bottom-color: #ff6b35;\n}\n\n.tab-text {\n font-size: 14px;\n color: #666;\n}\n\n.tab-item.active .tab-text {\n color: #ff6b35;\n font-weight: bold;\n}\n\n.product-list {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n padding: 8px;\n}\n\n.product-card {\n width: 48%;\n margin: 4px;\n background-color: white;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.product-image {\n width: 100%;\n height: 150px;\n}\n\n.product-info {\n padding: 8px;\n}\n\n.product-name {\n font-size: 14px;\n color: #333;\n lines: 2;\n text-overflow: ellipsis;\n}\n\n.product-desc {\n font-size: 12px;\n color: #999;\n margin-top: 4px;\n lines: 1;\n text-overflow: ellipsis;\n}\n\n.product-bottom {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-top: 8px;\n}\n\n.product-points {\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.points-num {\n font-size: 18px;\n font-weight: bold;\n color: #ff6b35;\n}\n\n.points-unit {\n font-size: 12px;\n color: #ff6b35;\n margin-left: 2px;\n}\n\n.product-stock {\n font-size: 12px;\n color: #ff6b35;\n}\n\n.product-original {\n font-size: 12px;\n color: #999;\n}\n\n.empty-state {\n padding: 60px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.empty-text {\n font-size: 14px;\n color: #999;\n}\n\n.loading-state {\n padding: 60px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.loading-text {\n font-size: 14px;\n color: #999;\n}\n\n.exchange-popup {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n}\n\n.popup-content {\n background-color: white;\n border-radius: 16px 16px 0 0;\n width: 100%;\n padding: 16px;\n}\n\n.popup-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.popup-title {\n font-size: 18px;\n font-weight: bold;\n color: #333;\n}\n\n.popup-close {\n font-size: 24px;\n color: #999;\n}\n\n.popup-product {\n display: flex;\n flex-direction: row;\n padding: 12px;\n background-color: #f9f9f9;\n border-radius: 8px;\n margin-bottom: 16px;\n}\n\n.popup-product-image {\n width: 80px;\n height: 80px;\n border-radius: 4px;\n}\n\n.popup-product-info {\n flex: 1;\n margin-left: 12px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.popup-product-name {\n font-size: 14px;\n color: #333;\n lines: 2;\n}\n\n.popup-product-points {\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-top: 8px;\n}\n\n.popup-points-num {\n font-size: 20px;\n font-weight: bold;\n color: #ff6b35;\n}\n\n.popup-points-unit {\n font-size: 12px;\n color: #ff6b35;\n margin-left: 2px;\n}\n\n.popup-quantity {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding: 12px 0;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.quantity-label {\n font-size: 14px;\n color: #333;\n}\n\n.quantity-control {\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.quantity-btn {\n width: 28px;\n height: 28px;\n background-color: #f5f5f5;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n color: #666;\n}\n\n.quantity-value {\n width: 40px;\n text-align: center;\n font-size: 16px;\n color: #333;\n}\n\n.popup-summary {\n padding: 12px 0;\n}\n\n.summary-row {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.summary-label {\n font-size: 14px;\n color: #666;\n}\n\n.summary-label.insufficient {\n color: #ff6b35;\n}\n\n.summary-value {\n font-size: 14px;\n color: #333;\n}\n\n.summary-value.insufficient {\n color: #ff6b35;\n}\n\n.popup-btn {\n background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);\n color: white;\n font-size: 16px;\n font-weight: bold;\n border-radius: 24px;\n height: 44px;\n line-height: 44px;\n margin-top: 16px;\n}\n\n.popup-btn.disabled {\n background: #ccc;\n}\n\n.success-popup {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1001;\n}\n\n.success-content {\n background-color: white;\n border-radius: 16px;\n padding: 32px;\n width: 280px;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.success-icon {\n width: 60px;\n height: 60px;\n background-color: #52c41a;\n border-radius: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 30px;\n color: white;\n margin-bottom: 16px;\n}\n\n.success-title {\n font-size: 20px;\n font-weight: bold;\n color: #333;\n margin-bottom: 8px;\n}\n\n.success-desc {\n font-size: 14px;\n color: #666;\n margin-bottom: 24px;\n}\n\n.success-btn {\n background-color: #ff6b35;\n color: white;\n font-size: 16px;\n border-radius: 20px;\n width: 100%;\n height: 40px;\n line-height: 40px;\n}\n</style>\n","<template>\n <scroll-view class=\"records-page\" direction=\"vertical\">\n <view class=\"empty-state\" v-if=\"!loading && records.length === 0\">\n <text class=\"empty-text\">暂无兑换记录</text>\n </view>\n\n <view class=\"loading-state\" v-if=\"loading\">\n <text class=\"loading-text\">加载中...</text>\n </view>\n\n <view class=\"record-list\" v-if=\"!loading && records.length > 0\">\n <view class=\"record-item\" v-for=\"record in records\" :key=\"record.id\">\n <view class=\"record-header\">\n <text class=\"record-product-name\">{{ record.product_name }}</text>\n <text class=\"record-status\" :class=\"getStatusClass(record.status)\">{{ getStatusText(record.status) }}</text>\n </view>\n \n <view class=\"record-info\">\n <view class=\"info-row\">\n <text class=\"info-label\">消耗积分</text>\n <text class=\"info-value\">{{ record.points_used }}</text>\n </view>\n <view class=\"info-row\">\n <text class=\"info-label\">兑换数量</text>\n <text class=\"info-value\">{{ record.quantity }}</text>\n </view>\n <view class=\"info-row\">\n <text class=\"info-label\">兑换时间</text>\n <text class=\"info-value\">{{ formatTime(record.created_at) }}</text>\n </view>\n <view class=\"info-row\" v-if=\"record.tracking_no\">\n <text class=\"info-label\">物流单号</text>\n <text class=\"info-value\">{{ record.tracking_no }}</text>\n </view>\n </view>\n </view>\n </view>\n </scroll-view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted } from 'vue'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype ExchangeRecord = {\n id: string\n product_name: string\n product_image: string | null\n product_type: string\n quantity: number\n points_used: number\n status: number\n tracking_no: string | null\n created_at: string\n}\n\nconst records = ref<ExchangeRecord[]>([])\nconst loading = ref<boolean>(true)\n\nconst loadRecords = async (): Promise<void> => {\n loading.value = true\n try {\n const result = await supabaseService.getExchangeRecords()\n const parsed: ExchangeRecord[] = []\n \n for (let i = 0; i < result.length; i++) {\n const item = result[i]\n const itemAny = item as any\n \n // 处理数组返回\n let recordData: any\n if (Array.isArray(itemAny)) {\n recordData = itemAny[0]\n } else {\n recordData = itemAny\n }\n \n let id = ''\n let quantity = 1\n let points_used = 0\n let status = 0\n let tracking_no: string | null = null\n let created_at = ''\n let product_name = ''\n let product_image: string | null = null\n let product_type = 'coupon'\n \n // 转换为 UTSJSONObject\n let recordObj: UTSJSONObject | null = null\n if (recordData instanceof UTSJSONObject) {\n recordObj = recordData\n } else {\n recordObj = JSON.parse(JSON.stringify(recordData)) as UTSJSONObject\n }\n \n id = recordObj.getString('id') ?? ''\n quantity = recordObj.getNumber('quantity') ?? 1\n points_used = recordObj.getNumber('points_used') ?? 0\n status = recordObj.getNumber('status') ?? 0\n tracking_no = recordObj.getString('tracking_no')\n created_at = recordObj.getString('created_at') ?? ''\n \n // 获取关联的商品信息\n const product = recordObj.get('product')\n if (product != null) {\n let productObj: UTSJSONObject | null = null\n if (product instanceof UTSJSONObject) {\n productObj = product\n } else {\n productObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject\n }\n product_name = productObj.getString('name') ?? ''\n product_image = productObj.getString('image_url')\n product_type = productObj.getString('product_type') ?? 'coupon'\n }\n \n parsed.push({\n id,\n product_name,\n product_image,\n product_type,\n quantity,\n points_used,\n status,\n tracking_no,\n created_at\n })\n }\n \n records.value = parsed\n } catch (e) {\n console.error('加载兑换记录失败:', e)\n } finally {\n loading.value = false\n }\n}\n\nconst getStatusText = (status: number): string => {\n if (status === 0) return '待处理'\n if (status === 1) return '已发货'\n if (status === 2) return '已完成'\n if (status === 3) return '已取消'\n return '未知'\n}\n\nconst getStatusClass = (status: number): string => {\n if (status === 0) return 'status-pending'\n if (status === 1) return 'status-shipped'\n if (status === 2) return 'status-completed'\n if (status === 3) return 'status-cancelled'\n return ''\n}\n\nconst formatTime = (timeStr: string): string => {\n if (timeStr == '') return ''\n const date = new Date(timeStr)\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n const hh = date.getHours().toString().padStart(2, '0')\n const mm = date.getMinutes().toString().padStart(2, '0')\n return `${y}-${m}-${d} ${hh}:${mm}`\n}\n\nonMounted(() => {\n loadRecords()\n})\n</script>\n\n<style>\n.records-page {\n flex: 1;\n height: 100%;\n background-color: #f5f5f5;\n padding: 12px;\n}\n\n.empty-state {\n padding: 60px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.empty-text {\n font-size: 14px;\n color: #999;\n}\n\n.loading-state {\n padding: 60px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.loading-text {\n font-size: 14px;\n color: #999;\n}\n\n.record-list {\n display: flex;\n flex-direction: column;\n}\n\n.record-item {\n background-color: white;\n border-radius: 8px;\n padding: 12px;\n margin-bottom: 8px;\n}\n\n.record-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n padding-bottom: 8px;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.record-product-name {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n flex: 1;\n}\n\n.record-status {\n font-size: 12px;\n padding: 4px 8px;\n border-radius: 4px;\n}\n\n.status-pending {\n background-color: #fff7e6;\n color: #d48806;\n}\n\n.status-shipped {\n background-color: #e6f7ff;\n color: #1890ff;\n}\n\n.status-completed {\n background-color: #f6ffed;\n color: #52c41a;\n}\n\n.status-cancelled {\n background-color: #f5f5f5;\n color: #999;\n}\n\n.record-info {\n display: flex;\n flex-direction: column;\n}\n\n.info-row {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding: 6px 0;\n}\n\n.info-label {\n font-size: 14px;\n color: #999;\n}\n\n.info-value {\n font-size: 14px;\n color: #333;\n}\n</style>\n","<template>\n <view class=\"reviews-page\">\n <view class=\"stats-section\" v-if=\"stats.total_count > 0\">\n <view class=\"stats-header\">\n <view class=\"stats-main\">\n <text class=\"stats-avg\">{{ stats.avg_rating }}</text>\n <text class=\"stats-label\">综合评分</text>\n </view>\n <view class=\"stats-detail\">\n <view class=\"stats-row\">\n <text class=\"stats-good\">{{ stats.good_rate }}%</text>\n <text class=\"stats-good-label\">好评率</text>\n </view>\n <view class=\"stats-row\">\n <text class=\"stats-total\">{{ stats.total_count }}</text>\n <text class=\"stats-total-label\">评价数</text>\n </view>\n </view>\n </view>\n <view class=\"rating-bars\">\n <view class=\"rating-bar\" v-for=\"i in 5\" :key=\"i\">\n <text class=\"rating-label\">{{ 6 - i }}星</text>\n <view class=\"rating-progress\">\n <view \n class=\"rating-fill\" \n :style=\"{ width: getRatingPercent(6 - i) + '%' }\"\n ></view>\n </view>\n <text class=\"rating-count\">{{ getRatingCount(6 - i) }}</text>\n </view>\n </view>\n </view>\n\n <view class=\"filter-section\">\n <scroll-view scroll-x class=\"filter-scroll\">\n <view class=\"filter-list\">\n <view \n class=\"filter-item\" \n :class=\"{ active: filterRating === 0 }\"\n @click=\"setFilterRating(0)\"\n >\n <text class=\"filter-text\">全部({{ stats.total_count }})</text>\n </view>\n <view \n class=\"filter-item\" \n :class=\"{ active: filterRating === 5 }\"\n @click=\"setFilterRating(5)\"\n >\n <text class=\"filter-text\">好评({{ getRatingCount(5) }})</text>\n </view>\n <view \n class=\"filter-item\" \n :class=\"{ active: filterRating === 4 }\"\n @click=\"setFilterRating(4)\"\n >\n <text class=\"filter-text\">中评({{ getRatingCount(4) + getRatingCount(3) }})</text>\n </view>\n <view \n class=\"filter-item\" \n :class=\"{ active: filterRating === 2 }\"\n @click=\"setFilterRating(2)\"\n >\n <text class=\"filter-text\">差评({{ getRatingCount(2) + getRatingCount(1) }})</text>\n </view>\n <view \n class=\"filter-item\" \n :class=\"{ active: hasImageFilter }\"\n @click=\"toggleHasImage\"\n >\n <text class=\"filter-text\">有图</text>\n </view>\n </view>\n </scroll-view>\n </view>\n\n <view class=\"review-list\">\n <view class=\"review-item\" v-for=\"review in reviews\" :key=\"review.id\">\n <view class=\"review-header\">\n <image \n class=\"user-avatar\" \n :src=\"review.user_avatar.length > 0 ? review.user_avatar : defaultAvatar\" \n mode=\"aspectFill\"\n />\n <view class=\"user-info\">\n <text class=\"user-name\">{{ review.user_name }}</text>\n <view class=\"rating-stars\">\n <text \n v-for=\"star in 5\" \n :key=\"star\" \n class=\"star\"\n :class=\"{ filled: star <= review.rating }\"\n >★</text>\n </view>\n </view>\n <text class=\"review-time\">{{ formatTime(review.created_at) }}</text>\n </view>\n\n <view class=\"review-content\">\n <text class=\"review-text\">{{ review.content }}</text>\n </view>\n\n <view class=\"review-images\" v-if=\"review.images.length > 0\">\n <image \n v-for=\"(img, idx) in review.images.slice(0, 3)\" \n :key=\"idx\"\n class=\"review-image\"\n :src=\"img\"\n mode=\"aspectFill\"\n @click=\"previewImage(review.images, idx)\"\n />\n <view class=\"more-images\" v-if=\"review.images.length > 3\">\n <text class=\"more-images-text\">+{{ review.images.length - 3 }}</text>\n </view>\n </view>\n\n <view class=\"review-append\" v-if=\"review.append_content\">\n <text class=\"append-label\">追评</text>\n <text class=\"append-text\">{{ review.append_content }}</text>\n <text class=\"append-time\">{{ formatTime(review.append_at) }}</text>\n </view>\n\n <view class=\"review-reply\" v-if=\"review.reply\">\n <text class=\"reply-label\">商家回复:</text>\n <text class=\"reply-text\">{{ review.reply }}</text>\n </view>\n\n <view class=\"review-footer\">\n <view \n class=\"like-btn\" \n :class=\"{ liked: review.is_liked }\"\n @click=\"toggleLike(review)\"\n >\n <text class=\"like-icon\">{{ review.is_liked ? '❤' : '♡' }}</text>\n <text class=\"like-count\">{{ review.like_count != null ? review.like_count : 0 }}</text>\n </view>\n </view>\n </view>\n </view>\n\n <view class=\"empty-state\" v-if=\"!loading && reviews.length === 0\">\n <text class=\"empty-text\">暂无评价</text>\n </view>\n\n <view class=\"loading-state\" v-if=\"loading\">\n <text class=\"loading-text\">加载中...</text>\n </view>\n\n <view class=\"load-more\" v-if=\"!loading && hasMore && reviews.length > 0\" @click=\"loadMore\">\n <text class=\"load-more-text\">加载更多</text>\n </view>\n\n <view class=\"no-more\" v-if=\"!loading && !hasMore && reviews.length > 0\">\n <text class=\"no-more-text\">没有更多了</text>\n </view>\n </view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted } from 'vue'\nimport { onLoad } from '@dcloudio/uni-app'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype ReviewItem = {\n id: string\n user_id: string\n user_name: string\n user_avatar: string\n rating: number\n content: string\n images: string[]\n is_anonymous: boolean\n like_count: number\n is_liked: boolean\n append_content: string | null\n append_at: string | null\n reply: string | null\n created_at: string\n}\n\ntype StatsType = {\n total_count: number\n avg_rating: number\n good_rate: number\n rating_distribution: Map<string, number>\n}\n\nconst productId = ref<string>('')\nconst reviews = ref<ReviewItem[]>([])\nconst stats = ref<StatsType>({\n total_count: 0,\n avg_rating: 0,\n good_rate: 0,\n rating_distribution: new Map<string, number>()\n})\nconst loading = ref<boolean>(true)\nconst hasMore = ref<boolean>(true)\nconst page = ref<number>(1)\nconst pageSize = 10\nconst filterRating = ref<number>(0)\nconst hasImageFilter = ref<boolean>(false)\n\nconst defaultAvatar: string = '/static/images/default-avatar.png'\n\nconst getRatingCount = (rating: number): number => {\n return stats.value.rating_distribution.get(rating.toString()) ?? 0\n}\n\nconst getRatingPercent = (rating: number): number => {\n if (stats.value.total_count === 0) return 0\n const count = getRatingCount(rating)\n return Math.round((count / stats.value.total_count) * 100)\n}\n\nconst loadStats = async (): Promise<void> => {\n try {\n const result = await supabaseService.getReviewStats(productId.value)\n const distMap = new Map<string, number>()\n \n const dist = result.get('rating_distribution')\n if (dist != null && dist instanceof UTSJSONObject) {\n for (let i = 1; i <= 5; i++) {\n distMap.set(i.toString(), dist.getNumber(i.toString()) ?? 0)\n }\n }\n \n const statsData: StatsType = {\n total_count: result.getNumber('total_count') ?? 0,\n avg_rating: result.getNumber('avg_rating') ?? 0,\n good_rate: result.getNumber('good_rate') ?? 0,\n rating_distribution: distMap\n }\n stats.value = statsData\n } catch (e) {\n console.error('加载统计失败:', e)\n }\n}\n\nconst loadReviews = async (pageNum: number): Promise<void> => {\n loading.value = true\n \n try {\n const result = await supabaseService.getProductReviews(\n productId.value,\n pageNum,\n pageSize,\n filterRating.value,\n hasImageFilter.value\n )\n \n const total = result.getNumber('total') ?? 0\n const data = result.get('data')\n const reviewList: ReviewItem[] = []\n \n if (data != null && Array.isArray(data)) {\n const rawList = data as any[]\n for (let i = 0; i < rawList.length; i++) {\n const item = rawList[i]\n let reviewObj: UTSJSONObject\n if (item instanceof UTSJSONObject) {\n reviewObj = item\n } else {\n reviewObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n \n let images: string[] = []\n const imagesRaw = reviewObj.get('images')\n if (imagesRaw != null && typeof imagesRaw === 'string') {\n try {\n const parsed = JSON.parse(imagesRaw as string)\n if (Array.isArray(parsed)) {\n images = parsed as string[]\n }\n } catch (e) {\n console.error('解析图片失败:', e)\n }\n }\n \n const review: ReviewItem = {\n id: reviewObj.getString('id') ?? '',\n user_id: reviewObj.getString('user_id') ?? '',\n user_name: reviewObj.getString('user_name') ?? '匿名用户',\n user_avatar: reviewObj.getString('user_avatar') ?? '',\n rating: reviewObj.getNumber('rating') ?? 5,\n content: reviewObj.getString('content') ?? '',\n images: images,\n is_anonymous: reviewObj.getBoolean('is_anonymous') ?? false,\n like_count: reviewObj.getNumber('like_count') ?? 0,\n is_liked: reviewObj.getBoolean('is_liked') ?? false,\n append_content: reviewObj.getString('append_content'),\n append_at: reviewObj.getString('append_at'),\n reply: reviewObj.getString('reply'),\n created_at: reviewObj.getString('created_at') ?? ''\n }\n reviewList.push(review)\n }\n }\n \n if (pageNum === 1) {\n reviews.value = reviewList\n } else {\n reviews.value = [...reviews.value, ...reviewList]\n }\n \n hasMore.value = reviews.value.length < total\n page.value = pageNum\n } catch (e) {\n console.error('加载评价失败:', e)\n } finally {\n loading.value = false\n }\n}\n\nconst loadMore = (): void => {\n if (!loading.value && hasMore.value) {\n loadReviews(page.value + 1)\n }\n}\n\nconst setFilterRating = (rating: number): void => {\n filterRating.value = rating\n hasImageFilter.value = false\n page.value = 1\n loadReviews(1)\n}\n\nconst toggleHasImage = (): void => {\n hasImageFilter.value = !hasImageFilter.value\n filterRating.value = 0\n page.value = 1\n loadReviews(1)\n}\n\nconst toggleLike = async (review: ReviewItem): Promise<void> => {\n try {\n const result = await supabaseService.toggleReviewLike(review.id)\n if (result.getBoolean('success') === true) {\n review.is_liked = result.getBoolean('is_liked') ?? false\n review.like_count = result.getNumber('like_count') ?? 0\n }\n } catch (e) {\n console.error('点赞失败:', e)\n }\n}\n\nconst previewImage = (images: string[], index: number): void => {\n uni.previewImage({\n urls: images,\n current: index\n })\n}\n\nconst formatTime = (timeStr: string | null): string => {\n if (timeStr == null || timeStr === '') return ''\n const date = new Date(timeStr)\n const now = new Date()\n const diff = now.getTime() - date.getTime()\n const days = Math.floor(diff / (24 * 60 * 60 * 1000))\n \n if (days === 0) {\n const hours = Math.floor(diff / (60 * 60 * 1000))\n if (hours === 0) {\n const minutes = Math.floor(diff / (60 * 1000))\n return minutes <= 1 ? '刚刚' : `${minutes}分钟前`\n }\n return `${hours}小时前`\n } else if (days < 7) {\n return `${days}天前`\n } else {\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n return `${y}-${m}-${d}`\n }\n}\n\nonLoad((options) => {\n if (options != null) {\n const idVal = options['product_id']\n if (idVal != null) {\n productId.value = idVal as string\n loadStats()\n loadReviews(1)\n }\n }\n})\n</script>\n\n<style>\n.reviews-page {\n flex: 1;\n background-color: #f5f5f5;\n}\n\n.stats-section {\n background-color: white;\n padding: 16px;\n margin-bottom: 8px;\n}\n\n.stats-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.stats-main {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.stats-avg {\n font-size: 36px;\n font-weight: bold;\n color: #ff6b35;\n}\n\n.stats-label {\n font-size: 12px;\n color: #999;\n}\n\n.stats-detail {\n display: flex;\n flex-direction: row;\n}\n\n.stats-row {\n display: flex;\n flex-direction: column;\n align-items: center;\n margin-left: 24px;\n}\n\n.stats-good {\n font-size: 18px;\n font-weight: bold;\n color: #ff6b35;\n}\n\n.stats-good-label {\n font-size: 12px;\n color: #999;\n}\n\n.stats-total {\n font-size: 18px;\n font-weight: bold;\n color: #333;\n}\n\n.stats-total-label {\n font-size: 12px;\n color: #999;\n}\n\n.rating-bars {\n display: flex;\n flex-direction: column;\n}\n\n.rating-bar {\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-bottom: 4px;\n}\n\n.rating-label {\n font-size: 12px;\n color: #999;\n width: 30px;\n}\n\n.rating-progress {\n flex: 1;\n height: 6px;\n background-color: #f0f0f0;\n border-radius: 3px;\n margin: 0 8px;\n overflow: hidden;\n}\n\n.rating-fill {\n height: 100%;\n background-color: #ff6b35;\n border-radius: 3px;\n}\n\n.rating-count {\n font-size: 12px;\n color: #999;\n width: 30px;\n text-align: right;\n}\n\n.filter-section {\n background-color: white;\n margin-bottom: 8px;\n}\n\n.filter-scroll {\n white-space: nowrap;\n}\n\n.filter-list {\n display: flex;\n flex-direction: row;\n padding: 12px 16px;\n}\n\n.filter-item {\n padding: 6px 16px;\n background-color: #f5f5f5;\n border-radius: 16px;\n margin-right: 12px;\n}\n\n.filter-item.active {\n background-color: #fff5f0;\n}\n\n.filter-text {\n font-size: 13px;\n color: #666;\n}\n\n.filter-item.active .filter-text {\n color: #ff6b35;\n}\n\n.review-list {\n background-color: white;\n}\n\n.review-item {\n padding: 16px;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.review-header {\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.user-avatar {\n width: 36px;\n height: 36px;\n border-radius: 18px;\n}\n\n.user-info {\n flex: 1;\n margin-left: 10px;\n display: flex;\n flex-direction: column;\n}\n\n.user-name {\n font-size: 14px;\n color: #333;\n}\n\n.rating-stars {\n display: flex;\n flex-direction: row;\n margin-top: 2px;\n}\n\n.star {\n font-size: 12px;\n color: #ddd;\n}\n\n.star.filled {\n color: #ff6b35;\n}\n\n.review-time {\n font-size: 12px;\n color: #999;\n}\n\n.review-content {\n margin-bottom: 12px;\n}\n\n.review-text {\n font-size: 14px;\n color: #333;\n line-height: 20px;\n}\n\n.review-images {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n margin-bottom: 12px;\n}\n\n.review-image {\n width: 80px;\n height: 80px;\n border-radius: 4px;\n margin-right: 8px;\n margin-bottom: 8px;\n}\n\n.more-images {\n width: 80px;\n height: 80px;\n border-radius: 4px;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.more-images-text {\n font-size: 14px;\n color: white;\n}\n\n.review-append {\n background-color: #f9f9f9;\n padding: 12px;\n border-radius: 4px;\n margin-bottom: 12px;\n}\n\n.append-label {\n font-size: 12px;\n color: #ff6b35;\n margin-right: 8px;\n}\n\n.append-text {\n font-size: 14px;\n color: #666;\n}\n\n.append-time {\n font-size: 12px;\n color: #999;\n margin-top: 8px;\n display: flex;\n}\n\n.review-reply {\n background-color: #f5f5f5;\n padding: 12px;\n border-radius: 4px;\n margin-bottom: 12px;\n}\n\n.reply-label {\n font-size: 12px;\n color: #999;\n}\n\n.reply-text {\n font-size: 14px;\n color: #666;\n}\n\n.review-footer {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n}\n\n.like-btn {\n display: flex;\n flex-direction: row;\n align-items: center;\n padding: 4px 12px;\n}\n\n.like-btn.liked .like-icon {\n color: #ff6b35;\n}\n\n.like-icon {\n font-size: 16px;\n color: #999;\n margin-right: 4px;\n}\n\n.like-count {\n font-size: 12px;\n color: #999;\n}\n\n.empty-state {\n padding: 60px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: white;\n}\n\n.empty-text {\n font-size: 14px;\n color: #999;\n}\n\n.loading-state {\n padding: 30px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: white;\n}\n\n.loading-text {\n font-size: 14px;\n color: #999;\n}\n\n.load-more {\n padding: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: white;\n}\n\n.load-more-text {\n font-size: 14px;\n color: #666;\n}\n\n.no-more {\n padding: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: white;\n}\n\n.no-more-text {\n font-size: 12px;\n color: #999;\n}\n</style>\n","<template>\n <view class=\"my-reviews-page\">\n <view class=\"tabs\">\n <view \n class=\"tab-item\" \n :class=\"{ active: activeTab === 'published' }\"\n @click=\"switchTab('published')\"\n >\n <text class=\"tab-text\">已评价</text>\n </view>\n <view \n class=\"tab-item\" \n :class=\"{ active: activeTab === 'pending' }\"\n @click=\"switchTab('pending')\"\n >\n <text class=\"tab-text\">待评价</text>\n </view>\n </view>\n\n <view class=\"review-list\" v-if=\"activeTab === 'published'\">\n <view class=\"review-item\" v-for=\"review in reviews\" :key=\"review.id\">\n <view class=\"product-info\" @click=\"goToProduct(review.product_id)\">\n <image \n class=\"product-image\" \n :src=\"review.product_image.length > 0 ? review.product_image : defaultImage\" \n mode=\"aspectFill\"\n />\n <view class=\"product-detail\">\n <text class=\"product-name\">{{ review.product_name }}</text>\n <view class=\"rating-row\">\n <view class=\"rating-stars\">\n <text \n v-for=\"star in 5\" \n :key=\"star\" \n class=\"star\"\n :class=\"{ filled: star <= review.rating }\"\n >★</text>\n </view>\n <text class=\"review-time\">{{ formatTime(review.created_at) }}</text>\n </view>\n </view>\n </view>\n\n <view class=\"review-content\">\n <text class=\"review-text\">{{ review.content }}</text>\n </view>\n\n <view class=\"review-images\" v-if=\"review.images.length > 0\">\n <image \n v-for=\"(img, idx) in review.images.slice(0, 4)\" \n :key=\"idx\"\n class=\"review-image\"\n :src=\"img\"\n mode=\"aspectFill\"\n @click=\"previewImage(review.images, idx)\"\n />\n </view>\n\n <view class=\"review-append\" v-if=\"review.append_content\">\n <text class=\"append-label\">追评:</text>\n <text class=\"append-text\">{{ review.append_content }}</text>\n </view>\n\n <view class=\"review-actions\">\n <view \n class=\"action-btn append\" \n v-if=\"review.can_append\"\n @click=\"showAppendPopup(review)\"\n >\n <text class=\"action-text\">追加评价</text>\n </view>\n <view \n class=\"action-btn delete\" \n @click=\"confirmDelete(review)\"\n >\n <text class=\"action-text\">删除</text>\n </view>\n </view>\n </view>\n </view>\n\n <view class=\"pending-list\" v-if=\"activeTab === 'pending'\">\n <view class=\"pending-item\" v-for=\"item in pendingItems\" :key=\"item.order_id\">\n <view class=\"product-info\">\n <image \n class=\"product-image\" \n :src=\"item.product_image.length > 0 ? item.product_image : defaultImage\" \n mode=\"aspectFill\"\n />\n <view class=\"product-detail\">\n <text class=\"product-name\">{{ item.product_name }}</text>\n <text class=\"order-time\">下单时间:{{ formatTime(item.order_time) }}</text>\n </view>\n </view>\n <view class=\"pending-actions\">\n <button class=\"review-btn\" @click=\"goToReview(item)\">去评价</button>\n </view>\n </view>\n </view>\n\n <view class=\"empty-state\" v-if=\"!loading && ((activeTab === 'published' && reviews.length === 0) || (activeTab === 'pending' && pendingItems.length === 0))\">\n <text class=\"empty-text\">{{ activeTab === 'published' ? '暂无评价记录' : '暂无待评价商品' }}</text>\n </view>\n\n <view class=\"loading-state\" v-if=\"loading\">\n <text class=\"loading-text\">加载中...</text>\n </view>\n\n <view class=\"append-popup\" v-if=\"showAppendModal\" @click=\"closeAppendPopup\">\n <view class=\"popup-content\" @click.stop>\n <view class=\"popup-header\">\n <text class=\"popup-title\">追加评价</text>\n <text class=\"popup-close\" @click=\"closeAppendPopup\">×</text>\n </view>\n \n <textarea \n class=\"append-input\"\n v-model=\"appendContent\"\n placeholder=\"请输入追加评价内容\"\n :maxlength=\"500\"\n />\n \n <view class=\"popup-footer\">\n <button class=\"cancel-btn\" @click=\"closeAppendPopup\">取消</button>\n <button class=\"submit-btn\" @click=\"submitAppend\">提交</button>\n </view>\n </view>\n </view>\n </view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted } from 'vue'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype MyReviewItem = {\n id: string\n product_id: string\n product_name: string\n product_image: string\n rating: number\n content: string\n images: string[]\n append_content: string | null\n can_append: boolean\n can_edit: boolean\n created_at: string\n}\n\ntype PendingItem = {\n order_id: string\n product_id: string\n product_name: string\n product_image: string\n order_time: string\n}\n\nconst activeTab = ref<string>('published')\nconst reviews = ref<MyReviewItem[]>([])\nconst pendingItems = ref<PendingItem[]>([])\nconst loading = ref<boolean>(true)\nconst showAppendModal = ref<boolean>(false)\nconst appendContent = ref<string>('')\nconst selectedReview = ref<MyReviewItem | null>(null)\n\nconst defaultImage: string = '/static/images/default-product.png'\n\nconst loadReviews = async (): Promise<void> => {\n loading.value = true\n try {\n const result = await supabaseService.getMyReviews()\n const parsed: MyReviewItem[] = []\n \n for (let i = 0; i < result.length; i++) {\n const item = result[i]\n let reviewObj: UTSJSONObject\n if (item instanceof UTSJSONObject) {\n reviewObj = item\n } else {\n reviewObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n \n let images: string[] = []\n const imagesRaw = reviewObj.get('images')\n if (imagesRaw != null && typeof imagesRaw === 'string') {\n try {\n const parsedImages = JSON.parse(imagesRaw as string)\n if (Array.isArray(parsedImages)) {\n images = parsedImages as string[]\n }\n } catch (e) {\n console.error('解析图片失败:', e)\n }\n }\n \n const review: MyReviewItem = {\n id: reviewObj.getString('id') ?? '',\n product_id: reviewObj.getString('product_id') ?? '',\n product_name: reviewObj.getString('product_name') ?? '',\n product_image: reviewObj.getString('product_image') ?? '',\n rating: reviewObj.getNumber('rating') ?? 5,\n content: reviewObj.getString('content') ?? '',\n images: images,\n append_content: reviewObj.getString('append_content'),\n can_append: reviewObj.getBoolean('can_append') ?? false,\n can_edit: reviewObj.getBoolean('can_edit') ?? false,\n created_at: reviewObj.getString('created_at') ?? ''\n }\n parsed.push(review)\n }\n \n reviews.value = parsed\n } catch (e) {\n console.error('加载评价失败:', e)\n } finally {\n loading.value = false\n }\n}\n\nconst loadPendingItems = async (): Promise<void> => {\n loading.value = true\n try {\n const orders = await supabaseService.getOrders(4)\n const pending: PendingItem[] = []\n \n for (let i = 0; i < orders.length; i++) {\n const order = orders[i]\n let orderObj: UTSJSONObject\n if (order instanceof UTSJSONObject) {\n orderObj = order\n } else {\n orderObj = JSON.parse(JSON.stringify(order)) as UTSJSONObject\n }\n \n const orderId = orderObj.getString('id') ?? ''\n const itemsRaw = orderObj.get('items')\n \n if (itemsRaw != null && Array.isArray(itemsRaw)) {\n const items = itemsRaw as any[]\n for (let j = 0; j < items.length; j++) {\n const orderItem = items[j]\n let itemObj: UTSJSONObject\n if (orderItem instanceof UTSJSONObject) {\n itemObj = orderItem\n } else {\n itemObj = JSON.parse(JSON.stringify(orderItem)) as UTSJSONObject\n }\n \n pending.push({\n order_id: orderId,\n product_id: itemObj.getString('product_id') ?? '',\n product_name: itemObj.getString('product_name') ?? '',\n product_image: itemObj.getString('product_image') ?? '',\n order_time: orderObj.getString('created_at') ?? ''\n })\n }\n }\n }\n \n pendingItems.value = pending\n } catch (e) {\n console.error('加载待评价商品失败:', e)\n } finally {\n loading.value = false\n }\n}\n\nconst switchTab = (tab: string): void => {\n activeTab.value = tab\n if (tab === 'published' && reviews.value.length === 0) {\n loadReviews()\n } else if (tab === 'pending' && pendingItems.value.length === 0) {\n loadPendingItems()\n }\n}\n\nconst goToProduct = (productId: string): void => {\n uni.navigateTo({\n url: `/pages/mall/consumer/product-detail?id=${productId}`\n })\n}\n\nconst goToReview = (item: PendingItem): void => {\n uni.navigateTo({\n url: `/pages/mall/consumer/review?order_id=${item.order_id}`\n })\n}\n\nconst showAppendPopup = (review: MyReviewItem): void => {\n selectedReview.value = review\n appendContent.value = ''\n showAppendModal.value = true\n}\n\nconst closeAppendPopup = (): void => {\n showAppendModal.value = false\n selectedReview.value = null\n appendContent.value = ''\n}\n\nconst submitAppend = async (): Promise<void> => {\n if (selectedReview.value == null || appendContent.value.trim() === '') {\n uni.showToast({ title: '请输入评价内容', icon: 'none' })\n return\n }\n \n uni.showLoading({ title: '提交中...' })\n \n try {\n const success = await supabaseService.appendReview(\n selectedReview.value.id,\n appendContent.value.trim(),\n []\n )\n \n if (success) {\n selectedReview.value.append_content = appendContent.value.trim()\n selectedReview.value.can_append = false\n closeAppendPopup()\n uni.showToast({ title: '追加成功', icon: 'success' })\n } else {\n uni.showToast({ title: '追加失败', icon: 'none' })\n }\n } catch (e) {\n console.error('追加评价失败:', e)\n uni.showToast({ title: '追加失败', icon: 'none' })\n } finally {\n uni.hideLoading()\n }\n}\n\nconst doDelete = async (review: MyReviewItem): Promise<void> => {\n uni.showLoading({ title: '删除中...' })\n \n try {\n const success = await supabaseService.deleteReview(review.id)\n if (success) {\n const index = reviews.value.indexOf(review)\n if (index > -1) {\n reviews.value.splice(index, 1)\n }\n uni.showToast({ title: '删除成功', icon: 'success' })\n } else {\n uni.showToast({ title: '删除失败', icon: 'none' })\n }\n } catch (e) {\n console.error('删除评价失败:', e)\n uni.showToast({ title: '删除失败', icon: 'none' })\n } finally {\n uni.hideLoading()\n }\n}\n\nconst confirmDelete = (review: MyReviewItem): void => {\n uni.showModal({\n title: '提示',\n content: '确定要删除这条评价吗?',\n success: (res) => {\n if (res.confirm) {\n doDelete(review)\n }\n }\n })\n}\n\nconst previewImage = (images: string[], index: number): void => {\n uni.previewImage({\n urls: images,\n current: index\n })\n}\n\nconst formatTime = (timeStr: string | null): string => {\n if (timeStr == null || timeStr === '') return ''\n const date = new Date(timeStr)\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n return `${y}-${m}-${d}`\n}\n\nonMounted(() => {\n loadReviews()\n})\n</script>\n\n<style>\n.my-reviews-page {\n flex: 1;\n background-color: #f5f5f5;\n}\n\n.tabs {\n display: flex;\n flex-direction: row;\n background-color: white;\n}\n\n.tab-item {\n flex: 1;\n padding: 14px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-bottom: 2px solid transparent;\n}\n\n.tab-item.active {\n border-bottom-color: #ff6b35;\n}\n\n.tab-text {\n font-size: 15px;\n color: #666;\n}\n\n.tab-item.active .tab-text {\n color: #ff6b35;\n font-weight: bold;\n}\n\n.review-list {\n padding: 8px;\n}\n\n.review-item {\n background-color: white;\n border-radius: 8px;\n padding: 12px;\n margin-bottom: 8px;\n}\n\n.product-info {\n display: flex;\n flex-direction: row;\n}\n\n.product-image {\n width: 60px;\n height: 60px;\n border-radius: 4px;\n}\n\n.product-detail {\n flex: 1;\n margin-left: 10px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.product-name {\n font-size: 14px;\n color: #333;\n lines: 2;\n text-overflow: ellipsis;\n}\n\n.rating-row {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-top: 6px;\n}\n\n.rating-stars {\n display: flex;\n flex-direction: row;\n}\n\n.star {\n font-size: 12px;\n color: #ddd;\n}\n\n.star.filled {\n color: #ff6b35;\n}\n\n.review-time {\n font-size: 12px;\n color: #999;\n}\n\n.review-content {\n margin-top: 10px;\n}\n\n.review-text {\n font-size: 14px;\n color: #333;\n line-height: 20px;\n}\n\n.review-images {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n margin-top: 10px;\n}\n\n.review-image {\n width: 70px;\n height: 70px;\n border-radius: 4px;\n margin-right: 8px;\n margin-bottom: 8px;\n}\n\n.review-append {\n background-color: #f9f9f9;\n padding: 10px;\n border-radius: 4px;\n margin-top: 10px;\n}\n\n.append-label {\n font-size: 12px;\n color: #ff6b35;\n}\n\n.append-text {\n font-size: 14px;\n color: #666;\n}\n\n.review-actions {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid #f0f0f0;\n}\n\n.action-btn {\n padding: 6px 16px;\n border-radius: 16px;\n margin-left: 10px;\n}\n\n.action-btn.append {\n background-color: #fff5f0;\n}\n\n.action-btn.append .action-text {\n color: #ff6b35;\n}\n\n.action-btn.delete {\n background-color: #f5f5f5;\n}\n\n.action-btn.delete .action-text {\n color: #999;\n}\n\n.action-text {\n font-size: 13px;\n}\n\n.pending-list {\n padding: 8px;\n}\n\n.pending-item {\n background-color: white;\n border-radius: 8px;\n padding: 12px;\n margin-bottom: 8px;\n}\n\n.pending-actions {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid #f0f0f0;\n}\n\n.review-btn {\n background-color: #ff6b35;\n color: white;\n font-size: 14px;\n border-radius: 16px;\n padding: 0 20px;\n height: 32px;\n line-height: 32px;\n}\n\n.order-time {\n font-size: 12px;\n color: #999;\n margin-top: 4px;\n}\n\n.empty-state {\n padding: 60px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.empty-text {\n font-size: 14px;\n color: #999;\n}\n\n.loading-state {\n padding: 30px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.loading-text {\n font-size: 14px;\n color: #999;\n}\n\n.append-popup {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 1000;\n}\n\n.popup-content {\n background-color: white;\n border-radius: 16px 16px 0 0;\n width: 100%;\n padding: 16px;\n}\n\n.popup-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.popup-title {\n font-size: 18px;\n font-weight: bold;\n color: #333;\n}\n\n.popup-close {\n font-size: 24px;\n color: #999;\n}\n\n.append-input {\n width: 100%;\n height: 120px;\n background-color: #f9f9f9;\n border-radius: 8px;\n padding: 12px;\n font-size: 14px;\n}\n\n.popup-footer {\n display: flex;\n flex-direction: row;\n margin-top: 16px;\n}\n\n.cancel-btn {\n flex: 1;\n background-color: #f5f5f5;\n color: #666;\n font-size: 16px;\n border-radius: 24px;\n height: 44px;\n line-height: 44px;\n margin-right: 10px;\n}\n\n.submit-btn {\n flex: 1;\n background-color: #ff6b35;\n color: white;\n font-size: 16px;\n border-radius: 24px;\n height: 44px;\n line-height: 44px;\n}\n</style>\n","<template>\n <scroll-view class=\"balance-page\" scroll-y>\n <view class=\"balance-header\">\n <view class=\"balance-info\">\n <text class=\"balance-label\">账户余额(元)</text>\n <text class=\"balance-value\">{{ balance }}</text>\n </view>\n <view class=\"balance-tips\">\n <text class=\"tips-text\">余额来源于免单奖励,请联系商家微信提现</text>\n </view>\n </view>\n\n <view class=\"stats-section\">\n <view class=\"stat-item\">\n <text class=\"stat-value\">{{ totalEarned }}</text>\n <text class=\"stat-label\">累计获得</text>\n </view>\n <view class=\"stat-divider\"></view>\n <view class=\"stat-item\">\n <text class=\"stat-value\">{{ totalWithdrawn }}</text>\n <text class=\"stat-label\">已提现</text>\n </view>\n </view>\n\n <view class=\"records-section\">\n <view class=\"section-header\">\n <text class=\"section-title\">余额明细</text>\n </view>\n\n <view v-if=\"loading\" class=\"loading-state\">\n <text class=\"loading-text\">加载中...</text>\n </view>\n\n <view v-else-if=\"records.length === 0\" class=\"empty-state\">\n <text class=\"empty-text\">暂无余额记录</text>\n </view>\n\n <view v-else class=\"record-list\">\n <view class=\"record-item\" v-for=\"record in records\" :key=\"record.id\">\n <view class=\"record-left\">\n <text class=\"record-type\">{{ getTypeText(record.type) }}</text>\n <text class=\"record-time\">{{ formatTime(record.created_at) }}</text>\n </view>\n <view class=\"record-right\">\n <text class=\"record-amount\" :class=\"record.amount > 0 ? 'positive' : 'negative'\">\n {{ record.amount > 0 ? '+' : '' }}{{ record.amount }}\n </text>\n <text class=\"record-balance\">余额: {{ record.balance_after }}</text>\n </view>\n </view>\n </view>\n </view>\n\n <view class=\"withdraw-section\">\n <button class=\"withdraw-btn\" @click=\"showWithdrawTips\">\n <text class=\"btn-text\">申请提现</text>\n </button>\n <text class=\"withdraw-tip\">提现请联系商家微信处理</text>\n </view>\n </scroll-view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted } from 'vue'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype BalanceRecord = {\n id: string\n type: string\n amount: number\n balance_before: number\n balance_after: number\n description: string | null\n created_at: string\n}\n\nconst balance = ref<number>(0)\nconst totalEarned = ref<number>(0)\nconst totalWithdrawn = ref<number>(0)\nconst records = ref<BalanceRecord[]>([])\nconst loading = ref<boolean>(true)\n\nconst loadBalance = async (): Promise<void> => {\n try {\n const result = await supabaseService.getUserBalance()\n balance.value = result.getNumber('balance') ?? 0\n totalEarned.value = result.getNumber('total_earned') ?? 0\n totalWithdrawn.value = result.getNumber('total_withdrawn') ?? 0\n } catch (e) {\n console.error('加载余额失败:', e)\n }\n}\n\nconst loadRecords = async (): Promise<void> => {\n loading.value = true\n try {\n const result = await supabaseService.getBalanceRecords(1, 50)\n const parsed: BalanceRecord[] = []\n \n for (let i = 0; i < result.length; i++) {\n const item = result[i]\n \n let id = ''\n let type = ''\n let amount = 0\n let balanceBefore = 0\n let balanceAfter = 0\n let description: string | null = null\n let createdAt = ''\n \n let itemObj: UTSJSONObject | null = null\n if (item instanceof UTSJSONObject) {\n itemObj = item\n } else {\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n \n id = itemObj.getString('id') ?? ''\n type = itemObj.getString('type') ?? ''\n amount = itemObj.getNumber('amount') ?? 0\n balanceBefore = itemObj.getNumber('balance_before') ?? 0\n balanceAfter = itemObj.getNumber('balance_after') ?? 0\n description = itemObj.getString('description')\n createdAt = itemObj.getString('created_at') ?? ''\n \n parsed.push({\n id,\n type,\n amount,\n balance_before: balanceBefore,\n balance_after: balanceAfter,\n description,\n created_at: createdAt\n })\n }\n \n records.value = parsed\n } catch (e) {\n console.error('加载余额记录失败:', e)\n } finally {\n loading.value = false\n }\n}\n\nconst loadData = async (): Promise<void> => {\n await Promise.all([\n loadBalance(),\n loadRecords()\n ])\n}\n\nconst getTypeText = (type: string): string => {\n if (type === 'free_order') return '免单奖励'\n if (type === 'rebate') return '返利'\n if (type === 'withdraw') return '提现'\n if (type === 'clear') return '余额清零'\n if (type === 'manual') return '手动调整'\n return '余额变动'\n}\n\nconst formatTime = (timeStr: string): string => {\n if (timeStr === '') return ''\n const date = new Date(timeStr)\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n const hh = date.getHours().toString().padStart(2, '0')\n const mm = date.getMinutes().toString().padStart(2, '0')\n return `${y}-${m}-${d} ${hh}:${mm}`\n}\n\nconst showWithdrawTips = (): void => {\n uni.showModal({\n title: '提现说明',\n content: '请添加商家微信进行提现处理,商家确认后将通过微信转账给您。',\n showCancel: false,\n confirmText: '我知道了'\n })\n}\n\nonMounted(() => {\n loadData()\n})\n</script>\n\n<style>\n.balance-page {\n flex: 1;\n height: 100%;\n background-color: #f5f5f5;\n}\n\n.balance-header {\n background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);\n padding: 30px 20px;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.balance-info {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.balance-label {\n font-size: 14px;\n color: rgba(255, 255, 255, 0.8);\n margin-bottom: 8px;\n}\n\n.balance-value {\n font-size: 42px;\n font-weight: bold;\n color: white;\n}\n\n.balance-tips {\n margin-top: 16px;\n padding: 8px 16px;\n background-color: rgba(255, 255, 255, 0.2);\n border-radius: 16px;\n}\n\n.tips-text {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.9);\n}\n\n.stats-section {\n display: flex;\n flex-direction: row;\n background-color: white;\n padding: 20px 0;\n margin-bottom: 8px;\n}\n\n.stat-item {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.stat-value {\n font-size: 24px;\n font-weight: bold;\n color: #333;\n}\n\n.stat-label {\n font-size: 12px;\n color: #999;\n margin-top: 4px;\n}\n\n.stat-divider {\n width: 1px;\n height: 40px;\n background-color: #f0f0f0;\n}\n\n.records-section {\n background-color: white;\n padding: 0 16px;\n min-height: 200px;\n}\n\n.section-header {\n padding: 16px 0;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.section-title {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n}\n\n.loading-state {\n padding: 40px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.loading-text {\n font-size: 14px;\n color: #999;\n}\n\n.empty-state {\n padding: 40px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.empty-text {\n font-size: 14px;\n color: #999;\n}\n\n.record-list {\n display: flex;\n flex-direction: column;\n}\n\n.record-item {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n padding: 16px 0;\n border-bottom: 1px solid #f9f9f9;\n}\n\n.record-left {\n display: flex;\n flex-direction: column;\n}\n\n.record-type {\n font-size: 15px;\n color: #333;\n margin-bottom: 4px;\n}\n\n.record-time {\n font-size: 12px;\n color: #999;\n}\n\n.record-right {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n}\n\n.record-amount {\n font-size: 18px;\n font-weight: bold;\n margin-bottom: 4px;\n}\n\n.record-amount.positive {\n color: #ff6b35;\n}\n\n.record-amount.negative {\n color: #333;\n}\n\n.record-balance {\n font-size: 12px;\n color: #999;\n}\n\n.withdraw-section {\n padding: 20px 16px;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.withdraw-btn {\n width: 100%;\n height: 44px;\n background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);\n border-radius: 22px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.btn-text {\n font-size: 16px;\n font-weight: bold;\n color: white;\n}\n\n.withdraw-tip {\n font-size: 12px;\n color: #999;\n margin-top: 12px;\n}\n</style>\n","<template>\n <scroll-view class=\"share-page\" direction=\"vertical\">\n <view class=\"share-summary\">\n <view class=\"summary-item\">\n <text class=\"summary-value\">{{ totalShares }}</text>\n <text class=\"summary-label\">分享次数</text>\n </view>\n <view class=\"summary-divider\"></view>\n <view class=\"summary-item\">\n <text class=\"summary-value\">{{ completedShares }}</text>\n <text class=\"summary-label\">免单成功</text>\n </view>\n <view class=\"summary-divider\"></view>\n <view class=\"summary-item\">\n <text class=\"summary-value\">{{ totalReward }}</text>\n <text class=\"summary-label\">累计奖励(元)</text>\n </view>\n </view>\n\n <view class=\"rules-section\">\n <view class=\"rules-header\" @click=\"toggleRules\">\n <text class=\"rules-title\">免单规则</text>\n <text class=\"rules-arrow\">{{ showRules ? '▲' : '▼' }}</text>\n </view>\n <view class=\"rules-content\" v-if=\"showRules\">\n <text class=\"rules-text\">1. 购买商品后可生成分享链接</text>\n <text class=\"rules-text\">2. 分享给好友,好友通过链接购买</text>\n <text class=\"rules-text\">3. 累计4人购买后即可免单</text>\n <text class=\"rules-text\">4. 免单金额存入余额,可联系商家提现</text>\n </view>\n </view>\n\n <view class=\"share-list-section\">\n <view class=\"section-header\">\n <text class=\"section-title\">我的分享</text>\n </view>\n\n <view v-if=\"loading\" class=\"loading-state\">\n <text class=\"loading-text\">加载中...</text>\n </view>\n\n <view v-else-if=\"shares.length === 0\" class=\"empty-state\">\n <text class=\"empty-text\">暂无分享记录</text>\n <text class=\"empty-tip\">购买商品后可以分享免单哦~</text>\n </view>\n\n <view v-else class=\"share-list\">\n <view class=\"share-item\" v-for=\"share in shares\" :key=\"share.id\" @click=\"goToShareDetail(share.id)\">\n <image class=\"product-image\" :src=\"share.product_image != null && share.product_image.length > 0 ? share.product_image : defaultImage\" mode=\"aspectFill\" />\n <view class=\"share-info\">\n <text class=\"product-name\">{{ share.product_name }}</text>\n <view class=\"progress-section\">\n <view class=\"progress-bar\">\n <view class=\"progress-fill\" :style=\"{ width: getProgressPercent(share.current_count, share.required_count) + '%' }\"></view>\n </view>\n <text class=\"progress-text\">{{ share.current_count }}/{{ share.required_count }}</text>\n </view>\n <view class=\"share-bottom\">\n <text class=\"share-code\">分享码: {{ share.share_code }}</text>\n <text class=\"share-status\" :class=\"getStatusClass(share.status)\">{{ getStatusText(share.status) }}</text>\n </view>\n </view>\n <text class=\"share-arrow\"></text>\n </view>\n </view>\n </view>\n </scroll-view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted, computed } from 'vue'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype ShareRecord = {\n id: string\n product_id: string\n product_name: string\n product_image: string | null\n product_price: number\n share_code: string\n required_count: number\n current_count: number\n status: number\n reward_amount: number | null\n created_at: string\n}\n\nconst shares = ref<ShareRecord[]>([])\nconst loading = ref<boolean>(true)\nconst showRules = ref<boolean>(false)\nconst defaultImage: string = '/static/images/default-product.png'\n\nconst totalShares = computed((): number => shares.value.length)\n\nconst completedShares = computed((): number => {\n let count = 0\n for (let i = 0; i < shares.value.length; i++) {\n if (shares.value[i].status === 1) count++\n }\n return count\n})\n\nconst totalReward = computed((): number => {\n let total = 0\n for (let i = 0; i < shares.value.length; i++) {\n if (shares.value[i].reward_amount != null) {\n total += shares.value[i].reward_amount!\n }\n }\n return total\n})\n\nconst loadShares = async (): Promise<void> => {\n loading.value = true\n try {\n const result = await supabaseService.getMyShareRecords()\n const parsed: ShareRecord[] = []\n \n for (let i = 0; i < result.length; i++) {\n const item = result[i]\n let itemObj: UTSJSONObject | null = null\n if (item instanceof UTSJSONObject) {\n itemObj = item\n } else {\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n \n parsed.push({\n id: itemObj.getString('id') ?? '',\n product_id: itemObj.getString('product_id') ?? '',\n product_name: itemObj.getString('product_name') ?? '',\n product_image: itemObj.getString('product_image'),\n product_price: itemObj.getNumber('product_price') ?? 0,\n share_code: itemObj.getString('share_code') ?? '',\n required_count: itemObj.getNumber('required_count') ?? 4,\n current_count: itemObj.getNumber('current_count') ?? 0,\n status: itemObj.getNumber('status') ?? 0,\n reward_amount: itemObj.getNumber('reward_amount'),\n created_at: itemObj.getString('created_at') ?? ''\n })\n }\n \n shares.value = parsed\n } catch (e) {\n console.error('加载分享记录失败:', e)\n } finally {\n loading.value = false\n }\n}\n\nconst toggleRules = (): void => {\n showRules.value = !showRules.value\n}\n\nconst getProgressPercent = (current: number, required: number): number => {\n if (required <= 0) return 0\n return Math.min(100, Math.round((current / required) * 100))\n}\n\nconst getStatusText = (status: number): string => {\n if (status === 0) return '进行中'\n if (status === 1) return '已免单'\n if (status === 2) return '已失效'\n if (status === 3) return '已过期'\n return '未知'\n}\n\nconst getStatusClass = (status: number): string => {\n if (status === 0) return 'status-progress'\n if (status === 1) return 'status-completed'\n if (status === 2) return 'status-invalid'\n if (status === 3) return 'status-expired'\n return ''\n}\n\nconst goToShareDetail = (shareId: string): void => {\n uni.navigateTo({\n url: `/pages/mall/consumer/share/detail?id=${shareId}`\n })\n}\n\nonMounted(() => {\n loadShares()\n})\n</script>\n\n<style>\n.share-page {\n flex: 1;\n height: 100%;\n background-color: #f5f5f5;\n}\n\n.share-summary {\n display: flex;\n flex-direction: row;\n background-color: white;\n padding: 20px 0;\n margin-bottom: 8px;\n}\n\n.summary-item {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.summary-value {\n font-size: 24px;\n font-weight: bold;\n color: #ff6b35;\n}\n\n.summary-label {\n font-size: 12px;\n color: #999;\n margin-top: 4px;\n}\n\n.summary-divider {\n width: 1px;\n height: 40px;\n background-color: #f0f0f0;\n}\n\n.rules-section {\n background-color: white;\n margin-bottom: 8px;\n}\n\n.rules-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding: 16px;\n}\n\n.rules-title {\n font-size: 15px;\n font-weight: bold;\n color: #333;\n}\n\n.rules-arrow {\n font-size: 12px;\n color: #999;\n}\n\n.rules-content {\n padding: 0 16px 16px;\n display: flex;\n flex-direction: column;\n}\n\n.rules-text {\n font-size: 13px;\n color: #666;\n line-height: 24px;\n}\n\n.share-list-section {\n background-color: white;\n}\n\n.section-header {\n padding: 16px;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.section-title {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n}\n\n.loading-state {\n padding: 40px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.loading-text {\n font-size: 14px;\n color: #999;\n}\n\n.empty-state {\n padding: 60px 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.empty-text {\n font-size: 14px;\n color: #999;\n}\n\n.empty-tip {\n font-size: 12px;\n color: #ccc;\n margin-top: 8px;\n}\n\n.share-list {\n display: flex;\n flex-direction: column;\n}\n\n.share-item {\n display: flex;\n flex-direction: row;\n padding: 16px;\n border-bottom: 1px solid #f9f9f9;\n}\n\n.product-image {\n width: 80px;\n height: 80px;\n border-radius: 8px;\n}\n\n.share-info {\n flex: 1;\n margin-left: 12px;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n}\n\n.product-name {\n font-size: 14px;\n color: #333;\n lines: 2;\n}\n\n.progress-section {\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-top: 8px;\n}\n\n.progress-bar {\n flex: 1;\n height: 6px;\n background-color: #f0f0f0;\n border-radius: 3px;\n overflow: hidden;\n}\n\n.progress-fill {\n height: 100%;\n background-color: #ff6b35;\n border-radius: 3px;\n}\n\n.progress-text {\n font-size: 12px;\n color: #ff6b35;\n margin-left: 8px;\n}\n\n.share-bottom {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-top: 8px;\n}\n\n.share-code {\n font-size: 12px;\n color: #999;\n}\n\n.share-status {\n font-size: 12px;\n padding: 2px 8px;\n border-radius: 4px;\n}\n\n.status-progress {\n background-color: #fff5f0;\n color: #ff6b35;\n}\n\n.status-completed {\n background-color: #f6ffed;\n color: #52c41a;\n}\n\n.status-invalid {\n background-color: #f5f5f5;\n color: #999;\n}\n\n.status-expired {\n background-color: #fff1f0;\n color: #ff4d4f;\n}\n\n.share-arrow {\n font-size: 20px;\n color: #ccc;\n margin-left: 8px;\n align-self: center;\n}\n</style>\n","<template>\n <scroll-view class=\"share-detail-page\" direction=\"vertical\">\n <view class=\"product-section\">\n <image class=\"product-image\" :src=\"shareRecord.product_image != null && shareRecord.product_image.length > 0 ? shareRecord.product_image : defaultImage\" mode=\"aspectFill\" />\n <view class=\"product-info\">\n <text class=\"product-name\">{{ shareRecord.product_name }}</text>\n <text class=\"product-price\">¥{{ shareRecord.product_price }}</text>\n </view>\n </view>\n\n <view class=\"progress-section\">\n <view class=\"progress-header\">\n <text class=\"progress-title\">免单进度</text>\n <text class=\"progress-status\" :class=\"getStatusClass(shareRecord.status)\">{{ getStatusText(shareRecord.status) }}</text>\n </view>\n \n <view class=\"progress-content\">\n <view class=\"progress-bar\">\n <view class=\"progress-fill\" :style=\"{ width: getProgressPercent() + '%' }\"></view>\n </view>\n <view class=\"progress-numbers\">\n <text class=\"current-count\">{{ shareRecord.current_count }}</text>\n <text class=\"divider\">/</text>\n <text class=\"required-count\">{{ shareRecord.required_count }}</text>\n </view>\n </view>\n\n <view class=\"progress-tip\" v-if=\"shareRecord.status === 0\">\n <text class=\"tip-text\">还需 {{ shareRecord.required_count - shareRecord.current_count }} 人购买即可免单</text>\n </view>\n\n <view class=\"reward-info\" v-if=\"shareRecord.status === 1\">\n <text class=\"reward-label\">已获得免单奖励</text>\n <text class=\"reward-amount\">¥{{ shareRecord.reward_amount }}</text>\n </view>\n </view>\n\n <view class=\"share-code-section\">\n <view class=\"code-header\">\n <text class=\"code-title\">分享码</text>\n <text class=\"copy-btn\" @click=\"copyShareCode\">复制</text>\n </view>\n <view class=\"code-content\">\n <text class=\"code-value\">{{ shareRecord.share_code }}</text>\n </view>\n <view class=\"code-tip\">\n <text class=\"tip-text\">将分享码告诉好友,好友下单时填写即可</text>\n </view>\n </view>\n\n <view class=\"buyers-section\">\n <view class=\"section-header\">\n <text class=\"section-title\">购买记录</text>\n <text class=\"section-count\">({{ buyers.length }}人)</text>\n </view>\n\n <view v-if=\"buyersLoading\" class=\"loading-state\">\n <text class=\"loading-text\">加载中...</text>\n </view>\n\n <view v-else-if=\"buyers.length === 0\" class=\"empty-state\">\n <text class=\"empty-text\">暂无购买记录</text>\n </view>\n\n <view v-else class=\"buyer-list\">\n <view class=\"buyer-item\" v-for=\"buyer in buyers\" :key=\"buyer.id\">\n <view class=\"buyer-avatar\">\n <text class=\"avatar-text\">{{ getBuyerInitial(buyer.buyer_name) }}</text>\n </view>\n <view class=\"buyer-info\">\n <text class=\"buyer-name\">{{ maskName(buyer.buyer_name) }}</text>\n <text class=\"buyer-time\">{{ formatTime(buyer.created_at) }}</text>\n </view>\n <view class=\"buyer-count\">\n <text class=\"count-text\">购买 {{ buyer.quantity }} 件</text>\n </view>\n </view>\n </view>\n </view>\n\n <view class=\"time-section\">\n <view class=\"time-item\">\n <text class=\"time-label\">创建时间</text>\n <text class=\"time-value\">{{ formatTime(shareRecord.created_at) }}</text>\n </view>\n <view class=\"time-item\" v-if=\"shareRecord.completed_at\">\n <text class=\"time-label\">完成时间</text>\n <text class=\"time-value\">{{ formatTime(shareRecord.completed_at) }}</text>\n </view>\n </view>\n </scroll-view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted } from 'vue'\nimport { onLoad } from '@dcloudio/uni-app'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype ShareRecordType = {\n id: string\n product_name: string\n product_image: string | null\n product_price: number\n share_code: string\n required_count: number\n current_count: number\n status: number\n reward_amount: number | null\n created_at: string\n completed_at: string | null\n}\n\ntype BuyerType = {\n id: string\n buyer_id: string\n buyer_name: string\n quantity: number\n created_at: string\n}\n\nconst shareId = ref<string>('')\nconst shareRecord = ref<ShareRecordType>({\n id: '',\n product_name: '',\n product_image: null,\n product_price: 0,\n share_code: '',\n required_count: 4,\n current_count: 0,\n status: 0,\n reward_amount: null,\n created_at: '',\n completed_at: null\n})\n\nconst buyers = ref<BuyerType[]>([])\nconst buyersLoading = ref<boolean>(false)\nconst defaultImage: string = '/static/images/default-product.png'\n\nconst loadShareDetail = async (): Promise<void> => {\n if (shareId.value === '') return\n\n try {\n const result = await supabaseService.getShareDetail(shareId.value)\n \n const recordRaw = result.get('share_record')\n if (recordRaw != null) {\n let recordObj: UTSJSONObject | null = null\n if (recordRaw instanceof UTSJSONObject) {\n recordObj = recordRaw\n } else {\n recordObj = JSON.parse(JSON.stringify(recordRaw)) as UTSJSONObject\n }\n \n const record: ShareRecordType = {\n id: recordObj.getString('id') ?? '',\n product_name: recordObj.getString('product_name') ?? '',\n product_image: recordObj.getString('product_image'),\n product_price: recordObj.getNumber('product_price') ?? 0,\n share_code: recordObj.getString('share_code') ?? '',\n required_count: recordObj.getNumber('required_count') ?? 4,\n current_count: recordObj.getNumber('current_count') ?? 0,\n status: recordObj.getNumber('status') ?? 0,\n reward_amount: recordObj.getNumber('reward_amount'),\n created_at: recordObj.getString('created_at') ?? '',\n completed_at: recordObj.getString('completed_at')\n }\n shareRecord.value = record\n }\n\n const purchasesRaw = result.get('secondary_purchases')\n if (purchasesRaw != null && Array.isArray(purchasesRaw)) {\n const parsed: BuyerType[] = []\n const arr = purchasesRaw as any[]\n \n for (let i = 0; i < arr.length; i++) {\n const item = arr[i]\n let itemObj: UTSJSONObject | null = null\n if (item instanceof UTSJSONObject) {\n itemObj = item\n } else {\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n \n parsed.push({\n id: itemObj.getString('id') ?? '',\n buyer_id: itemObj.getString('buyer_id') ?? '',\n buyer_name: '用户' + (i + 1),\n quantity: itemObj.getNumber('quantity') ?? 1,\n created_at: itemObj.getString('created_at') ?? ''\n })\n }\n \n buyers.value = parsed\n }\n } catch (e) {\n console.error('加载分享详情失败:', e)\n }\n}\n\nconst getProgressPercent = (): number => {\n if (shareRecord.value.required_count <= 0) return 0\n return Math.min(100, Math.round((shareRecord.value.current_count / shareRecord.value.required_count) * 100))\n}\n\nconst getStatusText = (status: number): string => {\n if (status === 0) return '进行中'\n if (status === 1) return '已免单'\n if (status === 2) return '已失效'\n if (status === 3) return '已过期'\n return '未知'\n}\n\nconst getStatusClass = (status: number): string => {\n if (status === 0) return 'status-progress'\n if (status === 1) return 'status-completed'\n if (status === 2) return 'status-invalid'\n if (status === 3) return 'status-expired'\n return ''\n}\n\nconst copyShareCode = (): void => {\n uni.setClipboardData({\n data: shareRecord.value.share_code,\n success: () => {\n uni.showToast({ title: '已复制分享码', icon: 'success' })\n }\n })\n}\n\nconst getBuyerInitial = (name: string): string => {\n if (name.length > 0) {\n return name.charAt(0)\n }\n return '用'\n}\n\nconst maskName = (name: string): string => {\n if (name.length <= 2) {\n return name.charAt(0) + '*'\n }\n return name.charAt(0) + '***' + name.charAt(name.length - 1)\n}\n\nconst formatTime = (timeStr: string | null): string => {\n if (timeStr == null || timeStr === '') return ''\n const date = new Date(timeStr)\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n const hh = date.getHours().toString().padStart(2, '0')\n const mm = date.getMinutes().toString().padStart(2, '0')\n return `${y}-${m}-${d} ${hh}:${mm}`\n}\n\nonLoad((options) => {\n if (options != null) {\n const idVal = options['id']\n if (idVal != null) {\n shareId.value = idVal as string\n loadShareDetail()\n }\n }\n})\n</script>\n\n<style>\n.share-detail-page {\n flex: 1;\n height: 100%;\n background-color: #f5f5f5;\n}\n\n.product-section {\n display: flex;\n flex-direction: row;\n background-color: white;\n padding: 16px;\n margin-bottom: 8px;\n}\n\n.product-image {\n width: 100px;\n height: 100px;\n border-radius: 8px;\n}\n\n.product-info {\n flex: 1;\n margin-left: 12px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.product-name {\n font-size: 15px;\n color: #333;\n lines: 2;\n margin-bottom: 8px;\n}\n\n.product-price {\n font-size: 18px;\n font-weight: bold;\n color: #ff6b35;\n}\n\n.progress-section {\n background-color: white;\n padding: 16px;\n margin-bottom: 8px;\n}\n\n.progress-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.progress-title {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n}\n\n.progress-status {\n font-size: 14px;\n padding: 4px 12px;\n border-radius: 12px;\n}\n\n.status-progress {\n background-color: #fff5f0;\n color: #ff6b35;\n}\n\n.status-completed {\n background-color: #f6ffed;\n color: #52c41a;\n}\n\n.status-invalid {\n background-color: #f5f5f5;\n color: #999;\n}\n\n.status-expired {\n background-color: #fff1f0;\n color: #ff4d4f;\n}\n\n.progress-content {\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.progress-bar {\n flex: 1;\n height: 12px;\n background-color: #f0f0f0;\n border-radius: 6px;\n overflow: hidden;\n}\n\n.progress-fill {\n height: 100%;\n background: linear-gradient(90deg, #ff6b35 0%, #ff8c42 100%);\n border-radius: 6px;\n}\n\n.progress-numbers {\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-left: 12px;\n}\n\n.current-count {\n font-size: 24px;\n font-weight: bold;\n color: #ff6b35;\n}\n\n.divider {\n font-size: 16px;\n color: #999;\n margin: 0 4px;\n}\n\n.required-count {\n font-size: 16px;\n color: #999;\n}\n\n.progress-tip {\n margin-top: 12px;\n padding: 10px;\n background-color: #fff5f0;\n border-radius: 8px;\n}\n\n.tip-text {\n font-size: 13px;\n color: #ff6b35;\n}\n\n.reward-info {\n margin-top: 12px;\n padding: 16px;\n background: linear-gradient(135deg, #f6ffed 0%, #e6fffb 100%);\n border-radius: 8px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n}\n\n.reward-label {\n font-size: 15px;\n color: #52c41a;\n}\n\n.reward-amount {\n font-size: 24px;\n font-weight: bold;\n color: #52c41a;\n}\n\n.share-code-section {\n background-color: white;\n padding: 16px;\n margin-bottom: 8px;\n}\n\n.code-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.code-title {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n}\n\n.copy-btn {\n font-size: 14px;\n color: #ff6b35;\n padding: 4px 12px;\n border: 1px solid #ff6b35;\n border-radius: 12px;\n}\n\n.code-content {\n background-color: #f9f9f9;\n padding: 16px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.code-value {\n font-size: 28px;\n font-weight: bold;\n color: #333;\n letter-spacing: 8px;\n}\n\n.code-tip {\n margin-top: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.buyers-section {\n background-color: white;\n margin-bottom: 8px;\n}\n\n.section-header {\n display: flex;\n flex-direction: row;\n align-items: center;\n padding: 16px;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.section-title {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n}\n\n.section-count {\n font-size: 14px;\n color: #999;\n margin-left: 4px;\n}\n\n.loading-state {\n padding: 30px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.loading-text {\n font-size: 14px;\n color: #999;\n}\n\n.empty-state {\n padding: 30px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.empty-text {\n font-size: 14px;\n color: #999;\n}\n\n.buyer-list {\n display: flex;\n flex-direction: column;\n}\n\n.buyer-item {\n display: flex;\n flex-direction: row;\n align-items: center;\n padding: 12px 16px;\n border-bottom: 1px solid #f9f9f9;\n}\n\n.buyer-avatar {\n width: 40px;\n height: 40px;\n border-radius: 20px;\n background-color: #f0f0f0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.avatar-text {\n font-size: 16px;\n color: #999;\n}\n\n.buyer-info {\n flex: 1;\n margin-left: 12px;\n display: flex;\n flex-direction: column;\n}\n\n.buyer-name {\n font-size: 14px;\n color: #333;\n}\n\n.buyer-time {\n font-size: 12px;\n color: #999;\n margin-top: 2px;\n}\n\n.buyer-count {\n display: flex;\n align-items: center;\n}\n\n.count-text {\n font-size: 13px;\n color: #666;\n}\n\n.time-section {\n background-color: white;\n padding: 16px;\n}\n\n.time-item {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n padding: 8px 0;\n}\n\n.time-label {\n font-size: 14px;\n color: #999;\n}\n\n.time-value {\n font-size: 14px;\n color: #333;\n}\n</style>\n","<template>\n <scroll-view class=\"member-page\" scroll-y>\n <view class=\"member-header\">\n <view class=\"member-info\">\n <view class=\"level-badge\" :class=\"'level-' + memberInfo.member_level\">\n <text class=\"level-name\">{{ memberInfo.level_name }}</text>\n </view>\n <view class=\"discount-info\">\n <text class=\"discount-value\">{{ getDiscountText(memberInfo.discount) }}</text>\n <text class=\"discount-label\">会员折扣</text>\n </view>\n </view>\n </view>\n\n <view class=\"progress-section\" v-if=\"memberInfo.next_level != null\">\n <view class=\"progress-header\">\n <text class=\"progress-title\">距离{{ getNextLevelName() }}还需</text>\n <text class=\"progress-amount\">{{ getRemainingAmount() }}元</text>\n </view>\n <view class=\"progress-bar\">\n <view class=\"progress-fill\" :style=\"{ width: memberInfo.progress_percent + '%' }\"></view>\n </view>\n <view class=\"progress-footer\">\n <text class=\"current-amount\">已消费 {{ memberInfo.total_spent }}元</text>\n <text class=\"target-amount\">目标 {{ getNextLevelMinAmount() }}元</text>\n </view>\n </view>\n\n <view class=\"levels-section\">\n <view class=\"section-header\">\n <text class=\"section-title\">会员等级</text>\n </view>\n <view class=\"level-list\">\n <view \n class=\"level-item\" \n v-for=\"level in levels\" \n :key=\"level.id\"\n :class=\"{ current: level.id === memberInfo.member_level }\"\n >\n <view class=\"level-left\">\n <view class=\"level-icon\" :class=\"'level-bg-' + level.id\">\n <text class=\"icon-text\">{{ level.name.charAt(0) }}</text>\n </view>\n <view class=\"level-detail\">\n <text class=\"level-title\">{{ level.name }}</text>\n <text class=\"level-condition\">{{ level.description != null && level.description != '' ? level.description : ('累计消费' + level.min_amount + '元') }}</text>\n </view>\n </view>\n <view class=\"level-right\">\n <text class=\"level-discount\">{{ getDiscountText(level.discount) }}</text>\n <view class=\"current-tag\" v-if=\"level.id === memberInfo.member_level\">\n <text class=\"tag-text\">当前</text>\n </view>\n </view>\n </view>\n </view>\n </view>\n\n <view class=\"benefits-section\">\n <view class=\"section-header\">\n <text class=\"section-title\">会员权益</text>\n </view>\n <view class=\"benefit-list\">\n <view class=\"benefit-item\">\n <text class=\"benefit-icon\">💰</text>\n <text class=\"benefit-text\">专属折扣价格</text>\n </view>\n <view class=\"benefit-item\">\n <text class=\"benefit-icon\">🎁</text>\n <text class=\"benefit-text\">生日专属优惠</text>\n </view>\n <view class=\"benefit-item\">\n <text class=\"benefit-icon\">🚀</text>\n <text class=\"benefit-text\">优先发货权益</text>\n </view>\n <view class=\"benefit-item\">\n <text class=\"benefit-icon\">📞</text>\n <text class=\"benefit-text\">专属客服通道</text>\n </view>\n </view>\n </view>\n\n <view class=\"logs-section\">\n <view class=\"section-header\">\n <text class=\"section-title\">等级变更记录</text>\n </view>\n \n <view v-if=\"logsLoading\" class=\"loading-state\">\n <text class=\"loading-text\">加载中...</text>\n </view>\n \n <view v-else-if=\"logs.length === 0\" class=\"empty-state\">\n <text class=\"empty-text\">暂无变更记录</text>\n </view>\n \n <view v-else class=\"log-list\">\n <view class=\"log-item\" v-for=\"log in logs\" :key=\"log.id\">\n <view class=\"log-left\">\n <text class=\"log-change\">{{ getLevelName(log.old_level) }} → {{ getLevelName(log.new_level) }}</text>\n <text class=\"log-reason\">{{ log.reason != null && log.reason != '' ? log.reason : '系统升级' }}</text>\n </view>\n <text class=\"log-time\">{{ formatDate(log.created_at) }}</text>\n </view>\n </view>\n </view>\n </scroll-view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted } from 'vue'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype MemberLevel = {\n id: number\n name: string\n min_amount: number\n discount: number\n description: string | null\n}\n\ntype MemberInfo = {\n member_level: number\n level_name: string\n discount: number\n total_spent: number\n next_level: MemberLevel | null\n progress_percent: number\n manual_level: boolean\n}\n\ntype LevelLog = {\n id: string\n old_level: number\n new_level: number\n reason: string | null\n created_at: string\n}\n\nconst memberInfo = ref<MemberInfo>({\n member_level: 0,\n level_name: '普通会员',\n discount: 1.0,\n total_spent: 0,\n next_level: null,\n progress_percent: 0,\n manual_level: false\n})\n\nconst levels = ref<MemberLevel[]>([])\nconst logs = ref<LevelLog[]>([])\nconst logsLoading = ref<boolean>(false)\n\nconst loadMemberInfo = async (): Promise<void> => {\n try {\n const result = await supabaseService.getUserMemberInfo()\n \n const info: MemberInfo = {\n member_level: result.getNumber('member_level') ?? 0,\n level_name: result.getString('level_name') ?? '普通会员',\n discount: result.getNumber('discount') ?? 1.0,\n total_spent: result.getNumber('total_spent') ?? 0,\n next_level: null,\n progress_percent: result.getNumber('progress_percent') ?? 0,\n manual_level: result.getBoolean('manual_level') ?? false\n }\n \n const nextLevelRaw = result.get('next_level')\n if (nextLevelRaw != null) {\n let nextLevelObj: UTSJSONObject | null = null\n if (nextLevelRaw instanceof UTSJSONObject) {\n nextLevelObj = nextLevelRaw\n } else {\n nextLevelObj = JSON.parse(JSON.stringify(nextLevelRaw)) as UTSJSONObject\n }\n const nextLevel: MemberLevel = {\n id: nextLevelObj.getNumber('id') ?? 0,\n name: nextLevelObj.getString('name') ?? '',\n min_amount: nextLevelObj.getNumber('min_amount') ?? 0,\n discount: 1.0,\n description: null\n }\n info.next_level = nextLevel\n }\n memberInfo.value = info\n } catch (e) {\n console.error('加载会员信息失败:', e)\n }\n}\n\nconst loadLevels = async (): Promise<void> => {\n try {\n const result = await supabaseService.getMemberLevels()\n const parsed: MemberLevel[] = []\n \n for (let i = 0; i < result.length; i++) {\n const item = result[i]\n let itemObj: UTSJSONObject | null = null\n if (item instanceof UTSJSONObject) {\n itemObj = item\n } else {\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n \n parsed.push({\n id: itemObj.getNumber('id') ?? 0,\n name: itemObj.getString('name') ?? '',\n min_amount: itemObj.getNumber('min_amount') ?? 0,\n discount: itemObj.getNumber('discount') ?? 1.0,\n description: itemObj.getString('description')\n })\n }\n \n levels.value = parsed\n } catch (e) {\n console.error('加载会员等级失败:', e)\n }\n}\n\nconst loadLogs = async (): Promise<void> => {\n logsLoading.value = true\n try {\n const result = await supabaseService.getMemberLevelLogs()\n const parsed: LevelLog[] = []\n \n for (let i = 0; i < result.length; i++) {\n const item = result[i]\n let itemObj: UTSJSONObject | null = null\n if (item instanceof UTSJSONObject) {\n itemObj = item\n } else {\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\n }\n \n parsed.push({\n id: itemObj.getString('id') ?? '',\n old_level: itemObj.getNumber('old_level') ?? 0,\n new_level: itemObj.getNumber('new_level') ?? 0,\n reason: itemObj.getString('reason'),\n created_at: itemObj.getString('created_at') ?? ''\n })\n }\n \n logs.value = parsed\n } catch (e) {\n console.error('加载变更记录失败:', e)\n } finally {\n logsLoading.value = false\n }\n}\n\nconst getDiscountText = (discount: number): string => {\n if (discount >= 1) return '无折扣'\n return Math.round(discount * 100) / 10 + '折'\n}\n\nconst getNextLevelName = (): string => {\n if (memberInfo.value.next_level != null) {\n return memberInfo.value.next_level.name\n }\n return ''\n}\n\nconst getNextLevelMinAmount = (): number => {\n if (memberInfo.value.next_level != null) {\n return memberInfo.value.next_level.min_amount\n }\n return 0\n}\n\nconst getRemainingAmount = (): number => {\n if (memberInfo.value.next_level != null) {\n return memberInfo.value.next_level.min_amount - memberInfo.value.total_spent\n }\n return 0\n}\n\nconst getLevelName = (level: number): string => {\n for (let i = 0; i < levels.value.length; i++) {\n if (levels.value[i].id === level) {\n return levels.value[i].name\n }\n }\n return '普通会员'\n}\n\nconst formatDate = (dateStr: string): string => {\n if (dateStr === '') return ''\n const date = new Date(dateStr)\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n return `${y}-${m}-${d}`\n}\n\nonMounted(() => {\n loadMemberInfo()\n loadLevels()\n loadLogs()\n})\n</script>\n\n<style>\n.member-page {\n flex: 1;\n height: 100%;\n background-color: #f5f5f5;\n}\n\n.member-header {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n padding: 30px 20px;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.member-info {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.level-badge {\n padding: 8px 24px;\n border-radius: 20px;\n margin-bottom: 16px;\n}\n\n.level-badge.level-0 {\n background-color: rgba(255, 255, 255, 0.3);\n}\n\n.level-badge.level-1 {\n background: linear-gradient(135deg, #cd7f32 0%, #daa520 100%);\n}\n\n.level-badge.level-2 {\n background: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 100%);\n}\n\n.level-badge.level-3 {\n background: linear-gradient(135deg, #ffd700 0%, #ffec8b 100%);\n}\n\n.level-badge.level-4 {\n background: linear-gradient(135deg, #b9f2ff 0%, #89cff0 100%);\n}\n\n.level-badge.level-5 {\n background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);\n}\n\n.level-name {\n font-size: 18px;\n font-weight: bold;\n color: white;\n}\n\n.discount-info {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.discount-value {\n font-size: 36px;\n font-weight: bold;\n color: white;\n}\n\n.discount-label {\n font-size: 14px;\n color: rgba(255, 255, 255, 0.8);\n margin-top: 4px;\n}\n\n.progress-section {\n background-color: white;\n padding: 16px;\n margin: 12px;\n border-radius: 12px;\n}\n\n.progress-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.progress-title {\n font-size: 14px;\n color: #666;\n}\n\n.progress-amount {\n font-size: 16px;\n font-weight: bold;\n color: #667eea;\n}\n\n.progress-bar {\n height: 8px;\n background-color: #f0f0f0;\n border-radius: 4px;\n overflow: hidden;\n}\n\n.progress-fill {\n height: 100%;\n background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);\n border-radius: 4px;\n}\n\n.progress-footer {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n margin-top: 8px;\n}\n\n.current-amount {\n font-size: 12px;\n color: #999;\n}\n\n.target-amount {\n font-size: 12px;\n color: #999;\n}\n\n.levels-section {\n background-color: white;\n margin: 12px;\n border-radius: 12px;\n overflow: hidden;\n}\n\n.section-header {\n padding: 16px;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.section-title {\n font-size: 16px;\n font-weight: bold;\n color: #333;\n}\n\n.level-list {\n display: flex;\n flex-direction: column;\n}\n\n.level-item {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding: 16px;\n border-bottom: 1px solid #f9f9f9;\n}\n\n.level-item.current {\n background-color: #f8f5ff;\n}\n\n.level-left {\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.level-icon {\n width: 40px;\n height: 40px;\n border-radius: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-right: 12px;\n}\n\n.level-bg-0 {\n background-color: #f0f0f0;\n}\n\n.level-bg-1 {\n background: linear-gradient(135deg, #cd7f32 0%, #daa520 100%);\n}\n\n.level-bg-2 {\n background: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 100%);\n}\n\n.level-bg-3 {\n background: linear-gradient(135deg, #ffd700 0%, #ffec8b 100%);\n}\n\n.level-bg-4 {\n background: linear-gradient(135deg, #b9f2ff 0%, #89cff0 100%);\n}\n\n.level-bg-5 {\n background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);\n}\n\n.icon-text {\n font-size: 16px;\n font-weight: bold;\n color: white;\n}\n\n.level-detail {\n display: flex;\n flex-direction: column;\n}\n\n.level-title {\n font-size: 15px;\n font-weight: bold;\n color: #333;\n}\n\n.level-condition {\n font-size: 12px;\n color: #999;\n margin-top: 2px;\n}\n\n.level-right {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n}\n\n.level-discount {\n font-size: 14px;\n font-weight: bold;\n color: #667eea;\n}\n\n.current-tag {\n background-color: #667eea;\n padding: 2px 8px;\n border-radius: 4px;\n margin-top: 4px;\n}\n\n.tag-text {\n font-size: 10px;\n color: white;\n}\n\n.benefits-section {\n background-color: white;\n margin: 12px;\n border-radius: 12px;\n}\n\n.benefit-list {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n padding: 8px;\n}\n\n.benefit-item {\n width: 50%;\n display: flex;\n flex-direction: row;\n align-items: center;\n padding: 12px 8px;\n}\n\n.benefit-icon {\n font-size: 20px;\n margin-right: 8px;\n}\n\n.benefit-text {\n font-size: 13px;\n color: #666;\n}\n\n.logs-section {\n background-color: white;\n margin: 12px;\n border-radius: 12px;\n}\n\n.loading-state {\n padding: 30px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.loading-text {\n font-size: 14px;\n color: #999;\n}\n\n.empty-state {\n padding: 30px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.empty-text {\n font-size: 14px;\n color: #999;\n}\n\n.log-list {\n display: flex;\n flex-direction: column;\n}\n\n.log-item {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-bottom: 1px solid #f9f9f9;\n}\n\n.log-left {\n display: flex;\n flex-direction: column;\n}\n\n.log-change {\n font-size: 14px;\n color: #333;\n}\n\n.log-reason {\n font-size: 12px;\n color: #999;\n margin-top: 2px;\n}\n\n.log-time {\n font-size: 12px;\n color: #999;\n}\n</style>\n","<template>\n <scroll-view class=\"message-detail-page\" scroll-y>\n <view class=\"message-header\">\n <text class=\"message-title\">{{ message.title }}</text>\n <text class=\"message-time\">{{ formatTime(message.created_at) }}</text>\n </view>\n\n <view class=\"message-content\">\n <text class=\"content-text\">{{ message.content }}</text>\n </view>\n\n <view v-if=\"message.link_url\" class=\"message-action\" @click=\"goToLink\">\n <text class=\"action-text\">查看详情</text>\n <text class=\"action-arrow\"></text>\n </view>\n\n <view v-if=\"message.icon_url\" class=\"message-image\">\n <image :src=\"message.icon_url\" mode=\"widthFix\" class=\"icon-image\" />\n </view>\n\n <view v-if=\"extraInfo.length > 0\" class=\"extra-info\">\n <view v-for=\"(item, index) in extraInfo\" :key=\"index\" class=\"extra-item\">\n <text class=\"extra-label\">{{ item.label }}</text>\n <text class=\"extra-value\">{{ item.value }}</text>\n </view>\n </view>\n </scroll-view>\n</template>\n\n<script setup lang=\"uts\">\nimport { ref, onMounted } from 'vue'\nimport { onLoad } from '@dcloudio/uni-app'\nimport { supabaseService } from '@/utils/supabaseService.uts'\n\ntype MessageType = {\n id: string\n type: string\n title: string\n content: string\n icon_url: string | null\n link_url: string | null\n extra_data: any | null\n created_at: string\n}\n\ntype ExtraInfoItem = {\n label: string\n value: string\n}\n\nconst message = ref<MessageType>({\n id: '',\n type: '',\n title: '',\n content: '',\n icon_url: null,\n link_url: null,\n extra_data: null,\n created_at: ''\n})\n\nconst extraInfo = ref<ExtraInfoItem[]>([])\n\nconst formatLabel = (key: string): string => {\n if (key === 'share_code') return '分享码'\n if (key === 'product_name') return '商品名称'\n if (key === 'reward_amount') return '奖励金额'\n if (key === 'order_no') return '订单号'\n if (key === 'buyer_name') return '购买者'\n if (key === 'quantity') return '数量'\n return key\n}\n\nconst parseExtraData = (data: any) => {\n extraInfo.value = []\n \n if (data == null) return\n \n try {\n let dataObj: UTSJSONObject | null = null\n if (typeof data === 'string') {\n const parsed = JSON.parse(data as string)\n if (parsed != null) {\n dataObj = parsed as UTSJSONObject\n }\n } else if (data instanceof UTSJSONObject) {\n dataObj = data\n } else {\n dataObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject\n }\n \n if (dataObj != null) {\n const keys = UTSJSONObject.keys(dataObj)\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i] as string\n const value = dataObj.get(key)\n if (value != null) {\n const item: ExtraInfoItem = {\n label: formatLabel(key),\n value: `${value}`\n }\n extraInfo.value.push(item)\n }\n }\n }\n } catch (e) {\n console.error('解析extra_data失败:', e)\n }\n}\n\nconst loadMessage = async (id: string) => {\n try {\n const notifications = await supabaseService.getUserNotifications(null)\n const found = notifications.find(n => n.id === id)\n \n if (found != null) {\n const extraData = found.extra_data\n const msg: MessageType = {\n id: found.id,\n type: found.type,\n title: found.title,\n content: found.content,\n icon_url: found.icon_url,\n link_url: found.link_url,\n extra_data: extraData,\n created_at: found.created_at ?? ''\n }\n message.value = msg\n\n if (extraData != null) {\n parseExtraData(extraData)\n }\n }\n } catch (e) {\n console.error('加载消息失败:', e)\n }\n}\n\nconst formatTime = (timeStr: string): string => {\n if (timeStr == null || timeStr === '') return ''\n const date = new Date(timeStr)\n const y = date.getFullYear()\n const m = (date.getMonth() + 1).toString().padStart(2, '0')\n const d = date.getDate().toString().padStart(2, '0')\n const hh = date.getHours().toString().padStart(2, '0')\n const mm = date.getMinutes().toString().padStart(2, '0')\n return `${y}-${m}-${d} ${hh}:${mm}`\n}\n\nconst goToLink = () => {\n const url = message.value.link_url\n if (url != null && url !== '') {\n if (url.startsWith('/pages/')) {\n uni.navigateTo({ url: url })\n } else {\n uni.setClipboardData({\n data: url,\n success: () => {\n uni.showToast({ title: '链接已复制', icon: 'success' })\n }\n })\n }\n }\n}\n\nonLoad((options) => {\n if (options != null) {\n const idVal = options['id']\n if (idVal != null) {\n loadMessage(idVal as string)\n }\n }\n})\n</script>\n\n<style>\n.message-detail-page {\n flex: 1;\n height: 100%;\n background-color: #f5f5f5;\n}\n\n.message-header {\n background-color: white;\n padding: 20px 16px;\n margin-bottom: 8px;\n}\n\n.message-title {\n font-size: 18px;\n font-weight: bold;\n color: #333;\n display: flex;\n margin-bottom: 10px;\n}\n\n.message-time {\n font-size: 13px;\n color: #999;\n}\n\n.message-content {\n background-color: white;\n padding: 16px;\n margin-bottom: 8px;\n}\n\n.content-text {\n font-size: 15px;\n color: #333;\n line-height: 1.8;\n}\n\n.message-action {\n background-color: white;\n padding: 16px;\n margin-bottom: 8px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n}\n\n.action-text {\n font-size: 15px;\n color: #ff6b35;\n}\n\n.action-arrow {\n font-size: 18px;\n color: #ccc;\n}\n\n.message-image {\n background-color: white;\n padding: 16px;\n margin-bottom: 8px;\n}\n\n.icon-image {\n width: 100%;\n border-radius: 8px;\n}\n\n.extra-info {\n background-color: white;\n padding: 16px;\n}\n\n.extra-item {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n padding: 10px 0;\n border-bottom: 1px solid #f5f5f5;\n}\n\n.extra-item:last-child {\n border-bottom: none;\n}\n\n.extra-label {\n font-size: 14px;\n color: #666;\n}\n\n.extra-value {\n font-size: 14px;\n color: #333;\n}\n</style>\n","<template>\r\n <view class=\"red-packets-page\">\r\n <view class=\"tab-header\" style=\"position: fixed; top: 0; left: 0; right: 0; z-index: 10;\">\r\n <text \r\n class=\"tab-item\" \r\n :class=\"{ active: currentTab === 0 }\" \r\n @click=\"currentTab = 0\">未使用</text>\r\n <text \r\n class=\"tab-item\" \r\n :class=\"{ active: currentTab === 1 }\" \r\n @click=\"currentTab = 1\">已使用/过期</text>\r\n </view>\r\n\r\n <view v-if=\"loading\" class=\"loading-state\">\r\n <text>加载中...</text>\r\n </view>\r\n\r\n <scroll-view v-else class=\"packet-list\" direction=\"vertical\">\r\n <view v-if=\"filteredPackets.length === 0\" class=\"empty-state\">\r\n <text class=\"empty-text\">暂无相关红包</text>\r\n </view>\r\n <view v-else v-for=\"item in filteredPackets\" :key=\"item.id\" class=\"packet-item\" :class=\"{ disabled: item.status !== 0 }\">\r\n <view class=\"packet-left\">\r\n <text class=\"packet-amount\">¥<text class=\"amount-num\">{{ item.amount }}</text></text>\r\n <text class=\"packet-condition\">无门槛</text>\r\n </view>\r\n <view class=\"packet-right\">\r\n <view class=\"packet-info\">\r\n <text class=\"packet-name\">{{ item.name }}</text>\r\n <text class=\"packet-date\">有效期至 {{ formatTime(item.expire_at) }}</text>\r\n </view>\r\n <view class=\"packet-action\">\r\n <button v-if=\"item.status === 0\" class=\"use-btn\" @click=\"usePacket(item)\">立即使用</button>\r\n <text v-else class=\"status-text\">{{ getStatusText(item.status) }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n </scroll-view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, computed, onMounted } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype RedPacket = {\r\n id: string\r\n user_id: string\r\n amount: number\r\n name: string\r\n status: number // 0: unused, 1: used, 2: expired\r\n expire_at: string\r\n created_at: string\r\n}\r\n\r\nconst loading = ref(true)\r\nconst currentTab = ref(0)\r\nconst packets = ref<Array<RedPacket>>([])\r\n\r\nconst filteredPackets = computed((): Array<RedPacket> => {\r\n const result: Array<RedPacket> = []\r\n if (currentTab.value === 0) {\r\n for (let i: number = 0; i < packets.value.length; i++) {\r\n if (packets.value[i].status === 0) {\r\n result.push(packets.value[i])\r\n }\r\n }\r\n } else {\r\n for (let i: number = 0; i < packets.value.length; i++) {\r\n if (packets.value[i].status !== 0) {\r\n result.push(packets.value[i])\r\n }\r\n }\r\n }\r\n return result\r\n})\r\n\r\nconst loadData = async () => {\r\n loading.value = true\r\n try {\r\n const rawList = await supabaseService.getUserRedPackets()\r\n const mappedList: Array<RedPacket> = []\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i] as UTSJSONObject\r\n const packet: RedPacket = {\r\n id: item.getString('id') ?? '',\r\n user_id: '',\r\n amount: item.getNumber('amount') ?? 0,\r\n name: item.getString('name') ?? '',\r\n status: item.getNumber('status') ?? 0,\r\n expire_at: item.getString('expire_at') ?? '',\r\n created_at: item.getString('created_at') ?? ''\r\n } as RedPacket\r\n mappedList.push(packet)\r\n }\r\n packets.value = mappedList\r\n } catch (e) {\r\n console.error(e)\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n\r\nonMounted(() => {\r\n loadData()\r\n})\r\n\r\nconst usePacket = (item: RedPacket) => {\r\n uni.switchTab({\r\n url: '/pages/main/index'\r\n })\r\n}\r\n\r\nconst getStatusText = (status: number): string => {\r\n if (status === 1) return '已使用'\r\n if (status === 2) return '已过期'\r\n return ''\r\n}\r\n\r\nconst formatTime = (timeStr: string): string => {\r\n if (timeStr == '') return '永久有效'\r\n const date = new Date(timeStr)\r\n return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`\r\n}\r\n</script>\r\n\r\n<style>\r\n.red-packets-page {\r\n background-color: #f5f5f5;\r\n flex: 1;\r\n}\r\n\r\n.tab-header {\r\n display: flex;\r\n background-color: #fff;\r\n padding: 10px 0;\r\n /* position: sticky is removed in flavor of inline fixed style */\r\n}\r\n\r\n.tab-item {\r\n flex: 1;\r\n text-align: center;\r\n font-size: 14px;\r\n color: #666;\r\n padding-bottom: 8px;\r\n border-bottom: 2px solid transparent;\r\n}\r\n\r\n.tab-item.active {\r\n color: #ff5000;\r\n border-bottom-color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.packet-list {\r\n flex: 1;\r\n padding: 15px;\r\n}\r\n\r\n.packet-item {\r\n display: flex;\r\n background-color: #fff;\r\n border-radius: 8px;\r\n margin-bottom: 12px;\r\n overflow: hidden;\r\n box-shadow: 0 2px 4px rgba(0,0,0,0.05);\r\n}\r\n\r\n.packet-item.disabled .packet-left,\r\n.packet-item.disabled .packet-name,\r\n.packet-item.disabled .amount-num {\r\n color: #999;\r\n background-color: #f0f0f0;\r\n}\r\n\r\n.packet-item.disabled .packet-left {\r\n background-color: #e0e0e0;\r\n}\r\n\r\n.packet-left {\r\n width: 100px;\r\n background-color: #fff5f0;\r\n color: #ff5000;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 15px 0;\r\n}\r\n\r\n.packet-amount {\r\n font-size: 14px;\r\n}\r\n\r\n.amount-num {\r\n font-size: 28px;\r\n font-weight: bold;\r\n}\r\n\r\n.packet-condition {\r\n font-size: 12px;\r\n margin-top: 4px;\r\n}\r\n\r\n.packet-right {\r\n flex: 1;\r\n padding: 15px;\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.packet-info {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.packet-name {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.packet-date {\r\n font-size: 12px;\r\n color: #999;\r\n}\r\n\r\n.use-btn {\r\n font-size: 12px;\r\n background-color: #ff5000;\r\n color: #fff;\r\n border-radius: 15px;\r\n padding: 4px 12px;\r\n line-height: 1.5;\r\n}\r\n\r\n.status-text {\r\n font-size: 14px;\r\n color: #999;\r\n}\r\n\r\n.loading-state, .empty-state {\r\n padding: 40px;\r\n align-items: center;\r\n justify-content: center;\r\n display: flex;\r\n}\r\n</style>","<template>\r\n <view class=\"bank-cards-page\">\r\n <view class=\"card-list\">\r\n <view v-for=\"card in cards\" :key=\"card.id\" class=\"card-item\" :class=\"getCardClass(card.bank_name)\">\r\n <view class=\"card-bg-mask\"></view>\r\n <view class=\"card-content\">\r\n <view class=\"card-header\">\r\n <text class=\"bank-name\">{{ card.bank_name }}</text>\r\n <text class=\"card-type\">{{ card.card_type === 'credit' ? '信用卡' : '储蓄卡' }}</text>\r\n </view>\r\n <view class=\"card-number\">\r\n <text class=\"dots\">**** **** ****</text>\r\n <text class=\"last-digits\">{{ card.card_no_last4 }}</text>\r\n </view>\r\n <view class=\"delete-btn\" @click.stop=\"deleteCard(card)\">\r\n <text class=\"del-text\">✕</text>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <view class=\"add-card-btn\" @click=\"addCard\">\r\n <text class=\"plus-icon\">+</text>\r\n <text>添加银行卡</text>\r\n </view>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, onMounted } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype BankCard = {\r\n id: string\r\n user_id: string\r\n bank_name: string\r\n card_no_last4: string\r\n card_type: string\r\n holder_name: string\r\n is_default: boolean\r\n}\r\n\r\nconst cards = ref<BankCard[]>([])\r\nconst loading = ref(true)\r\n\r\nconst loadData = async () => {\r\n loading.value = true\r\n try {\r\n const rawList = await supabaseService.getUserBankCards()\r\n const cardList: BankCard[] = []\r\n \r\n // Use for loop instead of map to avoid complex closure typing issues\r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n let id = ''\r\n let bankName = ''\r\n let last4 = ''\r\n let type = 'debit'\r\n let holder = ''\r\n let isDef = false\r\n \r\n if (item instanceof UTSJSONObject) {\r\n id = item.getString('id') ?? ''\r\n bankName = item.getString('bank_name') ?? ''\r\n last4 = item.getString('card_no_last4') ?? ''\r\n type = item.getString('card_type') ?? 'debit'\r\n holder = item.getString('holder_name') ?? ''\r\n isDef = item.getBoolean('is_default') ?? false\r\n } else {\r\n const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n id = obj.getString('id') ?? ''\r\n bankName = obj.getString('bank_name') ?? ''\r\n last4 = obj.getString('card_no_last4') ?? ''\r\n type = obj.getString('card_type') ?? 'debit'\r\n holder = obj.getString('holder_name') ?? ''\r\n isDef = obj.getBoolean('is_default') ?? false\r\n }\r\n \r\n cardList.push({\r\n id: id,\r\n user_id: '',\r\n bank_name: bankName,\r\n card_no_last4: last4,\r\n card_type: type,\r\n holder_name: holder,\r\n is_default: isDef\r\n } as BankCard)\r\n }\r\n \r\n cards.value = cardList\r\n } catch (e) {\r\n console.error(e)\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n\r\nonShow(() => {\r\n loadData()\r\n})\r\n\r\nconst addCard = () => {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/bank-cards/add'\r\n })\r\n}\r\n\r\nconst deleteCard = (card: BankCard) => {\r\n uni.showModal({\r\n title: '删除银行卡',\r\n content: `确认删除尾号${card.card_no_last4}的${card.bank_name}卡片吗?`,\r\n success: (res) => {\r\n if (res.confirm) {\r\n supabaseService.deleteBankCard(card.id).then((success) => {\r\n if (success) {\r\n uni.showToast({ title: '已删除' })\r\n loadData()\r\n } else {\r\n uni.showToast({ title: '删除失败', icon: 'none' })\r\n }\r\n })\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst getCardClass = (bankName: string): string => {\r\n if (bankName.includes('招商')) return 'cmb'\r\n if (bankName.includes('建设')) return 'ccb'\r\n if (bankName.includes('工商')) return 'icbc'\r\n if (bankName.includes('农业')) return 'abc'\r\n return 'default-bank'\r\n}\r\n\r\n</script>\r\n\r\n<style>\r\n.bank-cards-page {\r\n padding: 15px;\r\n background-color: #f5f5f5;\r\n flex: 1;\r\n height: 140px;\r\n border-radius: 12px;\r\n margin-bottom: 15px;\r\n color: #fff;\r\n position: relative;\r\n overflow: hidden;\r\n box-shadow: 0 4px 8px rgba(0,0,0,0.1);\r\n}\r\n\r\n.cmb { background: linear-gradient(135deg, #f55, #c00); }\r\n.ccb { background: linear-gradient(135deg, #09f, #00609c); }\r\n.icbc { background: linear-gradient(135deg, #f66, #c00); }\r\n.abc { background: linear-gradient(135deg, #0b9, #086); }\r\n.default-bank { background: linear-gradient(135deg, #666, #333); }\r\n\r\n.card-content {\r\n padding: 20px;\r\n z-index: 2;\r\n position: relative;\r\n height: 100%;\r\n box-sizing: border-box;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n}\r\n\r\n.card-header {\r\n display: flex;\r\n align-items: center; \r\n}\r\n\r\n.bank-name {\r\n font-size: 18px;\r\n font-weight: bold;\r\n margin-right: 10px;\r\n}\r\n\r\n.card-type {\r\n font-size: 12px;\r\n background-color: rgba(255,255,255,0.2);\r\n padding: 2px 6px;\r\n border-radius: 4px;\r\n}\r\n\r\n.card-number {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end; /* 右对齐 */\r\n margin-bottom: 10px;\r\n}\r\n\r\n.dots {\r\n font-size: 24px;\r\n margin-right: 15px;\r\n line-height: 1;\r\n}\r\n\r\n.last-digits {\r\n font-size: 24px;\r\n font-family: monospace;\r\n}\r\n\r\n.add-card-btn {\r\n background-color: #fff;\r\n height: 60px;\r\n border-radius: 8px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n color: #666;\r\n font-size: 16px;\r\n border: 1px dashed #ccc;\r\n}\r\n\r\n.plus-icon {\r\n font-size: 24px;\r\n margin-right: 5px;\r\n /* font-weight: 300; removed */\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: 15px;\r\n right: 15px;\r\n width: 24px;\r\n height: 24px;\r\n background-color: rgba(0,0,0,0.2);\r\n border-radius: 12px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.del-text {\r\n color: #fff;\r\n font-size: 14px;\r\n font-weight: bold;\r\n}\r\n</style>","<template>\r\n <view class=\"add-card-page\">\r\n <view class=\"form-container\">\r\n <view class=\"form-item\">\r\n <text class=\"label\">持卡人</text>\r\n <input class=\"input\" type=\"text\" v-model=\"form.holder_name\" placeholder=\"请输入持卡人姓名\" />\r\n </view>\r\n <view class=\"form-item\">\r\n <text class=\"label\">卡号</text>\r\n <input class=\"input\" type=\"number\" v-model=\"form.card_no\" placeholder=\"请输入银行卡号\" @input=\"detectBank\" maxlength=\"19\" />\r\n </view>\r\n <view class=\"form-item\">\r\n <text class=\"label\">银行</text>\r\n <input class=\"input\" type=\"text\" v-model=\"form.bank_name\" placeholder=\"自动识别或手动输入\" />\r\n </view>\r\n <view class=\"form-item\">\r\n <text class=\"label\">手机号</text>\r\n <input class=\"input\" type=\"number\" v-model=\"form.phone\" placeholder=\"银行预留手机号\" maxlength=\"11\" />\r\n </view>\r\n \r\n <view class=\"form-item switch-item\">\r\n <text class=\"label\">设为默认卡</text>\r\n <switch :checked=\"form.is_default\" @change=\"onSwitchChange\" color=\"#ff5000\" />\r\n </view>\r\n </view>\r\n \r\n <view class=\"action-section\">\r\n <button class=\"submit-btn\" :class=\"{ disabled: loading }\" :disabled=\"loading\" @click=\"submit\">确认添加</button>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref, reactive } from 'vue'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype BankCardForm = {\r\n holder_name: string\r\n card_no: string\r\n bank_name: string\r\n phone: string\r\n is_default: boolean\r\n}\r\n\r\nconst loading = ref(false)\r\nconst form = reactive({\r\n holder_name: '',\r\n card_no: '',\r\n bank_name: '',\r\n phone: '',\r\n is_default: false\r\n} as BankCardForm)\r\n\r\nconst onSwitchChange = (e: UniSwitchChangeEvent) => {\r\n form.is_default = e.detail.value\r\n}\r\n\r\n// 模拟卡号识别\r\nconst detectBank = (e: any) => {\r\n const val = form.card_no\r\n if (val.length >= 6) {\r\n if (val.startsWith('6222')) form.bank_name = '中国工商银行'\r\n else if (val.startsWith('6227')) form.bank_name = '中国建设银行'\r\n else if (val.startsWith('6225')) form.bank_name = '招商银行'\r\n else if (val.startsWith('6228')) form.bank_name = '中国农业银行'\r\n // else form.bank_name = '' \r\n }\r\n}\r\n\r\nconst submit = async () => {\r\n if (form.holder_name == '' || form.card_no == '' || form.bank_name == '') {\r\n uni.showToast({ title: '请完善卡片信息', icon: 'none' })\r\n return\r\n }\r\n \r\n loading.value = true\r\n \r\n try {\r\n const cardData = new UTSJSONObject()\r\n cardData.set('holder_name', form.holder_name)\r\n cardData.set('bank_name', form.bank_name)\r\n cardData.set('card_no', form.card_no) // Also save full card no if needed, or just last4\r\n // 截取后4位\r\n const last4 = form.card_no.length > 4 ? form.card_no.slice(-4) : form.card_no\r\n cardData.set('card_no_last4', last4)\r\n cardData.set('phone', form.phone)\r\n cardData.set('is_default', form.is_default)\r\n // 简单推定为储蓄卡\r\n cardData.set('card_type', 'debit') \r\n \r\n const success = await supabaseService.addBankCard(cardData)\r\n if (success) {\r\n uni.showToast({ title: '添加成功' })\r\n setTimeout(() => {\r\n uni.navigateBack()\r\n }, 1000)\r\n } else {\r\n uni.showToast({ title: '添加失败', icon: 'none' })\r\n }\r\n } catch (e) {\r\n console.error(e)\r\n uni.showToast({ title: '系统错误', icon: 'none' })\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n</script>\r\n\r\n<style>\r\n.add-card-page {\r\n background-color: #f5f5f5;\r\n flex: 1;\r\n}\r\n\r\n.form-container {\r\n background-color: #fff;\r\n padding: 0 15px;\r\n}\r\n\r\n.form-item {\r\n display: flex;\r\n align-items: center;\r\n padding: 15px 0;\r\n border-bottom: 1px solid #eee;\r\n}\r\n\r\n.form-item:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.label {\r\n width: 80px;\r\n font-size: 15px;\r\n color: #333;\r\n}\r\n\r\n.input {\r\n flex: 1;\r\n font-size: 15px;\r\n}\r\n\r\n.switch-item {\r\n justify-content: space-between;\r\n}\r\n\r\n.action-section {\r\n padding: 30px 15px;\r\n}\r\n\r\n.submit-btn {\r\n background-color: #ff5000;\r\n color: #fff;\r\n border-radius: 25px;\r\n font-size: 16px;\r\n}\r\n\r\n.submit-btn.disabled {\r\n opacity: 0.6;\r\n}\r\n</style>\r\n"],"names":[],"mappings":"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"}