1 line
1.0 MiB
1 line
1.0 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","../../../../../../../HBuilderX/HBuilderX/plugins/uniapp-cli-vite/node_modules/@dcloudio/uni-console/src/runtime/app/index.ts","App.uvue","uni_modules/i18n/index.uts","uni_modules/ak-req/interface.uts","ak/config.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","pages/user/types.uts","utils/supabaseService.uts","pages/mall/consumer/index.uvue","pages/mall/consumer/category.uvue","pages/mall/consumer/messages.uvue","pages/mall/consumer/cart.uvue","pages/mall/consumer/profile.uvue","pages/mall/consumer/settings.uvue","pages/mall/consumer/wallet.uvue","pages/mall/consumer/withdraw.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, 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 = {} as UTSJSONObject;\r\n\t\tif (apikey !== null && apikey !== \"\") {\r\n\t\t\theaders = Object.assign({}, headers, { 'apikey': apikey }) as UTSJSONObject;\r\n\t\t} try {\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: ({ refresh_token: refreshToken } as UTSJSONObject),\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// 统一 header,自动带上 Authorization/Content-Type/Accept\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\t\tlet contentType = options.contentType ?? '';\r\n\t\tif (headers != null && typeof headers.getString === 'function') {\r\n\t\t\tconst headerContentType = headers.getString('Content-Type');\r\n\t\t\tif (headerContentType != null) {\r\n\t\t\t\tcontentType = headerContentType;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (contentType != null && contentType != \"\") {\r\n\t\t\theaders = Object.assign({}, headers, { 'Content-Type': contentType }) 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\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 result = AkReq.createResponse<any>(\r\n\t\t\t\t\t\t\terr.errCode,\r\n\t\t\t\t\t\t\terr.data ?? {},\r\n\t\t\t\t\t\t\t{} as UTSJSONObject,\r\n\t\t\t\t\t\t\tnew UniError('uni-request', err.errCode, 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 result = AkReq.createResponse<any>(\r\n\t\t\t\t\terr.errCode,\r\n\t\t\t\t\terr.data ?? {},\r\n\t\t\t\t\t{} as UTSJSONObject,\r\n\t\t\t\t\tnew UniError('uni-upload', err.errCode, 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\t// 新增:支持类型转换的请求方法\r\n\tstatic async requestAs<T = any>(options : AkReqOptions, skipRefresh ?: boolean) : Promise<AkReqResponse<T|Array<T>>> {\r\n\t\tconst response = await this.request(options, skipRefresh);\r\n\t\t\r\n\t\t// 如果原始 data 是 null,直接返回 null\r\n\t\t// if (response.data == null) {\r\n\t\t// \treturn {\r\n\t\t// \t\tstatus: response.status,\r\n\t\t// \t\tdata: null,\r\n\t\t// \t\theaders: response.headers,\r\n\t\t// \t\terror: response.error,\r\n\t\t// \t\ttotal: response.total,\r\n\t\t// \t\tpage: response.page,\r\n\t\t// \t\tlimit: response.limit,\r\n\t\t// \t\thasmore: response.hasmore,\r\n\t\t// \t\torigin: response.origin\r\n\t\t// \t} as AkReqResponse<T|Array<T>>;\r\n\t\t// }\r\n\t\t\r\n\t\t// 尝试类型转换\r\n\t\tlet convertedData: T | null = null;\r\n\t\ttry {\r\n\r\n\t\t\tif (response.data instanceof UTSJSONObject) {\r\n\t\t\t\tconvertedData = response.data.parse<T>();\r\n\t\t\t} else if (Array.isArray(response.data)) {\r\n\t\t\t\tconst convertedArray: Array<any> = [];\r\n\t\t\t\tconst dataArray = response.data;\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\t\t\t\t\t\tconst parsed = item.parse<T>();\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} else {\r\n\t\t\t\t\t\tconvertedArray.push(item);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tconvertedData = convertedArray as T;\r\n\t\t\t}\r\n\r\n\t\t\t\r\n\r\n\r\n\r\n\t\t} catch (e) {\r\n\t\t\t__f__('warn','at uni_modules/ak-req/ak-req.uts:419','类型转换失败,使用原始 UTSJSONObject:', e);\r\n\t\t\t// 转换失败时,返回原始 UTSJSONObject\r\n\t\t\tconvertedData = response.data as T;\r\n\t\t}\r\n\t\t const aaa = {\r\n\t\t\tstatus: response.status,\r\n\t\t\tdata: convertedData!!,\r\n\t\t\theaders: response.headers,\r\n\t\t\terror: response.error,\r\n\t\t\ttotal: response.total,\r\n\t\t\tpage: response.page,\r\n\t\t\tlimit: response.limit,\r\n\t\t\thasmore: response.hasmore,\r\n\t\t\torigin: response.origin\r\n\t\t} ;\r\n\t\treturn aaa\r\n\t}\r\n}\r\n\r\nexport default AkReq;","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","<script lang=\"uts\">\r\n\texport default {\r\n\t\tonLaunch: function () {\r\n\t\t\tconsole.log('App Launch')\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}\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","// 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","// 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://192.168.1.61:18000'\r\nexport const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'\r\n\r\n// WebSocket 实时连接(内网使用 ws:// 而非 wss://)\r\nexport const WS_URL: string = 'ws://192.168.1.61:18000/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/mall/consumer/index'\r\nexport const TABORPAGE: string = '/pages/mall/consumer/index'\r\n\r\n// 测试模式:放开任意跳转(禁用启动页/登录/401 的强制重定向)\r\nexport const IS_TEST_MODE: boolean = true","// 通用 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// 将 null 转换为字符串 'null',避免构造对象时缺少 value 属性\r\n\t\tlet safeValue = value;\r\n\t\tif (value === null) {\r\n\t\t\tsafeValue = 'null';\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:125',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:209','设置 range:', from, 'to', to);\r\n\t\treturn this;\r\n\t}\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 => typeof x == 'object' ? encodeURIComponent(JSON.stringify(x)) : encodeURIComponent(x.toString())).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 {\r\n\t\t\t\tconst opvalstr: string = (typeof val == 'object') ? JSON.stringify(val) : (val as string);\r\n\t\t\t\tparams.push(`${k}=${op}.${encodeURIComponent(opvalstr)}`);\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(x as string)).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\treturn `${k}.${op}.${encodeURIComponent(val as string)}`;\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\tparams.push(`or=(${encodeURIComponent(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:293','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:296','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:301','delete action now')\r\n\t\tconst filter = this._buildFilter();\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:303',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:305','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:317','execute')\r\n\t\tconst filter = this._buildFilter();\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:319','execute', 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:330',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\tif (typeof res['count'] == 'number') {\r\n\t\t\t\t\t\ttotal = res['count'] as number ?? 0;\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 = any>() : Promise<AkReqResponse<T | Array<T>>> {\r\n\t\tconst result = await this.execute();\r\n\r\n\t\t// 如果原始 data 是 null,直接返回 null\r\n\t\tif (result.data == null) {\r\n\t\t\tconst aaa = {\r\n\t\t\t\tstatus: result.status,\r\n\t\t\t\tdata: null,\r\n\t\t\t\theaders: result.headers,\r\n\t\t\t\terror: result.error,\r\n\t\t\t\ttotal: result.total,\r\n\t\t\t\tpage: result.page,\r\n\t\t\t\tlimit: result.limit,\r\n\t\t\t\thasmore: result.hasmore,\r\n\t\t\t\torigin: result.origin\r\n\t\t\t}\r\n\t\t\treturn aaa;\r\n\t\t}\r\n\r\n\t\t// 尝试类型转换\r\n\t\tlet convertedData : T | Array<T> | null = null;\r\n\r\n\t\ttry {\r\n\t\t\tif (Array.isArray(result.data)) {\r\n\t\t\t\t// 处理数组数据\r\n\t\t\t\tconst dataArray = result.data;\r\n\t\t\t\tconst convertedArray : Array<T> = [];\r\n\t\t\t\t//__f__('log','at components/supadb/aksupa.uts:461',convertedArray)\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\t// //__f__('log','at components/supadb/aksupa.uts:466',item)\r\n\t\t\t\t\t\tconst parsed = item.parse<T>();\r\n\t\t\t\t\t\t// //__f__('log','at components/supadb/aksupa.uts:468','ak parsed')\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:476','转换失败,使用原始对象:', item);\r\n\t\t\t\t\t\t\tconvertedArray.push(item as T);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// 将普通对象转换为 UTSJSONObject 后再 parse\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:492','转换失败,使用原始对象:', item);\r\n\t\t\t\t\t\t\tconvertedArray.push(item as 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\tconvertedData = convertedArray;\r\n\r\n\t\t\t} else {\r\n\t\t\t\t// 处理单个对象\r\n\t\t\t\tconst convertedArray : Array<T> = [];\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:509','转换失败:', 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:518','转换失败:', 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:524','数据类型转换失败,使用原始数据:', e);\r\n\t\t\t__f__('log','at components/supadb/aksupa.uts:525',result.data)\r\n\t\t\t// 转换失败时,使用原始数据\r\n\t\t\tconvertedData = result.data as T | Array<T>;\r\n\t\t}\r\n\t\tresult.data = convertedData\r\n\t\tconst aaa = result as AkReqResponse<T | Array<T>\r\n\t\t// const aaa = {\r\n\t\t// \tstatus: result.status,\r\n\t\t// \tdata: convertedData,\r\n\t\t// \theaders: result.headers,\r\n\t\t// \terror: result.error,\r\n\t\t// \ttotal: result.total,\r\n\t\t// \tpage: result.page,\r\n\t\t// \tlimit: result.limit,\r\n\t\t// \thasmore: result.hasmore,\r\n\t\t// \torigin: result.origin\r\n\t\t// } \r\n\t\treturn aaa;\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 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: {\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, password } as UTSJSONObject,\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\tmsg = obj.getString('message') ?? obj.getString('error') ?? obj.getString('msg') ?? obj.getString('description') ?? obj.getString('error_description') ?? msg;\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) : Promise<UTSJSONObject> {\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: {\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, password } as UTSJSONObject,\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 = {\r\n\t\tapikey: this.apikey,\r\n\t\t'Content-Type': 'application/json',\r\n\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`\r\n\t} as UTSJSONObject;\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:820','设置 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:826','设置 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:836','使用 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:846','使用 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:871',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:877','使用 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 = {\r\n\t\t\tapikey: this.apikey,\r\n\t\t\t'Content-Type': 'application/json',\r\n\t\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`,\r\n\t\t\tPrefer: 'return=representation'\r\n\t\t} as UTSJSONObject;\r\n\r\n\t\t// 如果是数组,直接传递;如果是单个对象,也直接传递\r\n\t\t// Supabase REST API 原生支持两种格式\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 = {\r\n\t\tapikey: this.apikey,\r\n\t\t'Content-Type': 'application/json',\r\n\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`,\r\n\t\tPrefer: 'return=representation'\r\n\t} as UTSJSONObject;\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 = {\r\n\t\tapikey: this.apikey,\r\n\t\t'Content-Type': 'application/json',\r\n\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`,\r\n\t\tPrefer: 'return=representation'\r\n\t} as UTSJSONObject;\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 = {\r\n\t\t\tapikey: this.apikey,\r\n\t\t\t'Content-Type': 'application/json',\r\n\t\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`\r\n\t\t} as UTSJSONObject;\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 ?? {},\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 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: {\r\n\t\t\t\t\tapikey: this.apikey,\r\n\t\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t\t} as UTSJSONObject,\r\n\t\t\t\tdata: { refresh_token: this.session?.refresh_token } as UTSJSONObject,\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\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:1083','登录已过期,请重新登录');\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:1093',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:1100','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:1190','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:1265','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\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 通过 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.eq('auth_id', userId)\r\n\t\t\t.single()\r\n\t\t\t.execute()\r\n\t\t\r\n\t\t__f__('log','at utils/sapi.uts:29','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\tif (checkRes.status >= 200 && checkRes.status < 300 && checkRes.data != null) {\r\n\t\t\t// 用户已存在,返回现有资料(H5 下 checkRes.data 可能是 plain object,不一定是 UTSJSONObject)\r\n\t\t\tconst data = checkRes.data\r\n\t\t\tlet existingUser: UTSJSONObject\r\n\t\t\tif (data instanceof UTSJSONObject) {\r\n\t\t\t\texistingUser = data\r\n\t\t\t} else {\r\n\t\t\t\texistingUser = new UTSJSONObject(data)\r\n\t\t\t}\r\n\t\t\treturn {\r\n\t\t\t\tid: existingUser.getString('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('email', email)\r\n\t\tnewUserData.set('username', email.split('@')[0] ?? 'user') // 默认用户名为邮箱前缀\r\n\t\t\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.single()\r\n\t\t\t.execute()\r\n\t\t\r\n\t\t__f__('log','at utils/sapi.uts:72','ensureUserProfile insert ak_users status:', insertRes.status)\r\n\t\t\r\n\t\tif (insertRes.status >= 200 && insertRes.status < 300 && insertRes.data != null) {\r\n\t\t\tconst rawData = insertRes.data\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') ?? '',\r\n\t\t\t\tusername: newUser.getString('username') ?? '',\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:95','创建用户资料失败:', 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:99','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 = \"mall\"\n override appid: string = \"__UNI__YOUR_APP_ID__\"\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 GenPagesMallConsumerIndexClass from './pages/mall/consumer/index.uvue'\nimport GenPagesMallConsumerCategoryClass from './pages/mall/consumer/category.uvue'\nimport GenPagesMallConsumerMessagesClass from './pages/mall/consumer/messages.uvue'\nimport GenPagesMallConsumerCartClass from './pages/mall/consumer/cart.uvue'\nimport GenPagesMallConsumerProfileClass from './pages/mall/consumer/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 GenPagesMallConsumerSubscriptionPlanListClass from './pages/mall/consumer/subscription/plan-list.uvue'\nimport GenPagesMallConsumerSubscriptionPlanDetailClass from './pages/mall/consumer/subscription/plan-detail.uvue'\nimport GenPagesMallConsumerSubscriptionSubscribeCheckoutClass from './pages/mall/consumer/subscription/subscribe-checkout.uvue'\nimport GenPagesMallConsumerSubscriptionMySubscriptionsClass from './pages/mall/consumer/subscription/my-subscriptions.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/mall/consumer/index\", component: GenPagesMallConsumerIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"首页\"],[\"navigationStyle\",\"custom\"],[\"enablePullDownRefresh\",false]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/category\", component: GenPagesMallConsumerCategoryClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"分类\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/messages\", component: GenPagesMallConsumerMessagesClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"消息\"],[\"enablePullDownRefresh\",true]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/cart\", component: GenPagesMallConsumerCartClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"购物车\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/profile\", component: GenPagesMallConsumerProfileClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的\"]]) } 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/plan-list\", component: GenPagesMallConsumerSubscriptionPlanListClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"软件订阅\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/subscription/plan-detail\", component: GenPagesMallConsumerSubscriptionPlanDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"订阅详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/subscription/subscribe-checkout\", component: GenPagesMallConsumerSubscriptionSubscribeCheckoutClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"确认订阅\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/subscription/my-subscriptions\", component: GenPagesMallConsumerSubscriptionMySubscriptionsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的订阅\"]]) } 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/mall/consumer/index\"],[\"text\",\"首页\"],[\"iconPath\",\"static/tabbar/home.png\"],[\"selectedIconPath\",\"static/tabbar/home-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/category\"],[\"text\",\"分类\"],[\"iconPath\",\"static/tabbar/category.png\"],[\"selectedIconPath\",\"static/tabbar/category-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/messages\"],[\"text\",\"消息\"],[\"iconPath\",\"static/tabbar/messages.png\"],[\"selectedIconPath\",\"static/tabbar/messages-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/cart\"],[\"text\",\"购物车\"],[\"iconPath\",\"static/tabbar/cart.png\"],[\"selectedIconPath\",\"static/tabbar/cart-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/profile\"],[\"text\",\"我的\"],[\"iconPath\",\"static/tabbar/profile.png\"],[\"selectedIconPath\",\"static/tabbar/profile-active.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/mall/consumer/index\"],[\"text\",\"首页\"],[\"iconPath\",\"static/tabbar/home.png\"],[\"selectedIconPath\",\"static/tabbar/home-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/category\"],[\"text\",\"分类\"],[\"iconPath\",\"static/tabbar/category.png\"],[\"selectedIconPath\",\"static/tabbar/category-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/messages\"],[\"text\",\"消息\"],[\"iconPath\",\"static/tabbar/messages.png\"],[\"selectedIconPath\",\"static/tabbar/messages-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/cart\"],[\"text\",\"购物车\"],[\"iconPath\",\"static/tabbar/cart.png\"],[\"selectedIconPath\",\"static/tabbar/cart-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/profile\"],[\"text\",\"我的\"],[\"iconPath\",\"static/tabbar/profile.png\"],[\"selectedIconPath\",\"static/tabbar/profile-active.png\"]])]]])\n __uniConfig.tabBar = __uniConfig.getTabBarConfig()\n __uniConfig.conditionUrl = ''\n __uniConfig.uniIdRouter = new Map()\n \n __uniConfig.ready = true\n}\n","// 用户基础信息类型\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; // 所属学校ID\r\n grade_id?: string; // 所属年级ID \r\n class_id?: string; // 所属班级ID\r\n}\r\n\r\n// 语言选项类型 - 对应 ak_languages 表\r\nexport type LanguageOption = {\r\n id: string; // UUID\r\n code: string; // 语言代码,如 'zh-CN', 'en-US'\r\n name: string; // 英文名称\r\n native_name: string; // 本地语言名称\r\n}\r\n\r\nexport type UserStats = {\r\n trainings: number;\r\n points: number;\r\n streak: number;\r\n}","import supa from '@/components/supadb/aksupainstance.uts'\r\nimport type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'\r\n\r\n// 使用单例 Supabase 客户端\r\n// const supa = createClient(SUPA_URL, SUPA_KEY)\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 level?: number\r\n parent_id?: string\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_urls?: string // JSON string array\r\n video_urls?: string // JSON string array\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 tags?: string // array string in DB\r\n attributes?: string // JSON string\r\n created_at?: string\r\n updated_at?: string\r\n // Alias fields for compatibility\r\n price?: number\r\n original_price?: number\r\n stock?: number\r\n sales?: number\r\n images?: string\r\n cover?: string\r\n // View fields\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:273','获取用户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:282','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:308','获取分类失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Category[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:314','获取分类异常:', error)\r\n return []\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:330','获取一级分类失败:', 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 const cat: Category = {\r\n id: item['id'] as string,\r\n name: item['name'] as string,\r\n icon: icon,\r\n description: (item['description'] as string) ?? '',\r\n color: (item['color'] as string) ?? '#4CAF50',\r\n level: 1,\r\n slug: item['slug'] 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:357','获取一级分类异常:', 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 const response = await supa\r\n .from('ml_categories')\r\n .select('*')\r\n .eq('parent_id', parentId)\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:373','获取子分类失败:', 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 const cat: Category = {\r\n id: item['id'] as string,\r\n name: item['name'] as string,\r\n icon: icon,\r\n description: (item['description'] as string) ?? '',\r\n color: (item['color'] as string) ?? '#4CAF50',\r\n level: 2,\r\n parent_id: item['parent_id'] as string,\r\n slug: item['slug'] 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:401','获取子分类异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取分类图标\r\n getCategoryIcon(item: UTSJSONObject): string {\r\n const iconUrl = (item['icon_url'] as string) ?? ''\r\n const slug = (item['slug'] as string) ?? ''\r\n \r\n // 如果 icon_url 已经是 emoji,直接返回\r\n if (iconUrl.length > 0 && iconUrl.length <= 4) {\r\n return iconUrl\r\n }\r\n \r\n // slug 映射到 emoji\r\n if (slug === 'digital') return '📱'\r\n if (slug === 'fashion') return '👕'\r\n if (slug === 'home') return '🏠'\r\n if (slug === 'food') return '🍎'\r\n if (slug === 'beauty') return '💄'\r\n if (slug === 'sports') return '⚽'\r\n if (slug === 'books') return '📚'\r\n if (slug === 'baby') return '👶'\r\n if (slug === 'health') return '💊'\r\n // 二级分类\r\n if (slug === 'mobile') return '📱'\r\n if (slug === 'computer') return '💻'\r\n if (slug === 'appliance') return '🎥'\r\n if (slug === 'accessories') return '🔌'\r\n if (slug === 'mens-wear') return '👔'\r\n if (slug === 'womens-wear') return '👗'\r\n if (slug === 'mens-shoes') return '👞'\r\n if (slug === 'womens-shoes') return '👠'\r\n if (slug === 'furniture') return '🛋️'\r\n if (slug === 'decoration') return '🖼️'\r\n if (slug === 'kitchen') return '🍳'\r\n if (slug === 'daily') return '🧹'\r\n if (slug === 'fruits') return '🍊'\r\n if (slug === 'meat') return '🥩'\r\n if (slug === 'snacks') return '🍪'\r\n if (slug === 'drinks') return '🍺'\r\n \r\n return '📂'\r\n }\r\n\r\n // 获取所有品牌\r\n async getBrands(): Promise<Brand[]> {\r\n try {\r\n const response = await supa\r\n .from('ml_brands')\r\n .select('*')\r\n .eq('is_active', true)\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:458','获取品牌失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Brand[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:464','获取品牌异常:', 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 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) \r\n .order('sale_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:487','获取商品失败:', 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 return {\r\n data: response.data as Product[],\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:505','获取商品异常:', 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 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:527','获取商品SKU失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as ProductSku[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:533','获取商品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 let query = supa\r\n .from('ml_products_detail_view')\r\n .select('*', { count: 'exact' })\r\n .eq('status', 1)\r\n .or(`name.ilike.%${keyword}%,description.ilike.%${keyword}%,subtitle.ilike.%${keyword}%,brand_name.ilike.%${keyword}%`)\r\n \r\n // 根据sortBy和ascending设置排序\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 // 默认按销量降序\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 if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:569','搜索商品失败:', 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 return {\r\n data: response.data as Product[],\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:587','搜索商品异常:', 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 response = await supa\r\n .from('ml_shops')\r\n .select('*', { count: 'exact' })\r\n .eq('status', 1)\r\n .ilike('shop_name', `%${keyword}%`)\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:616','搜索店铺失败:', response.error)\r\n return { data: [] as Shop[], total: 0, page, limit, hasmore: false }\r\n }\r\n\r\n // 映射数据确保类型安全\r\n const shops: Shop[] = []\r\n const dataList = response.data as any[]\r\n for (let i = 0; i < dataList.length; i++) {\r\n shops.push(dataList[i] as Shop)\r\n }\r\n\r\n return {\r\n data: shops,\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:635','搜索店铺异常:', 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 const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('id', productId)\r\n .single()\r\n .executeAs<Product>()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:651','获取商品详情失败:', response.error)\r\n return null\r\n }\r\n \r\n return response.data as Product\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:657','获取商品详情异常:', 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:677','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:695','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:712','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:729','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:735','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 // 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 return (response.data as any[])[0] as 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:756','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:765','Found shop by ID instead of MerchantID')\r\n // Fix the merchant_id reference if we found it by ID\r\n const shop = (response.data as any[])[0] as Shop\r\n return shop\r\n }\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:772','获取店铺信息失败:', response.error)\r\n }\r\n return null\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:776','获取店铺信息异常:', error)\r\n return null\r\n }\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:784','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:801','获取商户商品失败 (View):', response.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:803','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:807','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:819','获取商户商品失败 (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:823',`Fallback (Raw) found: ${(res2.data as any[]).length} products`)\r\n \r\n // Map raw data to Product interface (manually if needed for extra safety)\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 mappedData.push(rawData[i] as 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:841',`Merchant products found: ${(response.data as any[]).length}`)\r\n return {\r\n data: response.data as Product[],\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:850','获取商户商品异常:', 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 const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('is_hot', true)\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:874','获取热销商品失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Product[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:880','获取热销商品异常:', 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('*')\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:897','获取价格排序商品失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Product[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:903','获取价格排序商品异常:', 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 const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('is_new', true) \r\n .eq('status', 1)\r\n .order('published_at', { ascending: false }) // Use published_at for newest\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:921','获取新品失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Product[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:927','获取新品异常:', 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 // 查询 is_featured = true 的商品\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('is_featured', true)\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:946','获取推荐商品失败:', response.error)\r\n return []\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:950','推荐商品查询结果条数:', (response.data as any[])?.length ?? 0)\r\n const data = response.data as Product[]\r\n return data ?? []\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:954','获取推荐商品异常:', 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:970','用户未登录,无法获取购物车')\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:986','获取购物车失败:', 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:991','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_detail_view')\r\n .select('id,name,main_image_url,base_price,attributes,merchant_id,shop_name')\r\n .in('id', productIdsAny)\r\n .execute()\r\n \r\n if (productRes.error == null && productRes.data != null) {\r\n const products = productRes.data as any[]\r\n for (let i = 0; i < products.length; i++) {\r\n let p = products[i]\r\n let pid: string = ''\r\n \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 \r\n if (pid !== '') {\r\n productMap.set(pid, p)\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('id, specifications, price, image_url')\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 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 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 shopNameStr = product.getString('shop_name') ?? '未知店铺'\r\n \r\n // 只有当没有sku信息时,才尝试使用商品的attributes(作为降级显示)\r\n // 修改策略:不使用 product.attributes 作为规格显示,因为那通常包含品牌、风格等静态属性,非用户选择的规格\r\n // 如果 sku 为空,则让前端显示默认\"标准规格\"\r\n /*\r\n if (sku == null) {\r\n const specRaw = product.get('attributes')\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 = UTSJSONObject.keys(specRaw)\r\n const parts: string[] = []\r\n for(let k = 0; k < keys.length; k++) {\r\n let val = specRaw.get(keys[k])\r\n if (val != null) {\r\n parts.push(`${keys[k]}: ${val}`)\r\n }\r\n }\r\n productSpec = parts.join('; ')\r\n } else {\r\n try {\r\n let jsonStr = JSON.stringify(specRaw)\r\n productSpec = jsonStr.replace(/[\"{}]/g, '').replace(/,/g, '; ')\r\n } catch (e) {}\r\n }\r\n }\r\n }\r\n */\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject\r\n merchantId = pObj.getString('merchant_id') ?? ''\r\n productName = pObj.getString('name') ?? ''\r\n productImage = pObj.getString('main_image_url') ?? ''\r\n productPrice = pObj.getNumber('base_price') ?? 0\r\n shopNameStr = pObj.getString('shop_name') ?? '未知店铺'\r\n \r\n // Same here: disable fallback to attributes\r\n /*\r\n if (sku == null) {\r\n const specRaw = product['attributes']\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 = UTSJSONObject.keys(specRaw)\r\n const parts: string[] = []\r\n for(let k = 0; k < keys.length; k++) {\r\n let val = specRaw.get(keys[k])\r\n if (val != null) {\r\n parts.push(`${keys[k]}: ${val}`)\r\n }\r\n }\r\n productSpec = parts.join('; ')\r\n } else {\r\n try {\r\n let jsonStr = JSON.stringify(specRaw)\r\n productSpec = jsonStr.replace(/[\"{}]/g, '').replace(/,/g, '; ')\r\n } catch (e) {}\r\n }\r\n }\r\n }\r\n */\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 // 优先使用SKU的规格\r\n if (typeof specRaw === 'string') {\r\n productSpec = specRaw\r\n } else if (specRaw instanceof UTSJSONObject) {\r\n const keys = UTSJSONObject.keys(specRaw)\r\n const parts: string[] = []\r\n for(let k = 0; k < keys.length; k++) {\r\n let val = specRaw.get(keys[k])\r\n if (val != null) {\r\n parts.push(`${keys[k]}: ${val}`)\r\n }\r\n }\r\n productSpec = parts.join('; ')\r\n } else {\r\n try {\r\n let jsonStr = JSON.stringify(specRaw)\r\n productSpec = jsonStr.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 // 优先使用SKU的规格\r\n if (typeof specRaw === 'string') {\r\n productSpec = specRaw\r\n } else if (specRaw instanceof UTSJSONObject) {\r\n const keys = UTSJSONObject.keys(specRaw)\r\n const parts: string[] = []\r\n for(let k = 0; k < keys.length; k++) {\r\n let val = specRaw.get(keys[k])\r\n if (val != null) {\r\n parts.push(`${keys[k]}: ${val}`)\r\n }\r\n }\r\n productSpec = parts.join('; ')\r\n } else {\r\n try {\r\n let jsonStr = JSON.stringify(specRaw)\r\n productSpec = jsonStr.replace(/[\"{}]/g, '').replace(/,/g, '; ')\r\n } catch (e) {}\r\n }\r\n }\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:1307','获取购物车异常:', 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 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:1330','获取通知失败:', response.error)\r\n return []\r\n }\r\n return response.data as Notification[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1335','获取通知异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取聊天会话列表\r\n async getChatRooms(): Promise<ChatRoom[]> {\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_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:1354','获取聊天会话失败:', response.error)\r\n return []\r\n }\r\n return response.data as ChatRoom[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1359','获取聊天会话异常:', 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:1379','获取聊天记录失败:', 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:1384','获取聊天记录异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取与特定商家的聊天记录\r\n async getChatMessages(merchantId: string): 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(`and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},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:1404','获取聊天记录失败:', 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:1409','获取聊天记录异常:', 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:1437','发送消息失败:', response.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1442','发送消息异常:', 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:1474','用户未登录,无法添加商品到购物车')\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:1513','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:1548','购物车已有商品但缺少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:1572','添加商品到购物车失败:', 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:1578','添加商品到购物车异常:', 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:1588','用户未登录,无法更新购物车')\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:1608','更新购物车商品数量失败:', 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:1614','更新购物车商品数量异常:', 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:1624','用户未登录,无法更新购物车')\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:1639','更新购物车商品选中状态失败:', 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:1645','更新购物车商品选中状态异常:', 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 const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1655','用户未登录,无法更新购物车')\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 if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1670','批量更新购物车商品选中状态失败:', 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:1676','批量更新购物车商品选中状态异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 删除购物车商品\r\n async deleteCartItem(cartItemId: string): Promise<boolean> {\r\n return true\r\n /*\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1686','正在执行删除购物车商品,ID:', cartItemId)\r\n const userId = this.getCurrentUserId()\r\n if (!userId) {\r\n __f__('error','at utils/supabaseService.uts:1689','用户未登录,无法删除购物车商品')\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) {\r\n __f__('error','at utils/supabaseService.uts:1701','删除购物车商品失败:', 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:1707','删除购物车商品异常:', error)\r\n return false\r\n }\r\n */\r\n }\r\n\r\n // 批量删除购物车商品\r\n async batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {\r\n return true\r\n /*\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (!userId) {\r\n __f__('error','at utils/supabaseService.uts:1720','用户未登录,无法删除购物车商品')\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 .in('id', cartItemIds)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error) {\r\n __f__('error','at utils/supabaseService.uts:1732','批量删除购物车商品失败:', 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:1738','批量删除购物车商品异常:', error)\r\n return false\r\n }\r\n */\r\n }\r\n\r\n // 清空购物车\r\n async clearCart(): Promise<boolean> {\r\n return true\r\n /*\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (!userId) {\r\n __f__('error','at utils/supabaseService.uts:1751','用户未登录,无法清空购物车')\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) {\r\n __f__('error','at utils/supabaseService.uts:1762','清空购物车失败:', 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:1768','清空购物车异常:', error)\r\n return false\r\n }\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:1778','[getAddresses] 用户未登录,无法获取地址')\r\n return []\r\n }\r\n\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1783','[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:1793','[getAddresses] response.error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:1794','[getAddresses] response.data:', JSON.stringify(response.data))\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1797','[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 addrObj = new UTSJSONObject()\r\n addrObj.set('id', itemObj.getString('id') ?? '')\r\n addrObj.set('user_id', itemObj.getString('user_id') ?? '')\r\n addrObj.set('recipient_name', itemObj.getString('receiver_name') ?? itemObj.getString('recipient_name') ?? '')\r\n addrObj.set('phone', itemObj.getString('receiver_phone') ?? itemObj.getString('phone') ?? '')\r\n addrObj.set('province', itemObj.getString('province') ?? '')\r\n addrObj.set('city', itemObj.getString('city') ?? '')\r\n addrObj.set('district', itemObj.getString('district') ?? '')\r\n addrObj.set('detail_address', itemObj.getString('address_detail') ?? itemObj.getString('detail_address') ?? '')\r\n addrObj.set('is_default', itemObj.getBoolean('is_default') ?? false)\r\n result.push(addrObj as UserAddress)\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1830','[getAddresses] 返回地址数量:', result.length)\r\n return result\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1833','[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:1842','用户未登录,无法获取地址')\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:1857','获取地址详情失败:', 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:1863','获取地址详情异常:', 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:1873','用户未登录,无法添加地址')\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:1900','添加地址失败:', 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:1906','添加地址异常:', 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:1916','用户未登录,无法更新地址')\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:1946','更新地址失败:', 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:1952','更新地址异常:', error)\r\n return false\r\n }\r\n }\r\n \r\n // 确认收货\r\n async confirmReceipt(orderId: string): Promise<ConfirmReceiptResponse> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return { success: false, error: '用户未登录' }\r\n }\r\n\r\n const response = await supa\r\n .from('ml_orders')\r\n .update({\r\n order_status: 4, // 4: 已完成\r\n delivered_at: new Date().toISOString(),\r\n completed_at: new Date().toISOString(), // 也更新完成时间\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 return { success: false, error: response.error.message }\r\n }\r\n \r\n return { success: true }\r\n } catch (e: any) {\r\n return { success: false, error: e.message }\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:1990','正在执行删除地址,ID:', addressId)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1993','用户未登录,无法删除地址')\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:2005','删除地址失败:', 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:2011','删除地址异常:', 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:2029','清除默认地址异常:', 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 const response = await supa\r\n .from('ml_user_profiles')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .single()\r\n .execute()\r\n \r\n if (response.error != null) {\r\n // 如果不存在 profile,可能只有 auth user,这里暂时返回空或创建默认\r\n return null\r\n }\r\n return response.data\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:2063','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 if (merchantId == null || merchantId == '' || merchantId == 'unknown') {\r\n merchantId = userId\r\n }\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:2098','[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload))\r\n __f__('log','at utils/supabaseService.uts:2099','[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:2106','[CreateOrder] insert 完成')\r\n __f__('log','at utils/supabaseService.uts:2107','[CreateOrder] orderResponse.error:', orderResponse.error)\r\n \r\n if (orderResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2110','[CreateOrder] 创建订单失败:', orderResponse.error)\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2114','[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:2122','[CreateOrder] queryResponse.error:', queryResponse.error)\r\n __f__('log','at utils/supabaseService.uts:2123','[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data))\r\n \r\n if (queryResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2126','[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 if (Array.isArray(queryData) && queryData.length > 0) {\r\n const firstItem = queryData[0] as Record<string, any>\r\n orderId = firstItem['id'] as string\r\n __f__('log','at utils/supabaseService.uts:2136','[CreateOrder] 找到新创建的订单, id:', orderId)\r\n } else {\r\n __f__('error','at utils/supabaseService.uts:2138','[CreateOrder] 未找到新创建的订单,插入可能失败')\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2142','[CreateOrder] 订单创建成功, orderId:', orderId)\r\n \r\n const orderItems: UTSJSONObject[] = []\r\n const rawItems = orderData.items as any[]\r\n \r\n for(let i = 0; i < rawItems.length; i++) {\r\n let item: UTSJSONObject\r\n const rawItem = rawItems[i]\r\n item = rawItem 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 \r\n itemJson.set('order_id', orderId)\r\n itemJson.set('product_id', pId)\r\n \r\n const skuIdVal = item.get('sku_id')\r\n if (skuIdVal != null && skuIdVal !== '') {\r\n itemJson.set('sku_id', skuIdVal)\r\n }\r\n \r\n itemJson.set('product_name', item.get('product_name') ?? '')\r\n \r\n const sName = item.get('sku_name')\r\n itemJson.set('sku_name', sName ?? '')\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 iPrice = item.getNumber('price') ?? 0\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:2202','[CreateOrder] 插入订单项数量:', orderItems.length)\r\n __f__('log','at utils/supabaseService.uts:2203','[CreateOrder] 订单项数据:', JSON.stringify(orderItems))\r\n \r\n const itemsResponse = await supa\r\n .from('ml_order_items')\r\n .insert(orderItems)\r\n .execute()\r\n \r\n if (itemsResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2211','[CreateOrder] 创建订单项失败:', itemsResponse.error)\r\n __f__('error','at utils/supabaseService.uts:2212','[CreateOrder] 错误详情:', JSON.stringify(itemsResponse.error))\r\n __f__('log','at utils/supabaseService.uts:2213','[CreateOrder] 订单主表已创建,但订单项插入失败,返回订单ID')\r\n return orderId\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2217','[CreateOrder] 订单项创建成功')\r\n \r\n const cartItemIds: string[] = []\r\n for(let i = 0; i < rawItems.length; i++) {\r\n const item = rawItems[i] 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:2234','[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 = groups[k] as UTSJSONObject\r\n // 安全获取 items 数组\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 = gItems[gi] as UTSJSONObject\r\n const itPrice = it.getNumber('price') ?? 0\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 = 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 = shopItems[j] as UTSJSONObject\r\n const siPrice = sItem.getNumber('price') ?? 0\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 const orderId = await this.createOrder({\r\n merchant_id: (mId != null && mId != '') ? mId : (sId ?? ''), // 兼容旧字段\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: shopItems\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:2304','批量创建订单异常:', 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 let query = supa\r\n .from('ml_orders')\r\n .select(`\r\n *,\r\n ml_order_items (*)\r\n `)\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 if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2334','获取订单列表失败:', 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 return data as any[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2346','获取订单列表异常:', 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 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(`\r\n *,\r\n ml_order_items (*)\r\n `)\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .single()\r\n .execute()\r\n \r\n if (response.error != null) {\r\n return null\r\n }\r\n return response.data\r\n } catch (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:2383','[payOrder] 用户未登录')\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2387','[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:2397','[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:2407','[payOrder] 更新订单失败:', response.error)\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2411','[payOrder] 订单状态更新成功')\r\n\r\n if (paymentMethod === 'balance') {\r\n __f__('log','at utils/supabaseService.uts:2414','[payOrder] 余额支付,暂不扣减余额')\r\n }\r\n\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2419','[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:2429','[getOrderById] 用户未登录')\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2433','[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:2443','[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:2449','[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:2461','[getOrderById] 订单数据:', JSON.stringify(orderObj))\r\n return orderObj\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2464','[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 const userId = this.getCurrentUserId()\r\n if (userId == null) return { success: false, message: '请先登录' }\r\n \r\n const d = 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 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 const response = await supa\r\n .from('ml_refunds')\r\n .insert(payload)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2501','提交售后失败:', response.error)\r\n return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') }\r\n }\r\n \r\n return { success: true, message: '申请提交成功' }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2507','提交售后异常:', 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 = 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 = 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 ?? null)\r\n }\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2547','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:2609','获取售后列表失败:', 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:2622','获取售后列表异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取用户钱包余额\r\n async getUserBalance(): Promise<number> {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n __f__('log','at utils/supabaseService.uts:2632','[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:2644','[Supabase] getUserBalance error:', walletRes.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:2646','[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:2678','[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:2692','[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:2701','[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:2713','[Supabase] getUserPoints error:', res.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:2715','[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:2753','[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:2779','获取交易记录失败:', 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:2792','获取交易记录异常:', 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:2847','获取红包失败:', 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:2858','获取红包异常:', 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:2881','获取银行卡失败:', 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:2892','获取银行卡异常:', 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:2910','充值失败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:2922','充值异常:', 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:2939','提现失败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:2949','提现异常:', 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:2969','添加银行卡失败:', res.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2974','添加银行卡异常:', 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:2993','删除银行卡失败:', res.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2998','删除银行卡异常:', 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:3007',`[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 for product\r\n .limit(1)\r\n .execute()\r\n \r\n // __f__('log','at utils/supabaseService.uts:3020',`[CheckFav] Response: ${JSON.stringify(response)}`)\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3023',`[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:3042',`[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:3059',`[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:3069',`[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:3073',`[ToggleFav] Current status: ${exists}`)\r\n \r\n if (exists) {\r\n // Delete\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:3086','取消收藏失败:', response.error)\r\n return true // 仍然是收藏状态\r\n }\r\n return false // 已取消收藏\r\n } else {\r\n // Add\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:3103','添加收藏失败:', 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:3109','切换收藏状态异常:', 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 pid = ''\r\n if (item instanceof UTSJSONObject) {\r\n pid = item.getString('target_id') ?? ''\r\n } else {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n pid = itemObj.getString('target_id') ?? ''\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 // Deep copy to ensure we have a fresh object to modify\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 let targetId = newItem.getString('target_id')\r\n // Careful with null targetId\r\n if (targetId != null) {\r\n const product = productMap.get(targetId as string)\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:3212','获取收藏列表异常:', 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:3222','[getFootprints] 用户未登录')\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:3227','[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:3238','[getFootprints] 足迹查询 error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:3239','[getFootprints] 足迹查询 data:', JSON.stringify(response.data))\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3242','[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:3249','[getFootprints] 没有足迹记录')\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:3254','[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:3289','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('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:3376','获取足迹异常:', 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:3386','[addFootprint] 用户未登录')\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3390','[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:3400','[addFootprint] 检查结果 error:', checkRes.error)\r\n __f__('log','at utils/supabaseService.uts:3401','[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:3407','[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:3415','[addFootprint] 更新结果 error:', updateRes.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:3417','[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:3429','[addFootprint] 插入结果 error:', insertRes.error)\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3433','[addFootprint] 添加足迹异常:', 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:3455','获取地址列表失败:', 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:3461','获取地址列表异常:', 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:3472','用户未登录,无法设置默认地址')\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:3491','设置默认地址失败:', 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:3497','设置默认地址异常:', 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(`\r\n *,\r\n template:ml_coupon_templates(name, amount, min_spend)\r\n `)\r\n .eq('user_id', userId!)\r\n .eq('status', status)\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:3526','获取优惠券失败:', response.error)\r\n const empty: UserCoupon[] = []\r\n return empty\r\n }\r\n\r\n // 映射数据,将 template 的字段展平\r\n const coupons: UserCoupon[] = []\r\n const rawData = response.data as any[]\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 const couponObj = new UTSJSONObject()\r\n couponObj.set('id', itemId)\r\n couponObj.set('user_id', itemUserId)\r\n couponObj.set('template_id', itemTmplId)\r\n couponObj.set('coupon_code', itemCode)\r\n couponObj.set('status', itemStatus)\r\n couponObj.set('received_at', itemRecv)\r\n couponObj.set('expire_at', itemExpire)\r\n couponObj.set('template_name', tName)\r\n couponObj.set('amount', tAmount)\r\n couponObj.set('min_spend', tMin)\r\n \r\n coupons.push(couponObj as UserCoupon)\r\n }\r\n\r\n return coupons\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3600','获取优惠券异常:', 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) // 1: unused\r\n .gt('expire_at', new Date().toISOString()) // 未过期\r\n .limit(1) // Limit to 1 to reduce data transfer, we only want the count\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 // 查询该商家的优惠券 + 平台通用券 (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)\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:3650','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 return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3662','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:3676','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:3687','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:3693','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:3699','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:3745','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:3753','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:3756','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:3772','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 getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise<ChatMessage[]> {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: ChatMessage[] = []\r\n return empty\r\n }\r\n \r\n // 计算分页 range\r\n const fromIndex = (page - 1) * pageSize\r\n const toIndex = fromIndex + pageSize - 1\r\n\r\n try {\r\n // 使用 or 组合条件查询:(sender_id=me AND receiver_id=merchant) OR (sender_id=merchant AND receiver_id=me)\r\n // 注意:Supabase postgrest-js 的 .or() 语法如果是针对同一列很简单,针对复杂逻辑用 string syntax\r\n // 这里简化处理,如果不加 userId 过滤,全靠 RLS\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .select('*')\r\n .or(`sender_id.eq.${merchantId},receiver_id.eq.${merchantId}`)\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:3806','getChatMessages error:', response.error)\r\n const empty: ChatMessage[] = []\r\n return empty\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n const empty: ChatMessage[] = []\r\n return empty\r\n }\r\n\r\n return data as ChatMessage[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3819','getChatMessages exception:', e)\r\n const empty: ChatMessage[] = []\r\n return empty\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:3830',\"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:3837',\"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:3854','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:3859','sendMessage exception:', e)\r\n return false\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// 导出单例实例\r\nexport const supabaseService = new SupabaseService()\r\n\r\n// 默认导出\r\nexport default supabaseService\r\n","<!-- pages/mall/consumer/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\">\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 <image v-if=\"brand.logo_url\" :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<view v-else class=\"card-icon\">\r\n\t\t\t\t\t\t\t<text class=\"card-icon-text\">🏢</text>\r\n\t\t\t\t\t\t</view>\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: #4CAF50;\">\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<!-- 热销药品专区 -->\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-badge\" v-if=\"product.is_hot\">热销</view>\r\n\t\t\t\t\t\t<image \r\n\t\t\t\t\t\t\tclass=\"product-image\" \r\n\t\t\t\t\t\t\t:src=\"product.main_image_url\" \r\n\t\t\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t<view class=\"product-info\">\r\n\t\t\t\t\t\t\t<view class=\"product-name\">{{ product.name }}</view>\r\n\t\t\t\t\t\t\t<!-- spec is omitted if not available -->\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t<view class=\"price-section\">\r\n\t\t\t\t\t\t\t\t<view class=\"current-price\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"price-symbol\">¥</text>\r\n\t\t\t\t\t\t\t\t\t<text class=\"price-value\">{{ product.base_price }}</text>\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t\t<text class=\"original-price\" v-if=\"product.market_price != null && product.market_price! > product.base_price\">\r\n\t\t\t\t\t\t\t\t\t¥{{ product.market_price }}\r\n\t\t\t\t\t\t\t\t</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=\"product-meta\">\r\n\t\t\t\t\t\t\t\t<text class=\"manufacturer\">{{ product.brand_name ?? product.shop_name ?? '自营' }}</text>\r\n\t\t\t\t\t\t\t\t<view class=\"sales-info\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"sales-count\">已售{{ product.sale_count }}</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\r\n\t\t\t\t\t\t\t<view class=\"product-action\">\r\n\t\t\t\t\t\t\t\t<view class=\"cart-btn\" @click.stop=\"addToCart(product)\">\r\n\t\t\t\t\t\t\t\t\t<text class=\"cart-icon\">+</text>\r\n\t\t\t\t\t\t\t\t\t<text class=\"cart-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</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\n\r\n// 数据源\r\nconst hotProducts = ref<Product[]>([])\r\nconst recommendedProducts = ref<Product[]>([])\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 } 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 const subData = await supabaseService.getSubCategories(parentId)\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 // 如果已经选中,则切换显示/隐藏二级分类\r\n if (selectedParentCategory.value != null && selectedParentCategory.value.id === category.id) {\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 \r\n // 加载二级分类\r\n await loadSubCategories(category.id)\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/mall/consumer/category?categoryId=${category.id}&name=${encodeURIComponent(category.name)}×tamp=${timestamp}&random=${randomParam}`\r\n \r\n uni.switchTab({\r\n url: '/pages/mall/consumer/category'\r\n })\r\n}\r\n\r\n// 获取品牌数据\r\nconst loadBrands = async () => {\r\n try {\r\n const brandsData = await supabaseService.getBrands()\r\n // 保持原始顺序\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 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('调用 getHotProducts')\r\n products = await supabaseService.getHotProducts(limit)\r\n break\r\n case 'price':\r\n console.log('调用 getProductsByPrice')\r\n // 按价格升序(从低到高)\r\n products = await supabaseService.getProductsByPrice(limit, true)\r\n break\r\n case 'new':\r\n console.log('调用 getProductsByNewest')\r\n // 按创建时间,最新的在前\r\n products = await supabaseService.getProductsByNewest(limit)\r\n break\r\n case 'recommend':\r\n console.log('调用 getRecommendedProducts')\r\n // 推荐商品(带badge的商品)\r\n products = await supabaseService.getRecommendedProducts(limit)\r\n break\r\n case 'discount':\r\n console.log('调用 getDiscountProducts')\r\n // 特价商品(badge为'特价')\r\n products = await supabaseService.getDiscountProducts(limit)\r\n break\r\n default:\r\n console.log('调用默认 getHotProducts')\r\n products = await supabaseService.getHotProducts(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 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 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: '#4CAF50',\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// 计算滚动区域高度 - 不再需要手动计算,使用 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\tconst eventObj = event as UTSJSONObject\r\n\tconst detail = eventObj.get('detail') as UTSJSONObject\r\n\tconst scrollTop = detail.getNumber('scrollTop') ?? 0\r\n\tconst currentTime = Date.now()\r\n\t\r\n\t// 判断滚动方向\r\n\tif (scrollTop > lastScrollTop.value) {\r\n\t\t// 向下滚动\r\n\t\tscrollingUp.value = false\r\n\t\t// 向下滚动超过阈值时隐藏导航栏\r\n\t\tif (scrollTop > scrollThreshold && showNavbar.value) {\r\n\t\t\tshowNavbar.value = false\r\n\t\t}\r\n\t} else if (scrollTop < lastScrollTop.value) {\r\n\t\t// 向上滚动\r\n\t\tscrollingUp.value = true\r\n\t\t// 向上滚动时显示导航栏\r\n\t\tif (!showNavbar.value) {\r\n\t\t\tshowNavbar.value = true\r\n\t\t}\r\n\t}\r\n\t\r\n\t// 滚动到顶部时强制显示导航栏\r\n\tif (scrollTop <= 10) {\r\n\t\tshowNavbar.value = true\r\n\t}\r\n\t\r\n\tlastScrollTop.value = scrollTop\r\n\t\r\n\t// 调试信息(开发时可启用)\r\n\t// console.log(`Scroll: ${scrollTop}, ShowNavbar: ${showNavbar.value}, ScrollingUp: ${scrollingUp.value}`)\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/mall/consumer/category?categoryId=${categoryId}&name=${encodeURIComponent(categoryName)}×tamp=${timestamp}&random=${randomParam}`\r\n\r\n uni.switchTab({\r\n url: '/pages/mall/consumer/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\tactiveSort.value = sortId\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 nextLimit = currentCount + 6\r\n\t\t\r\n\t\tconsole.log('开始加载更多,当前数量:', currentCount, '目标数量:', nextLimit)\r\n\t\t\r\n\t\t// 加载更多热销商品\r\n\t\tawait loadHotProducts(nextLimit)\r\n\t\t\r\n\t\t// 检查是否还有更多数据\r\n\t\tif (hotProducts.value.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\t/* uni.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} 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 // 尝试调用 Supabase 服务添加\r\n\t\tconst success = await supabaseService.addToCart(productId, 1, '')\r\n\t\tif (success) {\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 // 失败(如未登录),回退到本地存储或提示登录\r\n // 这里简单提示失败\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 (e) {\r\n\t\tconsole.error('添加到购物车异常', e)\r\n uni.showToast({\r\n title: '操作异常',\r\n icon: 'none'\r\n })\r\n\t} finally {\r\n uni.hideLoading()\r\n }\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/product1.jpg'\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: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);\r\n\tz-index: 1000;\r\n\tbox-shadow: 0 2px 12px rgba(76, 175, 80, 0.15); /* 调整为与分类页一致 */\r\n\ttransition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n\t/* will-change: transform, opacity; removed for uniapp-x support */\r\n\t/* pointer-events: auto; */\r\n\t/* backface-visibility: hidden; */\r\n\t/* -webkit-backface-visibility: hidden; */\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: #4CAF50;\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: #4CAF50;\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: 23%; /* 一行4个 */\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 uniapp-x support */\r\n\ttransition: all 0.3s ease;\r\n\tborder: 1px solid transparent;\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, #4CAF50);\r\n}\r\n\r\n.card-icon {\r\n\twidth: 56px;\r\n\theight: 56px;\r\n\tborder-radius: 28px;\r\n\tbackground: var(--card-color, #4CAF50);\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.card-icon-text {\r\n\tfont-size: 24px;\r\n\tcolor: white;\r\n}\r\n\r\n.card-name {\r\n\tfont-size: 15px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n\tmargin-bottom: 4px;\r\n\ttext-align: center;\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: #4CAF50;\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-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: #4CAF50;\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: #4CAF50;\r\n\tcolor: white;\r\n\tborder-color: #4CAF50;\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: #388E3C;\r\n}\r\n\r\n/* 产品网格 */\r\n.products-grid {\r\n\tdisplay: flex; /* 替换 block 为 flex */\r\n\tflex-direction: row; /* 确保横向排列 */\r\n\tflex-wrap: wrap; /* 确保网格布局 */\r\n\t/* gap: 10px; removed for uniapp-x support */\r\n justify-content: space-between; /* use space-between instead of gap */\r\n\tmargin-top: 20px;\r\n\tmin-height: 500px; /* 确保有足够高度触发滚动 */\r\n\tpadding-bottom: 20px;\r\n}\r\n\r\n.product-card {\r\n\t/* break-inside: avoid; removed for flex layout */\r\n\twidth: 48%; /* Fallback for calc(50% - 5px) */\r\n /* margin-right: 2%; Gap handled by space-between */\r\n\tmargin-bottom: 20px; /* 增加底部间距 */\r\n\tbackground: #ffffff; /* 改为纯白 */\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 #eee; /* 更淡一点的边框 */\r\n\tposition: relative;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.product-card:hover {\r\n\ttransform: translateY(-4px);\r\n\tbox-shadow: 0 12px 32px rgba(0, 0, 0, 0.1); /* 增强悬停阴影 */\r\n}\r\n\r\n.product-badge {\r\n\tposition: absolute;\r\n\ttop: 12px;\r\n\tleft: 12px;\r\n\tbackground: #FF5722;\r\n\tcolor: white;\r\n\tfont-size: 11px;\r\n\tpadding: 4px 12px;\r\n\tborder-radius: 12px;\r\n\tfont-weight: bold;\r\n\tz-index: 2;\r\n}\r\n\r\n.product-image {\r\n\twidth: 100%;\r\n\theight: 180px; /* 默认稍微高一点 */\r\n\tbackground: #f8f9fa;\r\n}\r\n\r\n.product-info {\r\n\tpadding: 16px;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tflex: 1; /* 撑开剩余空间 */\r\n}\r\n\r\n.product-name {\r\n\tfont-size: 15px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n\tmargin-bottom: 4px;\r\n\tline-height: 1.4;\r\n\t/* display: flex; removed for uniapp-x text support */\r\n /* overflow: hidden; */\r\n /* text-overflow: ellipsis; */\r\n /* display: -webkit-box; */\r\n /* -webkit-line-clamp: 2; */\r\n /* -webkit-box-orient: vertical; */\r\n /* Simplified for compatibility */\r\n display: flex;\r\n white-space: normal;\r\n}\r\n\r\n.product-spec {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tmargin-bottom: 12px;\r\n\tdisplay: flex;\r\n}\r\n\r\n.price-section {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\t/* gap: 8px; removed */\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.current-price {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n margin-right: 8px; /* Replacement for gap */\r\n}\r\n\r\n.price-symbol {\r\n\tfont-size: 14px;\r\n\tcolor: #FF5722;\r\n}\r\n\r\n.price-value {\r\n\tfont-size: 20px;\r\n\tfont-weight: bold;\r\n\tcolor: #FF5722;\r\n\tmargin-left: 2px;\r\n}\r\n\r\n.original-price {\r\n\tfont-size: 13px;\r\n\tcolor: #999;\r\n\t/* text-decoration: line-through; removed for uniapp-x support */\r\n}\r\n\r\n.product-meta {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tfont-size: 12px;\r\n\tmargin-bottom: 12px;\r\n}\r\n\r\n.manufacturer {\r\n\tcolor: #666;\r\n}\r\n\r\n.sales-count {\r\n\tcolor: #999;\r\n}\r\n\r\n.product-action {\r\n\tmargin-top: 12px;\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: #4CAF50;\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: #4CAF50;\r\n\tcolor: white;\r\n\tborder-color: #4CAF50;\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: #388E3C;\r\n}\r\n\r\n.recommend-grid {\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* Ensure items are in row */\r\n\tflex-wrap: wrap;\r\n\t/* gap: 20px; removed for uniapp-x support */\r\n\tmargin: 0 -1.5%;\r\n\tmargin-top: 20px;\r\n}\r\n\r\n.recommend-product {\r\n\twidth: 97%; /* 1 col */\r\n\tmargin: 0 1.5% 20px 1.5%;\r\n\tbackground: #f8f9fa;\r\n\tborder-radius: 12px;\r\n\toverflow: hidden;\r\n\t/* cursor: pointer; removed for uvue support */\r\n\ttransition: all 0.3s ease;\r\n\tborder: 1px solid #e0e0e0;\r\n}\r\n\r\n.recommend-product:hover {\r\n\ttransform: translateY(-4px);\r\n\tbox-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\r\n}\r\n\r\n.product-image-container {\r\n\tposition: relative;\r\n\theight: 180px;\r\n}\r\n\r\n.product-image-container .product-image {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tbackground: white;\r\n}\r\n\r\n.product-tags {\r\n\tposition: absolute;\r\n\ttop: 12px;\r\n\tleft: 12px;\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\t/* gap: 8px; removed */\r\n}\r\n\r\n.product-tag, .featured-tag {\r\n\tpadding: 4px 10px;\r\n\tborder-radius: 10px;\r\n\tfont-size: 11px;\r\n\tfont-weight: bold;\r\n\tcolor: white;\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.product-tag {\r\n\tbackground: rgba(76, 175, 80, 0.9);\r\n}\r\n\r\n.featured-tag {\r\n\tbackground: rgba(255, 87, 34, 0.9);\r\n}\r\n\r\n.product-details {\r\n\tpadding: 16px;\r\n}\r\n\r\n.product-title {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n\tmargin-bottom: 4px;\r\n\tline-height: 1.4;\r\n\tdisplay: flex;\r\n}\r\n\r\n.product-specification {\r\n\tfont-size: 13px;\r\n\tcolor: #666;\r\n\tmargin-bottom: 12px;\r\n\tdisplay: flex;\r\n}\r\n\r\n.product-rating {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\t/* gap: 8px; removed */\r\n\tmargin-bottom: 12px;\r\n\tfont-size: 13px;\r\n}\r\n\r\n.rating-stars {\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\t/* gap: 4px; removed */\r\n\tmargin-right: 8px;\r\n}\r\n\r\n.star-icon {\r\n\tfont-size: 14px;\r\n\tcolor: #FFC107;\r\n\tmargin-right: 2px;\r\n}\r\n\r\n.rating-value {\r\n\tfont-weight: bold;\r\n\tcolor: #333;\r\n}\r\n\r\n.reviews-count {\r\n\tcolor: #666;\r\n}\r\n\r\n.product-actions {\r\n\tdisplay: flex;\r\n\tjustify-content: flex-end;\r\n\tmargin-top: 12px;\r\n}\r\n\r\n.add-to-cart {\r\n\twidth: 36px;\r\n\theight: 36px;\r\n\tborder-radius: 18px;\r\n\tbackground: #4CAF50;\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: #388E3C;\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: #4CAF50;\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\t/* grid-template-columns: repeat(5, 1fr); removed for uniapp-x support */\r\n\t\t/* gap: 8px; removed */\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%; /* 5 cols : 20 - 2 */\r\n\t\tmargin: 0 1% 8px 1%;\r\n\t\tpadding: 8px 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: 44px; /* 减小图标尺寸 */\r\n\t\theight: 44px;\r\n\t\tborder-radius: 22px;\r\n\t\tmargin-bottom: 6px;\r\n\t}\r\n\t\r\n\t.card-icon-text {\r\n\t\tfont-size: 20px;\r\n\t}\r\n\t\r\n\t.card-name {\r\n\t\tfont-size: 11px; /* 减小文字大小 */\r\n\t\tfont-weight: normal;\r\n\t\tcolor: #333;\r\n\t}\r\n\t\r\n\t.card-desc {\r\n\t\tdisplay: none; /* 手机端隐藏描述文字,保持界面整洁 */\r\n\t}\r\n\t\r\n\t.services-grid .service-card {\r\n\t\twidth: 23%; /* 4 cols */\r\n\t\tmargin: 0 1% 8px 1%;\r\n\t}\r\n\t\r\n\t.service-card {\r\n\t\tpadding: 10px 4px;\r\n\t\tbackground: #f8f9fa; /* 保持淡色背景 */\r\n\t}\r\n\t\r\n\t.service-icon {\r\n\t\twidth: 40px;\r\n\t\theight: 40px;\r\n\t\tborder-radius: 20px;\r\n\t\tmargin-bottom: 8px;\r\n\t}\r\n\t\r\n\t.service-icon-text {\r\n\t\tfont-size: 20px;\r\n\t}\r\n\t\r\n\t.service-name {\r\n\t\tfont-size: 11px;\r\n\t}\r\n\t\r\n\t.service-desc {\r\n\t\tdisplay: none; /* 隐藏描述 */\r\n\t}\r\n\t\r\n\t.load-more-status {\r\n\t\tpadding: 20px;\r\n\t\tdisplay: flex;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\twidth: 100%;\r\n\t}\r\n\t\r\n\t.loading-text {\r\n\t\tcolor: #888;\r\n\t\tfont-size: 14px;\r\n\t}\r\n\r\n\t.products-grid {\r\n\t\t/* column-count: 2; removed for flex */\r\n\t\t/* column-gap: 8px; removed */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 48%;\r\n\t\tmargin: 0 1% 8px 1%;\r\n\t}\r\n\r\n\t.product-info,\r\n\t.product-details {\r\n\t\tpadding: 6px; /* 极小内边距 */\r\n\t}\r\n\r\n\t.product-name,\r\n\t.product-title {\r\n\t\tfont-size: 13px; /* 调整字体大小 */\r\n\t\theight: 36px; /* 限制高度,防止参差不齐 */\r\n\t\toverflow: hidden;\r\n\t\ttext-overflow: ellipsis;\r\n /* display: webkit-box removed for compatibility */\r\n display: flex;\r\n white-space: nowrap;\r\n\t}\r\n\r\n\t.product-image,\r\n\t.product-image-container {\r\n\t\theight: 140px; /* 稍微减小图片高度适配双列 */\r\n\t}\r\n\r\n\t/* 手机端商品卡片极简模式(热销 & 推荐) */\r\n\t.hot-products .product-spec,\r\n\t.hot-products .manufacturer,\r\n\t.hot-products .original-price,\r\n\t.hot-products .cart-text,\r\n\t.hot-products .sales-info,\r\n\t.hot-products .product-action, /* 隐藏热销区加购按钮 */\r\n\t.smart-recommend .product-specification,\r\n\t.smart-recommend .product-rating,\r\n\t.smart-recommend .original-price,\r\n\t.smart-recommend .product-actions /* 隐藏推荐区加购按钮 */\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n\t\r\n\t.hot-products .product-info,\r\n\t.smart-recommend .product-details {\r\n\t\tpadding: 6px; /* 极小内边距 */\r\n\t}\r\n\t\r\n\t.hot-products .product-image,\r\n\t.hot-products .product-image-container,\r\n\t.smart-recommend .product-image,\r\n\t.smart-recommend .product-image-container {\r\n\t\theight: 110px; /* 进一步减小图片高度 */\r\n\t}\r\n\t\r\n\t.hot-products .product-name,\r\n\t.smart-recommend .product-title {\r\n\t\tmargin-bottom: 2px;\r\n\t\tfont-size: 12px;\r\n\t\tline-height: 1.3;\r\n\t\theight: 32px; /* 限制2行高度 */\r\n\t}\r\n\t\r\n\t.hot-products .price-section,\r\n\t.smart-recommend .price-section {\r\n\t\tmargin-bottom: 0;\r\n\t\tmargin-top: 4px;\r\n\t}\r\n\t\r\n\t.hot-products .price-symbol,\r\n\t.smart-recommend .price-symbol {\r\n\t\tfont-size: 10px;\r\n\t\tcolor: #FF5722;\r\n\t}\r\n\t\r\n\t.hot-products .price-value,\r\n\t.smart-recommend .price-value {\r\n\t\tfont-size: 14px; /* 字体变小 */\r\n\t\tfont-weight: bold;\r\n\t}\r\n\t\r\n\t.family-grid .family-item {\r\n\t\twidth: 23%; /* 4 cols */\r\n\t\tmargin: 0 1% 8px 1%;\r\n\t\tpadding: 8px 4px;\r\n\t\tbackground: #f8f9fa;\r\n\t}\r\n\t\r\n\r\n\t\r\n\t.family-icon {\r\n\t\twidth: 36px;\r\n\t\theight: 36px;\r\n\t\tborder-radius: 18px;\r\n\t\tmargin-bottom: 6px;\r\n\t}\r\n\t\r\n\t.family-icon-text {\r\n\t\tfont-size: 18px;\r\n\t}\r\n\t\r\n\t.family-name {\r\n\t\tfont-size: 11px;\r\n\t}\r\n\t\r\n\t.family-desc {\r\n\t\tdisplay: none;\r\n\t}\r\n\t\r\n\t.news-swiper {\r\n\t\theight: 160px;\r\n\t}\r\n\t\r\n\t.sort-tabs,\r\n\t.recommend-filters {\r\n\t\t/* gap: 8px; removed */\r\n\t\tjustify-content: flex-start; /* 保持左对齐 */\r\n\t\t/* overflow-x: auto; REMOVED for uniapp-x support - use generic view wrapping instead */\r\n\t\t/* flex-wrap: nowrap; REMOVED to allow wrapping on mobile since overflow-x not supported on view */\r\n\t\tpadding-bottom: 4px; /* 滚动条空间 */\r\n\t}\r\n\t\r\n\t.sort-tab,\r\n\t.filter-item {\r\n\t\tpadding: 5px 12px;\r\n\t\tfont-size: 12px;\r\n\t\tflex-shrink: 0; /* 防止被压缩 */\r\n\t\tmin-width: 0; /* CHANGED from auto to 0 */\r\n\t\tflex: 0 0 auto; /* 取消均分 */\r\n\t\tmargin-right: 8px;\r\n\t\tmargin-bottom: 8px; /* Added spacing for wrapped items */\r\n\t}\r\n\t\r\n\t.section-header {\r\n\t\tflex-direction: column;\r\n\t\talign-items: stretch;\r\n\t\t/* gap: 12px; removed */\r\n\t}\r\n\t\r\n\t.title-section {\r\n\t\tjustify-content: center;\r\n\t\tmargin-bottom: 12px;\r\n\t}\r\n\t\r\n\t.sort-tabs,\r\n\t.recommend-filters {\r\n\t\twidth: 100%;\r\n\t\tjustify-content: center;\r\n\t}\r\n}\r\n\r\n/* 中屏手机/小平板 (415px-768px) */\r\n@media screen and (min-width: 415px) and (max-width: 768px) {\r\n\t.search-container {\r\n\t\tpadding: 0 16px;\r\n\t\theight: 44px;\r\n\t}\r\n\t\r\n\t.main-scroll {\r\n\t\t/* 移除 margin-top */\r\n\t}\r\n\t\r\n\t.category-grid .category-card {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.services-grid .service-card {\r\n\t\twidth: 47%;\r\n\t}\r\n\t\r\n\t.products-grid {\r\n\t\t/* column-count: 2; removed */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 47%;\r\n\t}\r\n\t\r\n\t.family-grid .family-item {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.sort-tabs,\r\n\t.recommend-filters {\r\n\t\t/* gap: 10px; removed */\r\n\t\tjustify-content: flex-start;\r\n\t}\r\n\t\r\n\t.sort-tab,\r\n\t.filter-item {\r\n\t\tpadding: 6px 14px;\r\n\t\tfont-size: 12px;\r\n\t\tflex-grow: 0;\r\n\t\tmargin-right: 10px;\r\n\t}\r\n\t\r\n\t.section-header {\r\n\t\tflex-direction: column;\r\n\t\talign-items: stretch;\r\n\t\t/* gap: 12px; removed */\r\n\t}\r\n\t\r\n\t.title-section {\r\n\t\tjustify-content: center;\r\n\t\tmargin-bottom: 12px;\r\n\t}\r\n\t\r\n\t.sort-tabs,\r\n\t.recommend-filters {\r\n\t\twidth: 100%;\r\n\t\tjustify-content: center;\r\n\t}\r\n}\r\n\r\n/* 平板设备 (769px-1024px) */\r\n@media screen and (min-width: 769px) and (max-width: 1024px) {\r\n\t.search-container {\r\n\t\tpadding: 0 24px;\r\n\t\theight: 44px;\r\n\t}\r\n\t\r\n\t.nav-search-tools .nav-tool-item {\r\n\t\tdisplay: none;\r\n\t}\r\n\t\r\n\t.main-scroll {\r\n\t\tpadding: 0 24px 20px;\r\n\t}\r\n\t\r\n\t.category-grid .category-card {\r\n\t\twidth: 22%;\r\n\t}\r\n\t\r\n\t.services-grid .service-card {\r\n\t\twidth: 22%;\r\n\t}\r\n\t\r\n\t.product-card {\r\n\t\t/* width: calc((100% - 20px) / 3); */\r\n width: 32%; /* Fallback for calc */\r\n /* margin-right: 1.33%; */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 47%;\r\n\t}\r\n\t\r\n\t.family-grid .family-item {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.news-swiper {\r\n\t\theight: 240px;\r\n\t}\r\n}\r\n\r\n/* 桌面端 (1025px以上) */\r\n@media screen and (min-width: 1025px) {\r\n\t.search-container {\r\n\t\tpadding: 0 32px;\r\n\t\theight: 44px;\r\n\t}\r\n\t\r\n\t.main-scroll {\r\n\t\tpadding: 0 32px 24px;\r\n\t}\r\n\t\r\n\t.category-grid .category-card {\r\n\t\twidth: 13.66%;\r\n\t}\r\n\t\r\n\t.services-grid .service-card {\r\n\t\twidth: 22%;\r\n\t}\r\n\t\r\n\t.product-card {\r\n\t\t/* width: calc((100% - 30px) / 4); */\r\n width: 23%;\r\n /* margin-right: 2%; */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.family-grid .family-item {\r\n\t\twidth: 30.33%;\r\n\t}\r\n\t\r\n\t.news-swiper {\r\n\t\theight: 260px;\r\n\t}\r\n}\r\n\r\n/* 大桌面端 (1400px以上) */\r\n@media screen and (min-width: 1400px) {\r\n\t.category-grid {\r\n\t\tmargin-right: -24px;\r\n\t}\r\n\t.category-grid .category-card {\r\n\t\twidth: 17%;\r\n\t\tmargin: 0 1.5% 24px 1.5%;\r\n\t}\r\n\t\r\n\t.product-card {\r\n\t\t/* width: calc((100% - 30px) / 4); */\r\n width: 23%;\r\n /* margin-right: 2%; */\r\n\t}\r\n\t\r\n\t.recommend-grid .recommend-product {\r\n\t\twidth: 22%;\r\n\t}\r\n}\r\n\r\n/* 暗黑模式适配 */\r\n@media (prefers-color-scheme: dark) {\r\n\t.medic-home {\r\n\t\tbackground: #121212;\r\n\t}\r\n\t\r\n\t.smart-categories,\r\n\t.health-news,\r\n\t.smart-services,\r\n\t.hot-products,\r\n\t.family-medicine,\r\n\t.smart-recommend {\r\n\t\tbackground: #1e1e1e;\r\n\t\tborder-color: #333;\r\n\t}\r\n\t\r\n\t.nav-search-box {\r\n\t\tbackground: rgba(30, 30, 30, 0.95);\r\n\t\tborder-color: #444;\r\n\t}\r\n\t\r\n\t.category-card,\r\n\t.service-card,\r\n\t.product-card,\r\n\t.family-item,\r\n\t.recommend-product {\r\n\t\tbackground: #2d2d2d;\r\n\t\tborder-color: #444;\r\n\t}\r\n\t\r\n\t.section-title,\r\n\t.card-name,\r\n\t.service-name,\r\n\t.product-name,\r\n\t.family-name,\r\n\t.product-title,\r\n\t.section-desc,\r\n\t.card-desc,\r\n\t.service-desc,\r\n\t.product-spec,\r\n\t.product-specification,\r\n\t.family-desc {\r\n\t\tcolor: #aaa;\r\n\t}\r\n\t\r\n\t.sort-tab,\r\n\t.filter-item {\r\n\t\tbackground: #333;\r\n\t\tborder-color: #444;\r\n\t\tcolor: #ccc;\r\n\t}\r\n\t\r\n\t.sort-tab.active,\r\n\t.filter-item.active {\r\n\t\tbackground: #4CAF50;\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: #388E3C;\r\n\t}\r\n\t\r\n\t.nav-tool-item {\r\n\t\tbackground: rgba(76, 175, 80, 0.2);\r\n\t\tcolor: #4CAF50;\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","<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\">\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 <!-- 导航栏占位 -->\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 scroll-y class=\"primary-category\">\r\n <view \r\n v-for=\"item in primaryCategories\" \r\n :key=\"item.id\"\r\n :class=\"['primary-item', { active: activePrimary === item.id }]\"\r\n @click=\"selectPrimaryCategory(item.id)\"\r\n :style=\"{ backgroundColor: activePrimary === item.id ? item.color : 'transparent' }\"\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 \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=\"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-badge\" v-if=\"product.is_hot\">热销</view>\r\n <image \r\n class=\"product-image\" \r\n :src=\"product.main_image_url\" \r\n mode=\"aspectFill\"\r\n />\r\n <view class=\"product-info\">\r\n <text class=\"product-name\">{{ product.name }}</text>\r\n \r\n <view class=\"price-section\">\r\n <view class=\"current-price\">\r\n <text class=\"price-symbol\">¥</text>\r\n <text class=\"price-value\">{{ product.base_price }}</text>\r\n </view>\r\n <text class=\"original-price\" v-if=\"product.market_price != null && product.market_price! > product.base_price\">\r\n ¥{{ product.market_price }}\r\n </text>\r\n </view>\r\n \r\n <view class=\"product-meta\">\r\n <text class=\"manufacturer\">{{ product.brand_name ?? product.shop_name ?? '自营' }}</text>\r\n <view class=\"sales-info\">\r\n <text class=\"sales-count\">已售{{ product.sale_count }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 空状态 -->\r\n <view v-else class=\"empty-state\">\r\n <text class=\"empty-icon\">💊</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 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\nconst primaryCategories = ref<LocalCategory[]>([])\r\nconst productList = ref<Product[]>([])\r\nconst activePrimary = ref<string>('')\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\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 const category = primaryCategories.value.find((cat: LocalCategory): boolean => cat.id === activePrimary.value)\r\n if (category != null) {\r\n currentCategoryName.value = category.name\r\n currentCategoryDesc.value = category.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\nasync function loadCategories(): Promise<void> {\r\n try {\r\n const categoriesData = await supabaseService.getCategories()\r\n console.log('加载分类数据成功,数量:', categoriesData.length)\r\n \r\n // 映射数据并添加默认颜色,防止选中时背景透明导致文字看不清\r\n // 过滤掉医药健康相关分类\r\n const categories: LocalCategory[] = []\r\n const rawList = categoriesData as any[]\r\n for (let i = 0; i < rawList.length; i++) {\r\n const raw = rawList[i]\r\n const catObj = (raw instanceof UTSJSONObject) ? (raw as UTSJSONObject) : (JSON.parse(JSON.stringify(raw)) as UTSJSONObject)\r\n const name = catObj.getString('name') ?? ''\r\n if (name.includes('医药') || name.includes('健康')) {\r\n continue\r\n }\r\n const id = catObj.getString('id') ?? ''\r\n const description = catObj.getString('description') ?? ''\r\n const icon = catObj.getString('icon') ?? catObj.getString('icon_url') ?? '📦'\r\n const color = catObj.getString('color') ?? '#4CAF50'\r\n categories.push({\r\n id,\r\n name,\r\n icon,\r\n description,\r\n color\r\n })\r\n }\r\n\r\n if (categories.length > 0) {\r\n primaryCategories.value = categories\r\n // 如果没有通过参数设置分类,则设置默认选中一个分类\r\n if (activePrimary.value == '') {\r\n // 优先查找\"厨具\"相关的分类作为默认\r\n const defaultCategory = categories.find((c: LocalCategory): boolean => c.name.includes('厨具')) ?? categories[0]\r\n \r\n activePrimary.value = defaultCategory.id\r\n console.log('设置默认分类为:', defaultCategory.name, 'ID:', defaultCategory.id)\r\n currentCategoryName.value = defaultCategory.name\r\n currentCategoryDesc.value = defaultCategory.description\r\n } else {\r\n // 如果已经选中了分类(可能来自Storage),更新显示信息\r\n const current = categories.find((c: LocalCategory): boolean => c.id == activePrimary.value)\r\n if (current != null) {\r\n currentCategoryName.value = current.name\r\n currentCategoryDesc.value = current.description\r\n // 如果此时没有商品列表(且没有正在加载),可能需要加载\r\n if (productList.value.length === 0 && !loading.value) {\r\n loadProducts()\r\n }\r\n }\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\nasync function selectPrimaryCategory(categoryId: string): Promise<void> {\r\n console.log('=== selectPrimaryCategory函数开始执行 ===')\r\n console.log('传入的categoryId:', categoryId)\r\n console.log('当前时间:', Date.now())\r\n \r\n // 验证categoryId是否有效\r\n if (categoryId == '') {\r\n console.error('categoryId为空,尝试使用第一个分类')\r\n if (primaryCategories.value.length > 0) {\r\n categoryId = primaryCategories.value[0].id\r\n } else {\r\n console.error('没有可用的分类')\r\n return\r\n }\r\n }\r\n \r\n console.log('验证后的categoryId:', categoryId)\r\n console.log('当前activePrimary的值:', activePrimary.value)\r\n \r\n // 更新活动分类\r\n activePrimary.value = categoryId\r\n console.log('更新后的activePrimary:', activePrimary.value)\r\n \r\n // 更新当前分类信息\r\n const category = primaryCategories.value.find((cat: LocalCategory): boolean => cat.id === categoryId)\r\n if (category != null) {\r\n currentCategoryName.value = category.name\r\n currentCategoryDesc.value = category.description\r\n console.log('✅ 找到分类:', category.name, '描述:', category.description)\r\n } else {\r\n console.error('❌ 未找到分类ID:', categoryId, ',使用第一个分类')\r\n // 如果找不到对应的分类,使用第一个分类\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 activePrimary.value = firstCategory.id\r\n categoryId = firstCategory.id\r\n console.log('使用默认分类:', firstCategory.name)\r\n }\r\n }\r\n \r\n console.log('准备加载商品数据...')\r\n \r\n // 重置分页并加载\r\n currentPage.value = 1\r\n hasMore.value = true\r\n await loadProducts()\r\n \r\n console.log('✅ 加载商品数据成功')\r\n console.log('分类:', categoryId)\r\n console.log('商品数量:', productList.value.length)\r\n console.log('商品列表:', productList.value)\r\n \r\n // 验证数据是否已正确更新\r\n console.log('数据更新验证:')\r\n console.log('activePrimary:', activePrimary.value)\r\n console.log('currentCategoryName:', currentCategoryName.value)\r\n console.log('currentCategoryDesc:', currentCategoryDesc.value)\r\n console.log('productList长度:', productList.value.length)\r\n \r\n console.log('=== selectPrimaryCategory函数执行完成 ===')\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\nonLoad((options: any) => {\r\n\tconsole.log('=== category页面onLoad被调用 ===')\r\n\tconsole.log('页面加载时间:', Date.now())\r\n\tconsole.log('传入的options参数:', options)\r\n\tconsole.log('当前活动分类:', activePrimary.value)\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\nonShow(() => {\r\n\tconsole.log('=== category页面onShow被调用 ===')\r\n\tconsole.log('页面显示时间:', Date.now())\r\n\tconsole.log('当前活动分类:', activePrimary.value)\r\n\t\r\n\t// 1. 优先检查 Storage 中的参数 (由首页传入)\r\n\tconst storageCategoryId = (uni.getStorageSync('selectedCategory') as string) ?? ''\r\n\tif (storageCategoryId !== '') {\r\n\t\tconsole.log('✅ onShow中找到Storage分类参数:', storageCategoryId)\r\n\t\thasLoadedFromParams.value = true\r\n\t\t// 清除Storage,防止下次误读\r\n\t\tuni.removeStorageSync('selectedCategory')\r\n\t\t\r\n\t\tif (activePrimary.value !== storageCategoryId) {\r\n\t\t\tselectPrimaryCategory(storageCategoryId)\r\n\t\t}\r\n\t\t// 如果分类还没加载完,这里设置了ID,等loadCategories完成后会自动匹配信息\r\n\t\treturn\r\n\t}\r\n\r\n\t// 在onShow中,我们也需要检查是否有新的参数\r\n\t// 因为当从主页再次点击分类跳转过来时,可能不会触发onLoad\r\n\t// 而是触发onShow\r\n\t\r\n\t// 获取当前页面实例和参数\r\n\tconst pages = getCurrentPages()\r\n\tif (pages.length > 0) {\r\n\t\tconst currentPage = pages[pages.length - 1]\r\n\t\tconst rawPageOptions = currentPage.options ?? {}\r\n\t\tconsole.log('onShow中获取参数:', rawPageOptions)\r\n\t\tconst pageOptObj = (rawPageOptions instanceof UTSJSONObject) ? (rawPageOptions as UTSJSONObject) : (JSON.parse(JSON.stringify(rawPageOptions)) as UTSJSONObject)\r\n\t\t\r\n\t\t// 检查是否有分类参数\r\n\t\tconst pageCategoryId = pageOptObj.getString('categoryId') ?? ''\r\n\t\tif (pageCategoryId !== '') {\r\n\t\t\thasLoadedFromParams.value = true\r\n\t\t\tconst categoryId = pageCategoryId\r\n\t\t\tconst categoryName = pageOptObj.getString('name') ?? ''\r\n\t\t\t\r\n\t\t\tconsole.log('✅ onShow中找到分类参数:', categoryId, categoryName)\r\n\t\t\tconsole.log('URL中的时间戳参数:', pageOptObj.getString('timestamp') ?? '')\r\n\t\t\tconsole.log('URL中的随机参数:', pageOptObj.getString('random') ?? '')\r\n\t\t\t\r\n\t\t\t// 检查是否需要更新分类\r\n\t\t\tif (activePrimary.value !== categoryId) {\r\n\t\t\t\tconsole.log('当前分类:', activePrimary.value, '与目标分类:', categoryId, '不同,需要更新')\r\n\t\t\t\tconsole.log('准备调用selectPrimaryCategory函数...')\r\n\t\t\t\tselectPrimaryCategory(categoryId)\r\n\t\t\t} else {\r\n\t\t\t\tconsole.log('当前分类已经是目标分类,但可能用户想要刷新页面')\r\n\t\t\t\tconsole.log('当前分类:', activePrimary.value, '目标分类:', categoryId)\r\n\t\t\t\t// 即使分类相同,也重新加载数据,确保数据是最新的\r\n\t\t\t\t// 添加一个小的延迟,确保页面完全显示后再更新数据\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tselectPrimaryCategory(categoryId)\r\n\t\t\t\t}, 100)\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tconsole.log('⚠️ onShow中未找到分类参数')\r\n\t\t}\r\n\t}\r\n\t\r\n\tconsole.log('=== category页面onShow执行完成 ===')\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 if (pid === '') {\r\n uni.hideLoading()\r\n uni.showToast({ title: '商品无效', icon: 'none' })\r\n return\r\n }\r\n const success = await supabaseService.addToCart(pid, 1, '')\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 } catch (e) {\r\n console.error('添加到购物车异常', e)\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n } finally {\r\n uni.hideLoading()\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/mall/consumer/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: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);\r\n z-index: 1000;\r\n box-shadow: 0 2px 12px rgba(76, 175, 80, 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 #4CAF50;\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: 120px;\r\n height: 100%; /* 占满父容器高度 */\r\n margin-right: 20px; /* gap replacement */\r\n background: white;\r\n border-radius: 12px;\r\n padding: 12px 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 8px;\r\n margin: 4px 8px;\r\n border-radius: 8px;\r\n /* cursor: pointer; removed for uniapp-x support */\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: 24px;\r\n margin-bottom: 6px;\r\n margin-right: 0; /* 移除右边距 */\r\n text-align: center;\r\n /* display: block; removed for uniapp-x support */\r\n}\r\n\r\n.primary-name {\r\n font-size: 13px;\r\n line-height: 1.4;\r\n /* display: block; removed for uniapp-x support */\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.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 background: white;\r\n border-radius: 12px;\r\n overflow: hidden;\r\n /* cursor: pointer; removed for uniapp-x support */\r\n transition: all 0.3s ease;\r\n border: 1px solid #e0e0e0;\r\n position: relative;\r\n /* margin: 10px; gap replacement - moved to logic */\r\n width: 44%; /* Decreased to 44% to ensure it fits (44 + 3 + 3 = 50%) */\r\n margin: 3%; /* Increased margin */\r\n box-sizing: border-box; /* Ensure border IS included in width */\r\n}\r\n\r\n.product-card:hover {\r\n transform: translateY(-4px);\r\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\r\n}\r\n\r\n.product-badge {\r\n position: absolute;\r\n top: 12px;\r\n left: 12px;\r\n background: #FF5722;\r\n color: white;\r\n font-size: 11px;\r\n padding: 4px 12px;\r\n border-radius: 12px;\r\n font-weight: 700;\r\n z-index: 2;\r\n}\r\n\r\n.product-image {\r\n width: 100%;\r\n height: 160px;\r\n /* object-fit: cover; REMOVED for uniapp-x support - default behavior is often acceptable or handle via image mode */\r\n background: white;\r\n}\r\n\r\n.product-info {\r\n padding: 16px;\r\n}\r\n\r\n.product-name {\r\n font-size: 15px;\r\n font-weight: 700;\r\n color: #333;\r\n margin-bottom: 4px;\r\n /* display: block; REMOVED for uniapp-x support */\r\n line-height: 1.4;\r\n}\r\n\r\n.product-spec {\r\n font-size: 13px;\r\n color: #666;\r\n margin-bottom: 12px;\r\n /* display: block; REMOVED for uniapp-x support */\r\n}\r\n\r\n.price-section {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: flex-end; /* changed from baseline */\r\n /* gap: 8px; */\r\n margin-bottom: 12px;\r\n}\r\n\r\n.current-price {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: flex-end; /* changed from baseline */\r\n margin-right: 8px; /* gap replacement */\r\n}\r\n\r\n.price-symbol {\r\n font-size: 14px;\r\n color: #FF5722;\r\n}\r\n\r\n.price-value {\r\n font-size: 20px;\r\n font-weight: bold;\r\n color: #FF5722;\r\n margin-left: 2px;\r\n}\r\n\r\n.original-price {\r\n font-size: 13px;\r\n color: #999;\r\n /* text-decoration: line-through; REMOVED for uniapp-x support */\r\n}\r\n\r\n.product-meta {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n font-size: 12px;\r\n margin-bottom: 12px;\r\n}\r\n\r\n.manufacturer {\r\n color: #666;\r\n}\r\n\r\n.sales-count {\r\n color: #999;\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: #4CAF50;\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.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: #4CAF50;\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 8px;\r\n }\r\n \r\n .primary-category {\r\n width: 80px; /* 减小宽度 */\r\n /* display: flex; 移除flex布局,保持默认 */\r\n /* flex-wrap: wrap; 移除换行 */\r\n padding: 8px 0;\r\n margin-right: 10px; /* Gap replacement */\r\n }\r\n \r\n .primary-item {\r\n /* width: calc(25% - 8px); 移除百分比宽度 */\r\n width: auto; /* 恢复自动宽度 */\r\n margin: 4px;\r\n padding: 8px 4px;\r\n /* text-align: center; 已经在通用样式中设置 */\r\n }\r\n \r\n .primary-icon {\r\n margin-right: 0;\r\n margin-bottom: 4px;\r\n font-size: 20px;\r\n }\r\n \r\n .primary-name {\r\n font-size: 11px;\r\n }\r\n \r\n .product-grid {\r\n /* grid-template-columns: repeat(2, 1fr); REMOVED */\r\n /* gap: 8px; REMOVED */\r\n padding: 0 4px 20px 4px; /* 增加底部内边距 */\r\n }\r\n\r\n .product-card {\r\n width: 48%; /* 2 columns for mobile */\r\n margin: 1%;\r\n }\r\n \r\n /* 手机端商品卡片极简模式 - 仿照主页样式 */\r\n .product-spec,\r\n .manufacturer,\r\n .original-price,\r\n .sales-info,\r\n .product-badge { /* 分类页也隐藏角标,保持整洁 */\r\n display: none;\r\n }\r\n \r\n .product-info {\r\n padding: 6px;\r\n }\r\n \r\n .product-image {\r\n height: 100px; /* 由于分类页右侧空间更窄,图片高度设得更小一点 */\r\n }\r\n \r\n .product-name {\r\n font-size: 12px;\r\n height: 32px;\r\n line-height: 1.3;\r\n margin-bottom: 2px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n /* display: -webkit-box; REMOVED for support */\r\n /* -webkit-line-clamp: 2; REMOVED for support */\r\n /* -webkit-box-orient: vertical; REMOVED for support */\r\n lines: 2; /* UTS text truncation */\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 .search-container {\r\n padding: 0 12px;\r\n height: 44px;\r\n }\r\n \r\n .search-box {\r\n padding: 8px 16px;\r\n }\r\n \r\n .category-header {\r\n padding: 12px 4px 0 4px;\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 .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 .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; /* Gap replacement */\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-grid {\r\n /* grid-template-columns: repeat(4, 1fr); REMOVED */\r\n /* gap: 24px; REMOVED */\r\n }\r\n \r\n .product-card {\r\n border-radius: 14px;\r\n width: 22%; /* 4 columns */\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 /* gap: 40px; REMOVED */\r\n padding: 0 32px;\r\n }\r\n \r\n .primary-category {\r\n margin-right: 40px; /* Gap replacement */\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-grid {\r\n /* grid-template-columns: repeat(5, 1fr); REMOVED */\r\n /* gap: 28px; REMOVED */\r\n }\r\n \r\n .product-card {\r\n border-radius: 16px;\r\n width: 17%; /* 5 columns */\r\n margin: 1.5%;\r\n }\r\n \r\n .product-image {\r\n height: 180px;\r\n }\r\n \r\n .product-info {\r\n padding: 24px;\r\n }\r\n \r\n .product-name {\r\n font-size: 17px;\r\n }\r\n \r\n .product-spec {\r\n font-size: 15px;\r\n }\r\n \r\n .price-value {\r\n font-size: 24px;\r\n }\r\n}\r\n</style>\r\n","<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\">\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<!-- 导航栏占位符 -->\r\n\t\t<view class=\"navbar-placeholder\" :style=\"{ height: (statusBarHeight + 10) + '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', { unread: !message.read, 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.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<view class=\"message-header-right\">\r\n\t\t\t\t\t\t\t\t<text class=\"message-time\">{{ message.time }}</text>\r\n\t\t\t\t\t\t\t\t<text v-if=\"message.unreadCount > 0\" class=\"message-unread-count\">{{ message.unreadCount }}</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=\"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.lastMessage\" class=\"last-message\">{{ message.lastMessage }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view v-if=\"message.tags\" class=\"message-tags\">\r\n\t\t\t\t\t\t\t<text v-for=\"tag in message.tags\" :key=\"tag\" class=\"message-tag\">{{ tag }}</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\t:class=\"['message-item', { unread: !message.read }]\"\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>\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.important\" class=\"important-tag\">重要</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\t:class=\"['message-item', { unread: !message.read }]\"\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>\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<text class=\"order-info\" v-if=\"message.order_no\">订单号: {{ message.order_no }}</text>\r\n\t\t\t\t\t\t<view v-if=\"message.status\" class=\"order-status\" :class=\"message.status\">\r\n\t\t\t\t\t\t\t{{ message.statusText }}\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 === '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\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\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 const notes = await supabaseService.getUserNotifications()\r\n \r\n notes.forEach((note: Notification) => {\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 // 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/shop-default.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/service-avatar.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: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);\r\n\tz-index: 1000;\r\n\tbox-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);\r\n\t/* height: 50px; 移除固定高度,由内容决定 */\r\n\tdisplay: flex;\r\n\tflex-direction: row; /* 显式设置行方向 */\r\n\talign-items: center;\r\n\tjustify-content: center;\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: #4CAF50;\r\n\tborder-bottom-color: #4CAF50;\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: #E8F5E9;\r\n\tcolor: #4CAF50;\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: 10px;\r\n\tdisplay: flex;\r\n\talign-items: flex-start;\r\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\r\n\ttransition: all 0.3s ease;\r\n\t/* cursor: pointer; removed for uniapp-x support */\r\n}\r\n\r\n.message-item:hover {\r\n\ttransform: translateY(-2px);\r\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);\r\n}\r\n\r\n.message-item.unread {\r\n\tbackground-color: #f0f9f0;\r\n\tborder-left: 3px solid #4CAF50;\r\n}\r\n\r\n.message-item.active {\r\n\tborder: 1px solid #4CAF50;\r\n}\r\n\r\n.message-icon-wrapper {\r\n\twidth: 50px;\r\n\theight: 50px;\r\n\tborder-radius: 25px;\r\n\tbackground-color: #f5f5f5;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin-right: 15px;\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: 25px;\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: 24px;\r\n\tcolor: white;\r\n}\r\n\r\n.message-avatar {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tborder-radius: 25px;\r\n}\r\n\r\n.online-dot {\r\n\tposition: absolute;\r\n\tbottom: 2px;\r\n\tright: 2px;\r\n\twidth: 12px;\r\n\theight: 12px;\r\n\tbackground-color: #4CAF50;\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}\r\n\r\n.message-header {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: flex-start;\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.message-title-wrapper {\r\n\tflex: 1;\r\n\tmin-width: 0;\r\n\tmargin-right: 10px;\r\n}\r\n\r\n.message-title {\r\n\tfont-size: 16px;\r\n\tcolor: #333;\r\n\tfont-weight: bold;\r\n\t/* display: block; REMOVED for uniapp-x support */\r\n\tmargin-bottom: 4px;\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: 12px;\r\n\tcolor: #4CAF50;\r\n\tbackground: #E8F5E9;\r\n\tpadding: 2px 8px;\r\n\tborder-radius: 10px;\r\n\t/* display: inline-block; REMOVED for uniapp-x support */\r\n}\r\n\r\n.message-header-right {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\talign-items: flex-end;\r\n\t/* gap: 4px; removed for uniapp-x support */\r\n}\r\n\r\n.message-time {\r\n\tfont-size: 12px;\r\n\tcolor: #999;\r\n\twhite-space: nowrap;\r\n margin-bottom: 4px; /* replaced gap */\r\n}\r\n\r\n.message-unread-count {\r\n\tfont-size: 11px;\r\n\tcolor: white;\r\n\tbackground: #FF5722;\r\n\tpadding: 2px 6px;\r\n\tborder-radius: 10px;\r\n\tmin-width: 18px;\r\n\ttext-align: center;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.message-preview-wrapper {\r\n\tmargin-bottom: 8px;\r\n}\r\n\r\n.message-preview {\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n\tline-height: 1.4;\r\n\tmargin-bottom: 4px;\r\n\t/* display: -webkit-box; REMOVED for uniapp-x support */\r\n\t/* -webkit-line-clamp: 2; REMOVED for uniapp-x support */\r\n\t/* -webkit-box-orient: vertical; REMOVED for uniapp-x support */\r\n lines: 2; /* UTS text truncation */\r\n\toverflow: hidden;\r\n text-overflow: ellipsis; /* Ensure standard CSS property is present */\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: #4CAF50;\r\n\tbackground-color: #E8F5E9;\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: #E8F5E9;\r\n\tcolor: #4CAF50;\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/* 空状态 */\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: 80px 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}\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, #4CAF50, #2E7D32);\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: #4CAF50;\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","<!-- pages/mall/consumer/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\">\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<!-- 导航栏占位符 - 已移除 -->\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 scroll-y class=\"cart-content\" :style=\"{ paddingTop: (statusBarHeight + 10) + 'px' }\">\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 </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</view>\r\n\t\t\t\t<view class=\"recommend-list\">\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tv-for=\"product in recommendProducts\" \r\n\t\t\t\t\t\t:key=\"product.id\"\r\n\t\t\t\t\t\tclass=\"recommend-item\"\r\n\t\t\t\t\t\t@click=\"navigateToProduct(product)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<image \r\n\t\t\t\t\t\t\tclass=\"recommend-image\" \r\n\t\t\t\t\t\t\t:src=\"product.image\" \r\n\t\t\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t<text class=\"recommend-name\" :lines=\"2\">{{ product.name }}</text>\r\n\t\t\t\t\t\t<view class=\"recommend-bottom\">\r\n\t\t\t\t\t\t\t<text class=\"recommend-price\">¥{{ product.price }}</text>\r\n\t\t\t\t\t\t\t<view class=\"recommend-add-btn\" @click.stop=\"addToCart(product)\">\r\n\t\t\t\t\t\t\t\t<text class=\"recommend-add-icon\">+</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\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 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\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}\r\n\r\n// 响应式数据\r\nconst cartItems = ref<LocalCartItem[]>([])\r\nconst recommendProducts = ref<RecommendProduct[]>([])\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\nconst cartGroups = computed<CartGroup[]>(() => {\r\n\tconst groups = new Map<string, CartGroup>()\r\n\r\n\tcartItems.value.forEach((item: LocalCartItem) => {\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\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) => sum + item.price * 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 = cartItems.value.filter((item: LocalCartItem): boolean => item.shopId === shopId)\r\n\treturn shopItems.length > 0 && shopItems.every((item: LocalCartItem): boolean => item.selected)\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}\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n\tinitPage()\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// 从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\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: item.product_price != null ? item.product_price : 0,\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,\r\n\t\t\t\t\timage: p.main_image_url ?? '/static/images/default-product.png',\r\n\t\t\t\t\tskuId: ''\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\t// 获取该店铺下所有商品的ID\r\n\tconst shopItems = cartItems.value.filter((item: LocalCartItem): boolean => item.shopId === shopId)\r\n\tif (shopItems.length === 0) return\r\n\t\r\n\tconst isAllShopSelected = shopItems.every((item: LocalCartItem): boolean => item.selected)\r\n\tconst newState = !isAllShopSelected\r\n\t\r\n\tconst shopItemIds = shopItems.map((item: LocalCartItem): string => item.id)\r\n\t\r\n // 乐观更新本地状态\r\n const oldStates = new Map<string, boolean>()\r\n cartItems.value.forEach(item => {\r\n if (item.shopId === shopId) {\r\n oldStates.set(item.id, item.selected)\r\n item.selected = newState\r\n }\r\n })\r\n cartItems.value = [...cartItems.value]\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 // 回滚\r\n cartItems.value.forEach(item => {\r\n if (item.shopId === shopId && oldStates.has(item.id)) {\r\n item.selected = oldStates.get(item.id)!\r\n }\r\n })\r\n cartItems.value = [...cartItems.value]\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 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: any) => {\r\n\ttry {\r\n\t\t// 调用SupabaseService添加商品到购物车\r\n // 显式访问属性,避免any类型导致的编译错误\r\n const target = product as UTSJSONObject\r\n const productId = target.getString('id') ?? ''\r\n const skuId = target.getString('skuId') ?? ''\r\n\t\tconst success = await supabaseService.addToCart(productId, 1, skuId)\r\n\t\tif (success) {\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\t\r\n\t\t\t// 重新加载购物车数据\r\n\t\t\tloadCartData()\r\n\t\t} else {\r\n\t\t\tconsole.error('添加商品到购物车失败')\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 (error) {\r\n\t\tconsole.error('添加商品到购物车异常:', error)\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/mall/consumer/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/product1.jpg'\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\twidth: 100%;\r\n\theight: 100%;\r\n\tbackground-color: #f5f5f5;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\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: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);\r\n\tz-index: 1000;\r\n\tbox-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);\r\n\tdisplay: flex;\r\n\tflex-direction: row;\r\n\talign-items: center;\r\n\tjustify-content: center;\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;\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\theight: 0; /* 配合 flex: 1 实现自适应滚动 */\r\n\tpadding-bottom: 60px; /* 为底部结算栏留出空间 */\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\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tfont-size: 10px;\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: 24px;\r\n}\r\n\r\n.quantity-btn {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tfont-size: 14px;\r\n\tcolor: #666;\r\n}\r\n\r\n.quantity-value {\r\n\twidth: 30px;\r\n\ttext-align: center;\r\n\tfont-size: 13px;\r\n\tline-height: 24px;\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}\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</style>\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\">\r\n <!-- 头像 -->\r\n <image \r\n :src=\"userInfo.avatar_url != '' ? userInfo.avatar_url : '/static/default-avatar.png'\" \r\n class=\"nav-avatar\" \r\n @click=\"editProfile\"\r\n />\r\n \r\n <!-- 用户信息横向排列 (名字、积分、余额、优惠券) -->\r\n <view class=\"nav-user-stats\">\r\n <text class=\"nav-user-name\">{{ userInfo.nickname != '' ? userInfo.nickname : userInfo.phone }}</text>\r\n \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\">\r\n <text class=\"nav-stat-label\">余额</text>\r\n <text class=\"nav-stat-value\" @click=\"goToWallet\">¥{{ 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 <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 </view>\r\n </view>\r\n\r\n <scroll-view class=\"profile-scroll-content\" direction=\"vertical\" style=\"flex:1; height: 0; width: 100%;\">\r\n <!-- 导航栏占位符 - 恢复 -->\r\n <view :style=\"{ height: (statusBarHeight + 10) + '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=\"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=\"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=\"order-shortcuts\">\r\n <view class=\"section-title\">我的订单</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 <text class=\"view-all\" @click=\"goToOrders(currentOrderTab)\">查看更多 ></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-header\">\r\n <text class=\"order-no\">订单号: {{ order.order_no }}</text>\r\n <text class=\"order-status\" :class=\"getOrderStatusClass(order.status)\">{{ getOrderStatusText(order.status) }}</text>\r\n </view>\r\n <view class=\"order-content\">\r\n <image :src=\"getOrderMainImage(order)\" class=\"order-image\" mode=\"aspectFill\" />\r\n <view class=\"order-info\">\r\n <text class=\"order-title\">{{ getOrderTitle(order) }}</text>\r\n <text class=\"order-amount\">¥{{ order.actual_amount }}</text>\r\n <text class=\"order-time\">{{ formatTime(order.created_at) }}</text>\r\n </view>\r\n </view>\r\n <view class=\"order-actions\">\r\n <button v-if=\"order.status === 1\" class=\"action-btn pay\" @click.stop=\"payOrder(order)\">立即支付</button>\r\n <button v-if=\"order.status === 3\" class=\"action-btn confirm\" @click.stop=\"confirmReceive(order)\">确认收货</button>\r\n <button v-if=\"order.status === 4\" class=\"action-btn review\" @click.stop=\"reviewOrder(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, OrderType } 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}\r\n\r\ntype ServiceCountsType = {\r\n coupons: number\r\n favorites: 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\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 } as any,\r\n serviceCounts: {\r\n coupons: 0,\r\n favorites: 0\r\n } as ServiceCountsType,\r\n recentOrders: [] as Array<OrderType>,\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 currentOrderTab: 'all' as string, // 当前选中的订单Tab\r\n allOrders: [] as Array<OrderType> // 存储所有订单数据\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 // 根据当前Tab筛选订单\r\n filteredOrders(): Array<OrderType> {\r\n if (this.currentOrderTab === 'all') {\r\n return this.allOrders\r\n } else if (this.currentOrderTab === 'pending') {\r\n return this.allOrders.filter((order: OrderType): boolean => order.status === 1)\r\n } else if (this.currentOrderTab === 'toship') {\r\n return this.allOrders.filter((order: OrderType): boolean => order.status === 2)\r\n } else if (this.currentOrderTab === 'shipped') {\r\n return this.allOrders.filter((order: OrderType): boolean => order.status === 3)\r\n } else if (this.currentOrderTab === 'review') {\r\n return this.allOrders.filter((order: OrderType): boolean => order.status === 4)\r\n }\r\n return []\r\n }\r\n },\r\n methods: {\r\n // 加载订单数据\r\n async loadOrders() {\r\n try {\r\n const orders = await supabaseService.getOrders()\r\n \r\n // 映射数据库字段到前端类型\r\n this.allOrders = orders.map((o: any): OrderType => {\r\n // 确保 status 字段存在\r\n if (o['status'] == null && o['order_status'] != null) {\r\n o['status'] = o['order_status']\r\n }\r\n // 确保 actual_amount 存在\r\n if (o['actual_amount'] == null && o['total_amount'] != null) {\r\n o['actual_amount'] = o['total_amount']\r\n }\r\n return o as OrderType\r\n })\r\n \r\n // 按时间倒序 (created_at)\r\n this.allOrders.sort((a: any, b: any) => {\r\n const dateA = a['created_at']\r\n const dateB = b['created_at']\r\n const timeA = new Date(dateA != null ? dateA : 0).getTime()\r\n const timeB = new Date(dateB != null ? dateB : 0).getTime()\r\n return timeB - timeA\r\n })\r\n \r\n // 过滤最近的订单\r\n this.recentOrders = this.allOrders.slice(0, 5)\r\n \r\n // 更新角标统计 (确保状态码一致: 1=待支付, 2=待发货, 3=待收货, 4=待评价)\r\n this.orderCounts = {\r\n total: this.allOrders.length,\r\n pending: this.allOrders.filter((o: any) => o.status === 1).length,\r\n toship: this.allOrders.filter((o: any) => o.status === 2).length,\r\n shipped: this.allOrders.filter((o: any) => o.status === 3).length,\r\n review: this.allOrders.filter((o: any) => o.status === 4).length\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 const titles: Record<string, string> = {\r\n 'all': '全部订单',\r\n 'pending': '待支付订单',\r\n 'shipped': '待收货订单',\r\n 'review': '待评价订单'\r\n }\r\n const title = titles[this.currentOrderTab]\r\n return title != null ? title : '我的订单'\r\n },\r\n\r\n initPage() {\r\n const systemInfo = uni.getSystemInfoSync()\r\n this.statusBarHeight = systemInfo.statusBarHeight ?? 0\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 uId = (profile['user_id'] as string) ?? ''\r\n uPhone = (profile['phone'] as string) ?? ''\r\n uEmail = (profile['email'] as string) ?? ''\r\n uNickname = (profile['nickname'] as string) ?? ''\r\n uAvatar = (profile['avatar_url'] as string) ?? ''\r\n uGender = (profile['gender'] as number) ?? 0\r\n }\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/default-avatar.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 // 获取积分和余额(并行获取)\r\n const [balance, points] = await Promise.all([\r\n supabaseService.getUserBalance(),\r\n supabaseService.getUserPoints()\r\n ])\r\n\r\n this.userStats = {\r\n points: points,\r\n balance: balance,\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 // 模拟加载消费统计数据\r\n const statsData: Record<string, ConsumptionStatsType> = {\r\n month: {\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 },\r\n quarter: {\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 },\r\n year: {\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 },\r\n all: {\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 }\r\n }\r\n \r\n this.currentStats = statsData[this.activeStatsPeriod]\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 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 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 getOrderMainImage(order: any): string {\r\n // 尝试从 ml_order_items 获取第一张图\r\n const items = order['ml_order_items'] as any[]\r\n if (items != null && items.length > 0) {\r\n const firstItem = items[0]\r\n // 数据库字段通常是 image_url\r\n const imgUrl = firstItem['image_url'] as string\r\n const prodImg = firstItem['product_image'] as string\r\n const img = (imgUrl != null && imgUrl != '') ? imgUrl : prodImg\r\n \r\n if (img != null && img != '') return img\r\n }\r\n return '/static/product1.jpg'\r\n },\r\n \r\n getOrderTitle(order: any): string {\r\n const items = order['ml_order_items'] as any[]\r\n if (items != null && items.length > 0) {\r\n const firstItem = items[0]\r\n const pName = firstItem['product_name'] as string\r\n const name = (pName != null && pName != '') ? pName : '商品'\r\n \r\n if (items.length > 1) {\r\n return `${name} 等${items.length}件商品`\r\n }\r\n return name\r\n }\r\n return '精选商品'\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/mall/consumer/index'\r\n })\r\n },\r\n \r\n viewOrderDetail(order: OrderType) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/order-detail?orderId=${order.id}`\r\n })\r\n },\r\n \r\n payOrder(order: OrderType) {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/payment?orderId=${order.id}`\r\n })\r\n },\r\n \r\n confirmReceive(order: OrderType) {\r\n uni.showModal({\r\n title: '确认收货',\r\n content: '确认已收到商品吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n uni.showToast({\r\n title: '确认收货成功',\r\n icon: 'success'\r\n })\r\n this.refreshData()\r\n }\r\n }\r\n })\r\n },\r\n \r\n reviewOrder(order: OrderType) {\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 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 goToSubscriptions() {\r\n uni.navigateTo({\r\n url: '/pages/mall/consumer/subscription/plan-list'\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 // 处理订单更新事件\r\n handleOrderUpdated(data: any) {\r\n // 当收到订单更新事件时,刷新订单数据\r\n console.log('收到订单更新事件:', data)\r\n this.refreshData()\r\n \r\n // 显示提示\r\n if (data.status === 1) {\r\n uni.showToast({\r\n title: '订单已保存到待支付',\r\n icon: 'success'\r\n })\r\n } else if (data.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}\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: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);\r\n z-index: 1000;\r\n box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);\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.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-stats {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n justify-content: flex-start; /* 靠左对齐,紧跟头像 */\r\n margin-right: 12px;\r\n overflow: hidden; /* 防止溢出 */\r\n}\r\n\r\n.nav-user-name {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: white;\r\n margin-right: 12px;\r\n /* max-width: 30%; REMOVED */\r\n width: 100px; /* Use fixed width approx */\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.2);\r\n border-radius: 12px;\r\n padding: 2px 8px;\r\n margin-right: 8px;\r\n flex-shrink: 0; /* 防止被压缩 */\r\n}\r\n\r\n.nav-stat-label {\r\n font-size: 11px;\r\n color: rgba(255, 255, 255, 0.9);\r\n margin-right: 4px;\r\n}\r\n\r\n.nav-stat-value {\r\n font-size: 12px;\r\n font-weight: bold;\r\n color: white;\r\n}\r\n\r\n.nav-avatar {\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 18px;\r\n border: 2px solid rgba(255, 255, 255, 0.8);\r\n margin-right: 12px;\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.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: #4CAF50;\r\n font-weight: bold;\r\n}\r\n\r\n.order-tab.active {\r\n background-color: #f0f9f0;\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: #ff4444;\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 padding: 25rpx 0;\r\n border-bottom: 1rpx solid #f5f5f5;\r\n}\r\n\r\n.order-item:last-child {\r\n border-bottom: none;\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 margin-bottom: 15rpx;\r\n}\r\n\r\n.order-no {\r\n font-size: 26rpx;\r\n color: #333;\r\n}\r\n\r\n.order-status {\r\n font-size: 24rpx;\r\n padding: 6rpx 12rpx;\r\n border-radius: 10rpx;\r\n color: #fff;\r\n}\r\n\r\n.order-status.pending {\r\n background-color: #ffa726;\r\n}\r\n\r\n.order-status.processing {\r\n background-color: #2196f3;\r\n}\r\n\r\n.order-status.shipping {\r\n background-color: #9c27b0;\r\n}\r\n\r\n.order-status.completed {\r\n background-color: #4caf50;\r\n}\r\n\r\n.order-content {\r\n display: flex;\r\n align-items: center;\r\n margin-bottom: 15rpx;\r\n}\r\n\r\n.order-image {\r\n width: 100rpx;\r\n height: 100rpx;\r\n border-radius: 8rpx;\r\n margin-right: 20rpx;\r\n}\r\n\r\n.order-info {\r\n flex: 1;\r\n}\r\n\r\n.order-title {\r\n font-size: 26rpx;\r\n color: #333;\r\n margin-bottom: 8rpx;\r\n}\r\n\r\n.order-amount {\r\n font-size: 28rpx;\r\n color: #ff4444;\r\n font-weight: bold;\r\n margin-bottom: 5rpx;\r\n}\r\n\r\n.order-time {\r\n font-size: 22rpx;\r\n color: #999;\r\n}\r\n\r\n.order-actions {\r\n display: flex;\r\n justify-content: flex-end;\r\n /* gap: 15rpx; REMOVED */\r\n}\r\n\r\n.order-actions .action-btn {\r\n margin-left: 15px; /* Replace gap */\r\n}\r\n\r\n.action-btn {\r\n padding: 12rpx 25rpx;\r\n border-radius: 20rpx;\r\n font-size: 24rpx;\r\n border: none;\r\n}\r\n\r\n.action-btn.pay {\r\n background-color: #ff4444;\r\n color: #fff;\r\n}\r\n\r\n.action-btn.confirm {\r\n background-color: #4caf50;\r\n color: #fff;\r\n}\r\n\r\n.action-btn.review {\r\n background-color: #ffa726;\r\n color: #fff;\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: #ff4444;\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<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\" scroll-y>\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=\"{ bound: userInfo.phone }\">\r\n\t\t\t\t\t\t\t\t{{ 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=\"{ bound: userInfo.email }\">\r\n\t\t\t\t\t\t\t\t{{ 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/mall/consumer/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(0)\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n\tconst systemInfo = uni.getSystemInfoSync()\r\n\tstatusBarHeight.value = systemInfo.statusBarHeight\r\n\tloadUserInfo()\r\n\tloadSettings()\r\n})\r\n\r\n// 加载用户信息\r\nconst loadUserInfo = () => {\r\n\tconst userStore = uni.getStorageSync('userInfo')\r\n\tif (userStore) {\r\n\t\tuserInfo.value = userStore\r\n\t}\r\n}\r\n\r\n// 加载设置\r\nconst loadSettings = () => {\r\n\t// 从本地存储加载设置\r\n\tconst savedNotifications = uni.getStorageSync('userNotifications')\r\n\tif (savedNotifications) {\r\n\t\tnotifications.value = savedNotifications\r\n\t}\r\n\t\r\n\tconst savedPrivacy = uni.getStorageSync('userPrivacy')\r\n\tif (savedPrivacy) {\r\n\t\tprivacy.value = savedPrivacy\r\n\t}\r\n\t\r\n\t// 计算缓存大小\r\n\tcalculateCacheSize()\r\n\t\r\n\t// 获取应用版本\r\n\t// @ts-ignore\r\n\tconst appInfo = uni.getAppBaseInfo()\r\n\tif (appInfo?.appVersion) {\r\n\t\tappVersion.value = appInfo.appVersion\r\n\t}\r\n}\r\n\r\n// 计算缓存大小\r\nconst calculateCacheSize = () => {\r\n\t// 这里应该计算实际缓存大小,这里使用模拟数据\r\n\tcacheSize.value = '12.5 MB'\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: keyof NotificationType) => {\r\n\tnotifications.value[type] = !notifications.value[type]\r\n\tuni.setStorageSync('userNotifications', notifications.value)\r\n}\r\n\r\n// 切换隐私设置\r\nconst togglePrivacy = (type: keyof PrivacyType) => {\r\n\tprivacy.value[type] = !privacy.value[type]\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\n// 给个好评\r\nconst rateApp = () => {\r\n\t// 这里应该跳转到应用商店评分\r\n\tuni.showModal({\r\n\t\ttitle: '给个好评',\r\n\t\tcontent: '如果喜欢我们的应用,请给个好评吧!',\r\n\t\tconfirmText: '去评分',\r\n\t\tsuccess: (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\t// 跳转到应用商店\r\n\t\t\t\t// @ts-ignore\r\n\t\t\t\tuni.navigateToMiniProgram({\r\n\t\t\t\t\tappId: 'wx1234567890', // 示例AppID\r\n\t\t\t\t\tfail: () => {\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\n// 退出登录\r\nconst logout = () => {\r\n\tuni.showModal({\r\n\t\ttitle: '退出登录',\r\n\t\tcontent: '确定要退出登录吗?',\r\n\t\tsuccess: async (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tuni.showLoading({\r\n\t\t\t\t\t\ttitle: '正在退出...'\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// 调用登出接口\r\n\t\t\t\t\tconst { error } = await supa.auth.signOut()\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (error !== null) {\r\n\t\t\t\t\t\tconsole.error('登出失败:', error)\r\n\t\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// 清除本地存储的用户信息\r\n\t\t\t\t\tuni.removeStorageSync('userInfo')\r\n\t\t\t\t\tuni.removeStorageSync('user_id')\r\n\t\t\t\t\tuni.removeStorageSync('access_token')\r\n\t\t\t\t\t\r\n\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\t\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\t\r\n\t\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t\tuni.reLaunch({\r\n\t\t\t\t\t\t\turl: '/pages/user/login'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}, 1000)\r\n\t\t\t\t} catch (e) {\r\n\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\tconsole.error('Logout Exception:', e)\r\n\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\ttitle: '退出异常',\r\n\t\t\t\t\t\ticon: 'none'\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// 强制退出\r\n\t\t\t\t\tuni.removeStorageSync('userInfo')\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}\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n}\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: async (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tuni.showLoading({\r\n\t\t\t\t\t\ttitle: '注销中...'\r\n\t\t\t\t\t})\r\n \r\n let userId = userInfo.value.getString('id')\r\n if (userId == null) {\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 try {\r\n // 标记用户状态为注销 (status=3)\r\n await supa\r\n .from('ml_user_profiles')\r\n .update({ status: 3 })\r\n .eq('user_id', userId)\r\n } catch(e) {\r\n console.error('Update status failed', e)\r\n }\r\n \r\n // 登出\r\n await supa.auth.signOut()\r\n }\r\n\t\t\t\t\t\r\n\t\t\t\t\t// 清除本地存储\r\n\t\t\t\t\tuni.removeStorageSync('userInfo')\r\n\t\t\t\t\tuni.removeStorageSync('user_id')\r\n\t\t\t\t\tuni.removeStorageSync('access_token')\r\n\t\t\t\t\t\r\n\t\t\t\t\t// 提示并跳转\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\tduration: 2000\r\n\t\t\t\t\t})\r\n\t\t\t\t\t\r\n\t\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t\tuni.reLaunch({\r\n\t\t\t\t\t\t\turl: '/pages/user/login'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}, 1500)\r\n\t\t\t\t\t\r\n\t\t\t\t} catch (err) {\r\n\t\t\t\t\tuni.hideLoading()\r\n\t\t\t\t\tconsole.error('注销账号失败:', err)\r\n\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\ttitle: '注销失败',\r\n\t\t\t\t\t\ticon: 'none'\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</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\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\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}\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<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\" 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 && 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 }\"\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\tcurrent_balance: number\r\n\tchange_type: string // 'recharge' | 'consume' | 'withdraw' | 'refund' | 'reward'\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// 计算属性\r\nconst canRecharge = computed(() => {\r\n\tconst amount = parseFloat(rechargeAmount.value)\r\n\treturn !isNaN(amount) && amount >= 10 && amount <= 5000\r\n})\r\n\r\n// 监听过滤器变化\r\nwatch(activeFilter, () => {\r\n\tresetTransactions()\r\n\tloadTransactions()\r\n})\r\n\r\n// 生命周期\r\nonShow(() => {\r\n\tloadWalletData()\r\n})\r\n\r\n// 重置交易记录\r\nconst resetTransactions = () => {\r\n\ttransactions.value = []\r\n\tcurrentPage.value = 1\r\n\thasMore.value = true\r\n}\r\n\r\n// 加载钱包数据\r\nconst loadWalletData = async () => {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId == '') {\r\n\t\t// uni.navigateTo({\r\n\t\t// \turl: '/pages/user/login'\r\n\t\t// })\r\n\t\treturn\r\n\t}\r\n\r\n\tawait Promise.all([\r\n\t\tloadBalance(),\r\n\t\tloadTransactions()\r\n\t])\r\n}\r\n\r\n// 加载余额信息\r\nconst loadBalance = async () => {\r\n try {\r\n // 调用 Supabase 服务获取真实余额\r\n const realBalance = await supabaseService.getUserBalance()\r\n balance.value = realBalance\r\n \r\n // 统计数据暂时保持 mock 或设为 0,因为后端还未实现具体统计接口\r\n stats.value = {\r\n totalRecharge: 0,\r\n totalConsume: 0,\r\n totalWithdraw: 0\r\n }\r\n } catch (err) {\r\n console.error('加载钱包异常:', err)\r\n }\r\n}\r\n\r\n// 加载交易记录\r\nconst loadTransactions = async (loadMore: boolean = false) => {\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 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 // 使用 Supabase 获取真实数据\r\n // 注意:目前后端接口暂不支持 activeFilter 筛选,会返回所有记录\r\n const data = await supabaseService.getTransactions(page, limit)\r\n \r\n const mappedData: TransactionType[] = []\r\n for (let i = 0; i < data.length; i++) {\r\n const item = data[i]\r\n let id = ''\r\n let amount = 0\r\n let balance = 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 balance = 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 id = (item['id'] as string) ?? ''\r\n amount = (item['amount'] as number) ?? 0\r\n balance = (item['balance_after'] as number) ?? 0\r\n type = (item['type'] as string) ?? 'consume'\r\n remark = (item['description'] as string) ?? ''\r\n createdAt = (item['created_at'] as string) ?? ''\r\n }\r\n \r\n mappedData.push({\r\n id: id,\r\n user_id: userId,\r\n change_amount: amount,\r\n current_balance: balance,\r\n change_type: type,\r\n related_id: null,\r\n remark: remark,\r\n created_at: createdAt\r\n })\r\n }\r\n\r\n\t\tif (loadMore) {\r\n\t\t\ttransactions.value.push(...mappedData)\r\n\t\t} else {\r\n\t\t\ttransactions.value = mappedData\r\n\t\t}\r\n\t\t\r\n\t\tif (mappedData.length < limit) {\r\n\t\t\thasMore.value = false\r\n\t\t} else {\r\n hasMore.value = true\r\n }\r\n\t\t\r\n\t\tcurrentPage.value = page\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\n// 获取当前用户ID\r\nconst getCurrentUserId = (): string => {\r\n\tconst userStore = uni.getStorageSync('userInfo')\r\n\treturn userStore?.getString('id') ?? ''\r\n}\r\n\r\n// 获取交易图标\r\nconst getTransactionIcon = (type: string): string => {\r\n\tconst icons: Record<string, string> = {\r\n\t\trecharge: '💳',\r\n\t\tconsume: '🛒',\r\n\t\twithdraw: '🏦',\r\n\t\trefund: '🔄',\r\n\t\treward: '🎁',\r\n\t\tincome: '💰',\r\n\t\texpense: '📤'\r\n\t}\r\n\tconst icon = icons[type]\r\n\treturn icon != null ? icon : '💰'\r\n}\r\n\r\n// 获取交易标题\r\nconst getTransactionTitle = (type: string): string => {\r\n\tconst titles: Record<string, string> = {\r\n\t\trecharge: '账户充值',\r\n\t\tconsume: '商品消费',\r\n\t\twithdraw: '余额提现',\r\n\t\trefund: '订单退款',\r\n\t\treward: '活动奖励',\r\n\t\tincome: '收入',\r\n\t\texpense: '支出'\r\n\t}\r\n\tconst title = titles[type]\r\n\treturn title != null ? title : '交易'\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) {\r\n\t\tloadTransactions(true)\r\n\t}\r\n}\r\n\r\n// 选择快捷金额\r\nconst selectQuickAmount = (amount: number) => {\r\n\trechargeAmount.value = amount.toString()\r\n}\r\n\r\n// 确认充值\r\nconst confirmRecharge = async () => {\r\n\tif (!canRecharge.value) return\r\n\r\n\tconst amount = parseFloat(rechargeAmount.value)\r\n\tif (isNaN(amount)) 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 // 刷新数据\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 closeRechargePopup = () => {\r\n\tshowRechargePopup.value = false\r\n\trechargeAmount.value = ''\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/* 基础样式 */\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>","<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\">\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\" \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?.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 if (isNaN(val) || 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\nonMounted(() => {\r\n loadData()\r\n})\r\n\r\nconst loadData = async () => {\r\n try {\r\n const bal = await supabaseService.getUserBalance()\r\n balance.value = bal\r\n \r\n // 获取银行卡\r\n const res = await supabaseService.getUserBankCards()\r\n // 转换类型\r\n const list: BankCard[] = []\r\n for(let i=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 m = item as Map<string, any>\r\n const idVal = m.get('id')\r\n id = idVal != null ? (idVal as string) : ''\r\n \r\n const nameVal = m.get('bank_name')\r\n bankName = nameVal != null ? (nameVal as string) : ''\r\n \r\n const numVal = m.get('card_number')\r\n cardNum = numVal != null ? (numVal as string) : ''\r\n }\r\n\r\n if (id != '') {\r\n list.push({\r\n id: id,\r\n bank_name: bankName,\r\n card_number: cardNum\r\n })\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\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) 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>","<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 coupons.value = userCoupons.map((item: UserCoupon): Coupon => {\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 return {\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 })\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/mall/consumer/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>","<template>\r\n <scroll-view class=\"favorites-page\" direction=\"vertical\">\r\n <view class=\"product-grid\">\r\n <view v-if=\"favorites.length === 0\" class=\"empty-state\">\r\n <text class=\"empty-icon\">❤️</text>\r\n <text class=\"empty-text\">暂无收藏商品</text>\r\n <button class=\"go-shopping-btn\" @click=\"goShopping\">去逛逛</button>\r\n </view>\r\n \r\n <view v-else v-for=\"(product, index) in favorites\" :key=\"index\" class=\"product-item\" @click=\"goToDetail(product.id)\">\r\n <image :src=\"product.image\" class=\"product-image\" mode=\"aspectFill\" />\r\n <view class=\"product-info\">\r\n <text class=\"product-name\">{{ product.name }}</text>\r\n <text class=\"product-price\">¥{{ product.price }}</text>\r\n <view class=\"product-footer\">\r\n <text class=\"product-sales\">已售 {{ product.sales }}</text>\r\n <view class=\"action-btns\">\r\n <view class=\"cart-btn\" @click.stop=\"addToCart(product)\">\r\n <text class=\"cart-icon\">🛒</text>\r\n </view>\r\n <view class=\"remove-btn\" @click.stop=\"removeFavorite(product.id)\">\r\n <text class=\"remove-icon\">🗑️</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n </scroll-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 Product = {\r\n id: string\r\n name: string\r\n price: number\r\n image: string\r\n sales: number\r\n shopId?: string\r\n shopName?: string\r\n}\r\n\r\nconst favorites = ref<Product[]>([])\r\n\r\nconst addToCart = async (product: Product) => {\r\n uni.showLoading({ title: '添加中' })\r\n const success = await supabaseService.addToCart(product.id, 1, '')\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 loadFavorites = async () => {\r\n const res = await supabaseService.getFavorites()\r\n \r\n favorites.value = res.map((item: any): Product => {\r\n let prod: any | null = null\r\n let itemObj: UTSJSONObject | null = null\r\n \r\n if (item instanceof UTSJSONObject) {\r\n itemObj = item as UTSJSONObject\r\n prod = itemObj.get('ml_products')\r\n } else {\r\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n prod = itemObj.get('ml_products')\r\n }\r\n \r\n let image = '/static/default-product.png'\r\n let id = ''\r\n let name = '未知商品'\r\n let price = 0\r\n let sales = 0\r\n \r\n if (prod != null) {\r\n let prodObj: UTSJSONObject\r\n if (prod instanceof UTSJSONObject) {\r\n prodObj = prod as UTSJSONObject\r\n } else {\r\n prodObj = JSON.parse(JSON.stringify(prod)) as UTSJSONObject\r\n }\r\n \r\n id = prodObj.getString('id') ?? ''\r\n name = prodObj.getString('name') ?? '未知商品'\r\n price = prodObj.getNumber('base_price') ?? 0\r\n image = prodObj.getString('main_image_url') ?? image\r\n sales = prodObj.getNumber('sale_count') ?? 0\r\n \r\n if (image === '/static/default-product.png') {\r\n const imgUrls = prodObj.getString('image_urls')\r\n if (imgUrls != null) {\r\n try {\r\n const arr = JSON.parse(imgUrls)\r\n if (Array.isArray(arr) && arr.length > 0) {\r\n image = arr[0] as string\r\n }\r\n } catch(e) {}\r\n }\r\n }\r\n } else {\r\n if (itemObj != null) {\r\n id = itemObj.getString('target_id') ?? ''\r\n }\r\n }\r\n\r\n return {\r\n id: id,\r\n name: name,\r\n price: price,\r\n image: image,\r\n sales: sales,\r\n shopId: '', \r\n shopName: ''\r\n } as Product\r\n })\r\n}\r\n\r\nconst goShopping = () => {\r\n uni.switchTab({\r\n url: '/pages/mall/consumer/index'\r\n })\r\n}\r\n\r\nconst goToDetail = (id: string) => {\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/product-detail?productId=${id}`\r\n })\r\n}\r\n\r\nconst removeFavorite = (id: string) => {\r\n uni.showModal({\r\n title: '取消收藏',\r\n content: '确定要取消收藏该商品吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n supabaseService.toggleFavorite(id).then((isStillFavorite) => {\r\n if (!isStillFavorite) {\r\n const index = favorites.value.findIndex((item): Boolean => {\r\n return item.id === id\r\n })\r\n if (index !== -1) {\r\n favorites.value.splice(index, 1)\r\n }\r\n uni.showToast({\r\n title: '已取消收藏',\r\n icon: 'none'\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\n\r\nonMounted(() => {\r\n loadFavorites()\r\n})\r\n</script>\r\n\r\n<style>\r\n.favorites-page {\r\n padding: 15px;\r\n background-color: #f5f5f5;\r\n flex: 1;\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 width: 100%;\r\n}\r\n\r\n.empty-state {\r\n width: 100%;\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 color: #ddd;\r\n}\r\n\r\n.empty-text {\r\n font-size: 16px;\r\n color: #999;\r\n margin-bottom: 20px;\r\n}\r\n\r\n.go-shopping-btn {\r\n background-color: #ff5000;\r\n color: white;\r\n padding: 8px 24px;\r\n border-radius: 20px;\r\n font-size: 14px;\r\n border: none;\r\n}\r\n\r\n.product-item {\r\n width: 48%; /* Default Mobile: 2 items per row */\r\n background-color: white;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n display: flex;\r\n flex-direction: column;\r\n box-sizing: border-box; /* Important for grid */\r\n}\r\n\r\n/* PC/Tablet Responsive */\r\n@media (min-width: 768px) {\r\n .product-item {\r\n width: 31% !important; /* Tablet: 3 items (gap 15px roughly distributed) */\r\n }\r\n}\r\n\r\n@media (min-width: 1024px) {\r\n .product-item {\r\n width: 15% !important; /* PC: 6 items */\r\n }\r\n \r\n /* Center content on large screens */\r\n .product-grid, .header {\r\n max-width: 1200px;\r\n margin: 0 auto;\r\n }\r\n}\r\n\r\n.product-image {\r\n width: 100%;\r\n height: 170px;\r\n background-color: #f5f5f5;\r\n}\r\n\r\n.product-info {\r\n padding: 10px;\r\n display: flex;\r\n flex-direction: column;\r\n flex: 1;\r\n}\r\n\r\n.product-name {\r\n font-size: 14px;\r\n color: #333;\r\n margin-bottom: 6px;\r\n text-overflow: ellipsis;\r\n lines: 2;\r\n overflow: hidden;\r\n height: 40px;\r\n line-height: 20px;\r\n}\r\n\r\n.product-price {\r\n font-size: 16px;\r\n color: #ff5000;\r\n font-weight: bold;\r\n margin-bottom: 6px;\r\n}\r\n\r\n.product-footer {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-top: auto;\r\n}\r\n\r\n.product-sales {\r\n font-size: 12px;\r\n color: #999;\r\n}\r\n\r\n.action-btns {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n}\r\n\r\n.cart-btn, .remove-btn {\r\n width: 28px;\r\n height: 28px;\r\n border-radius: 14px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n margin-left: 8px; /* Replacement for gap */\r\n}\r\n\r\n.cart-btn {\r\n background-color: #ff5000;\r\n}\r\n\r\n.cart-icon {\r\n font-size: 14px;\r\n color: white;\r\n}\r\n\r\n.remove-btn {\r\n background-color: #f0f0f0;\r\n}\r\n\r\n.remove-icon {\r\n font-size: 14px;\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 @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<view class=\"product-info\">\r\n\t\t\t\t\t\t\t\t<text class=\"product-name\">{{ item.name }}</text>\r\n\t\t\t\t\t\t\t\t<view class=\"product-bottom\">\r\n\t\t\t\t\t\t\t\t\t<view class=\"product-price-row\">\r\n\t\t\t\t\t\t\t\t\t\t<text class=\"current-price\">¥{{ item.price }}</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</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}\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\tfootprints.value = []\r\n\t\t\t\tuni.removeStorageSync('footprints')\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\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\tuni.hideLoading()\r\n\t\t\t\t\r\n\t\t\t\tfootprints.value = footprints.value.filter((item): Boolean => item.selected !== true)\r\n\t\t\t\t\r\n\t\t\t\tconst dataToSave: FootprintSaveType[] = []\r\n\t\t\t\tfor (let i = 0; i < footprints.value.length; i++) {\r\n\t\t\t\t\tconst item = footprints.value[i]\r\n\t\t\t\t\tdataToSave.push({\r\n\t\t\t\t\t\tid: item.id,\r\n\t\t\t\t\t\tname: item.name,\r\n\t\t\t\t\t\tprice: item.price,\r\n\t\t\t\t\t\toriginal_price: item.original_price,\r\n\t\t\t\t\t\timage: item.image,\r\n\t\t\t\t\t\tsales: item.sales,\r\n\t\t\t\t\t\tshopId: item.shopId,\r\n\t\t\t\t\t\tshopName: item.shopName,\r\n\t\t\t\t\t\tviewTime: item.viewTime\r\n\t\t\t\t\t} as FootprintSaveType)\r\n\t\t\t\t}\r\n\t\t\t\tuni.setStorageSync('footprints', JSON.stringify(dataToSave))\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\tif (footprints.value.length === 0) {\r\n\t\t\t\t\tisEditMode.value = false\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 = (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/mall/consumer/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} 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}\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\tmargin-bottom: 10px;\r\n\tborder-bottom: none;\r\n\twidth: 48%;\r\n\tbackground-color: #fff;\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\tflex: 1;\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: 5px;\r\n\tmargin-right: 0;\r\n\tmargin-bottom: 8px;\r\n\tbackground-color: #f5f5f5;\r\n}\r\n\r\n.product-info {\r\n\tflex: 1;\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\tjustify-content: space-between;\r\n\tpadding: 0 4px;\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: 6px;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\tlines: 2;\r\n\theight: 40px;\r\n}\r\n\r\n.product-bottom {\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.product-price-row {\r\n\tdisplay: flex;\r\n\talign-items: flex-end;\r\n}\r\n\r\n.current-price {\r\n\tfont-size: 16px;\r\n\tcolor: #ff4757;\r\n\tfont-weight: bold;\r\n\tmargin-right: 0;\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","<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 = supabaseAddresses.map((item: SupabaseUserAddress): Address => {\r\n return {\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: '' // Supabase表没有label字段\r\n } as Address\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.item-actions {\r\n padding: 10px;\r\n border-left: 1px solid #f0f0f0;\r\n display: flex;\r\n flex-direction: column; /* 竖向排列图标 */\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.action-item {\r\n margin-bottom: 15px;\r\n}\r\n\r\n.action-item:last-child {\r\n margin-bottom: 0px;\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","<template>\r\n <view class=\"page-container\">\r\n <scroll-view class=\"address-edit-page\" direction=\"vertical\">\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=\"请填写收货人姓名\" />\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=\"请填写手机号码\" />\r\n </view>\r\n <view class=\"form-item\">\r\n <text class=\"label\">所在地区</text>\r\n <input class=\"input\" v-model=\"regionString\" placeholder=\"省市区县、乡镇等\" />\r\n </view>\r\n <view class=\"form-item\">\r\n <text class=\"label\">详细地址</text>\r\n <input class=\"input\" v-model=\"formData.detail\" placeholder=\"街道、楼牌号等\" />\r\n </view>\r\n </view>\r\n \r\n <view class=\"form-group\">\r\n <view class=\"form-item\">\r\n <text class=\"label\">智能填写</text>\r\n <textarea class=\"smart-textarea\" v-model=\"smartInput\" placeholder=\"粘贴姓名+电话+地址,自动识别填充\" @input=\"parseSmartInput\" maxlength=\"200\"></textarea>\r\n <text class=\"smart-tip\">示例:张三 13800138000 北京市朝阳区三里屯SOHO A座</text>\r\n </view>\r\n <view class=\"form-item\">\r\n <text class=\"label\">标签</text>\r\n <view class=\"tags-container\">\r\n <text \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 >{{ tag }}</text>\r\n </view>\r\n </view>\r\n <view class=\"form-item switch-item\">\r\n <text class=\"label\">设为默认收货地址</text>\r\n <switch :checked=\"formData.isDefault\" color=\"#ff5000\" @change=\"onSwitchChange\" />\r\n </view>\r\n </view>\r\n \r\n <view class=\"footer-btn\">\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 </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 display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.address-edit-page {\r\n flex: 1; /* Replace min-height: 100vh */\r\n}\r\n\r\n.form-group {\r\n background-color: white;\r\n margin-bottom: 15px;\r\n padding: 0 15px;\r\n}\r\n\r\n.form-item {\r\n display: flex;\r\n align-items: center;\r\n border-bottom: 1px solid #f5f5f5;\r\n padding: 15px 0;\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 color: #333;\r\n}\r\n\r\n.switch-item {\r\n justify-content: space-between;\r\n}\r\n\r\n.tags-container {\r\n flex: 1;\r\n display: flex;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.tag-item {\r\n font-size: 12px;\r\n color: #666;\r\n border: 1px solid #ddd;\r\n padding: 4px 12px;\r\n border-radius: 15px;\r\n margin-right: 10px;\r\n}\r\n\r\n.tag-item.active {\r\n background-color: #ff5000;\r\n color: white;\r\n border-color: #ff5000;\r\n}\r\n\r\n.footer-btn {\r\n margin-top: 30px;\r\n padding: 0 15px;\r\n}\r\n\r\n.save-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 margin-bottom: 15px;\r\n}\r\n\r\n.delete-btn {\r\n background-color: white;\r\n color: #333;\r\n border-radius: 25px;\r\n font-size: 16px;\r\n height: 44px;\r\n line-height: 44px;\r\n border: 1px solid #ddd;\r\n}\r\n</style>\r\n","<!-- 结算页面 -->\r\n<template>\r\n\t<view class=\"checkout-page\">\r\n\t\t<scroll-view class=\"checkout-content\" scroll-y>\r\n\t\t\t<!-- 收货地址 -->\r\n\t\t\t<view class=\"address-section\" @click=\"selectAddress\">\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 <text class=\"recipient\">{{ selectedAddress!!.recipient_name }}</text>\r\n <text class=\"phone\">{{ selectedAddress!!.phone }}</text>\r\n <view v-if=\"selectedAddress!!.is_default\" class=\"default-tag\">\r\n <text class=\"tag-text\">默认</text>\r\n </view>\r\n </view>\r\n <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\t<text class=\"no-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=\"products-section\">\r\n <!-- 调试信息 -->\r\n\t\t\t\t<view v-if=\"checkoutItems.length > 0\" class=\"debug-info\">\r\n\t\t\t\t\t<text class=\"debug-text\">共 {{ checkoutItems.length }} 件商品</text>\r\n\t\t\t\t</view>\r\n\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 <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 <text class=\"product-name\">{{ item.product_name }}</text>\r\n <text v-if=\"item.sku_specifications\" class=\"product-spec\">{{ formatSpecs(item.sku_specifications) }}</text>\r\n <view class=\"product-bottom\">\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 \r\n <!-- 店铺小计 -->\r\n <view class=\"shop-subtotal\">\r\n <text class=\"subtotal-label\">配送方式</text>\r\n <text class=\"subtotal-value\">快递 免邮</text>\r\n </view>\r\n <view class=\"shop-subtotal\">\r\n <text class=\"subtotal-text\">小计: </text>\r\n <text class=\"subtotal-price\">¥{{ getGroupTotal(group) }}</text>\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=\"delivery-section\">\r\n\t\t\t\t<text class=\"section-title\">配送方式</text>\r\n\t\t\t\t<view class=\"delivery-options\">\r\n\t\t\t\t\t<view v-for=\"option in deliveryOptions\" \r\n\t\t\t\t\t\t\t\t:key=\"option.id\" \r\n\t\t\t\t\t\t\t\t:class=\"['delivery-option', { selected: selectedDelivery === option.id }]\"\r\n\t\t\t\t\t\t\t\t@click=\"selectDelivery(option)\">\r\n\t\t\t\t\t\t<text class=\"option-name\">{{ option.name }}</text>\r\n\t\t\t\t\t\t<text class=\"option-price\">¥{{ option.price }}</text>\r\n\t\t\t\t\t\t<text v-if=\"selectedDelivery === option.id\" class=\"option-selected\">✓</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=\"coupon-section\" @click=\"selectCoupon\">\r\n\t\t\t\t<text class=\"section-title\">优惠券</text>\r\n\t\t\t\t<view class=\"coupon-info\">\r\n\t\t\t\t\t<text v-if=\"selectedCoupon != null\" class=\"coupon-selected\">{{ selectedCoupon.template?.name ?? '已选择优惠券 (¥' + (selectedCoupon.template?.discount_value ?? 0) + ')' }}</text>\r\n\t\t\t\t\t<text v-else class=\"coupon-placeholder\">选择优惠券</text>\r\n\t\t\t\t\t<text class=\"coupon-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=\"remark-section\">\r\n\t\t\t\t<text class=\"section-title\">买家留言</text>\r\n\t\t\t\t<textarea class=\"remark-input\" \r\n\t\t\t\t\t\t\t\t\tv-model=\"remark\" \r\n\t\t\t\t\t\t\t\t\tplaceholder=\"选填,请先和商家协商一致\"\r\n\t\t\t\t\t\t\t\t\tmaxlength=\"100\" />\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 价格明细 -->\r\n\t\t\t<view class=\"price-section\">\r\n\t\t\t\t<text class=\"section-title\">价格明细</text>\r\n\t\t\t\t<view class=\"price-detail\">\r\n\t\t\t\t\t<view class=\"price-row\">\r\n\t\t\t\t\t\t<text class=\"price-label\">商品总价</text>\r\n\t\t\t\t\t\t<text class=\"price-value\">¥{{ totalAmount.toFixed(2) }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"price-row\">\r\n\t\t\t\t\t\t<text class=\"price-label\">运费</text>\r\n\t\t\t\t\t\t<text class=\"price-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-row\">\r\n\t\t\t\t\t\t<text class=\"price-label\">优惠减免</text>\r\n\t\t\t\t\t\t<text class=\"price-value discount\">-¥{{ discountAmount.toFixed(2) }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"price-row total\">\r\n\t\t\t\t\t\t<text class=\"price-label\">应付金额</text>\r\n\t\t\t\t\t\t<text class=\"price-value total-price\">¥{{ actualAmount.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</scroll-view>\r\n\r\n\t\t<!-- 底部结算栏 -->\r\n\t\t<view class=\"bottom-bar\">\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\">¥{{ actualAmount.toFixed(2) }}</text>\r\n\t\t\t</view>\r\n\t\t\t<button class=\"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\" scroll-y>\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<text class=\"form-close\" @click=\"cancelNewAddress\">×</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t\r\n\t\t\t\t<scroll-view class=\"form-content\" scroll-y>\r\n\t\t\t\t\t<view class=\"form-item\">\r\n\t\t\t\t\t\t<text class=\"form-label\">收货人</text>\r\n\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 placeholder=\"请输入收货人姓名\" />\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\">\r\n\t\t\t\t\t\t<text class=\"form-label\">手机号</text>\r\n\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 placeholder=\"请输入手机号码\" type=\"number\" />\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\">\r\n\t\t\t\t\t<text class=\"form-label\">智能填写地址</text>\r\n\t\t\t\t\t<textarea class=\"form-textarea smart-address-input\" \r\n\t\t\t\t\t\t\t\t\t\tv-model=\"smartAddressInput\" \r\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"粘贴如:北京市朝阳区三里屯SOHO A座 张三 13800138000\"\r\n\t\t\t\t\t\t\t\t\t\t@input=\"parseSmartAddress\"\r\n\t\t\t\t\t\t\t\t\t\tmaxlength=\"200\" />\r\n\t\t\t\t\t<text class=\"smart-tip\">自动识别:地址+姓名+电话(支持粘贴文本)</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t\t\r\n\t\t\t\t\t<view class=\"form-item\">\r\n\t\t\t\t\t\t<text class=\"form-label\">所在地区</text>\r\n\t\t\t\t\t\t<view class=\"region-inputs\">\r\n\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 placeholder=\"省\" readonly />\r\n\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 placeholder=\"市\" readonly />\r\n\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 placeholder=\"区/县\" readonly />\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\">\r\n\t\t\t\t\t\t<text class=\"form-label\">详细地址</text>\r\n\t\t\t\t\t\t<textarea class=\"form-textarea\" v-model=\"newAddress.detail\" \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=\"100\" />\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 ? false : true\">\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-cancel-btn\" @click=\"cancelNewAddress\">取消</button>\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\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: 'standard', name: '快递配送', price: 8.00, description: '1-3天送达' },\r\n\t{ id: 'express', name: '加急配送', price: 15.00, description: '当天送达' }\r\n])\r\nconst selectedDelivery = ref<string>('standard')\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\tconst price = item.price\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\tconst price = item.price\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 = (items: any[]) => {\r\n\t\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\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\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, 'shop:', item.shop_id)\r\n\t\t})\r\n\t}\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// 获取当前用户ID\r\nfunction getCurrentUserId(): string {\r\n\tconst userId = supabaseService.getCurrentUserId()\r\n\treturn userId ?? ''\r\n}\r\n\r\n// 用户登录状态\r\nconst isLoggedIn = computed(() => {\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\nfunction loadFromLocalStorage(): 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\tprocessCheckoutItems(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\nonLoad((options: any) => {\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 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 loadFromLocalStorage()\r\n }\r\n \r\n loadDefaultAddress()\r\n loadAddressList()\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 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 product_name: item.product_name,\r\n product_image: item.product_image,\r\n specifications: item.sku_specifications\r\n })\r\n }\r\n groups.push({\r\n merchant_id: (group.merchant_id != null && group.merchant_id != '') ? group.merchant_id : group.shopId,\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 flex: 1; /* replace height: 100vh */\r\n background-color: #f5f5f5;\r\n}\r\n/* 顶部栏 */\r\n.checkout-header {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 15px;\r\n\tborder-bottom: 1px solid #e5e5e5;\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}\r\n\r\n.address-section {\r\n\tbackground-color: #ffffff;\r\n\tmargin-bottom: 10px;\r\n\tpadding: 20px 15px;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n}\r\n\r\n.address-info { flex: 1; }\r\n.address-header { display: flex; align-items: center; margin-bottom: 10px; }\r\n.recipient { font-size: 16px; font-weight: bold; color: #333333; margin-right: 15px; }\r\n.phone { font-size: 14px; color: #666666; margin-right: 10px; }\r\n.default-tag { background-color: #ff4757; padding: 2px 8px; border-radius: 10px; }\r\n.tag-text { color: #ffffff; font-size: 12px; }\r\n.address-detail { font-size: 14px; color: #333333; line-height: 1.4; }\r\n.no-address { flex: 1; display: flex; justify-content: space-between; align-items: center; }\r\n.no-address-text { font-size: 16px; color: #999999; }\r\n.no-address-arrow { color: #999999; font-size: 18px; }\r\n\r\n.products-section { background-color: #ffffff; margin-bottom: 10px; padding: 0 15px; }\r\n.debug-info { background-color: #f8f9fa; padding: 10px 15px; border-bottom: 1px solid #e5e5e5; margin-bottom: 10px; }\r\n .debug-text { font-size: 12px; color: #666; text-align: center; }\r\n.shop-group { background-color: #fff; margin: 10px 0; border-radius: 12px; padding: 10px; }\r\n.shop-header { display: flex; flex-direction: row; align-items: center; padding-bottom: 10px; border-bottom: 1px solid #f0f0f0; }\r\n.shop-icon { font-size: 18px; margin-right: 8px; }\r\n.shop-name { font-size: 16px; font-weight: bold; color: #333; }\r\n.shop-subtotal { display: flex; justify-content: flex-end; align-items: center; padding-top: 10px; margin-top: 5px; border-top: 1px dashed #f0f0f0; font-size: 14px; }\r\n.subtotal-label { color: #666; margin-right: 10px; }\r\n.subtotal-value { color: #333; }\r\n.subtotal-text { color: #333; margin-right: 5px; }\r\n.subtotal-price { color: #ff4757; font-weight: bold; font-size: 16px; }\r\n\r\n.product-item { display: flex; padding: 15px 0; border-bottom: 1px solid #f5f5f5; }\r\n.product-item:last-child { border-bottom: none; }\r\n.product-image { width: 80px; height: 80px; border-radius: 5px; margin-right: 15px; }\r\n.product-info { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }\r\n.product-name { font-size: 14px; color: #333333; line-height: 1.4; margin-bottom: 5px; lines: 2; text-overflow: ellipsis; overflow: hidden; }\r\n.product-spec { font-size: 12px; color: #999999; margin-bottom: 10px; }\r\n.product-bottom { display: flex; justify-content: space-between; align-items: center; }\r\n.product-price { font-size: 16px; color: #ff4757; font-weight: bold; }\r\n.product-quantity { font-size: 14px; color: #666666; }\r\n\r\n.delivery-section, .coupon-section, .remark-section, .price-section { background-color: #ffffff; margin-bottom: 10px; padding: 15px; }\r\n.section-title { font-size: 16px; font-weight: bold; color: #333333; margin-bottom: 15px; }\r\n\r\n.delivery-options { display: flex; flex-direction: column; }\r\n.delivery-option { display: flex; align-items: center; justify-content: space-between; padding: 15px; border: 1px solid #e5e5e5; border-radius: 8px; margin-bottom: 10px; }\r\n.delivery-option:last-child { margin-bottom: 0; }\r\n.delivery-option.selected { border-color: #007aff; background-color: #f0f8ff; }\r\n.option-name { font-size: 14px; color: #333333; }\r\n.option-price { font-size: 14px; color: #ff4757; font-weight: bold; }\r\n.option-selected { color: #007aff; font-size: 16px; }\r\n\r\n.coupon-info { display: flex; align-items: center; justify-content: space-between; padding: 15px; border: 1px solid #e5e5e5; border-radius: 8px; }\r\n.coupon-selected { font-size: 14px; color: #007aff; }\r\n.coupon-placeholder { font-size: 14px; color: #999999; }\r\n.coupon-arrow { color: #999999; font-size: 16px; }\r\n\r\n.remark-input { width: 100%; min-height: 40px; padding: 10px; border: 1px solid #e5e5e5; border-radius: 8px; font-size: 14px; color: #333333; }\r\n\r\n.price-detail { padding: 15px; background-color: #f8f9fa; border-radius: 8px; }\r\n.price-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; }\r\n.price-row.total { border-top: 1px solid #e5e5e5; margin-top: 8px; padding-top: 15px; }\r\n.price-label { font-size: 14px; color: #666666; }\r\n.price-value { font-size: 14px; color: #333333; }\r\n.price-value.discount { color: #4caf50; }\r\n.price-value.total-price { font-size: 18px; color: #ff4757; font-weight: bold; }\r\n\r\n.bottom-bar { background-color: #ffffff; padding: 15px; border-top: 1px solid #e5e5e5; display: flex; align-items: center; justify-content: space-between; }\r\n.price-summary { display: flex; align-items: flex-end; }\r\n.summary-label { font-size: 14px; color: #333333; margin-right: 5px; }\r\n.summary-price { font-size: 20px; color: #ff4757; font-weight: bold; }\r\n.submit-btn { background-color: #007aff; color: #ffffff; padding: 0 40px; height: 45px; border-radius: 22.5px; font-size: 16px; font-weight: bold; border: none; }\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; }\r\n.address-popup { background-color: #ffffff; width: 100%; height: 500px; border-radius: 20px 20px 0 0; display: flex; flex-direction: column; }\r\n.address-form-popup { background-color: #ffffff; width: 90%; max-width: 500px; height: 600px; border-radius: 12px; display: flex; flex-direction: column; }\r\n.confirm-popup { background-color: #ffffff; width: 80%; max-width: 320px; border-radius: 12px; overflow: hidden; }\r\n\r\n.popup-header, .form-header { padding: 20px 15px; border-bottom: 1px solid #e5e5e5; display: flex; align-items: center; justify-content: space-between; }\r\n.popup-title, .form-title { font-size: 18px; font-weight: bold; color: #333333; }\r\n.popup-close, .form-close { font-size: 24px; color: #999999; padding: 5px; }\r\n\r\n.address-list-container, .form-content { flex: 1; padding: 15px; }\r\n.popup-address-item { padding: 15px; margin-bottom: 10px; border: 1px solid #e5e5e5; border-radius: 8px; position: relative; }\r\n.popup-address-header { display: flex; align-items: center; margin-bottom: 10px; }\r\n.popup-address-name { font-size: 16px; font-weight: bold; color: #333333; margin-right: 15px; }\r\n.popup-address-phone { font-size: 14px; color: #666666; margin-right: 10px; }\r\n.popup-default-tag { background-color: #ff4757; padding: 2px 8px; border-radius: 10px; }\r\n.popup-tag-text { color: #ffffff; font-size: 12px; }\r\n .popup-address-detail { font-size: 14px; color: #333333; line-height: 1.4; }\r\n\r\n.popup-empty-address { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; }\r\n.popup-empty-icon { font-size: 60px; margin-bottom: 15px; }\r\n.popup-empty-text { font-size: 16px; color: #999999; }\r\n.popup-add-address-btn { background-color: #007aff; margin: 15px; padding: 15px; border-radius: 8px; display: flex; align-items: center; justify-content: center; }\r\n.popup-btn-icon { color: #ffffff; font-size: 20px; margin-right: 10px; }\r\n.popup-btn-text { color: #ffffff; font-size: 16px; font-weight: bold; }\r\n\r\n.form-item { margin-bottom: 20px; }\r\n.form-label { font-size: 14px; color: #333333; margin-bottom: 8px; }\r\n.form-input { width: 100%; padding: 12px; border: 1px solid #e5e5e5; border-radius: 8px; font-size: 14px; color: #333333; box-sizing: border-box; }\r\n.form-input-readonly { background-color: #f9f9f9; color: #666666; }\r\n.region-inputs { display: flex; justify-content: space-between; }\r\n.region-input { flex: 1; margin-right: 10px; }\r\n.region-input:last-child { margin-right: 0; }\r\n.form-textarea { width: 100%; min-height: 80px; padding: 12px; border: 1px solid #e5e5e5; border-radius: 8px; font-size: 14px; color: #333333; box-sizing: border-box; }\r\n.smart-address-input { min-height: 60px; }\r\n.smart-tip { font-size: 12px; color: #999999; margin-top: 5px; }\r\n.checkbox-item { margin-top: 20px; }\r\n.checkbox-wrapper { display: flex; align-items: center; }\r\n.checkbox { width: 20px; height: 20px; border: 1px solid #e5e5e5; border-radius: 4px; margin-right: 10px; display: flex; align-items: center; justify-content: center; }\r\n.checkbox.checked { background-color: #007aff; border-color: #007aff; }\r\n.checkbox-check { color: #ffffff; font-size: 14px; }\r\n.checkbox-label { font-size: 14px; color: #333333; }\r\n\r\n.form-buttons { display: flex; padding: 15px; border-top: 1px solid #e5e5e5; }\r\n.form-cancel-btn { flex: 1; background-color: #f5f5f5; color: #333333; padding: 12px; border-radius: 8px; font-size: 16px; border: none; margin-right: 10px; }\r\n.form-submit-btn { flex: 1; background-color: #007aff; color: #ffffff; padding: 12px; border-radius: 8px; font-size: 16px; border: none; }\r\n\r\n.confirm-header { padding: 20px 0 10px; text-align: center; }\r\n.confirm-title { font-size: 18px; font-weight: bold; color: #333333; }\r\n.confirm-content { padding: 0 20px 20px; text-align: center; }\r\n.confirm-message { font-size: 16px; color: #666666; }\r\n.confirm-buttons { display: flex; border-top: 1px solid #e5e5e5; }\r\n.confirm-btn { flex: 1; height: 50px; line-height: 50px; text-align: center; font-size: 16px; background-color: #ffffff; border: none; border-radius: 0; }\r\n.confirm-btn.cancel { color: #666666; border-right: 1px solid #e5e5e5; }\r\n.confirm-btn.confirm { color: #007aff; font-weight: bold; }\r\n</style>","<!-- 支付页面 -->\r\n<template>\r\n\t<view class=\"payment-page\">\r\n\t\t<view class=\"payment-content\">\r\n\t\t\t<!-- 价格明细 -->\r\n\t\t\t<view class=\"price-detail-section\">\r\n\t\t\t\t<text class=\"section-title\">价格明细</text>\r\n\t\t\t\t<view class=\"price-detail\">\r\n\t\t\t\t\t<view class=\"price-row\">\r\n\t\t\t\t\t\t<text class=\"price-label\">商品总价</text>\r\n\t\t\t\t\t\t<text class=\"price-value\">¥{{ productAmount.toFixed(2) }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"price-row\">\r\n\t\t\t\t\t\t<text class=\"price-label\">运费</text>\r\n\t\t\t\t\t\t<text class=\"price-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-row\">\r\n\t\t\t\t\t\t<text class=\"price-label\">优惠减免</text>\r\n\t\t\t\t\t\t<text class=\"price-value discount\">-¥{{ discountAmount.toFixed(2) }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"price-row total\">\r\n\t\t\t\t\t\t<text class=\"price-label\">应付金额</text>\r\n\t\t\t\t\t\t<text class=\"price-value total-price\">¥{{ amount.toFixed(2) }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<text class=\"order-no\">订单号: {{ orderNo }}</text>\r\n\t\t\t</view>\r\n\r\n\t\t\t<!-- 支付方式 -->\r\n\t\t\t<view class=\"methods-section\">\r\n\t\t\t\t<text class=\"section-title\">选择支付方式</text>\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\t:class=\"['method-item', { selected: selectedMethod === method.id }]\"\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<text class=\"method-icon\">{{ getMethodIcon(method.id) }}</text>\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\">{{ 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 v-if=\"selectedMethod === method.id\" class=\"method-selected\">\r\n\t\t\t\t\t\t\t<text class=\"selected-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</view>\r\n\r\n\t\t\t<!-- 余额支付 -->\r\n\t\t\t<view v-if=\"selectedMethod === 'balance' && userBalance > 0\" class=\"balance-section\">\r\n\t\t\t\t<view class=\"balance-info\">\r\n\t\t\t\t\t<text class=\"balance-label\">账户余额</text>\r\n\t\t\t\t\t<text class=\"balance-value\">¥{{ userBalance.toFixed(2) }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view v-if=\"userBalance < amount\" class=\"balance-tip\">\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\r\n\t\t\t<!-- 密码输入 -->\r\n\t\t\t<view v-if=\"showPassword\" class=\"password-section\">\r\n\t\t\t\t<text class=\"password-title\">请输入支付密码</text>\r\n\t\t\t\t<view class=\"password-input\">\r\n\t\t\t\t\t<view v-for=\"(_, index) in 6\" \r\n\t\t\t\t\t\t\t\t:key=\"index\" \r\n\t\t\t\t\t\t\t\tclass=\"password-dot\">\r\n\t\t\t\t\t\t<text v-if=\"password.length > index\" class=\"password-dot-text\">●</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<text class=\"forgot-password\" @click=\"forgotPassword\">忘记密码?</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<!-- 底部支付按钮 -->\r\n\t\t<view class=\"payment-bottom\">\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\t<!-- 密码键盘 -->\r\n\t\t<view v-if=\"showPassword\" class=\"password-keyboard\">\r\n\t\t\t<view class=\"keyboard-grid\">\r\n\t\t\t\t<view v-for=\"num in 9\" \r\n\t\t\t\t\t\t\t:key=\"num\" \r\n\t\t\t\t\t\t\tclass=\"keyboard-key\"\r\n\t\t\t\t\t\t\t@click=\"inputPassword(num.toString())\">\r\n\t\t\t\t\t<text class=\"key-text\">{{ num }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"keyboard-key\"></view>\r\n\t\t\t\t<view class=\"keyboard-key\" @click=\"inputPassword('0')\">\r\n\t\t\t\t\t<text class=\"key-text\">0</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"keyboard-key\" @click=\"deletePassword\">\r\n\t\t\t\t\t<text class=\"key-text\">⌫</text>\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, onMounted, watch, computed, onUnmounted } from 'vue'\r\nimport { onLoad, onBackPress } from '@dcloudio/uni-app'\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.getUserBalance()\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) {\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\t\t\t\t\torders.push(parsed[i] as Record<string, any>)\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 console.warn('在Storage (orders)中未找到订单:', 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 const orderObj = order as Record<string, any>\r\n orderNo.value = orderObj['order_no'] as string\r\n // Only update amount if not passed via options (options is priority for UI flow usually, but DB is source of truth)\r\n // But checking consistency is good\r\n const totalAmount = orderObj['total_amount']\r\n const dbAmount = totalAmount != null ? parseFloat(totalAmount.toString()) : 0\r\n if (dbAmount > 0) {\r\n amount.value = dbAmount\r\n }\r\n const items = orderObj['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\nonMounted(() => {\r\n\tconst pages = getCurrentPages()\r\n\tconst currentPage = pages[pages.length - 1]\r\n\tconst options = currentPage.options as Record<string, any>\r\n\t\r\n\tconst orderIdValue = options['orderId']\r\n\tif (orderIdValue != null) {\r\n\t\torderId.value = orderIdValue as string\r\n\t\tloadOrderInfo()\r\n\t}\r\n\t\r\n\tconst amountValue = options['amount']\r\n\tif (amountValue != null) {\r\n\t\tamount.value = parseFloat(amountValue.toString())\r\n\t}\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\t\tcalculatePriceDetails(amount.value)\r\n }\r\n\t\r\n\tloadPaymentMethods()\r\n\tloadUserBalance()\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\t\tconst userObj = userStore as Record<string, any>\r\n\t\tconst id = userObj['id']\r\n\t\tif (id != null) {\r\n\t\t\treturn id as string\r\n\t\t}\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 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\tshowPassword.value = method.id === 'balance' || method.id === 'bankcard'\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') {\r\n\t\tif (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\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\tflex: 1;\r\n\tbackground-color: #f5f5f5;\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}\r\n\r\n/* 价格明细部分 */\r\n.price-detail-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 20px 15px;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.price-detail {\r\n\tpadding: 15px;\r\n\tbackground-color: #f8f9fa;\r\n\tborder-radius: 8px;\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.price-row {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tpadding: 8px 0;\r\n}\r\n\r\n.price-row.total {\r\n\tborder-top: 1px solid #e5e5e5;\r\n\tmargin-top: 8px;\r\n\tpadding-top: 15px;\r\n}\r\n\r\n.price-label {\r\n\tfont-size: 14px;\r\n\tcolor: #666666;\r\n}\r\n\r\n.price-value {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n}\r\n\r\n.price-value.discount {\r\n\tcolor: #4caf50;\r\n}\r\n\r\n.price-value.total-price {\r\n\tfont-size: 18px;\r\n\tcolor: #ff4757;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.order-no {\r\n\t/* display: block; */\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n\ttext-align: center;\r\n}\r\n\r\n.methods-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 20px 15px;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.section-title {\r\n\t/* display: block; */\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.method-list {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n\t/* gap: 10px; */\r\n}\r\n\r\n.method-item {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: space-between;\r\n\tpadding: 15px;\r\n\tborder: 1px solid #e5e5e5;\r\n\tborder-radius: 8px;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.method-item.selected {\r\n\tborder-color: #007aff;\r\n\tbackground-color: #f0f8ff;\r\n}\r\n\r\n.method-left {\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n}\r\n\r\n.method-icon {\r\n\tfont-size: 24px;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.method-info {\r\n\tdisplay: flex;\r\n\tflex-direction: column;\r\n}\r\n\r\n.method-name {\r\n\tfont-size: 16px;\r\n\tfont-weight: bold;\r\n\tcolor: #333333;\r\n\tmargin-bottom: 5px;\r\n}\r\n\r\n.method-desc {\r\n\tfont-size: 12px;\r\n\tcolor: #999999;\r\n}\r\n\r\n.method-selected {\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tborder-radius: 12px;\r\n\tbackground-color: #007aff;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.selected-icon {\r\n\tcolor: #ffffff;\r\n\tfont-size: 14px;\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.password-section {\r\n\tbackground-color: #ffffff;\r\n\tpadding: 30px 15px;\r\n\ttext-align: center;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.password-title {\r\n\t/* display: block; */\r\n\tfont-size: 16px;\r\n\tcolor: #333333;\r\n\tmargin-bottom: 30px;\r\n}\r\n\r\n.password-input {\r\n\tdisplay: flex;\r\n\tjustify-content: center;\r\n\t/* gap: 15px; */\r\n\tmargin-bottom: 20px;\r\n}\r\n\r\n.password-dot {\r\n\twidth: 12px;\r\n\theight: 12px;\r\n\tborder-radius: 6px;\r\n\tbackground-color: #333333;\r\n\tdisplay: flex;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tmargin: 0 7.5px;\r\n}\r\n\r\n.password-dot-text {\r\n\tcolor: #ffffff;\r\n\tfont-size: 8px;\r\n}\r\n\r\n.forgot-password {\r\n\tcolor: #007aff;\r\n\tfont-size: 14px;\r\n}\r\n\r\n.payment-bottom {\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.price-summary {\r\n\tdisplay: flex;\r\n\talign-items: flex-end;\r\n}\r\n\r\n.summary-label {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tmargin-right: 5px;\r\n}\r\n\r\n.summary-price {\r\n\tfont-size: 20px;\r\n\tcolor: #ff4757;\r\n\tfont-weight: bold;\r\n}\r\n\r\n.pay-btn {\r\n\tbackground-color: #007aff;\r\n\tcolor: #ffffff;\r\n\tpadding: 0 40px;\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.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","<!-- pages/mall/consumer/orders.uvue -->\r\n<template>\r\n <view class=\"orders-page\">\r\n <!-- 顶部标题栏 -->\r\n <view class=\"orders-header\">\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\">\r\n <scroll-view scroll-x class=\"tab-scroll\" :show-scrollbar=\"false\">\r\n <view class=\"tab-container\">\r\n <view \r\n v-for=\"tab in orderTabs\" \r\n :key=\"tab.id\"\r\n :class=\"['tab-item', { 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 scroll-y \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 >\r\n <!-- 订单头部 -->\r\n <view class=\"order-header\">\r\n <text class=\"order-no\">订单号:{{ order.order_no }}</text>\r\n <text :class=\"['order-status', getStatusClass(order.status)]\">\r\n {{ getStatusText(order.status) }}\r\n </text>\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 @click=\"navigateToProduct(product)\"\r\n >\r\n <image \r\n class=\"product-image\" \r\n :src=\"product.image\" \r\n mode=\"aspectFill\"\r\n />\r\n <view class=\"product-info\">\r\n <text class=\"product-name\">{{ product.name }}</text>\r\n <text class=\"product-spec\">{{ product.spec }}</text>\r\n <view class=\"product-footer\">\r\n <text class=\"product-price\">¥{{ product.price }}</text>\r\n <text class=\"product-quantity\">×{{ product.quantity }}</text>\r\n </view>\r\n </view>\r\n </view>\r\n </view>\r\n \r\n <!-- 订单信息 -->\r\n <view class=\"order-info\">\r\n <view class=\"info-row\">\r\n <text class=\"info-label\">商品合计</text>\r\n <text class=\"info-value\">¥{{ order.product_amount }}</text>\r\n </view>\r\n <view class=\"info-row\">\r\n <text class=\"info-label\">运费</text>\r\n <text class=\"info-value\">¥{{ order.shipping_fee }}</text>\r\n </view>\r\n <view class=\"info-row total\">\r\n <text class=\"info-label\">实付款</text>\r\n <text class=\"info-value total-price\">¥{{ order.total_amount }}</text>\r\n </view>\r\n </view>\r\n \r\n <!-- 订单操作 -->\r\n <view class=\"order-actions\">\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=\"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=\"viewLogistics(order.id)\">查看物流</button>\r\n <button class=\"action-btn confirm\" @click=\"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=\"goReview(order)\">评价</button>\r\n <button class=\"action-btn refund\" @click.stop=\"onApplyRefund(order)\">申请售后</button>\r\n <button class=\"action-btn repurchase\" @click=\"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=\"viewOrderDetail(order.id)\">查看详情</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\nonBackPress((options) => {\r\n if (options.from === 'navigateBack') {\r\n const pages = getCurrentPages()\r\n if (pages.length > 1) {\r\n const prevPage = pages[pages.length - 2]\r\n // 如果上一页是登录页,则重定向到个人中心\r\n if (prevPage.route.includes('login')) {\r\n uni.redirectTo({\r\n url: '/pages/mall/consumer/profile'\r\n })\r\n return true\r\n }\r\n }\r\n }\r\n return false\r\n})\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 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 searchKeyword = ref<string>('')\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: 'cancelled', name: '已取消', count: 0 }\r\n])\r\n\r\n// Removed Mock Data\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 return 0\r\n}\r\n\r\n// 辅助函数:解析规格文本\r\nconst parseSpecText = (specs: any): string => {\r\n if (specs == null) return ''\r\n if (typeof specs === 'string') return specs\r\n // 对于对象类型,尝试转为JSON字符串或简单处理\r\n try {\r\n return JSON.stringify(specs)\r\n } catch (e) {\r\n return ''\r\n }\r\n}\r\n\r\n// 辅助函数:更新标签计数\r\nconst updateTabsCounts = (allOrders: any[]) => {\r\n // 直接重新赋值整个数组\r\n const tabsData = orderTabs.value\r\n // 计算各状态数量\r\n const countAll = allOrders.length\r\n const countPending = allOrders.filter((o: any) => {\r\n const obj = o as Record<string, any>\r\n return obj['status'] === 1\r\n }).length\r\n const countShipping = allOrders.filter((o: any) => {\r\n const obj = o as Record<string, any>\r\n return obj['status'] === 2\r\n }).length\r\n const countDelivering = allOrders.filter((o: any) => {\r\n const obj = o as Record<string, any>\r\n return obj['status'] === 3\r\n }).length\r\n const countCompleted = allOrders.filter((o: any) => {\r\n const obj = o as Record<string, any>\r\n return obj['status'] === 4\r\n }).length\r\n const countCancelled = allOrders.filter((o: any) => {\r\n const obj = o as Record<string, any>\r\n return obj['status'] === 5\r\n }).length\r\n \r\n // 更新数组元素\r\n const tabsArr = tabsData as any[]\r\n const tab0 = tabsArr[0] as Record<string, any>\r\n tab0['count'] = countAll\r\n const tab1 = tabsArr[1] as Record<string, any>\r\n tab1['count'] = countPending\r\n const tab2 = tabsArr[2] as Record<string, any>\r\n tab2['count'] = countShipping\r\n const tab3 = tabsArr[3] as Record<string, any>\r\n tab3['count'] = countDelivering\r\n const tab4 = tabsArr[4] as Record<string, any>\r\n tab4['count'] = countCompleted\r\n const tab5 = tabsArr[5] as Record<string, any>\r\n tab5['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 {\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 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 \r\n // Map to View Model\r\n const mappedOrders: any[] = []\r\n for (let i = 0; i < fetchedOrders.length; i++) {\r\n const order = fetchedOrders[i]\r\n const orderObj = order as Record<string, any>\r\n const items = orderObj['ml_order_items'] as any[]\r\n const productsList: any[] = []\r\n \r\n if (items != null) {\r\n for (let j = 0; j < items.length; j++) {\r\n const item = items[j]\r\n const itemObj = item as Record<string, any>\r\n const specRaw = itemObj['specifications']\r\n const specText = specRaw != null ? parseSpecText(specRaw) : ''\r\n productsList.push({\r\n id: itemObj['product_id'],\r\n name: itemObj['product_name'],\r\n price: itemObj['price'],\r\n image: itemObj['image_url'] ?? '/static/default-product.png',\r\n spec: specText,\r\n quantity: itemObj['quantity']\r\n })\r\n }\r\n }\r\n \r\n mappedOrders.push({\r\n id: orderObj['id'],\r\n order_no: orderObj['order_no'],\r\n status: orderObj['order_status'],\r\n create_time: orderObj['created_at'],\r\n product_amount: orderObj['product_amount'] ?? 0,\r\n shipping_fee: orderObj['shipping_fee'] ?? 0,\r\n total_amount: orderObj['total_amount'] ?? orderObj['paid_amount'] ?? 0,\r\n products: productsList\r\n })\r\n }\r\n\r\n // Sort by created_at desc\r\n mappedOrders.sort((a: any, b: any) => {\r\n const aObj = a as Record<string, any>\r\n const bObj = b as Record<string, any>\r\n const timeA = new Date(aObj['create_time'] as string).getTime()\r\n const timeB = new Date(bObj['create_time'] as string).getTime()\r\n return timeB - timeA\r\n })\r\n\r\n // 将 mappedOrders 转换为 OrderItem[] 类型\r\n const typedOrders: OrderItem[] = []\r\n for (let i = 0; i < mappedOrders.length; i++) {\r\n const mo = mappedOrders[i] as Record<string, any>\r\n typedOrders.push({\r\n id: mo['id'] as string,\r\n order_no: mo['order_no'] as string,\r\n status: mo['status'] as number,\r\n create_time: mo['create_time'] as string,\r\n product_amount: mo['product_amount'] as number,\r\n shipping_fee: mo['shipping_fee'] as number,\r\n total_amount: mo['total_amount'] as number,\r\n products: mo['products'] as OrderProduct[]\r\n })\r\n }\r\n allOrdersList.value = typedOrders\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 } 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 if (options['status'] != null) {\r\n const status = options['status'] as string\r\n if (['all', 'pending', 'shipping', 'delivering', 'completed', 'cancelled'].includes(status)) {\r\n activeTab.value = status\r\n }\r\n }\r\n if (options['type'] != null) {\r\n const type = options['type'] 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 = 'all' // 申请售后默认显示全部\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 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 return 'status-unknown'\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 // 这里应该是实际的API调用\r\n uni.showToast({\r\n title: '订单已取消',\r\n icon: 'success'\r\n })\r\n \r\n // 更新订单状态\r\n const index = orders.value.findIndex((o: any) => {\r\n const obj = o as Record<string, any>\r\n return obj['id'] === orderId\r\n })\r\n if (index !== -1) {\r\n const orderObj = orders.value[index] as Record<string, any>\r\n orderObj['status'] = 5\r\n orders.value = [...orders.value]\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 = (orderId: string) => {\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: any) => {\r\n const orderObj = order as Record<string, any>\r\n const products = orderObj['products'] as any[]\r\n const productIds = products.map((p: any) => {\r\n const pObj = p as Record<string, any>\r\n const pid = pObj['id']\r\n return pid != null ? pid as string : ''\r\n }).join(',')\r\n const orderId = orderObj['id'] as string\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 // 更新本地状态\r\n const index = orders.value.findIndex((o: any) => {\r\n const obj = o as Record<string, any>\r\n return obj['id'] === orderId\r\n })\r\n if (index !== -1) {\r\n const orderObj = orders.value[index] as Record<string, any>\r\n orderObj['status'] = 4\r\n orders.value = [...orders.value]\r\n }\r\n\r\n // 跳转到评价页面\r\n setTimeout(() => {\r\n const order = orders.value.find((o: any) => {\r\n const obj = o as Record<string, any>\r\n return obj['id'] === orderId\r\n })\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: any) => {\r\n uni.showModal({\r\n title: '再次购买',\r\n content: '确定要将这些商品加入购物车吗?',\r\n success: (res) => {\r\n if (res.confirm) {\r\n // 这里应该是实际的API调用\r\n uni.showToast({\r\n title: '已加入购物车',\r\n icon: 'success'\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: any) => {\r\n const orderObj = order as Record<string, any>\r\n const orderId = orderObj['id']\r\n uni.navigateTo({\r\n url: `/pages/mall/consumer/apply-refund?orderId=${orderId}`\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: any) => {\r\n const productObj = product as Record<string, any>\r\n const productId = productObj['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/mall/consumer/index' })\r\n}\r\n</script>\r\n\r\n<style>\r\n.orders-page {\r\n width: 100%;\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n background-color: #f5f5f5;\r\n}\r\n\r\n/* 头部 */\r\n.orders-header {\r\n background-color: white;\r\n padding: 15px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-bottom: 1px solid #eee;\r\n /* position: sticky; removed */\r\n /* top: 0; removed */\r\n z-index: 10;\r\n}\r\n\r\n.header-search.full-width {\r\n display: flex;\r\n align-items: center;\r\n position: relative;\r\n width: 100%;\r\n}\r\n\r\n.search-input {\r\n flex: 1;\r\n height: 36px;\r\n border: 1px solid #ddd;\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: #333;\r\n width: 100%;\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 {\r\n background-color: #ffffff;\r\n border-bottom: 1px solid #e5e5e5;\r\n /* position: sticky; removed */\r\n /* top: 50px; removed */\r\n z-index: 10;\r\n}\r\n\r\n.tab-scroll {\r\n width: 100%;\r\n white-space: nowrap;\r\n}\r\n\r\n.tab-container {\r\n display: flex;\r\n flex-direction: row;\r\n padding: 0 10px;\r\n /* width: max-content; removed */\r\n /* min-width: 100%; removed */\r\n}\r\n\r\n.tab-item {\r\n /* 移除 flex: 1,改为自适应宽度或固定最小宽度 */\r\n padding: 15px 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}\r\n\r\n.tab-item.active {\r\n color: #ff5000;\r\n font-weight: bold;\r\n}\r\n\r\n.tab-item.active::after {\r\n /* content: ''; removed */\r\n/* content: '';\r\n position: absolute;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n height: 2px;\r\n background-color: #ff5000; */\r\n}\r\n\r\n.active-indicator {\r\n position: absolute;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n height: 2px;\r\n background-color: #ff5000;\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}\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 text-align: center;\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}\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}\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/* 订单商品 */\r\n.order-products {\r\n padding: 15px;\r\n}\r\n\r\n.order-product {\r\n display: flex;\r\n margin-bottom: 15px;\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: 15px;\r\n}\r\n\r\n.product-info {\r\n flex: 1;\r\n}\r\n\r\n.product-name {\r\n font-size: 15px;\r\n color: #333;\r\n margin-bottom: 5px;\r\n line-height: 1.4;\r\n}\r\n\r\n.product-spec {\r\n font-size: 13px;\r\n color: #999;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.product-footer {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\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: 14px;\r\n color: #666;\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.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 (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<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-icon\">\r\n <text class=\"status-emoji\">{{ getStatusIcon() }}</text>\r\n </view>\r\n <view class=\"status-info\">\r\n <text class=\"status-text\">{{ getStatusText() }}</text>\r\n <text class=\"status-desc\">{{ getStatusDesc() }}</text>\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-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 <text class=\"courier-label\">物流单号:</text>\r\n <text class=\"courier-value\">{{ deliveryInfo?.tracking_no ?? '' }}</text>\r\n <text class=\"copy-btn\" @click=\"copyText(deliveryInfo?.tracking_no ?? '')\">复制</text>\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 <!-- 底部操作 -->\r\n <view class=\"bottom-actions\" v-if=\"order != null\">\r\n <view class=\"action-left\">\r\n <view class=\"service-btn\" @click=\"contactService\">\r\n <text class=\"service-icon\">🎧</text>\r\n <text>客服</text>\r\n </view>\r\n </view>\r\n <view class=\"action-right\">\r\n <button v-if=\"order?.order_status === 1\" class=\"btn primary\" @click=\"payOrder\">立即支付</button>\r\n <button v-if=\"order?.order_status === 1\" class=\"btn\" @click=\"cancelOrder\">取消订单</button>\r\n \r\n <button v-if=\"order?.order_status === 2\" class=\"btn\" @click=\"remindDelivery\">提醒发货</button>\r\n <button v-if=\"order?.order_status === 2\" class=\"btn\" @click=\"applyRefund\">申请退款</button>\r\n \r\n <button v-if=\"order?.order_status === 3\" class=\"btn primary\" @click=\"confirmReceive\">确认收货</button>\r\n <button v-if=\"order?.order_status === 3\" class=\"btn\" @click=\"viewLogistics\">查看物流</button>\r\n \r\n <button v-if=\"order?.order_status === 4\" class=\"btn primary\" @click=\"goToReview\">评价</button>\r\n <button v-if=\"order?.order_status === 4\" class=\"btn\" @click=\"rePurchase\">再次购买</button>\r\n <button v-if=\"order?.order_status === 4\" class=\"btn\" @click=\"applyAfterSales\">申请售后</button>\r\n \r\n <button v-if=\"order?.order_status === 5\" class=\"btn\" @click=\"rePurchase\">重新购买</button>\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 } 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\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}\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 // 兼容简单的字符串地址和对象地址\r\n if (typeof addr === 'string') return addr\r\n const addrObj = addr as Record<string, any>\r\n if (addrObj['address'] != null) return addrObj['address'] as string\r\n return ((addrObj['province'] as string) ?? '') + ((addrObj['city'] as string) ?? '') + ((addrObj['district'] as string) ?? '') + ((addrObj['detail'] as string) ?? (addrObj['address_detail'] as string) ?? '')\r\n}\r\n\r\nconst getSpecText = (specs: any): string => {\r\n if (specs == null) return ''\r\n if (typeof specs === 'string') return specs\r\n // 简化处理:直接返回字符串形式\r\n return JSON.stringify(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 const result = await supa\r\n .from('ml_shops')\r\n .select('shop_name')\r\n .eq('merchant_id', merchantId)\r\n .single()\r\n const resultObj = result as Record<string, any>\r\n const resultData = resultObj['data']\r\n if (resultData != null) {\r\n const dataObj = resultData as Record<string, any>\r\n shopName.value = dataObj['shop_name'] as string\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 if (data != null) {\r\n const dataObj = data as Record<string, any>\r\n order.value = data as OrderType\r\n const items = dataObj['ml_order_items']\r\n orderItems.value = items != null ? (items as OrderItemType[]) : []\r\n deliveryAddress.value = dataObj['shipping_address'] as AddressType\r\n \r\n // 获取店铺信息\r\n const merchantId = dataObj['merchant_id'] as string\r\n if (merchantId != null && merchantId != '') {\r\n loadShopInfo(merchantId)\r\n }\r\n } else {\r\n uni.showToast({ title: '订单不存在', icon: 'none' })\r\n }\r\n } catch (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 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 const result = await supa.from('ml_orders').update({ order_status: 5 }).eq('id', orderId.value)\r\n const resultObj = result as Record<string, any>\r\n const resultError = resultObj['error']\r\n if(resultError == null) {\r\n if (order.value != null) {\r\n order.value.order_status = 5\r\n }\r\n uni.showToast({ title: '订单已取消' })\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 = () => {\r\n uni.showToast({ title: '已提醒商家尽快发货' })\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 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}\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 const orderData = order.value as any\r\n const success = await supabaseService.rePurchase(orderData)\r\n uni.hideLoading()\r\n if (success) {\r\n uni.showToast({ title: '已加入购物车' })\r\n setTimeout(() => {\r\n uni.switchTab({ url: '/pages/mall/consumer/cart' })\r\n }, 1000)\r\n } else {\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n }\r\n}\r\n\r\nconst doApplyRefund = async (reason: string) => {\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}\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 // uni.navigateTo({ url: `/pages/mall/shop/index?id=${order.value.merchant_id}` })\r\n uni.showToast({ title: '进入店铺: ' + shopName.value })\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\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(to right, #ff9000, #ff5000);\r\n padding: 20px 25px;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.status-emoji {\r\n font-size: 32px;\r\n margin-right: 15px;\r\n}\r\n\r\n.status-text {\r\n font-size: 18px;\r\n font-weight: bold;\r\n}\r\n\r\n.status-desc {\r\n font-size: 12px;\r\n opacity: 0.9;\r\n margin-top: 5px;\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: 10px;\r\n padding-top: 10px;\r\n border-top: 1px solid #eee;\r\n font-size: 13px;\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.copy-btn {\r\n margin-left: 10px;\r\n color: #ff5000;\r\n font-size: 12px;\r\n border: 1px solid #ff5000;\r\n padding: 1px 6px;\r\n border-radius: 10px;\r\n}\r\n\r\n/* 店铺与商品 */\r\n.shop-header {\r\n display: flex;\r\n align-items: center;\r\n margin-bottom: 15px;\r\n padding-bottom: 10px;\r\n border-bottom: 1px solid #f5f5f5;\r\n}\r\n\r\n.shop-icon {\r\n margin-right: 5px;\r\n}\r\n\r\n.shop-name {\r\n font-size: 14px;\r\n font-weight: bold;\r\n flex: 1;\r\n}\r\n\r\n.arrow-right {\r\n color: #999;\r\n}\r\n\r\n.product-item {\r\n display: flex;\r\n margin-bottom: 15px;\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: 80px;\r\n height: 80px;\r\n border-radius: 6px;\r\n margin-right: 10px;\r\n background-color: #f9f9f9;\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}\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; /* uvue specific */\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 justify-content: space-between;\r\n align-items: center;\r\n margin-top: 5px;\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-row {\r\n display: flex;\r\n justify-content: space-between;\r\n margin-bottom: 8px;\r\n font-size: 13px;\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: 18px;\r\n font-weight: bold;\r\n}\r\n\r\n/* 底部按钮 */\r\n.bottom-actions {\r\n background-color: #ffffff;\r\n padding: 10px 15px;\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n box-shadow: 0 -2px 10px rgba(0,0,0,0.05);\r\n padding-bottom: 30px;\r\n}\r\n\r\n.action-left {\r\n display: flex;\r\n}\r\n\r\n.service-btn {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n font-size: 10px;\r\n color: #666;\r\n background-color: transparent;\r\n line-height: 1.2;\r\n}\r\n\r\n.service-icon {\r\n font-size: 18px;\r\n margin-bottom: 2px;\r\n}\r\n\r\n.action-right {\r\n display: flex;\r\n}\r\n\r\n/* Add margin to buttons inside action-right for spacing */\r\n.action-right .btn {\r\n margin-left: 10px;\r\n}\r\n\r\n.btn {\r\n margin: 0;\r\n padding: 0 15px;\r\n height: 32px;\r\n line-height: 30px;\r\n font-size: 13px;\r\n border-radius: 16px;\r\n background: #ffffff;\r\n border: 1px solid #cccccc;\r\n color: #666;\r\n}\r\n\r\n.btn.primary {\r\n border-color: #ff5000;\r\n color: #ff5000;\r\n background-color: #fff0ec;\r\n}\r\n\r\n/* 状态样式 */\r\n.status-4 .status-text { /* Completed */ }\r\n</style>","<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\n\r\nconst orderId = ref('')\r\nconst productImage = ref('/static/product1.jpg')\r\nconst logisticsStatus = ref('运输中')\r\nconst courierName = ref('顺丰速运')\r\nconst courierPhone = ref('95338')\r\nconst trackingNo = ref('SF1234567890')\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 desc: '【深圳市】快件已到达 深圳南山集散中心',\r\n time: '2024-01-26 14:30:00'\r\n },\r\n {\r\n desc: '【广州市】快件已从 广州转运中心 发出,准备发往 深圳南山集散中心',\r\n time: '2024-01-26 09:20:00'\r\n },\r\n {\r\n desc: '【广州市】快件已到达 广州转运中心',\r\n time: '2024-01-25 22:15:00'\r\n },\r\n {\r\n desc: '【杭州市】商家已发货',\r\n time: '2024-01-25 18:00:00'\r\n }\r\n])\r\n\r\nonMounted(() => {\r\n const pages = getCurrentPages()\r\n const currentPage = pages[pages.length - 1]\r\n const options = currentPage.options\r\n if (options != null) {\r\n const optionsObj = options as UTSJSONObject\r\n const orderIdValue = optionsObj.getString('orderId')\r\n if (orderIdValue != null) {\r\n orderId.value = orderIdValue\r\n // 这里可以根据orderId去请求真实的物流信息\r\n }\r\n }\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<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<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=\"review-content\" scroll-y>\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?.order_no }}</text>\r\n\t\t\t\t<text class=\"order-time\">下单时间: {{ formatTime(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\" 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 || isSubmitting }\"\r\n\t\t\t\t\t\t\t@click=\"submitReview\">\r\n\t\t\t\t<text v-if=\"!isSubmitting\" 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 supa from '@/components/supadb/aksupainstance.uts'\r\n\r\ntype OrderItemType = {\r\n\tid: string\r\n\tproduct_id: string\r\n\tproduct_name: string\r\n\tproduct_image: string\r\n\tsku_specifications: any\r\n\tprice: number\r\n\tquantity: 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<any>({})\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({\r\n\tdescription: 5,\r\n\tlogistics: 5,\r\n\tservice: 5\r\n})\r\nconst isSubmitting = ref<boolean>(false)\r\n\r\n// 计算属性\r\nconst canSubmit = computed(() => {\r\n\t// 检查是否所有商品都已评分\r\n\tif (ratings.value.length === 0) return false\r\n\treturn ratings.value.every(rating => rating > 0)\r\n})\r\n\r\n// 生命周期\r\nonLoad((options: any) => {\r\n\tconst optObj = (options instanceof UTSJSONObject) ? (options as UTSJSONObject) : (JSON.parse(JSON.stringify(options ?? {})) as UTSJSONObject)\r\n\torderId.value = optObj.getString('orderId') ?? ''\r\n\tif (orderId.value != '') loadOrderData()\r\n})\r\n\r\n// 加载订单数据\r\nconst loadOrderData = async () => {\r\n\ttry {\r\n\t\tconst { data: orderData, error: orderError } = await supa\r\n\t\t\t.from('ml_orders')\r\n\t\t\t.select('*')\r\n\t\t\t.eq('id', orderId.value)\r\n\t\t\t.single()\r\n\r\n\t\tif (orderError !== null) {\r\n\t\t\tconsole.error('加载订单失败:', orderError)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\torder.value = orderData\r\n\r\n\t\t// 加载订单商品\r\n\t\tconst { data: itemsData, error: itemsError } = await supa\r\n\t\t\t.from('ml_order_items')\r\n\t\t\t.select(`\r\n\t\t\t\t*,\r\n\t\t\t\tproduct:product_id(images)\r\n\t\t\t`)\r\n\t\t\t.eq('order_id', orderId.value)\r\n\r\n\t\tif (itemsError !== null) {\r\n\t\t\tconsole.error('加载订单商品失败:', itemsError)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\torderItems.value = (itemsData ?? []).map((item: any) => ({\r\n\t\t\t...item,\r\n\t\t\tproduct_image: item.product?.images?.[0] ?? '/static/default-product.png'\r\n\t\t}))\r\n\r\n\t\t// 初始化评分和内容数组\r\n\t\tconst count = orderItems.value.length\r\n\t\tratings.value = new Array(count).fill(5)\r\n\t\tcontents.value = new Array(count).fill('')\r\n\t\timages.value = new Array(count).fill([])\r\n\r\n\t\t// 加载商家信息\r\n\t\tif (order.value.merchant_id) {\r\n\t\t\tconst { data: merchantData, error: merchantError } = await supa\r\n\t\t\t\t.from('ml_shops')\r\n\t\t\t\t.select('id, shop_name, rating')\r\n\t\t\t\t.eq('id', order.value.merchant_id)\r\n\t\t\t\t.single()\r\n\r\n\t\t\tif (merchantError == null) {\r\n\t\t\t\tmerchant.value = merchantData\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}\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\n// 获取规格文本\r\nconst getSpecText = (specs: any): string => {\r\n\tif (specs == null) return ''\r\n\tif (typeof specs === 'object') {\r\n\t\treturn Object.keys(specs)\r\n\t\t\t.map(key => `${key}: ${specs[key]}`)\r\n\t\t\t.join('; ')\r\n\t}\r\n\treturn String(specs)\r\n}\r\n\r\n// 获取评分文本\r\nconst getRatingText = (rating: number): string => {\r\n\tconst texts = ['非常差', '差', '一般', '好', '非常好']\r\n\treturn texts[rating - 1] ?? '未评价'\r\n}\r\n\r\n// 设置商品评分\r\nconst setRating = (index: number, rating: number) => {\r\n\tratings.value[index] = rating\r\n\tratings.value = [...ratings.value]\r\n}\r\n\r\n// 设置商家评分\r\nconst setMerchantRating = (type: keyof typeof merchantRating.value, rating: number) => {\r\n\tmerchantRating.value[type] = rating\r\n\tmerchantRating.value = { ...merchantRating.value }\r\n}\r\n\r\n// 切换匿名\r\nconst toggleAnonymous = (event: any) => {\r\n\tanonymous.value = event.detail.value\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 tempFiles = res.tempFilePaths\r\n\t\t\t\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\t// 这里应该调用真实的上传接口\r\n\t\t\t\timages.value[index].push(...tempFiles)\r\n\t\t\t\timages.value = [...images.value]\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\timages.value = [...images.value]\r\n}\r\n\r\n// 提交评价\r\nconst submitReview = async () => {\r\n\tif (!canSubmit.value || 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\t// 提交商品评价\r\n\t\tconst productReviews = orderItems.value.map((item, index) => ({\r\n\t\t\tuser_id: userId,\r\n\t\t\tproduct_id: item.product_id,\r\n\t\t\torder_id: orderId.value,\r\n\t\t\trating: ratings.value[index],\r\n\t\t\tcontent: contents.value[index] != '' ? contents.value[index] : '',\r\n\t\t\timages: images.value[index],\r\n\t\t\tis_anonymous: anonymous.value\r\n\t\t}))\r\n\r\n\t\tconst { error: reviewsError } = await supa\r\n\t\t\t.from('ml_product_reviews')\r\n\t\t\t.insert(productReviews)\r\n\r\n\t\tif (reviewsError !== null) {\r\n\t\t\tthrow reviewsError\r\n\t\t}\r\n\r\n\t\t// 提交店铺评价\r\n\t\tif (merchant.value) {\r\n\t\t\tconst merchantReview = {\r\n\t\t\t\tuser_id: userId,\r\n\t\t\t\tshop_id: merchant.value.id,\r\n\t\t\t\torder_id: orderId.value,\r\n\t\t\t\tdescription_rating: merchantRating.value.description,\r\n\t\t\t\tlogistics_rating: merchantRating.value.logistics,\r\n\t\t\t\tservice_rating: merchantRating.value.service\r\n\t\t\t}\r\n\r\n\t\t\tconst { error: merchantError } = await supa\r\n\t\t\t\t.from('ml_shop_reviews')\r\n\t\t\t\t.insert(merchantReview)\r\n\r\n\t\t\tif (merchantError !== null) {\r\n\t\t\t\tconsole.error('提交店铺评价失败:', merchantError)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// 更新订单状态为已评价 (如果需要标记为已评价,可以在这里处理,例如 status=5 implies Reviewed or keeping at 4)\r\n\t\t// 这里保持为 4 (Completed)\r\n\t\tconst { error: orderError } = await supa\r\n\t\t\t.from('ml_orders')\r\n\t\t\t.update({ order_status: 4 }) \r\n\t\t\t.eq('id', orderId.value)\r\n\r\n\t\tif (orderError !== null) {\r\n\t\t\tconsole.error('更新订单状态失败:', orderError)\r\n\t\t}\r\n\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\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// 获取当前用户ID\r\nconst getCurrentUserId = (): string => {\r\n\tconst userStore = uni.getStorageSync('userInfo')\r\n\treturn userStore?.getString('id') ?? ''\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.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: 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.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-review:last-child {\r\n\tborder-bottom: none;\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\talign-items: center;\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.rating-label {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\r\n\tmargin-right: 15px;\r\n}\r\n\r\n.rating-stars {\r\n\tdisplay: flex;\r\n\t/* gap: 10px; removed */\r\n}\r\n\r\n.rating-stars.small {\r\n\t/* gap: 5px; removed */\r\n}\r\n\r\n.star-icon {\r\n\tfont-size: 24px;\r\n\tcolor: #cccccc;\r\n\tmargin-right: 10px;\r\n}\r\n\r\n.star-icon.active {\r\n\tcolor: #ffa726;\r\n}\r\n\r\n.rating-text {\r\n\tmargin-left: 15px;\r\n\tfont-size: 14px;\r\n\tcolor: #666666;\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\nmargin-right: 10px;\r\n\tmargin-bottom: 10px;\r\n\t\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: 15px;\r\n\tmargin-bottom: 10px;\r\n}\r\n\r\n.section-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: 15px;\r\n}\r\n\r\n.merchant-rating {\r\n\tdisplay: flex;\r\n\tjustify-content: space-between;\r\n\talign-items: center;\r\n\tmargin-bottom: 15px;\r\n}\r\n\r\n.rating-item {\r\n\tfont-size: 14px;\r\n\tcolor: #333333;\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.tip-item:last-child {\r\n\tmargin-bottom: 0;\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<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\" scroll-y @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?.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\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\n// 监听标签页变化\r\nwatch(activeTab, () => {\r\n\tresetData()\r\n\tloadRefunds()\r\n})\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n\tloadRefunds()\r\n\tloadTabCounts()\r\n})\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\n// 加载售后数据\r\nconst loadRefunds = async (loadMore: boolean = false) => {\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 // Map data to UI structure (RefundType)\r\n\t\tconst newRefunds = rawData.map((item: any): RefundType => {\r\n const orderObj: any = item['order'] ?? {}\r\n const dbItems: any[] = (orderObj['ml_order_items'] as any[]) ?? []\r\n const uiItems = dbItems.map((di: any) : RefundOrderItem => ({\r\n id: di['id'] ?? '',\r\n product_name: di['product_name'] ?? '',\r\n sku_specifications: di['specifications'],\r\n price: 0,\r\n quantity: di['quantity'] ?? 1,\r\n product: { images: [di['image_url'] ?? '/static/default-product.png'] }\r\n }))\r\n \r\n return {\r\n id: item['id'],\r\n user_id: item['user_id'],\r\n order_id: item['order_id'],\r\n refund_no: item['refund_no'],\r\n refund_type: item['refund_type'],\r\n refund_reason: item['refund_reason'],\r\n refund_amount: Number(item['refund_amount']),\r\n status: item['status'],\r\n // Handle missing timeline by defaulting or leaving empty\r\n status_history: (item['status_history'] as RefundStatusHistoryItem[]) ?? [], \r\n created_at: item.created_at,\r\n order: {\r\n id: item.order_id,\r\n order_no: orderObj.order_no,\r\n created_at: orderObj.created_at,\r\n order_items: uiItems\r\n }\r\n } as RefundType\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\n// 加载标签页计数\r\nconst loadTabCounts = async () => {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId == '') return\r\n\r\n\ttry {\r\n\t\tconst { count, error } = await supa\r\n\t\t\t.from('refunds')\r\n\t\t\t.select('*', { count: 'exact' })\r\n\t\t\t.eq('user_id', userId)\r\n\t\t\t.in('status', [1, 2])\r\n\r\n\t\tif (error !== null) {\r\n\t\t\tconsole.error('加载计数失败:', error)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\ttabCounts.value.processing = count ?? 0\r\n\t} catch (err) {\r\n\t\tconsole.error('加载计数异常:', err)\r\n\t}\r\n}\r\n\r\n// 获取当前用户ID\r\nconst getCurrentUserId = (): string => {\r\n\tconst userStore = uni.getStorageSync('userInfo')\r\n\treturn userStore['id'] ?? ''\r\n}\r\n\r\n// 获取状态文本\r\nconst getStatusText = (status: number): string => {\r\n\tconst statusMap: Record<number, string> = {\r\n\t\t1: '待处理',\r\n\t\t2: '处理中',\r\n\t\t3: '已完成',\r\n\t\t4: '已取消',\r\n\t\t5: '已拒绝'\r\n\t}\r\n\treturn statusMap[status] ?? '未知状态'\r\n}\r\n\r\n// 获取状态样式类\r\nconst getStatusClass = (status: number): string => {\r\n\tconst classMap: Record<number, string> = {\r\n\t\t1: 'status-pending',\r\n\t\t2: 'status-processing',\r\n\t\t3: 'status-completed',\r\n\t\t4: 'status-cancelled',\r\n\t\t5: 'status-rejected'\r\n\t}\r\n\treturn classMap[status] ?? '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\n// 获取时间线步骤\r\nconst getTimelineSteps = (refund: RefundType): Array<any> => {\r\n\tconst steps = [\r\n\t\t{ status: 0, title: '提交申请', time: refund.created_at },\r\n\t\t{ status: 1, title: '商家处理', time: '' },\r\n\t\t{ status: 3, title: '退款完成', time: '' }\r\n\t]\r\n\t\r\n\t// 如果有状态历史,更新时间和描述\r\n\tif (refund.status_history) {\r\n\t\trefund.status_history.forEach(history => {\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\t// 标记当前状态\r\n\treturn steps.map((step, index) => ({\r\n\t\t...step,\r\n\t\tactive: index === getCurrentStepIndex(refund.status),\r\n\t\tcompleted: index < getCurrentStepIndex(refund.status)\r\n\t}))\r\n}\r\n\r\n// 获取当前步骤索引\r\nconst getCurrentStepIndex = (status: number): number => {\r\n\tswitch (status) {\r\n\t\tcase 1: return 0 // 待处理\r\n\t\tcase 2: return 1 // 处理中\r\n\t\tcase 3: return 2 // 已完成\r\n\t\tcase 4: return 0 // 已取消\r\n\t\tcase 5: return 1 // 已拒绝\r\n\t\tdefault: return 0\r\n\t}\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\n// 取消退款申请\r\nconst cancelRefund = (refund: RefundType) => {\r\n\tuni.showModal({\r\n\t\ttitle: '取消申请',\r\n\t\tcontent: '确定要取消这个退款申请吗?',\r\n\t\tsuccess: async (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst { error } = await supa\r\n\t\t\t\t\t\t.from('refunds')\r\n\t\t\t\t\t\t.update({ \r\n\t\t\t\t\t\t\tstatus: 4, // 已取消\r\n\t\t\t\t\t\t\tstatus_history: [...(refund.status_history ?? []), {\r\n\t\t\t\t\t\t\t\tstatus: 4,\r\n\t\t\t\t\t\t\t\tremark: '用户取消申请',\r\n\t\t\t\t\t\t\t\tcreated_at: new Date().toISOString()\r\n\t\t\t\t\t\t\t}]\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.eq('id', refund.id)\r\n\r\n\t\t\t\t\tif (error !== null) {\r\n\t\t\t\t\t\tthrow error\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\trefund.status = 4\r\n\t\t\t\t\tloadTabCounts() // 重新加载计数\r\n\t\t\t\t\t\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\t\r\n\t\t\t\t} catch (err) {\r\n\t\t\t\t\tconsole.error('取消退款失败:', err)\r\n\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\ttitle: '取消失败',\r\n\t\t\t\t\t\ticon: 'none'\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 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\n// 删除记录\r\nconst deleteRefund = (refund: RefundType) => {\r\n\tuni.showModal({\r\n\t\ttitle: '删除记录',\r\n\t\tcontent: '确定要删除这个售后记录吗?',\r\n\t\tsuccess: async (res) => {\r\n\t\t\tif (res.confirm) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst { error } = await supa\r\n\t\t\t\t\t\t.from('refunds')\r\n\t\t\t\t\t\t.delete()\r\n\t\t\t\t\t\t.eq('id', refund.id)\r\n\r\n\t\t\t\t\tif (error !== null) {\r\n\t\t\t\t\t\tthrow error\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tconst index = refunds.value.findIndex(r => r.id === refund.id)\r\n\t\t\t\t\tif (index !== -1) {\r\n\t\t\t\t\t\trefunds.value.splice(index, 1)\r\n\t\t\t\t\t\trefunds.value = [...refunds.value]\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\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\t\r\n\t\t\t\t} catch (err) {\r\n\t\t\t\t\tconsole.error('删除记录失败:', err)\r\n\t\t\t\t\tuni.showToast({\r\n\t\t\t\t\t\ttitle: '删除失败',\r\n\t\t\t\t\t\ticon: 'none'\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 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: 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.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: #ffa726;\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: #ffa726;\r\n\tcolor: #ffa726;\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>","<!-- 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 <text class=\"chat-title\">{{ headerTitle }}</text>\r\n <text class=\"chat-status\">在线</text>\r\n </view>\r\n <view class=\"header-actions\">\r\n <text class=\"action-icon\" @click=\"showMoreActions\">⋮</text>\r\n </view>\r\n </view>\r\n \r\n <!-- 聊天内容 -->\r\n <scroll-view \r\n scroll-y \r\n class=\"chat-content\"\r\n :scroll-into-view=\"scrollToView\"\r\n scroll-with-animation\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=\"'msg-' + message.id\"\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=\"/static/icons/shop-default.png\" \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\">\r\n <text 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 <text 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/avatar-default.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\ttype: string\r\n\tcontent: string\r\n\ttime: string\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 navPaddingTop = ref<string>('30px') // 默认值,包含状态栏高度+原有内边距\r\nlet realtimeChannel: AkSupaRealtimeChannel | null = null\r\n\r\n// 模拟表情列表\r\nconst emojiList = ['😊', '😂', '🤣', '😍', '😘', '🥰', '😭', '😡', '👍', '👏', '🙏', '🎉', '❤️', '🔥', '⭐']\r\n\r\nfunction scrollToBottom(): void {\r\n nextTick(() => {\r\n if (messages.value.length > 0) {\r\n const lastMsgId = messages.value[messages.value.length - 1].id\r\n scrollToView.value = 'msg-' + lastMsgId\r\n }\r\n })\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\tconst filter = ({\r\n\t\tevent: 'INSERT',\r\n\t\tschema: 'public',\r\n\t\ttable: 'ml_chat_messages'\r\n\t} as UTSJSONObject)\r\n\r\n\trealtimeChannel = supa.channel('public:ml_chat_messages')\r\n\t\t.on('postgres_changes', filter, (payload: any) => {\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) return\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\r\n\t\t\tif (senderId === currentUserId.value) {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tif (receiverId === currentUserId.value) {\r\n\t\t\t\tif (merchantId.value != '' && senderId !== merchantId.value) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\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\tconst incomingMsg: UiChatMessage = {\r\n\t\t\t\t\tid: newMsg.getString('id') ?? Date.now().toString(),\r\n\t\t\t\t\ttype: 'received',\r\n\t\t\t\t\tcontent: newMsg.getString('content') ?? '',\r\n\t\t\t\t\ttime: timeStr\r\n\t\t\t\t}\r\n\r\n\t\t\t\tmessages.value.push(incomingMsg)\r\n\t\t\t\tscrollToBottom()\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})\r\n}\r\n\r\nasync function loadChatHistory(): Promise<void> {\r\n\tlet rawMsgs: ChatMessage[] = []\r\n\r\n\tif (merchantId.value != '') {\r\n\t\trawMsgs = await supabaseService.getChatMessages(merchantId.value)\r\n\t} else {\r\n\t\tconsole.warn(\"No merchant ID provided for chat\")\r\n\t\treturn\r\n\t}\r\n\r\n\tmessages.value = rawMsgs.reverse().map((m: ChatMessage): UiChatMessage => {\r\n\t\tconst date = new Date(m.created_at ?? new Date().toISOString())\r\n\t\tconst timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`\r\n\r\n\t\tconst sender = m.sender_id ?? ''\r\n\t\tconst msgType = (currentUserId.value != '' && sender == currentUserId.value) ? 'sent' : 'received'\r\n\t\tconst rawId = (m.id ?? '').toString()\r\n\t\tconst msgId = rawId !== '' ? rawId : Date.now().toString()\r\n\t\treturn {\r\n\t\t\tid: msgId,\r\n\t\t\ttype: msgType,\r\n\t\t\tcontent: m.content ?? '',\r\n\t\t\ttime: timeStr\r\n\t\t}\r\n\t})\r\n\r\n\tsetTimeout(() => {\r\n\t\tscrollToBottom()\r\n\t}, 100)\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\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 const newMessage: UiChatMessage = {\r\n id: Date.now().toString(),\r\n type: 'sent',\r\n content: content,\r\n time: getCurrentTime()\r\n }\r\n \r\n messages.value.push(newMessage)\r\n inputMessage.value = ''\r\n \r\n // 滚动到底部\r\n scrollToBottom()\r\n\r\n // 发送到 Supabase\r\n if (merchantId.value != '') {\r\n const success = await supabaseService.sendMessage(merchantId.value, content)\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// 模拟客服回复 (已禁用,改用 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 inputFocus.value = true\r\n}\r\n\r\n// 显示表情选择器\r\nfunction showEmojiPicker(): void {\r\n showEmoji.value = !showEmoji.value\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('选择图片:', res.tempFilePaths)\r\n // 这里可以处理图片上传\r\n }\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}\r\n\r\n/* 聊天头部 */\r\n.chat-header {\r\n background-color: white;\r\n padding: 10px 15px;\r\n /* padding-top 由内联样式控制 */\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}\r\n\r\n.header-back {\r\n width: 40px;\r\n}\r\n\r\n.back-icon {\r\n font-size: 24px;\r\n color: #333;\r\n}\r\n\r\n.header-info {\r\n flex: 1;\r\n text-align: 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 text-align: right;\r\n}\r\n\r\n/* 聊天内容区 */\r\n.chat-content {\r\n flex: 1;\r\n padding: 15px;\r\n padding-bottom: 70px; /* 为输入区留出空间 */\r\n}\r\n\r\n/* 系统消息 */\r\n.message-item.system {\r\n text-align: 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\r\n}\r\n\r\n/* 时间分割线 */\r\n.time-divider {\r\n text-align: 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}\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 /* max-width removed */\r\n}\r\n\r\n.sender-name {\r\n font-size: 12px;\r\n color: #999;\r\n margin-bottom: 5px;\r\n}\r\n\r\n.message-bubble {\r\n background-color: white;\r\n padding: 10px 15px;\r\n border-radius: 18px;\r\n position: relative;\r\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);\r\n /* max-width wrap removed */\r\n}\r\n\r\n.message-bubble.me {\r\n background-color: #95ec69;\r\n border-bottom-right-radius: 4px;\r\n}\r\n\r\n.message-bubble-not-me .message-content {\r\n border-bottom-left-radius: 4px;\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}\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 position: fixed;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n z-index: 100;\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 /* overflow-y removed */\r\n position: fixed;\r\n bottom: 60px;\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","<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\" 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<FollowedShop[]>([])\r\nconst loading = ref(true)\r\n\r\nonMounted(() => {\r\n loadFollowedShops()\r\n})\r\n\r\nconst loadFollowedShops = async () => {\r\n loading.value = true\r\n const userId = supabaseService.getCurrentUserId()\r\n if (!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 // res is array of { id, user_id, shop_id, ml_shops: {...} }\r\n \r\n const list: FollowedShop[] = []\r\n res.forEach((item: any) => {\r\n const shopData = item['ml_shops'] as any\r\n if (shopData != null) {\r\n list.push({\r\n id: shopData['id'] as string, // Shop ID\r\n merchant_id: shopData['merchant_id'] as string, \r\n shop_name: shopData['shop_name'] as string,\r\n shop_logo: shopData['shop_logo'] as string | null,\r\n description: shopData['description'] as string | null,\r\n rating_avg: (shopData['rating_avg'] != null) ? (shopData['rating_avg'] as number) : 5.0,\r\n total_sales: (shopData['total_sales'] != null) ? (shopData['total_sales'] as number) : 0\r\n })\r\n }\r\n })\r\n \r\n shops.value = list\r\n loading.value = false\r\n}\r\n\r\nconst unfollow = async (shop: FollowedShop) => {\r\n const userId = supabaseService.getCurrentUserId()\r\n if (!userId) return\r\n\r\n uni.showModal({\r\n title: '提示',\r\n content: '确定取消关注该店铺吗?',\r\n success: async (res) => {\r\n if (res.confirm) {\r\n const success = await supabaseService.unfollowShop(shop.id, userId)\r\n if (success) {\r\n uni.showToast({ title: '已取消', icon: 'none' })\r\n loadFollowedShops() // Reload list\r\n } else {\r\n uni.showToast({ title: '操作失败', icon: 'none' })\r\n }\r\n }\r\n }\r\n })\r\n}\r\n\r\nconst goToShop = (shop: FollowedShop) => {\r\n // Navigate using the Shop ID or Merchant ID?\r\n // shop-detail uses merchantId parameter but we patched it to handle ShopID too.\r\n // Let's prefer passing the raw ID we have.\r\n // If shop.id is UUID of shop, and shop.merchant_id is User UUID.\r\n // Since shop-detail handles both, passing shop.id (which is ml_shops.id) is fine?\r\n // Wait, shop-detail logic: 1. getShopByMerchantId(id) [tries merchant_id then id].\r\n // So passing shop.id is safer if merchant_id is not unique or confusing.\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 = () => {\r\n uni.switchTab({ url: '/pages/mall/consumer/index' })\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>","<template>\r\n <view class=\"points-page\">\r\n <view class=\"points-header\">\r\n <view class=\"points-info\">\r\n <text class=\"points-label\">当前积分</text>\r\n <text class=\"points-value\">{{ totalPoints }}</text>\r\n </view>\r\n <view class=\"points-actions\">\r\n <button class=\"exchange-btn\" @click=\"handleExchange\">积分兑换</button>\r\n </view>\r\n </view>\r\n \r\n <view class=\"records-section\">\r\n <text class=\"section-title\">积分明细</text>\r\n \r\n <view v-if=\"loading\" class=\"loading-state\">\r\n <text>加载中...</text>\r\n </view>\r\n \r\n <view v-else-if=\"records.length === 0\" class=\"empty-state\">\r\n <text class=\"empty-text\">暂无积分记录</text>\r\n </view>\r\n \r\n <view v-else class=\"record-list\">\r\n <view v-for=\"item in records\" :key=\"item.id\" class=\"record-item\">\r\n <view class=\"record-left\">\r\n <text class=\"record-title\">{{ item.description ?? getTypeText(item.type) }}</text>\r\n <text class=\"record-time\">{{ formatTime(item.created_at) }}</text>\r\n </view>\r\n <view class=\"record-right\">\r\n <text class=\"record-amount\" :class=\"{ positive: item.points > 0, negative: item.points < 0 }\">\r\n {{ item.points > 0 ? '+' : '' }}{{ item.points }}\r\n </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 { supabaseService } from '@/utils/supabaseService.uts'\r\n\r\ntype PointRecord = {\r\n id: string\r\n user_id: string\r\n points: number\r\n type: string\r\n description: string\r\n created_at: string\r\n}\r\n\r\nconst totalPoints = ref<number>(0)\r\nconst records = ref<PointRecord[]>([])\r\nconst loading = ref<boolean>(true)\r\n\r\n// 函数必须按调用顺序定义:先定义被调用的函数\r\nconst loadPoints = async (): Promise<void> => {\r\n try {\r\n const points = await supabaseService.getUserPoints()\r\n totalPoints.value = points\r\n } catch (e) {\r\n console.error('获取积分失败', e)\r\n }\r\n}\r\n\r\nconst loadRecords = async (): Promise<void> => {\r\n try {\r\n const list = await supabaseService.getPointRecords()\r\n records.value = list as PointRecord[]\r\n } catch (e) {\r\n console.error('获取积分记录失败', e)\r\n }\r\n}\r\n\r\n// loadData 在 loadPoints 和 loadRecords 之后定义\r\nconst loadData = async (): Promise<void> => {\r\n loading.value = true\r\n await loadPoints()\r\n await loadRecords()\r\n loading.value = false\r\n}\r\n\r\nonMounted(() => {\r\n loadData()\r\n})\r\n\r\nconst handleExchange = () => {\r\n uni.showToast({\r\n title: '积分商城开发中',\r\n icon: 'none'\r\n })\r\n}\r\n\r\nconst getTypeText = (type: string): string => {\r\n // 不支持 Record<string, string>,使用 if-else\r\n if (type == 'signin') {\r\n return '每日签到'\r\n } else if (type == 'shopping') {\r\n return '购物奖励'\r\n } else if (type == 'redeem') {\r\n return '积分兑换'\r\n } else if (type == 'admin') {\r\n return '系统调整'\r\n } else if (type == 'register') {\r\n return '注册赠送'\r\n } else {\r\n return '积分变动'\r\n }\r\n}\r\n\r\nconst formatTime = (timeStr: string): string => {\r\n if (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 hh = date.getHours().toString().padStart(2, '0')\r\n const mm = date.getMinutes().toString().padStart(2, '0')\r\n return `${y}-${m}-${d} ${hh}:${mm}`\r\n}\r\n</script>\r\n\r\n<style>\r\n.points-page {\r\n flex: 1;\r\n}\r\n\r\n.points-header {\r\n background-color: #ff5000;\r\n padding: 30px 20px;\r\n color: white;\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.points-info {\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.points-label {\r\n font-size: 14px;\r\n opacity: 0.9;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.points-value {\r\n font-size: 36px;\r\n font-weight: bold;\r\n}\r\n\r\n.exchange-btn {\r\n background-color: rgba(255,255,255,0.2);\r\n color: white;\r\n border: 1px solid rgba(255,255,255,0.4);\r\n font-size: 14px;\r\n border-radius: 20px;\r\n padding: 0 15px;\r\n height: 32px;\r\n line-height: 32px;\r\n}\r\n\r\n.records-section {\r\n background-color: white;\r\n margin-top: 10px;\r\n padding: 0 16px;\r\n min-height: 500px;\r\n}\r\n\r\n.section-title {\r\n font-size: 16px;\r\n font-weight: bold;\r\n padding: 16px 0;\r\n border-bottom: 1px solid #f0f0f0;\r\n display: flex;\r\n}\r\n\r\n.record-item {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 16px 0;\r\n border-bottom: 1px solid #f9f9f9;\r\n}\r\n\r\n.record-left {\r\n display: flex;\r\n flex-direction: column;\r\n margin-bottom: 4px;\r\n font-size: 15px;\r\n color: #333;\r\n}\r\n\r\n.record-time {\r\n font-size: 12px;\r\n color: #999;\r\n}\r\n\r\n.record-amount {\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #333;\r\n}\r\n\r\n.record-amount.positive {\r\n color: #ff5000;\r\n}\r\n\r\n.record-amount.negative {\r\n color: #333;\r\n}\r\n\r\n.empty-state {\r\n padding: 40px 0;\r\n display: flex;\r\n justify-content: center;\r\n}\r\n\r\n.empty-text {\r\n color: #999;\r\n font-size: 14px;\r\n}\r\n</style>","<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\" scroll-y>\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<RedPacket[]>([])\r\n\r\nconst filteredPackets = computed((): RedPacket[] => {\r\n if (currentTab.value === 0) {\r\n return packets.value.filter((p:RedPacket):boolean => p.status === 0)\r\n } else {\r\n return packets.value.filter((p:RedPacket):boolean => p.status !== 0)\r\n }\r\n})\r\n\r\nonMounted(() => {\r\n loadData()\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 packets.value = rawList.map((item: any): RedPacket => {\r\n let id = ''\r\n let amount = 0\r\n let name = ''\r\n let status = 0\r\n let expireAt = ''\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 name = item.getString('name') ?? ''\r\n status = item.getNumber('status') ?? 0\r\n expireAt = item.getString('expire_at') ?? ''\r\n createdAt = item.getString('created_at') ?? ''\r\n } else {\r\n id = (item['id'] as string) ?? ''\r\n amount = (item['amount'] as number) ?? 0\r\n name = (item['name'] as string) ?? ''\r\n status = (item['status'] as number) ?? 0\r\n expireAt = (item['expire_at'] as string) ?? ''\r\n createdAt = (item['created_at'] as string) ?? ''\r\n }\r\n\r\n return {\r\n id: id,\r\n user_id: '',\r\n amount: amount,\r\n name: name,\r\n status: status,\r\n expire_at: expireAt,\r\n created_at: createdAt\r\n } as RedPacket\r\n })\r\n } catch (e) {\r\n console.error(e)\r\n } finally {\r\n loading.value = false\r\n }\r\n}\r\n\r\nconst usePacket = (item: RedPacket) => {\r\n uni.switchTab({\r\n url: '/pages/mall/consumer/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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"} |