1 line
1.5 MiB
1 line
1.5 MiB
{"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/user/center.uvue","pages/main/index.uvue","pages/main/category.uvue","pages/main/messages.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/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// 之所以又写了一份,是因为外层的socket,connectSocket的时候必须传入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 GenPagesUserBootClass from './pages/user/boot.uvue'\nimport GenPagesUserRegisterClass from './pages/user/register.uvue'\nimport GenPagesUserForgotPasswordClass from './pages/user/forgot-password.uvue'\nimport GenPagesUserTermsClass from './pages/user/terms.uvue'\nimport GenPagesUserCenterClass from './pages/user/center.uvue'\nimport GenPagesUserProfileClass from './pages/user/profile.uvue'\nimport GenPagesUserChangePasswordClass from './pages/user/change-password.uvue'\nimport GenPagesUserBindPhoneClass from './pages/user/bind-phone.uvue'\nimport GenPagesUserBindEmailClass from './pages/user/bind-email.uvue'\nimport GenPagesMainIndexClass from './pages/main/index.uvue'\nimport GenPagesMainCategoryClass from './pages/main/category.uvue'\nimport GenPagesMainMessagesClass from './pages/main/messages.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 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/user/boot\", component: GenPagesUserBootClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/register\", component: GenPagesUserRegisterClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"注册\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/forgot-password\", component: GenPagesUserForgotPasswordClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"忘记密码\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/terms\", component: GenPagesUserTermsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"用户协议与隐私政策\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/center\", component: GenPagesUserCenterClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"用户中心\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/profile\", component: GenPagesUserProfileClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"个人资料\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/change-password\", component: GenPagesUserChangePasswordClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"修改密码\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/bind-phone\", component: GenPagesUserBindPhoneClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"绑定手机\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/bind-email\", component: GenPagesUserBindEmailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"绑定邮箱\"]]) } 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/messages\", component: GenPagesMainMessagesClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"消息\"],[\"navigationStyle\",\"custom\"],[\"enablePullDownRefresh\",true]]) } 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/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\"],[\"backgroundColor\",\"#ffffff\"],[\"borderStyle\",\"black\"],[\"list\",[_uM([[\"pagePath\",\"pages/main/index\"],[\"text\",\"首页\"],[\"iconPath\",\"static/tabbar/home.png\"],[\"selectedIconPath\",\"static/tabbar/home.png\"]]),_uM([[\"pagePath\",\"pages/main/category\"],[\"text\",\"分类\"],[\"iconPath\",\"static/tabbar/category.png\"],[\"selectedIconPath\",\"static/tabbar/category.png\"]]),_uM([[\"pagePath\",\"pages/main/messages\"],[\"text\",\"消息\"],[\"iconPath\",\"static/tabbar/message.png\"],[\"selectedIconPath\",\"static/tabbar/message.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\",\"mall\"],[\"navigationBarBackgroundColor\",\"#FFFFFF\"],[\"backgroundColor\",\"#F8F8F8\"]])\n __uniConfig.getTabBarConfig = ():Map<string, any> | null => _uM([[\"color\",\"#999999\"],[\"selectedColor\",\"#ff5000\"],[\"backgroundColor\",\"#ffffff\"],[\"borderStyle\",\"black\"],[\"list\",[_uM([[\"pagePath\",\"pages/main/index\"],[\"text\",\"首页\"],[\"iconPath\",\"static/tabbar/home.png\"],[\"selectedIconPath\",\"static/tabbar/home.png\"]]),_uM([[\"pagePath\",\"pages/main/category\"],[\"text\",\"分类\"],[\"iconPath\",\"static/tabbar/category.png\"],[\"selectedIconPath\",\"static/tabbar/category.png\"]]),_uM([[\"pagePath\",\"pages/main/messages\"],[\"text\",\"消息\"],[\"iconPath\",\"static/tabbar/message.png\"],[\"selectedIconPath\",\"static/tabbar/message.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遍历Map(UTS支持)\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","<template>\r\n <view class=\"page-wrapper\">\r\n <view class=\"top-section\">\r\n <view class=\"language-switch\">\r\n <button class=\"language-btn\" @click=\"toggleLanguage\">\r\n {{ currentLocale === 'zh-CN' ? 'EN' : '中文' }}\r\n </button>\r\n </view>\r\n </view>\r\n \r\n <view class=\"main-section\">\r\n <scroll-view direction=\"vertical\" class=\"user-center-container\">\r\n <view class=\"user-header\">\r\n <view class=\"user-info\">\r\n <image class=\"user-avatar\" :src=\"userAvatar\" mode=\"aspectFill\"></image>\r\n <view class=\"user-details\">\r\n <text class=\"user-name\">{{ profile != null && profile.username != null ? profile.username : '未命名用户' }}</text>\r\n <view class=\"edit-profile-link\" @click=\"navigateToProfile\">\r\n <text class=\"edit-text\">编辑资料</text>\r\n <text class=\"edit-icon\">✏️</text>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <view class=\"stats-container\">\r\n <view class=\"stat-item\">\r\n <text class=\"stat-value\">{{ userStats.trainings }}</text>\r\n <text class=\"stat-label\">训练</text>\r\n </view>\r\n <view class=\"stat-divider\"></view>\r\n <view class=\"stat-item\">\r\n <text class=\"stat-value\">{{ userStats.points }}</text>\r\n <text class=\"stat-label\">积分</text>\r\n </view>\r\n <view class=\"stat-divider\"></view>\r\n <view class=\"stat-item\">\r\n <text class=\"stat-value\">{{ userStats.streak }}</text>\r\n <text class=\"stat-label\">连续</text>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <view class=\"menu-sections\">\r\n <view class=\"menu-section\">\r\n <view class=\"section-header\">\r\n <text class=\"section-title\">设置</text>\r\n </view>\r\n \r\n <view class=\"section-items\">\r\n <view class=\"menu-item\" @click=\"navigateTo('/pages/settings/app')\">\r\n <view class=\"menu-icon app-settings\">⚙️</view>\r\n <text class=\"menu-text\">应用设置</text>\r\n <text class=\"menu-arrow\">></text>\r\n </view>\r\n \r\n <view class=\"menu-item\" @click=\"navigateTo('/pages/settings/about')\">\r\n <view class=\"menu-icon about\">ℹ️</view>\r\n <text class=\"menu-text\">关于</text>\r\n <text class=\"menu-arrow\">></text>\r\n </view>\r\n \r\n <view class=\"menu-item\" @click=\"navigateTo('/pages/user/notifications')\">\r\n <view class=\"menu-icon notifications\">🔔</view>\r\n <text class=\"menu-text\">通知</text>\r\n <text class=\"menu-arrow\">></text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <button class=\"logout-button\" @click=\"showLogoutConfirm\">退出登录</button>\r\n </scroll-view>\r\n </view>\r\n </view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\nimport { ref } from 'vue'\r\nimport { onShow } from '@dcloudio/uni-app'\r\nimport { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype ProfileType = {\r\n id: string\r\n username: string | null\r\n email: string | null\r\n avatar_url: string | null\r\n}\r\n\r\ntype UserStatsType = {\r\n trainings: number\r\n points: number\r\n streak: number\r\n}\r\n\r\nconst profile = ref<ProfileType | null>(null)\r\nconst userStats = ref<UserStatsType>({\r\n trainings: 0,\r\n points: 0,\r\n streak: 0\r\n} as UserStatsType)\r\nconst currentLocale = ref<string>('zh-CN')\r\nconst userAvatar = ref<string>('/static/images/default-product.png')\r\n\r\nconst toggleLanguage = (): void => {\r\n if (currentLocale.value === 'zh-CN') {\r\n currentLocale.value = 'en-US'\r\n } else {\r\n currentLocale.value = 'zh-CN'\r\n }\r\n uni.showToast({\r\n title: '语言已切换',\r\n icon: 'success'\r\n })\r\n}\r\n\r\nconst loadProfile = async (): Promise<void> => {\r\n try {\r\n const res = await supabaseService.getUserProfile()\r\n if (res != null) {\r\n const profileData = res as UTSJSONObject\r\n const p: ProfileType = {\r\n id: profileData.getString('id') ?? '',\r\n username: profileData.getString('username'),\r\n email: profileData.getString('email'),\r\n avatar_url: profileData.getString('avatar_url')\r\n } as ProfileType\r\n profile.value = p\r\n \r\n if (p.avatar_url != null && p.avatar_url != '') {\r\n userAvatar.value = p.avatar_url\r\n }\r\n }\r\n } catch (e) {\r\n console.error('加载用户资料失败:', e)\r\n }\r\n}\r\n\r\nconst loadUserStats = (): void => {\r\n userStats.value = {\r\n trainings: 12,\r\n points: 480,\r\n streak: 5\r\n } as UserStatsType\r\n}\r\n\r\nconst navigateToProfile = (): void => {\r\n uni.navigateTo({\r\n url: '/pages/user/profile'\r\n })\r\n}\r\n\r\nconst navigateTo = (url: string): void => {\r\n const implementedPages: Array<string> = ['/pages/user/profile']\r\n let found = false\r\n for (let i: number = 0; i < implementedPages.length; i++) {\r\n if (implementedPages[i] == url) {\r\n found = true\r\n break\r\n }\r\n }\r\n \r\n if (found) {\r\n uni.navigateTo({ url: url })\r\n } else {\r\n uni.showToast({\r\n title: '功能开发中',\r\n icon: 'none'\r\n })\r\n }\r\n}\r\n\r\nconst handleLogout = (): void => {\r\n uni.removeStorageSync('userInfo')\r\n uni.removeStorageSync('user_id')\r\n uni.removeStorageSync('access_token')\r\n \r\n uni.showToast({\r\n title: '已退出登录',\r\n icon: 'success'\r\n })\r\n \r\n setTimeout(() => {\r\n uni.reLaunch({\r\n url: '/pages/user/login'\r\n })\r\n }, 1000)\r\n}\r\n\r\nconst showLogoutConfirm = (): void => {\r\n uni.showModal({\r\n title: '提示',\r\n content: '确定要退出登录吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n handleLogout()\r\n }\r\n }\r\n })\r\n}\r\n\r\nonShow(() => {\r\n loadProfile()\r\n loadUserStats()\r\n})\r\n</script>\r\n\r\n<style>\r\n.page-wrapper {\r\n flex: 1;\r\n background-color: #f5f5f5;\r\n}\r\n\r\n.top-section {\r\n padding: 10px 15px;\r\n background-color: #fff;\r\n}\r\n\r\n.language-switch {\r\n display: flex;\r\n justify-content: flex-end;\r\n}\r\n\r\n.language-btn {\r\n font-size: 12px;\r\n padding: 5px 15px;\r\n background-color: #f0f0f0;\r\n border-radius: 15px;\r\n}\r\n\r\n.main-section {\r\n flex: 1;\r\n}\r\n\r\n.user-center-container {\r\n flex: 1;\r\n}\r\n\r\n.user-header {\r\n background-color: #fff;\r\n padding: 20px 15px;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.user-info {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.user-avatar {\r\n width: 60px;\r\n height: 60px;\r\n border-radius: 30px;\r\n background-color: #eee;\r\n}\r\n\r\n.user-details {\r\n margin-left: 15px;\r\n flex: 1;\r\n}\r\n\r\n.user-name {\r\n font-size: 18px;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n\r\n.edit-profile-link {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n margin-top: 5px;\r\n}\r\n\r\n.edit-text {\r\n font-size: 12px;\r\n color: #007aff;\r\n}\r\n\r\n.edit-icon {\r\n font-size: 12px;\r\n margin-left: 5px;\r\n}\r\n\r\n.stats-container {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-around;\r\n margin-top: 20px;\r\n padding-top: 15px;\r\n border-top: 1px solid #eee;\r\n}\r\n\r\n.stat-item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n}\r\n\r\n.stat-value {\r\n font-size: 20px;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n\r\n.stat-label {\r\n font-size: 12px;\r\n color: #999;\r\n margin-top: 5px;\r\n}\r\n\r\n.stat-divider {\r\n width: 1px;\r\n height: 30px;\r\n background-color: #eee;\r\n}\r\n\r\n.menu-sections {\r\n background-color: #fff;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.menu-section {\r\n padding: 15px;\r\n}\r\n\r\n.section-header {\r\n margin-bottom: 10px;\r\n}\r\n\r\n.section-title {\r\n font-size: 14px;\r\n color: #999;\r\n}\r\n\r\n.section-items {\r\n background-color: #fff;\r\n}\r\n\r\n.menu-item {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n padding: 12px 0;\r\n border-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.menu-item:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.menu-icon {\r\n width: 30px;\r\n font-size: 18px;\r\n}\r\n\r\n.menu-text {\r\n flex: 1;\r\n font-size: 14px;\r\n color: #333;\r\n}\r\n\r\n.menu-arrow {\r\n font-size: 14px;\r\n color: #ccc;\r\n}\r\n\r\n.logout-button {\r\n margin: 20px 15px;\r\n background-color: #ff4444;\r\n color: #fff;\r\n font-size: 16px;\r\n padding: 12px;\r\n border-radius: 8px;\r\n text-align: center;\r\n}\r\n</style>\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<view class=\"product-image-wrapper\">\r\n\t\t\t\t\t\t\t<image \r\n\t\t\t\t\t\t\t\tclass=\"product-image\" \r\n\t\t\t\t\t\t\t\t:src=\"product.main_image_url\" \r\n\t\t\t\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t</view>\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)}×tamp=${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)}×tamp=${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; /* 确保 highlighting 触发滚动 */\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-wrapper {\r\n\twidth: 100%;\r\n\tpadding-bottom: 100%;\r\n\tposition: relative;\r\n\tborder-radius: 8px;\r\n\toverflow: hidden;\r\n\tbackground: #f5f5f5;\r\n}\r\n\r\n.product-image {\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tborder-radius: 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.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\twidth: 100%;\r\n\tpadding-bottom: 100%;\r\n}\r\n\r\n.product-image-container .product-image {\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\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.product-card {\r\n\t\twidth: 48%;\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-wrapper {\r\n\t\tpadding-bottom: 100%;\r\n\t}\r\n\t\r\n\t.product-image,\r\n\t.product-image-container {\r\n\t\tpadding-bottom: 100%;\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-wrapper,\r\n\t.hot-products .product-image-container,\r\n\t.smart-recommend .product-image-wrapper,\r\n\t.smart-recommend .product-image-container {\r\n\t\tpadding-bottom: 100%;\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.product-card {\r\n\t\twidth: 48%;\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\twidth: 32%;\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\twidth: 23%;\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\twidth: 18%;\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 <view class=\"product-image-wrapper\">\r\n <image \r\n class=\"product-image\" \r\n :src=\"product.main_image_url\" \r\n mode=\"aspectFill\"\r\n />\r\n </view>\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-wrapper {\r\n width: 100%;\r\n padding-bottom: 100%;\r\n position: relative;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n background: #f5f5f5;\r\n}\r\n\r\n.product-image {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n border-radius: 8px;\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-wrapper {\r\n padding-bottom: 100%;\r\n }\r\n \r\n .product-image {\r\n padding-bottom: 100%;\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-wrapper {\r\n padding-bottom: 100%;\r\n }\r\n \r\n .product-image {\r\n padding-bottom: 100%;\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","<template>\r\n\t<view class=\"messages-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=\"clearAllUnread\">\r\n\t\t\t\t\t\t<text class=\"action-icon\">🧹</text>\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\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<view class=\"tabs-container\">\r\n\t\t\t<view class=\"message-tabs\">\r\n\t\t\t\t<view \r\n\t\t\t\t\tv-for=\"tab in messageTabs\" \r\n\t\t\t\t\t:key=\"tab.id\"\r\n\t\t\t\t\t:class=\"['tab-item', { active: activeTab === tab.id }]\"\r\n\t\t\t\t\t@click=\"switchTab(tab.id)\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text class=\"tab-name\">{{ tab.name }}</text>\r\n\t\t\t\t\t<text v-if=\"tab.unread > 0\" class=\"tab-badge\">{{ tab.unread > 99 ? '99+' : tab.unread }}</text>\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<!-- 消息列表内容区 -->\r\n\t\t<scroll-view \r\n\t\t\tscroll-y \r\n\t\t\tclass=\"messages-content\"\r\n\t\t\trefresher-enabled\r\n\t\t\t:refresher-triggered=\"refreshing\"\r\n\t\t\t@refresherrefresh=\"onRefresh\"\r\n\t\t\t:scroll-top=\"scrollTop\"\r\n\t\t>\r\n\r\n\t\t\t<!-- 客服消息 -->\r\n\t\t\t<view v-if=\"activeTab === 'service'\" class=\"message-section\">\r\n\t\t\t\t<!-- 在线客服卡片 (hidden) -->\r\n\t\t\t\t<!-- <view class=\"customer-service-info\">\r\n\t\t\t\t\t<view class=\"service-header\">\r\n\t\t\t\t\t\t<text class=\"service-title\">康乐医药在线客服</text>\r\n\t\t\t\t\t\t<text class=\"service-status online\">在线</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<text class=\"service-desc\">专业医药顾问在线解答,服务时间 9:00-22:00</text>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<view class=\"service-categories\">\r\n\t\t\t\t\t\t<view class=\"category-item\" @click=\"startQuickService('用药咨询')\">\r\n\t\t\t\t\t\t\t<text class=\"category-icon\">💊</text>\r\n\t\t\t\t\t\t\t<text class=\"category-name\">用药咨询</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"category-item\" @click=\"startQuickService('处方咨询')\">\r\n\t\t\t\t\t\t\t<text class=\"category-icon\">📋</text>\r\n\t\t\t\t\t\t\t<text class=\"category-name\">处方咨询</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"category-item\" @click=\"startQuickService('副作用咨询')\">\r\n\t\t\t\t\t\t\t<text class=\"category-icon\">⚠️</text>\r\n\t\t\t\t\t\t\t<text class=\"category-name\">副作用咨询</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"category-item\" @click=\"startQuickService('药品配送')\">\r\n\t\t\t\t\t\t\t<text class=\"category-icon\">🚚</text>\r\n\t\t\t\t\t\t\t<text class=\"category-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\r\n\t\t\t\t<!-- 客服消息列表 -->\r\n\t\t\t\t<view \r\n\t\t\t\t\tv-for=\"message in serviceMessages\" \r\n\t\t\t\t\t:key=\"message.id\"\r\n\t\t\t\t\t:class=\"['message-item', { active: message.active }]\"\r\n\t\t\t\t\t@click=\"startChatWithService(message)\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view class=\"message-icon-wrapper\">\r\n\t\t\t\t\t\t<image \r\n\t\t\t\t\t\t\tv-if=\"message.avatar\" \r\n\t\t\t\t\t\t\tclass=\"message-avatar\" \r\n\t\t\t\t\t\t\t:src=\"message.avatar\" \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<view v-else class=\"message-icon-default\" :style=\"{ backgroundColor: message.color }\">\r\n\t\t\t\t\t\t\t<text class=\"message-icon-text\">{{ message.icon }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view v-if=\"message.unreadCount > 0\" class=\"unread-dot\"></view>\r\n\t\t\t\t\t\t<view v-if=\"message.online\" class=\"online-dot\"></view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"message-content\">\r\n\t\t\t\t\t\t<view class=\"message-header\">\r\n\t\t\t\t\t\t\t<view class=\"message-title-wrapper\">\r\n\t\t\t\t\t\t\t\t<text class=\"message-title\">{{ message.title }}</text>\r\n\t\t\t\t\t\t\t\t<text v-if=\"message.role\" class=\"message-role\">{{ message.role }}</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<text class=\"message-time\">{{ message.time }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"message-preview-wrapper\">\r\n\t\t\t\t\t\t\t<text class=\"message-preview\">{{ message.content }}</text>\r\n\t\t\t\t\t\t\t<text v-if=\"message.unreadCount > 0\" class=\"message-unread-count\">{{ message.unreadCount > 99 ? '99+' : message.unreadCount }}</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=\"service-tips\">\r\n\t\t\t\t\t<text class=\"tip-icon\">💡</text>\r\n\t\t\t\t\t<text class=\"tip-text\">温馨提示:请勿相信任何要求转账、付款的信息,谨防诈骗</text>\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\t\t\t<view v-if=\"activeTab === 'system'\" class=\"message-section\">\r\n\t\t\t\t<view \r\n\t\t\t\t\tv-for=\"message in systemMessages\" \r\n\t\t\t\t\t:key=\"message.id\"\r\n\t\t\t\t\tclass=\"message-item\"\r\n\t\t\t\t\t@click=\"viewSystemMessage(message)\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view class=\"message-icon-wrapper\">\r\n\t\t\t\t\t\t<text class=\"message-icon-text\">📢</text>\r\n\t\t\t\t\t\t<view v-if=\"!message.read\" class=\"unread-dot\"></view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"message-content\">\r\n\t\t\t\t\t\t<view class=\"message-header\">\r\n\t\t\t\t\t\t\t<view class=\"message-title-wrapper\">\r\n\t\t\t\t\t\t\t\t<text class=\"message-title\">{{ message.title }}</text>\r\n\t\t\t\t\t\t\t\t<text v-if=\"message.important\" class=\"important-tag\">重要</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<text class=\"message-time\">{{ message.time }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"message-preview-wrapper\">\r\n\t\t\t\t\t\t\t<text class=\"message-preview\">{{ message.content }}</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\t\r\n\t\t\t<!-- 订单消息 -->\r\n\t\t\t<view v-if=\"activeTab === 'order'\" class=\"message-section\">\r\n\t\t\t\t<view \r\n\t\t\t\t\tv-for=\"message in orderMessages\" \r\n\t\t\t\t\t:key=\"message.id\"\r\n\t\t\t\t\tclass=\"message-item\"\r\n\t\t\t\t\t@click=\"viewOrderMessage(message)\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view class=\"message-icon-wrapper\">\r\n\t\t\t\t\t\t<text class=\"message-icon-text\">📦</text>\r\n\t\t\t\t\t\t<view v-if=\"!message.read\" class=\"unread-dot\"></view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"message-content\">\r\n\t\t\t\t\t\t<view class=\"message-header\">\r\n\t\t\t\t\t\t\t<view class=\"message-title-wrapper\">\r\n\t\t\t\t\t\t\t\t<text class=\"message-title\">{{ message.title }}</text>\r\n\t\t\t\t\t\t\t\t<view v-if=\"message.status\" class=\"order-status-tag\" :class=\"message.status\">\r\n\t\t\t\t\t\t\t\t\t{{ message.statusText }}\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=\"message-time\">{{ message.time }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"message-preview-wrapper\">\r\n\t\t\t\t\t\t\t<text class=\"message-preview\">{{ message.content }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"order-no-text\" v-if=\"message.order_no\">订单号: {{ message.order_no }}</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<!-- 优惠活动 -->\r\n\t\t\t<view v-if=\"activeTab === 'promo'\" class=\"message-section\">\r\n\t\t\t\t<view \r\n\t\t\t\t\tv-for=\"message in promoMessages\" \r\n\t\t\t\t\t:key=\"message.id\"\r\n\t\t\t\t\t:class=\"['message-item', { unread: !message.read }]\"\r\n\t\t\t\t\t@click=\"viewPromoMessage(message)\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view class=\"message-icon-wrapper\">\r\n\t\t\t\t\t\t<text class=\"message-icon\">🎁</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"message-content\">\r\n\t\t\t\t\t\t<view class=\"message-header\">\r\n\t\t\t\t\t\t\t<text class=\"message-title\">{{ message.title }}</text>\r\n\t\t\t\t\t\t\t<text class=\"message-time\">{{ message.time }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text class=\"message-preview\">{{ message.content }}</text>\r\n\t\t\t\t\t\t<view v-if=\"message.coupon\" class=\"coupon-info\" @click.stop=\"claimCoupon(message)\">\r\n\t\t\t\t\t\t\t<text class=\"coupon-text\">{{ message.coupon }}优惠券</text>\r\n\t\t\t\t\t\t\t<text class=\"coupon-expiry\">有效期至 {{ message.expiry }}</text>\r\n\t\t\t\t\t\t\t<text class=\"coupon-action\">{{ message.claimed ? '已领取' : '点击领取' }}</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\t\r\n\t\t\t<!-- 空状态 -->\r\n\t\t\t<view v-if=\"!loading && currentMessages.length === 0 && activeTab !== 'service'\" class=\"empty-messages\">\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</view>\r\n\t\t\t\r\n\t\t\t<!-- 底部安全区域 -->\r\n\t\t\t<view class=\"safe-area\"></view>\r\n\t\t</scroll-view>\r\n\t\t\r\n\t\t<!-- 底部固定按钮 (Hidden) -->\r\n\t\t<!-- <view class=\"floating-action\">\r\n\t\t\t<button class=\"action-button\" @click=\"startNewChat\">\r\n\t\t\t\t<text class=\"button-icon\">✏️</text>\r\n\t\t\t\t<text class=\"button-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, reactive, computed, onMounted } from 'vue'\r\nimport { onShow } from '@dcloudio/uni-app'\r\nimport { supabaseService, type Notification, type ChatMessage, type ChatRoom } from '@/utils/supabaseService.uts'\r\n\r\n// 定义消息项类型\r\ntype MessageItem = {\r\n\tid: string,\r\n\ttitle: string,\r\n\tcontent: string,\r\n\ttime: string,\r\n\tread: boolean,\r\n\ttype: string,\r\n\tavatar: string | null,\r\n\timportant: boolean,\r\n\tcoupon: string,\r\n\texpiry: string,\r\n\tclaimed: boolean,\r\n\torder_no: string,\r\n\tstatus: string,\r\n\tstatusText: string,\r\n\trole: string,\r\n\tlastMessage: string,\r\n\tonline: boolean,\r\n\tunreadCount: number,\r\n\ttags: string[],\r\n\ticon: string,\r\n\tcolor: string,\r\n\tactive: boolean\r\n}\r\n\r\n// 定义标签类型\r\ntype MessageTab = {\r\n\tid: string,\r\n\tname: string,\r\n\tunread: number\r\n}\r\n\r\n// 响应式数据\r\nconst activeTab = ref<string>('service')\r\nconst refreshing = ref<boolean>(false)\r\nconst loading = ref<boolean>(false)\r\nconst unreadCount = ref<number>(12)\r\nconst statusBarHeight = ref(0)\r\nconst scrollTop = ref(0)\r\nconst scrollHeight = ref(0)\r\nconst navBarRight = ref(0)\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\n\r\n// 消息分类标签\r\nconst messageTabs = reactive<MessageTab[]>([\r\n\t{ id: 'service', name: '客服消息', unread: 5 },\r\n\t{ id: 'system', name: '系统通知', unread: 3 },\r\n\t{ id: 'order', name: '订单消息', unread: 2 },\r\n\t{ id: 'promo', name: '优惠活动', unread: 2 }\r\n])\r\n\r\n// 消息数据\r\nconst serviceMessages = reactive<MessageItem[]>([])\r\nconst systemMessages = reactive<MessageItem[]>([])\r\nconst orderMessages = reactive<MessageItem[]>([])\r\nconst promoMessages = reactive<MessageItem[]>([])\r\n\r\n// 计算当前显示的消息\r\nconst currentMessages = computed<MessageItem[]>(() => {\r\n\tswitch (activeTab.value) {\r\n\t\tcase 'system': return systemMessages\r\n\t\tcase 'order': return orderMessages\r\n\t\tcase 'service': return serviceMessages\r\n\t\tcase 'promo': return promoMessages\r\n\t\tdefault: return []\r\n\t}\r\n})\r\n\r\n// 简单的日期格式化\r\nconst formatTime = (isoString: string): string => {\r\n if (isoString == '') return ''\r\n try {\r\n return isoString.split('T')[0]\r\n } catch(e) {\r\n return isoString\r\n }\r\n}\r\n\r\n// 更新未读数量 - 必须在 loadMessages 之前定义\r\nconst updateUnreadCount = () => {\r\n\tlet totalUnread = 0\r\n\t\r\n\tlet serviceUnread = 0\r\n\tserviceMessages.forEach((msg: MessageItem) => {\r\n\t\tif (!msg.read) serviceUnread++\r\n\t})\r\n\tmessageTabs[0].unread = serviceUnread\r\n\ttotalUnread += serviceUnread\r\n\t\r\n\tlet systemUnread = 0\r\n\tsystemMessages.forEach((msg: MessageItem) => {\r\n\t\tif (!msg.read) systemUnread++\r\n\t})\r\n\tmessageTabs[1].unread = systemUnread\r\n\ttotalUnread += systemUnread\r\n\t\r\n\tlet orderUnread = 0\r\n\torderMessages.forEach((msg: MessageItem) => {\r\n\t\tif (!msg.read) orderUnread++\r\n\t})\r\n\tmessageTabs[2].unread = orderUnread\r\n\ttotalUnread += orderUnread\r\n\t\r\n\tlet promoUnread = 0\r\n\tpromoMessages.forEach((msg: MessageItem) => {\r\n\t\tif (!msg.read) promoUnread++\r\n\t})\r\n\tmessageTabs[3].unread = promoUnread\r\n\ttotalUnread += promoUnread\r\n\t\r\n\tunreadCount.value = totalUnread\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\t\r\n\r\n\tnavBarRight.value = 0\r\n\r\n\t\r\n\tconst windowHeight = systemInfo.windowHeight\r\n\tscrollHeight.value = windowHeight - statusBarHeight.value - 44 - 42\r\n}\r\n\r\n// 加载消息函数 - 必须在 updateUnreadCount 之后定义\r\nconst loadMessages = async () => {\r\n\tloading.value = true\r\n\t\r\n try {\r\n // 清空现有数据\r\n serviceMessages.length = 0\r\n systemMessages.length = 0\r\n orderMessages.length = 0\r\n promoMessages.length = 0\r\n\r\n // 1. 获取通知 (系统、订单、优惠)\r\n console.log('[loadMessages] 开始获取通知...')\r\n const notes = await supabaseService.getUserNotifications()\r\n console.log('[loadMessages] 获取到通知数量:', notes.length)\r\n \r\n // 使用 for 循环替代 forEach\r\n for (let i = 0; i < notes.length; i++) {\r\n const note = notes[i]\r\n console.log('[loadMessages] 通知类型:', note.type, '标题:', note.title)\r\n \r\n const item: MessageItem = {\r\n id: note.id,\r\n title: note.title,\r\n content: note.content,\r\n time: formatTime(note.created_at ?? ''),\r\n read: note.is_read,\r\n type: note.type,\r\n avatar: note.icon_url,\r\n important: note.type == 'system',\r\n coupon: '点击查看',\r\n expiry: '',\r\n claimed: false,\r\n order_no: '',\r\n status: '',\r\n statusText: '',\r\n role: '',\r\n lastMessage: '',\r\n online: false,\r\n unreadCount: 0,\r\n tags: [] as string[],\r\n icon: '',\r\n color: '',\r\n\t\t\t\tactive: false\r\n }\r\n\r\n if (note.type == 'system') {\r\n systemMessages.push(item)\r\n } else if (note.type == 'order') {\r\n orderMessages.push(item)\r\n } else if (note.type == 'promotion') {\r\n item.type = 'promo'\r\n promoMessages.push(item)\r\n }\r\n }\r\n \r\n console.log('[loadMessages] 系统消息:', systemMessages.length, '订单消息:', orderMessages.length, '优惠消息:', promoMessages.length)\r\n\r\n // 2. 获取客服消息 (Chat)\r\n const rooms = await supabaseService.getChatRooms()\r\n rooms.forEach((room: ChatRoom) => {\r\n const msgItem: MessageItem = {\r\n id: room.merchant_id,\r\n title: room.shop_name,\r\n role: '商家客服',\r\n content: room.last_message ?? '暂无消息',\r\n lastMessage: room.last_message ?? '暂无消息',\r\n time: formatTime(room.last_message_at ?? ''),\r\n read: room.unread_count === 0,\r\n type: 'service',\r\n avatar: room.shop_logo ?? '/static/icons/customer-service.png',\r\n online: true,\r\n unreadCount: room.unread_count,\r\n tags: [] as string[],\r\n icon: '🏪',\r\n color: '#FF9800',\r\n important: false,\r\n coupon: '',\r\n expiry: '',\r\n claimed: false,\r\n order_no: '',\r\n status: '',\r\n statusText: '',\r\n\t\t\t\tactive: false\r\n }\r\n serviceMessages.push(msgItem)\r\n })\r\n\t\t\r\n\t\t// 如果没有消息,添加默认客服\r\n\t\tif (serviceMessages.length === 0) {\r\n const defaultService: MessageItem = {\r\n id: 'default_service',\r\n title: '平台客服',\r\n role: '智能助手',\r\n content: '有问题请随时联系我们',\r\n lastMessage: '欢迎咨询',\r\n time: '刚刚',\r\n read: true,\r\n type: 'service',\r\n avatar: '/static/icons/customer-service.png',\r\n online: true,\r\n unreadCount: 0,\r\n tags: ['自动回复'],\r\n icon: '🤖',\r\n color: '#2196F3',\r\n important: false,\r\n coupon: '',\r\n expiry: '',\r\n claimed: false,\r\n order_no: '',\r\n status: '',\r\n statusText: '',\r\n\t\t\t\tactive: false\r\n }\r\n serviceMessages.push(defaultService)\r\n }\r\n \r\n } catch (e) {\r\n console.error('加载消息失败', e)\r\n } finally {\r\n\t\tupdateUnreadCount()\r\n\t\tloading.value = false\r\n }\r\n}\r\n\r\n// 生命周期钩子\r\nonMounted(() => {\r\n\tconsole.log('Messages Page Mounted')\r\n\tinitPage()\r\n})\r\n\r\nonShow(() => {\r\n console.log('Messages Page Show')\r\n loadMessages()\r\n})\r\n\r\n// 切换标签\r\nconst switchTab = (tabId: string) => {\r\n\tactiveTab.value = tabId\r\n\t// 切换标签时回到顶部,使用微小变化触发滚动更新\r\n\tscrollTop.value = scrollTop.value === 0 ? 0.01 : 0\r\n}\r\n\r\n// 开始与客服聊天\r\nconst startChatWithService = (message: MessageItem) => {\r\n\tmessage.read = true\r\n\tmessage.unreadCount = 0\r\n\tupdateUnreadCount()\r\n\t\r\n // 这里的 message.id 已经被我们修改为 conversation partner (merchantId)\r\n // 所以参数传递需要调整\r\n const merchantId = message.id === 'default_service' ? '' : message.id\r\n \r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/chat?merchantId=${merchantId}&merchantName=${encodeURIComponent(message.title)}`\r\n\t})\r\n}\r\n\r\n// 快速开始服务\r\nconst startQuickService = (category: string) => {\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/chat?category=${encodeURIComponent(category)}`\r\n\t})\r\n}\r\n\r\n// 新建聊天\r\nconst startNewChat = () => {\r\n\tuni.showActionSheet({\r\n\t\titemList: ['用药咨询', '处方咨询', '副作用咨询', '药品配送', '其他问题'],\r\n\t\tsuccess: (res) => {\r\n\t\t\tconst categories = ['用药咨询', '处方咨询', '副作用咨询', '药品配送', '其他问题']\r\n\t\t\tconst category = categories[res.tapIndex]\r\n\t\t\tstartQuickService(category)\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 查看系统消息\r\nconst viewSystemMessage = (message: MessageItem) => {\r\n\tmessage.read = true\r\n\tupdateUnreadCount()\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/message-detail?id=${message.id}&type=system`\r\n\t})\r\n}\r\n\r\n// 查看订单消息\r\nconst viewOrderMessage = (message: MessageItem) => {\r\n\tmessage.read = true\r\n\tupdateUnreadCount()\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/order-detail?id=${message.order_no}`\r\n\t})\r\n}\r\n\r\n// 查看优惠活动\r\nconst viewPromoMessage = (message: MessageItem) => {\r\n\tmessage.read = true\r\n\tupdateUnreadCount()\r\n\tuni.navigateTo({\r\n\t\turl: `/pages/mall/consumer/coupons`\r\n\t})\r\n}\r\n\r\n// 领取优惠券\r\nconst claimCoupon = (message: MessageItem) => {\r\n\tif (message.claimed) {\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\tmessage.claimed = true\r\n\t// 保存领取状态到本地存储,供个人页读取\r\n\tconst claimedCouponsCount = uni.getStorageSync('claimedCoupons')\r\n const count = (claimedCouponsCount != null) ? (claimedCouponsCount as number) : 0\r\n\tuni.setStorageSync('claimedCoupons', count + 1)\r\n\t\r\n\t// 保存详细的优惠券信息到 myCoupons 列表\r\n\tconst myCoupons = uni.getStorageSync('myCoupons')\r\n\tlet couponsList: any[] = []\r\n\tif (myCoupons != null) {\r\n\t\ttry {\r\n\t\t\tcouponsList = JSON.parse(myCoupons as string) as any[]\r\n\t\t} catch (e) {\r\n\t\t\tconsole.error('Failed to parse myCoupons', e)\r\n\t\t}\r\n\t}\r\n\t\r\n\tcouponsList.push({\r\n\t\ttitle: message.title,\r\n\t\tamount: message.coupon,\r\n\t\texpiry: message.expiry,\r\n\t\tid: message.id\r\n\t})\r\n\tuni.setStorageSync('myCoupons', JSON.stringify(couponsList))\r\n\t\r\n\tuni.showToast({\r\n\t\ttitle: '领取成功',\r\n\t\ticon: 'success'\r\n\t})\r\n}\r\n\r\n// 清除所有未读\r\nconst clearAllUnread = () => {\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\tserviceMessages.forEach((msg: MessageItem) => {\r\n\t\t\t\t\tmsg.read = true\r\n\t\t\t\t\tmsg.unreadCount = 0\r\n\t\t\t\t})\r\n\t\t\t\tsystemMessages.forEach((msg: MessageItem) => {\r\n\t\t\t\t\tmsg.read = true\r\n\t\t\t\t})\r\n\t\t\t\torderMessages.forEach((msg: MessageItem) => {\r\n\t\t\t\t\tmsg.read = true\r\n\t\t\t\t})\r\n\t\t\t\tpromoMessages.forEach((msg: MessageItem) => {\r\n\t\t\t\t\tmsg.read = true\r\n\t\t\t\t})\r\n\t\t\t\t\r\n\t\t\t\tmessageTabs.forEach((tab: MessageTab) => {\r\n\t\t\t\t\ttab.unread = 0\r\n\t\t\t\t})\r\n\t\t\t\tunreadCount.value = 0\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}\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 下拉刷新\r\nconst onRefresh = () => {\r\n\trefreshing.value = true\r\n\tsetTimeout(() => {\r\n\t\tloadMessages()\r\n\t\trefreshing.value = false\r\n\t\tuni.showToast({\r\n\t\t\ttitle: '刷新成功',\r\n\t\t\ticon: 'success'\r\n\t\t})\r\n\t}, 1000)\r\n}\r\n</script>\r\n\r\n<style>\r\n/* 页面结构优化 - 避免双滚动条 */\r\n.messages-page {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tbackground-color: #f8fafc;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\toverflow: hidden; /* 关键:防止body滚动 */\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\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tjustify-content: flex-start;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.nav-container {\r\n\tpadding: 0 16px;\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* 关键修复:UVUE默认是column,必须显式设置为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\tflex: 1; /* 自适应宽度 */\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 for uniapp-x support */\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}\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.tabs-container {\r\n\tbackground: white;\r\n\tpadding: 0 10px;\r\n\tborder-bottom: 1px solid #e0e0e0;\r\n\tbox-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\r\n\tz-index: 900;\r\n\theight: 42px; /* 减小高度,更紧凑 */\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.message-tabs {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* 横向排列 */\r\n\theight: 100%;\r\n\t/* overflow-x: auto; 移除自动滚动,改为自适应宽度 */\r\n\tmax-width: 1400px;\r\n\tmargin: 0 auto;\r\n\t/* gap: 4px; removed for uniapp-x support */\r\n\tjustify-content: space-between; /* 均匀分布 */\r\n}\r\n\r\n.message-tabs::-webkit-scrollbar {\r\n\tdisplay: none;\r\n}\r\n\r\n.tab-item {\r\n\tpadding: 0 4px;\r\n\tmargin: 0 2px; /* replaced gap */\r\n\tdisplay: flex; /* 改为 flex 布局 */\r\n\tflex-direction: row; /* 关键:横向排列 文字和数字 */\r\n\talign-items: center; /* 垂直居中 */\r\n\tjustify-content: center;\r\n\tposition: relative;\r\n\theight: 100%;\r\n\tborder-bottom: 3px solid transparent;\r\n\ttransition: all 0.3s ease;\r\n\twhite-space: nowrap; /* 防止换行 */\r\n\tflex: 1; /* 关键:均分宽度,消除滚动条 */\r\n\tmin-width: 0; /* 允许压缩 */\r\n}\r\n\r\n.tab-item.active {\r\n\tcolor: #ff5000;\r\n\tborder-bottom-color: #ff5000;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.tab-name {\r\n\tfont-size: 14px; /* 微调字体大小适配小屏 */\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n}\r\n\r\n/* 徽标样式优化 - 放在文字右边 */\r\n.tab-badge {\r\n\tbackground-color: #FF5722;\r\n\tcolor: white;\r\n\tfont-size: 10px;\r\n\tpadding: 1px 5px;\r\n\tborder-radius: 10px;\r\n\tmin-width: 16px;\r\n\ttext-align: center;\r\n\tfont-weight: bold;\r\n\tmargin-left: 4px; /* 距离文字的间距 */\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\theight: 16px;\r\n}\r\n\r\n/* 消息内容区 */\r\n.messages-content {\r\n\tflex: 1; /* 关键:自适应剩余高度 */\r\n\theight: 0; /* 关键:强制flex item计算高度 */\r\n\twidth: 100%;\r\n\tmax-width: 1400px;\r\n\tmargin: 0 auto;\r\n\tpadding-bottom: 20px;\r\n}\r\n\r\n/* 客服信息区域 */\r\n.customer-service-info {\r\n\tbackground: white;\r\n\tborder-radius: 12px;\r\n\tpadding: 20px;\r\n\tmargin: 15px;\r\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.service-header {\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.service-title {\r\n\tfont-size: 18px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.service-status {\r\n\tfont-size: 12px;\r\n\tpadding: 4px 10px;\r\n\tborder-radius: 12px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.service-status.online {\r\n\tbackground: #FFF3E0;\r\n\tcolor: #ff5000;\r\n}\r\n\r\n.service-desc {\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n\tline-height: 1.5;\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.service-categories {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\tflex-wrap: wrap; /* allow wrapping to simulate grid */\r\n\t/* grid-template-columns: repeat(2, 1fr); REMOVED */\r\n\t/* gap: 12px; removed for uniapp-x support */\r\n padding: 6px; /* compensated padding */\r\n}\r\n\r\n.category-item {\r\n\tbackground: #f8f9fa;\r\n\tborder-radius: 10px;\r\n\tpadding: 15px;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\t/* cursor: pointer; removed for uniapp-x support */\r\n\ttransition: all 0.3s ease;\r\n margin: 1%; /* replaced gap */\r\n width: 48%; /* 2 columns */\r\n}\r\n\r\n.category-item:hover {\r\n\tbackground: #e8f5e9;\r\n\ttransform: translateY(-2px);\r\n\tbox-shadow: 0 4px 8px rgba(76, 175, 80, 0.2);\r\n}\r\n\r\n.category-icon {\r\n\tfont-size: 24px;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.category-name {\r\n\tfont-size: 13px;\r\n\tcolor: #333;\r\n\tfont-weight: bold;\r\n}\r\n\r\n/* 消息项 */\r\n.message-section {\r\n\tpadding: 10px;\r\n}\r\n\r\n.message-item {\r\n\tbackground-color: white;\r\n\tborder-radius: 12px;\r\n\tpadding: 15px;\r\n\tmargin-bottom: 12px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center; /* 居中对齐头像和内容 */\r\n\tbox-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);\r\n\ttransition: opacity 0.2s ease;\r\n}\r\n\r\n.message-item:active {\r\n\topacity: 0.7;\r\n}\r\n\r\n.message-item.unread {\r\n\tbackground-color: white; /* 保持白色背景,通过红点示未读 */\r\n}\r\n\r\n.message-icon-wrapper {\r\n\twidth: 48px;\r\n\theight: 48px;\r\n\tborder-radius: 24px;\r\n\tbackground-color: #f8fafc;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin-right: 12px;\r\n\tflex-shrink: 0;\r\n\tposition: relative;\r\n}\r\n\r\n.message-icon-default {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tborder-radius: 24px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.message-icon-text {\r\n\tfont-size: 22px;\r\n}\r\n\r\n.message-avatar {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tborder-radius: 24px;\r\n}\r\n\r\n.unread-dot {\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tright: 0;\r\n\twidth: 10px;\r\n\theight: 10px;\r\n\tbackground-color: #ff5000;\r\n\tborder-radius: 5px;\r\n\tborder: 1.5px solid white;\r\n}\r\n\r\n.online-dot {\r\n\tposition: absolute;\r\n\tbottom: 0;\r\n\tright: 0;\r\n\twidth: 12px;\r\n\theight: 12px;\r\n\tbackground-color: #4cd964;\r\n\tborder-radius: 6px;\r\n\tborder: 2px solid white;\r\n}\r\n\r\n.message-content {\r\n\tflex: 1;\r\n\tmin-width: 0;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.message-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: 4px;\r\n}\r\n\r\n.message-title-wrapper {\r\n\tflex: 1;\r\n\tmin-width: 0;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n}\r\n\r\n.message-title {\r\n\tfont-size: 16px;\r\n\tcolor: #1a1a1a;\r\n\tfont-weight: bold;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\twhite-space: nowrap;\r\n}\r\n\r\n.message-role {\r\n\tfont-size: 10px;\r\n\tcolor: #ff5000;\r\n\tbackground: #fff0eb;\r\n\tpadding: 1px 6px;\r\n\tborder-radius: 4px;\r\n\tmargin-left: 6px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.message-time {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n\tmargin-left: 10px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.message-preview-wrapper {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n}\r\n\r\n.message-preview {\r\n\tflex: 1;\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tline-height: 1.4;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\twhite-space: nowrap;\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.message-unread-count {\r\n\tfont-size: 10px;\r\n\tcolor: white;\r\n\tbackground: #ff5000;\r\n\tpadding: 0 5px;\r\n\tborder-radius: 10px;\r\n\tmin-width: 16px;\r\n\theight: 16px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tfont-weight: bold;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.last-message {\r\n\tfont-size: 13px;\r\n\tcolor: #999;\r\n\t/* display: block; REMOVED for uniapp-x support */\r\n}\r\n\r\n.message-tags {\r\n\tdisplay: flex;\r\n\t/* gap: 6px; removed for uniapp-x support */\r\n\tflex-wrap: wrap;\r\n}\r\n\r\n.message-tag {\r\n\tfont-size: 11px;\r\n\tcolor: #666;\r\n\tbackground: #f0f0f0;\r\n\tpadding: 3px 8px;\r\n\tborder-radius: 10px;\r\n margin-right: 6px; /* replaced gap */\r\n margin-bottom: 4px; /* for wrapping */\r\n}\r\n\r\n.order-info {\r\n\tfont-size: 12px;\r\n\tcolor: #ff5000;\r\n\tbackground-color: #FFF3E0;\r\n\tpadding: 4px 10px;\r\n\tborder-radius: 4px;\r\n\t/* display: inline-block; REMOVED for uniapp-x support */\r\n\tmargin-top: 8px;\r\n align-self: flex-start; /* Ensure it doesn't stretch */\r\n}\r\n\r\n.order-status {\r\n\t/* display: inline-block; REMOVED for uniapp-x support */\r\n\tfont-size: 12px;\r\n\tpadding: 4px 10px;\r\n\tborder-radius: 12px;\r\n\tmargin-top: 8px;\r\n\tmargin-left: 8px;\r\n align-self: flex-start; /* Ensure it doesn't stretch */\r\n}\r\n\r\n.order-status.shipping {\r\n\tbackground: #E3F2FD;\r\n\tcolor: #2196F3;\r\n}\r\n\r\n.order-status.processing {\r\n\tbackground: #FFF3E0;\r\n\tcolor: #FF9800;\r\n}\r\n\r\n.order-status.completed {\r\n\tbackground: #FFF3E0;\r\n\tcolor: #ff5000;\r\n}\r\n\r\n.important-tag {\r\n\t/* display: inline-block; REMOVED for uniapp-x support */\r\n\tbackground-color: #FF5722;\r\n\tcolor: white;\r\n\tfont-size: 11px;\r\n\tpadding: 3px 8px;\r\n\tborder-radius: 10px;\r\n\tmargin-top: 8px;\r\n align-self: flex-start; /* Ensure it doesn't stretch */\r\n}\r\n\r\n.coupon-info {\r\n\tbackground: linear-gradient(135deg, #FF9800, #FF5722);\r\n\tborder-radius: 8px;\r\n\tpadding: 10px;\r\n\tmargin-top: 8px;\r\n\tcolor: white;\r\n}\r\n\r\n.coupon-text {\r\n\tfont-size: 14px;\r\n\tfont-weight: bold;\r\n\t/* display: block; REMOVED for uniapp-x support */\r\n\tmargin-bottom: 4px;\r\n}\r\n\r\n.coupon-expiry {\r\n\tfont-size: 12px;\r\n\topacity: 0.9;\r\n}\r\n\r\n.coupon-action {\r\n\tfont-size: 12px;\r\n\tcolor: #fff;\r\n\tbackground-color: rgba(255, 255, 255, 0.2);\r\n\tpadding: 2px 8px;\r\n\tborder-radius: 10px;\r\n\tmargin-top: 4px;\r\n\talign-self: flex-start;\r\n}\r\n\r\n/* 客服系统提示 */\r\n.service-tips {\r\n\tbackground: #FFF3E0;\r\n\tborder-radius: 10px;\r\n\tpadding: 15px;\r\n\tmargin: 15px;\r\n\tdisplay: flex;\r\n\talign-items: flex-start;\r\n\t/* gap: 10px; removed for uniapp-x support */\r\n}\r\n\r\n.tip-icon {\r\n\tfont-size: 18px;\r\n\tcolor: #FF9800;\r\n\tflex-shrink: 0;\r\n\tmargin-top: 2px;\r\n margin-right: 10px; /* replaced gap */\r\n}\r\n\r\n.tip-text {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tline-height: 1.5;\r\n}\r\n\r\n.order-no-text {\r\n\tfont-size: 11px;\r\n\tcolor: #999;\r\n\tmargin-top: 4px;\r\n}\r\n\r\n.order-status-tag {\r\n\tfont-size: 10px;\r\n\tpadding: 1px 6px;\r\n\tborder-radius: 4px;\r\n\tmargin-left: 6px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.order-status-tag.shipping {\r\n\tbackground: #e3f2fd;\r\n\tcolor: #2196f3;\r\n}\r\n\r\n.order-status-tag.processing {\r\n\tbackground: #fff8e1;\r\n\tcolor: #ffb300;\r\n}\r\n\r\n.order-status-tag.completed {\r\n\tbackground: #fff0eb;\r\n\tcolor: #ff5000;\r\n}\r\n\r\n.important-tag {\r\n\tbackground-color: #ff5000;\r\n\tcolor: white;\r\n\tfont-size: 10px;\r\n\tpadding: 1px 6px;\r\n\tborder-radius: 4px;\r\n\tmargin-left: 6px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n/* 空状态 */\r\n.empty-messages {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpadding-top: 100px;\r\n}\r\n\r\n.empty-icon {\r\n\tfont-size: 60px;\r\n\tmargin-bottom: 16px;\r\n\topacity: 0.3;\r\n}\r\n\r\n.empty-title {\r\n\tfont-size: 16px;\r\n\tcolor: #333;\r\n\tfont-weight: bold;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.empty-desc {\r\n\tfont-size: 14px;\r\n\tcolor: #999;\r\n}\r\n\r\n/* 底部浮动按钮 */\r\n.floating-action {\r\n\tposition: fixed;\r\n\tbottom: 20px;\r\n\tright: 20px;\r\n\tz-index: 100;\r\n}\r\n\r\n.action-button {\r\n\tbackground: linear-gradient(135deg, #ff5000, #e64a00);\r\n\tcolor: white;\r\n\tborder: none;\r\n\tborder-radius: 25px;\r\n\tpadding: 12px 20px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tbox-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);\r\n\tfont-weight: bold;\r\n}\r\n\r\n.button-icon {\r\n\tfont-size: 18px;\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.button-text {\r\n\tfont-size: 14px;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.safe-area {\r\n\theight: 80px;\r\n}\r\n\r\n/* 响应式适配 */\r\n@media screen and (max-width: 320px) {\r\n\t.tab-name {\r\n\t\tfont-size: 13px;\r\n\t}\r\n\t\r\n\t.service-categories {\r\n /* grid-template-columns: 1fr; REMOVED */\r\n\t}\r\n \r\n .category-item {\r\n width: 100%; /* 1 column */\r\n }\r\n}\r\n\r\n@media screen and (min-width: 415px) {\r\n\t.message-item {\r\n\t\tpadding: 20px;\r\n\t}\r\n\t\r\n\t.message-icon-wrapper {\r\n\t\twidth: 60px;\r\n\t\theight: 60px;\r\n\t\tborder-radius: 30px;\r\n\t}\r\n\t\r\n\t.message-icon-text {\r\n\t\tfont-size: 28px;\r\n\t}\r\n\t\r\n\t.customer-service-info {\r\n\t\tpadding: 25px;\r\n\t}\r\n\t\r\n\t.service-categories {\r\n\t\t/* grid-template-columns: repeat(4, 1fr); REMOVED */\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row;\r\n\t\tflex-wrap: wrap;\r\n\t}\r\n\t\r\n\t.service-categories .category-item {\r\n\t\twidth: 25%;\r\n\t}\r\n\r\n\t/* 平板和桌面端优化标签栏显示 */\r\n\t.message-tabs {\r\n\t\tjustify-content: flex-start; /* 左对齐 */\r\n\t}\r\n\t\r\n\t.tab-item {\r\n\t\tpadding: 0 24px; /* 增加点击区域 */\r\n\t}\r\n}\r\n\r\n/* 平板设备 */\r\n@media screen and (min-width: 768px) {\r\n\t.smart-navbar {\r\n\t\tpadding: 0 30px;\r\n\t}\r\n\t\r\n\t.tabs-container {\r\n\t\tpadding: 0 30px;\r\n\t}\r\n\t\r\n\t.message-section {\r\n\t\tpadding: 20px 30px;\r\n\t}\r\n\t\r\n\t.customer-service-info {\r\n\t\tmargin: 20px 30px;\r\n\t}\r\n\t\r\n\t.service-tips {\r\n\t\tmargin: 20px 30px;\r\n\t}\r\n}\r\n\r\n/* 暗黑模式适配 */\r\n@media (prefers-color-scheme: dark) {\r\n\t.messages-page {\r\n\t\tbackground-color: #121212;\r\n\t}\r\n\t\r\n\t.smart-navbar {\r\n\t\tbackground: linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%);\r\n\t}\r\n\t\r\n\t.tabs-container {\r\n\t\tbackground-color: #1e1e1e;\r\n\t\tborder-bottom-color: #333;\r\n\t}\r\n\t\r\n\t.tab-item.active {\r\n\t\tcolor: #ff5000;\r\n\t}\r\n\t\r\n\t.customer-service-info,\r\n\t.message-item,\r\n\t.service-tips {\r\n\t\tbackground-color: #1e1e1e;\r\n\t}\r\n\t\r\n\t.message-item.unread {\r\n\t\tbackground-color: #2d2d2d;\r\n\t}\r\n\t\r\n\t.category-item {\r\n\t\tbackground: #2d2d2d;\r\n\t}\r\n\t\r\n\t.category-name {\r\n\t\tcolor: #fff;\r\n\t}\r\n\t\r\n\t.service-title,\r\n\t.message-title {\r\n\t\tcolor: #fff;\r\n\t}\r\n\t\r\n\t.service-desc,\r\n\t.message-preview,\r\n\t.last-message,\r\n\t.tip-text {\r\n\t\tcolor: #aaa;\r\n\t}\r\n\t\r\n\t.message-icon-wrapper {\r\n\t\tbackground-color: #2d2d2d;\r\n\t}\r\n\t\r\n\t.order-info {\r\n\t\tbackground-color: #1b5e20;\r\n\t\tcolor: #a5d6a7;\r\n\t}\r\n\t\r\n\t.coupon-info {\r\n\t\tbackground: linear-gradient(135deg, #ff8f00, #ef6c00);\r\n\t}\r\n\t\r\n\t.message-tag {\r\n\t\tbackground: #333;\r\n\t\tcolor: #ccc;\r\n\t}\r\n\t\r\n\t.action-btn {\r\n\t\tbackground: rgba(255, 255, 255, 0.1);\r\n\t}\r\n\t\r\n\t.action-btn:hover {\r\n\t\tbackground: rgba(255, 255, 255, 0.2);\r\n\t}\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<view class=\"recommend-image-wrapper\">\r\n\t\t\t\t\t\t\t<image \r\n\t\t\t\t\t\t\t\tclass=\"recommend-image\" \r\n\t\t\t\t\t\t\t\t:src=\"product.image\" \r\n\t\t\t\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t</view>\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>\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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"} |