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

1 line
1.0 MiB
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"version":3,"sources":["../../../../../../../HBuilderX/HBuilderX/plugins/uniapp-cli-vite/node_modules/@dcloudio/uni-console/src/runtime/app/socket.ts","uni_modules/ak-req/ak-req.uts","../../../../../../../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// 之所以又写了一份是因为外层的socketconnectSocket的时候必须传入multiple:true\n// 但是android又不能传入目前代码里又不能写条件编译之类的。\nexport function initRuntimeSocket(\n hosts: string,\n port: string,\n id: string\n): Promise<SocketTask | null> {\n if (hosts == '' || port == '' || id == '') return Promise.resolve(null)\n return hosts\n .split(',')\n .reduce<Promise<SocketTask | null>>(\n (\n promise: Promise<SocketTask | null>,\n host: string\n ): Promise<SocketTask | null> => {\n return promise.then((socket): Promise<SocketTask | null> => {\n if (socket != null) return Promise.resolve(socket)\n return tryConnectSocket(host, port, id)\n })\n },\n Promise.resolve(null)\n )\n}\n\nconst SOCKET_TIMEOUT = 500\nfunction tryConnectSocket(\n host: string,\n port: string,\n id: string\n): Promise<SocketTask | null> {\n return new Promise((resolve, reject) => {\n const socket = uni.connectSocket({\n url: `ws://${host}:${port}/${id}`,\n fail() {\n resolve(null)\n },\n })\n const timer = setTimeout(() => {\n // @ts-expect-error\n socket.close({\n code: 1006,\n reason: 'connect timeout',\n } as CloseSocketOptions)\n resolve(null)\n }, SOCKET_TIMEOUT)\n\n socket.onOpen((e) => {\n clearTimeout(timer)\n resolve(socket)\n })\n socket.onClose((e) => {\n clearTimeout(timer)\n resolve(null)\n })\n socket.onError((e) => {\n clearTimeout(timer)\n resolve(null)\n })\n })\n}\n","import { AkReqUploadOptions, AkReqOptions, AkReqResponse, AkReqError } from './interface.uts';\r\nimport { SUPA_URL, 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)}&timestamp=${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)}&timestamp=${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":";;;;;;;;;;;;;;+BAgCuB,iBAAA;+BCRP,kBAAA;+BAoBV,qBAAA;+BAkGE,WAAA;+BA5HF,kBAAA;+BAmNE,aAAA;+BA6CW,cAAA;;;;;;AD/QZ,IAAS,kBACd,OAAO,MAAM,EACb,MAAM,MAAM,EACZ,IAAI,MAAM,GACT,WAAQ,aAAmB;IAC5B,IAAI,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QAAI,OAAO,WAAQ,OAAO,CAAC,IAAI;;IACtE,OAAO,MACJ,KAAK,CAAC,KACN,MAAM,CAAC,WAAQ,cACd,IACE,SAAS,WAAQ,cACjB,MAAM,MAAM,GACX,WAAQ,aAAsB;QAC/B,OAAO,QAAQ,IAAI,CAAC,IAAC,SAAS,WAAQ,aAAsB;YAC1D,IAAI,OAAO,EAAE,CAAC,IAAI;gBAAE,OAAO,WAAQ,OAAO,CAAC;;YAC3C,OAAO,iBAAiB,MAAM,MAAM;QACtC;;IACF;MACA,WAAQ,OAAO,CAAC,IAAI;AAE1B;AAEA,IAAM,yBAAiB,GAAG;AAC1B,IAAS,iBACP,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,IAAI,MAAM,GACT,WAAQ,aAAmB;IAC5B,OAAO,AAAI,WAAQ,IAAC,SAAS,OAAW;QACtC,IAAM,SAAS,AAAI,uCACjB,MAAK,AAAC,UAAO,OAAK,MAAG,OAAK,MAAG,IAC7B,OAAA,OAAO;YACL,QAAQ,IAAI;QACd;;QAEF,IAAM,QAAQ,WAAW,KAAM;YAE7B,OAAO,KAAK,CAGP,mBAFH,OAAM,IAAI,EACV,SAAQ;YAEV,QAAQ,IAAI;QACd;UAAG;QAEH,OAAO,MAAM,CAAC,IAAC,EAAM;YACnB,aAAa;YACb,QAAQ;QACV;;QACA,OAAO,OAAO,CAAC,IAAC,EAAM;YACpB,aAAa;YACb,QAAQ,IAAI;QACd;;QACA,OAAO,OAAO,CAAC,IAAC,EAAM;YACpB,aAAa;YACb,QAAQ,IAAI;QACd;;IACF;;AACF;AE1DO,IAAS,4BAA4B,WAAQ,OAAO,EAAE;IAC3D,IAAM,OAAO,MAAM;IACnB,IAAM,MAAM,MAAM;IAClB,IAAM,IAAI,MAAM;IAChB,IAAI,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QAAI,OAAO,WAAQ,OAAO,CAAC,KAAK;;IACvE,IAAI,YAAY,cAAoB,IAAI;IACxC,4BACE,OAAI,MAAM,CAAI;QACZ;IACF;MACA,IAAC,MAAM,MAAM,CAAK;QAChB,YAAY,KAEP,yBADH,OAAA;IAEJ;;IAEF,OAAO,WAAQ,OAAO,GACnB,IAAI,CAAC,OAAI,WAAQ,OAAO,EAAK;QAC5B,OAAO,kBAAkB,OAAO,MAAM,IAAI,IAAI,CAAC,IAAC,SAAS,OAAO,CAAI;YAClE,IAAI,OAAO,EAAE,CAAC,IAAI,EAAE;gBAClB,OAAO,KAAK;YACd;YACA,aAAa;YACb,OAAO,IAAI;QACb;;IACF;MACC,OAAK,CAAC,OAAI,OAAO,CAAI;QACpB,OAAO,KAAK;IACd;;AACJ;;IAEA;;AChCM;;iBACM,wBAAA;YACT,QAAQ,GAAG,CAAC,cAAY;QACzB;;kBACQ,sBAAA;YACP,QAAQ,GAAG,CAAC,YAAU;QACvB;;kBACQ,MAAA;YACP,QAAQ,GAAG,CAAC,YAAU;QACvB;;;;;;;;;;;;;;AACD;;;;;;;;ACPD,IAAM,UAAU,gBAAgB,AAAI;AAGpC,IAAM,gBAAgB;;AAGtB,IAAI,gBAAgB;AAGpB,IAAS,EAAE,KAAK,MAAM,EAAE,QAAQ,iBAAuB,IAAI,EAAE,QAAQ,MAAM,IAAU,IAAI,GAAG,MAAM,CAAA;IAIjG,OAAO;AACR;AAGA,WAAM;;;;aACE,OAAS,MAAM;eAAN,MAAM,CAAA;YACf,OAAO;QACX;YACU,WAAW,MAAM,EAAA;YACvB,gBAAgB;QACpB;;AAEJ,IAAM,YAAY,AAAI;AAGtB,WAAM;;;;IACL,SAAA,EAAE,KAAK,MAAM,EAAE,QAAQ,iBAAuB,IAAI,EAAE,QAAQ,MAAM,IAAU,IAAI,GAAG,MAAM,CAAA;QACxF,OAAO,mBAAE,KAAK,QAAQ;IACvB;IACA,SAAA,QAAQ,gBAAgB,SAAS;;AAIlC,WAAM;;;;IACL,SAAA,QAAQ,aAAa,AAAI,YAAY;;AAItC,IAAM,OAAO,AAAI;AC5CU,WAAf;IACV;kBAAK,MAAM,CAAC;IACZ,iBAAS,MAAK,SAA+C;IAC7D,2BAA4C;IAC5C,kBAAU,sBAAc;IACxB,kBAAU,MAAM,SAAC;IACjB,sBAAc,MAAM,SAAC;IAErB,qBAAa,MAAM,SAAC;IACpB,uBAAe,MAAM,SAAC;;;;;;AAGS,WAArB;IACV;kBAAK,MAAM,CAAC;IACZ;uBAAU,MAAM,CAAC;IACjB;mBAAM,MAAM,CAAC;IACb,mBAAW,sBAAc;IACzB,kBAAU,sBAAc;IACxB,iBAAS,MAAM,SAAC;IAChB,kBAAU,MAAM,SAAC;IAEjB,uBAAc,UAAU,MAAM,EAAE,kBAAmB,MAAM,GAAE,YAAa,MAAM,MAAK,IAAI,UAAC;IAExF,qBAAa,MAAM,SAAC;IACpB,uBAAe,MAAM,SAAA;;;;;;AAGc,WAAzB,cAAc;IACxB;qBAAQ,MAAM,CAAC;IACf,2BAA0B;IAC1B;sBAAS,cAAc;IACvB,gBAAO,iBAAgB;IACvB,gBAAM,MAAM,SAAO;IACnB,eAAM,MAAM,SAAO;IACnB,gBAAO,MAAM,SAAO;IACpB,kBAAQ,OAAO,SAAO;IACtB,iBAAQ,GAAG,SAAQ;;;;;;AC9Bd,IAAM,UAAU,MAAM,GAAG;AACzB,IAAM,UAAU,MAAM,GAAG;AA2BzB,IAAM,cAAc,OAAO,GAAG,IAAI;AL/BzC,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB;AAGvB,IAAI,cAAe,MAAM,IAAU,IAAI;AACvC,IAAI,eAAgB,MAAM,IAAU,IAAI;AACxC,IAAI,YAAa,MAAM,IAAU,IAAI;AAE/B,WAAO;;;;;QACZ,IAAO,SAAS,OAAQ,MAAM,EAAE,cAAe,MAAM,EAAE,WAAY,MAAM,EAAA;YACxE,eAAe;YACf,gBAAgB;YAChB,aAAa;YACT,mBAAe,kBAAkB;YAAjC,mBACe,mBAAmB;YADlC,mBAEe,gBAAgB;QACpC;QACA,IAAO,YAAa,MAAM,EAAO;YAChC,IAAI,aAAY,EAAA,CAAI,IAAI;gBAAE,OAAO;;YACjC,IAAM,IAAI,AAAI,mBAAe,kBAAiB,EAAA,CAAI,MAAM;YACxD,eAAe;YACf,OAAO;QACR;QACA,IAAO,mBAAoB,MAAM,EAAO;YACvC,IAAI,cAAa,EAAA,CAAI,IAAI;gBAAE,OAAO;;YAClC,IAAM,IAAI,AANI,mBAMe,mBAAkB,EAAA,CAAI,MAAM;YACzD,gBAAgB;YAChB,OAAO;QACR;QAAE,IAAO,gBAAiB,MAAM,EAAO;YACtC,IAAM,OAAM;YACZ,IAAI,AADE,KACC,EAAA,CAAI,IAAI;gBAAE,OADX;;YAEN,IAAM,IAAI,AAZI,mBAYe,gBAAe,EAAA,CAAI,MAAM;YACtD,aAAa;YACb,OAAO;QACR;QACA,IAAO,aAAU;YAChB,eAAe,IAAI;YACnB,gBAAgB,IAAI;YACpB,aAAa,IAAI;YACb,sBAAkB;YAAlB,sBACkB;YADlB,sBAEkB;QACvB;QACA,IAAO,mBAAoB,OAAO,CAAA;YACjC,IAAM,YAAY,IAAI,CAAC,YAAY;YACnC,IAAI,UAAS,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,UAAS,EAAA,CAAI,CAAC,EAAE;gBACzC,OAAO,IAAI;;YAEZ,IAAM,MAAM,KAAK,KAAK,CAAC,KAAK,GAAG,GAAE,CAAA,CAAG,IAAI;YACxC,OAAO,CAAC,UAAS,CAAA,CAAG,GAAG,EAAC,CAAA,CAAG,GAAG;QAC/B;QAGA,IAAa,qBAAqB,QAAU,MAAM,CAAA,GAAI,WAAQ,OAAO,EAAC;YAAA,OAAA,eAAA;oBAErE,IAAM,cAAc,IAAI,CAAC,QAAQ;oBACjC,IAAI,YAAW,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,YAAW,GAAA,CAAK,IAAI;wBAC/C,SAAO,KAAK;;oBAEb,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI;wBAC5B,SAAO,KAAK;;oBAEb,IAAM,eAAe,IAAI,CAAC,eAAe;oBACzC,IAAI,aAAY,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,aAAY,GAAA,CAAK,IAAI;wBACjD,IAAI,CAAC,UAAU;wBACf,SAAO,KAAK;;oBAGb,IAAI,yBAAU;qBAAE;oBAChB,IAAI,OAAM,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,OAAM,GAAA,CAAK,IAAI;wBACrC,UAAU,OAAO,MAAM,CAAC,eAAE,EAAE,SAAS;4BAAE,aAAU;yBAAQ,EAAC,EAAA,CAAI;;oBAC7D,IAAI;wBACL,IAAM,MAAM,MAAM,IAAI,CAAC,OAAO,CAM7B,aALA,MAAK,SAAQ,CAAA,CAAG,2CAChB,SAAQ,QACR,OAAM,CAAC;4BAAE,IAAA,gBAAe;yBAAc,AAAiB,GACvD,UAAS,SACT,cAAa,qBACX,IAAI;wBACP,IAAM,OAAO,IAAI,IAAI,CAAA,EAAA,CAAI;wBACzB,IAAI,aAAc,MAAM,IAAU,IAAI;wBACtC,IAAI,iBAAkB,MAAM,IAAU,IAAI;wBAC1C,IAAI,WAAY,MAAM,IAAU,IAAI;wBACpC,IAAI,KAAI,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,oBAAO,IAAI,CAAA,YAAU,EAAA,GAAA,CAAK,WAAU,EAAA,CAAI,oBAAO,IAAI,CAAA,YAAU,EAAA,GAAA,CAAK,YAAY;4BACjG,cAAc,KAAK,SAAS,CAAC;4BAC7B,kBAAkB,KAAK,SAAS,CAAC;4BACjC,YAAY,KAAK,SAAS,CAAC;;wBAE5B,IAAI,YAAW,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,gBAAe,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,UAAS,EAAA,CAAK,IAAI,EAAE;4BAC3E,IAAI,CAAC,QAAQ,CAAC,aAAa,iBAAiB;4BAC5C,SAAO,IAAI;0BACL,IAGN,CAHM;4BACN,IAAI,CAAC,UAAU;4BACf,SAAO,KAAK;;;qBAEZ,OAAO,cAAG;wBACX,IAAI,CAAC,UAAU;wBACf,SAAO,KAAK;;aAEb;QAAD;QAEA,IAAa,QAAQ,qBAAsB,EAAE,aAAe,OAAO,CAAA,GAAI,yBAAsB,GAAG,GAAE;YAAA,OAAA,eAAA;oBAEjG,IAAI,YAAW,EAAA,CAAI,IAAI,EAAE;wBACxB,IAAI,QAAS,MAAM,IAAU,IAAI;wBACjC,IAAM,aAAa,QAAQ,OAAO;wBAClC,IAAI,WAAU,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,oBAAO,UAAU,CAAA,YAAU,EAAA,GAAA,CAAK,YAAY;4BACrE,SAAS,WAAW,SAAS,CAAC;;wBAE/B,MAAM,IAAI,CAAC,oBAAoB,CAAC;;oBAIjC,IAAI,UAAU,QAAQ,OAAO,CAAA,EAAA,CAAI,CAAC,eAAE,AAAiB;oBACrD,IAAM,QAAQ,IAAI,CAAC,QAAQ;oBAC3B,IAAI,MAAK,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,MAAK,EAAA,CAAI,IAAI;wBACjC,UAAU,OAAO,MAAM,CAAC,eAAE,EAAE,SAAS;4BAAE,IAAA,gBAAe,YAAU;yBAAS,EAAC,EAAA,CAAI;;oBAE/E,IAAI,cAAc,QAAQ,WAAW,CAAA,EAAA,CAAI;oBACzC,IAAI,QAAO,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,oBAAO,OAAO,CAAA,YAAU,EAAA,GAAA,CAAK,YAAY;wBAC/D,IAAM,oBAAoB,QAAQ,SAAS,CAAC;wBAC5C,IAAI,kBAAiB,EAAA,CAAI,IAAI,EAAE;4BAC9B,cAAc;;;oBAGhB,IAAI,YAAW,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,YAAW,EAAA,CAAI,IAAI;wBAC7C,UAAU,OAAO,MAAM,CAAC,eAAE,EAAE,SAAS;4BAAE,qBAAgB;yBAAa,EAAC,EAAA,CAAI;;oBAG1E,UAAU,OAAO,MAAM,CAAC;wBAAE,IAAA,SAAQ;qBAAoB,EAAmB,SAAQ,EAAA,CAAI;oBAErF,IAAM,UAAU,QAAQ,OAAO,CAAA,EAAA,CAAI,KAAK;oBACxC,IAAM,WAAW,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,UAAU,CAAA,EAAA,CAAI,CAAC;oBACpD,IAAM,YAAY,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,YAAY,CAAA,EAAA,CAAI,GAAG;oBAEzD,IAAM,SAAS,OAAI,yBAAsB,GAAG,GAAK;wBAChD,OAAO,AAAI,yBAAsB,GAAG,GAAG,IAAC,SAAO,QAAI;4BAC9C,gCACH,MAAK,QAAQ,GAAG,EAChB,SAAQ,QAAQ,MAAM,CAAA,EAAA,CAAI,OAC1B,OAAM,QAAQ,IAAI,EAClB,SAAQ,SACR,UAAS,SACT,UAAS,IAAC,IAAO;gCAEhB,IAAI,QAAQ,MAAM,CAAA,EAAA,CAAI,QAAQ;oCAC7B,IAAM,SAAS,MAAM,cAAc,CAAC,GAAG,EACtC,IAAI,UAAU,EACd,IAAY,GAAG,KACf,IAAI,MAAM,CAAA,EAAA,CAAI;oCAEf,QAAQ;oCACR;;gCAID,IAAI;gCACJ,IAAI,oBAAO,IAAI,IAAI,EAAA,EAAA,CAAI,UAAU;oCAChC,IAAM,UAAU,IAAI,IAAI,CAAA,EAAA,CAAI,MAAM;oCAClC,IAAI,QAAQ,MAAM,CAAA,CAAA,CAAG,CAAC,CAAA,EAAA,CAAI,wBAAQ,IAAI,CAAC,UAAU;wCAChD,IAAI;4CACH,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,mDAAN,EAAA,CAAkB;0CAC7B,OAAO,cAAG;4CAEX,OAAO,AAAI,cAAc;gDAAE,IAAA,MAAK;6CAAS;;sCAEpC,IAEN,CAFM;wCACN,OAAO,IAAI;qCACX;kCACK,IAaN,CAbM,IAAI,SAAM,OAAO,CAAC,IAAI,IAAI,GAAG;oCACnC,OAAO,IAAI,IAAI,CAAA,EAAA,UAAI;kCACb,IAWN,CAXM;oCACN,IAAM,UAAU,IAAI,IAAI,CAAA,EAAA,CAAI;oCAC5B,OAAO;oCACP,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;wCACpB,IAAM,cAAc,QAAQ,SAAS,CAAC;wCACtC,IAAM,kBAAkB,QAAQ,SAAS,CAAC;wCAC1C,IAAM,YAAY,QAAQ,SAAS,CAAC;wCACpC,IAAI,YAAW,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,gBAAe,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,UAAS,EAAA,CAAK,IAAI,EAAE;4CAC3E,MAAM,QAAQ,CAAC,aAAa,iBAAiB;;;;gCAIhD,IAAM,SAAS,MAAM,cAAc,CAAC,GAAG,EACtC,IAAI,UAAU,EACd,KAAI,EAAA,CAAI,eAAE,EACV,IAAI,MAAM,CAAA,EAAA,CAAI;gCAEf,QAAQ;4BACT;8BACA,OAAM,IAAC,IAAO;gCACb,IAAM,SAAS,MAAM,cAAc,CAAC,GAAG,EACtC,IAAI,OAAO,EACX,IAAI,IAAI,CAAA,EAAA,CAAI,eAAE,EACd,eAAE,EACF,AAAI,SAAS,eAAe,IAAI,OAAO,EAAE,IAAI,MAAM,CAAA,EAAA,CAAI;gCAExD,QAAQ;4BACT;;wBAEF;;oBACD;oBAEA,IAAI,kBAAU,CAAC;oBACf,IAAI,uBAAuB,GAAG,KAAW,IAAI;oBAC7C,MAAO,QAAO,EAAA,CAAI,SAAU;wBAC3B,IAAM,MAAM,MAAM;wBAClB,UAAU;wBAEV,IAAM,SAAS,IAAI,MAAM,CAAA,EAAA,CAAI,CAAC;wBAC9B,IAAM,OAAO,OAAM,EAAA,CAAI,GAAG,CAAA,EAAA,CAAI,OAAM,CAAA,CAAG,GAAG;wBAC1C,IAAI;4BAAM,SAAO;;wBACjB,IAAI,QAAO,GAAA,CAAK;4BAAU,KAAM;;wBAEhC,IAAM,QAAQ,UAAS,CAAA,CAAG,KAAK,GAAG,CAAC,CAAC,EAAE;wBACrC,MAAM,AAAI,WAAQ,IAAI,EAAE,IAAC,GAAC,QAAI;4BAAG,WAAW,KAAK;gCAAG,EAAC;4BAAI;8BAAG;wBAAQ;;wBACrE;;oBAED,IAAM,WAAW;oBAGjB,IAAI,CAAC,SAAS,MAAM,CAAA,GAAA,CAAK,GAAG,EAAC,EAAA,CAAI,CAAC,YAAW,GAAA,CAAK,IAAI,GAAG;wBACxD,IAAI;4BACH,IAAI,CAAC,UAAU;4BACX,+BAAY,QAAO,mBAAmB,OAAM;;yBAC/C,OAAO,cAAG,CAAA;wBACZ,IAAI,GAQF,OAAO,cAAG;;oBAIb,SAAO;aACP;QAAD;QAGA,IAAa,OAAO,2BAA4B,GAAI,yBAAsB,GAAG,GAAE;YAAA,OAAA,eAAA;oBAE9E,IAAI,QAAQ,MAAM,IAAU,IAAI;oBAChC,IAAM,MAAM,QAAQ,OAAO;oBAC3B,IAAI,IAAG,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,oBAAO,GAAG,CAAA,YAAU,EAAA,GAAA,CAAK,YAAY;wBACvD,SAAS,IAAI,SAAS,CAAC;;oBAEtB,IAAI,OAAM,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,QAAQ,MAAM,CAAA,EAAA,CAAI,IAAI;wBAAE,SAAS,QAAQ,MAAM;;oBACrE,MAAM,IAAI,CAAC,oBAAoB,CAAC,IAAA,OAAM,EAAA,CAAI,IAAI,EAAG;wBAAA;oBAAA,EAAS,IAAI,CAAJ;wBAAA,IAAI;oBAAJ;oBAAI;oBAEhE,IAAI,UAAU,QAAQ,OAAO,CAAA,EAAA,CAAI,CAAC,eAAE,AAAiB;oBACrD,IAAM,QAAQ,IAAI,CAAC,QAAQ;oBAC3B,IAAI,MAAK,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,MAAK,GAAA,CAAK,IAAI;wBAClC,UAAU,OAAO,MAAM,CAAC,eAAE,EAAE,SAAS;4BAAE,IAAA,gBAAe,YAAU;yBAAS,EAAC,EAAA,CAAI;;oBAE7E,IAAI,OAAM,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,OAAM,GAAA,CAAK,IAAI;wBACtC,UAAU,OAAO,MAAM,CAAC,eAAE,EAAE,SAAS,IAAE,YAAQ,SAAS,EAAA,CAAI;;oBAG7D,UAAU,OAAO,MAAM,CAAC;wBAAE,IAAA,SAAQ;qBAAoB,EAAmB,SAAQ,EAAA,CAAI;oBAErF,IAAM,UAAU,QAAQ,OAAO,CAAA,EAAA,CAAI,KAAK;oBACxC,IAAM,WAAW,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,UAAU,CAAA,EAAA,CAAI,CAAC;oBACpD,IAAM,YAAY,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,YAAY,CAAA,EAAA,CAAI,GAAG;oBAEzD,IAAM,SAAS,OAAI,yBAAsB,GAAG,GAAK;wBAChD,OAAO,AAAI,yBAAsB,GAAG,GAAG,IAAC,SAAO,QAAI;4BACpD,IAAM,OAAO,AAAI,iCAChB,MAAK,QAAQ,GAAG,EAChB,WAAU,QAAQ,QAAQ,EAC1B,OAAM,QAAQ,IAAI,EAClB,WAAU,QAAQ,QAAQ,CAAA,EAAA,CAAI,eAAE,EAChC,SAAQ,SACR,UAAS,SACT,UAAS,IAAC,KAAM,kBAAqB;gCACpC,IAAI,QAAQ,iBAAuB,IAAI;gCACvC,IAAI;oCACH,SAAS,4BAAI,CAAJ,KAAK,KAAK,CAAC,IAAI,IAAI,4CAAd,EAAA,CAAmB;;iCAChC,OAAO,cAAG;oCACX,SAAS,IAAI;;gCAEd,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;oCACnB,IAAM,cAAc,OAAO,SAAS,CAAC;oCACrC,IAAM,kBAAkB,OAAO,SAAS,CAAC;oCACzC,IAAM,YAAY,OAAO,SAAS,CAAC;oCACnC,IAAI,YAAW,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,gBAAe,EAAA,CAAK,IAAI,CAAA,EAAA,CAAI,UAAS,EAAA,CAAK,IAAI,EAAE;wCAC3E,MAAM,QAAQ,CAAC,aAAa,iBAAiB;;;gCAG/C,IAAM,SAAS,MAAM,cAAc,CAAC,GAAG,EACtC,IAAI,UAAU,EACd,OAAM,EAAA,CAAI,eAAE,EACZ;gCAED,QAAQ;4BACT;8BACA,OAAM,IAAC,IAAO;gCACb,IAAM,SAAS,MAAM,cAAc,CAAC,GAAG,EACtC,IAAI,OAAO,EACX,IAAI,IAAI,CAAA,EAAA,CAAI,eAAE,EACd,eAAE,EACF,AAAI,SAAS,cAAc,IAAI,OAAO,EAAE,IAAI,MAAM,CAAA,EAAA,CAAI;gCAEvD,QAAQ;4BACT;;4BAED,IAAI,QAAQ,UAAU,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,KAAI,EAAA,CAAI,IAAI,EAAE;gCAC/C,IAAM,mBAAmB,IAAC,KAAK,uBAA0B;oCACxD,IAAM,UAAU,IAAI,QAAQ,CAAA,EAAA,CAAI,MAAM;oCACtC,IAAM,OAAO,IAAI,cAAc,CAAA,EAAA,CAAI,MAAM;oCACzC,IAAM,WAAW,IAAI,wBAAwB,CAAA,EAAA,CAAI,MAAM;oCACvD,IAAI,QAAQ,UAAU,CAAA,EAAA,CAAI,IAAI,EAAE;wCAC/B,QAAQ,UAAU,GAAC,SAAS,MAAM;;gCAEpC;gCACA,KAAK,gBAAgB,CAAC;;wBAEtB;;oBACD;oBAEA,IAAI,kBAAU,CAAC;oBACf,IAAI,uBAAuB,GAAG,KAAW,IAAI;oBAC7C,MAAO,QAAO,EAAA,CAAI,SAAU;wBAC3B,IAAM,MAAM,MAAM;wBAClB,UAAU;wBACV,IAAM,SAAS,IAAI,MAAM,CAAA,EAAA,CAAI,CAAC;wBAC9B,IAAM,OAAO,OAAM,EAAA,CAAI,GAAG,CAAA,EAAA,CAAI,OAAM,CAAA,CAAG,GAAG;wBAC1C,IAAI;4BAAM,SAAO;;wBACjB,IAAI,QAAO,GAAA,CAAK;4BAAU,KAAM;;wBAChC,IAAM,QAAQ,UAAS,CAAA,CAAG,KAAK,GAAG,CAAC,CAAC,EAAE;wBACtC,MAAM,AAAI,WAAQ,IAAI,EAAE,IAAC,SAAO,QAAI;4BACnC,WAAW,KAAK;gCACf,QAAO;4BACR;8BAAG;wBACJ;;wBACA;;oBAED,SAAO;aACP;QAAD;QAEA,KAAsB,GAAf,eACN,QAAQ,MAAM,EACd,SAAkB,EAClB,SAAS,aAAa,EACtB,OAAO,YAAkB,IAAI,EAC7B,OAAO,MAAM,IAAU,IAAI,EAC3B,MAAM,MAAM,IAAU,IAAI,EAC1B,OAAO,MAAM,IAAU,IAAI,EAC3B,SAAS,OAAO,IAAU,IAAI,EAC9B,QAAQ,GAAG,IAAU,IAAI,iBACT,GAAE;YAClB,OAUC,cATA,SAAA,QACA,OAAA,MACA,UAAA,SACA,QAAA,OACA,QAAA,OACA,OAAA,MACA,QAAA,OACA,UAAA,SACA,SAAA;QAEF;QAGA,oBAAuB,GAAV,UAAmB,qBAAsB,EAAE,aAAe,OAAO,CAAA,GAAI,+BAAkC;YAAA,OAAA,eAAA;;;oBACnH,IAAM,WAAW,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS;oBAkB7C,IAAI,eAAe,KAAW,IAAI;oBAClC,IAAI;wBAEH,IAAI,SAAS,IAAI,CAAA,EAAA,CAAY,eAAe;4BAC3C,gBAAgB,CAAA,SAAS,IAAI,CAAA,EAAA,CAAA,aAAA,EAAC,KAAK,CAAC;0BAC9B,IAeN,CAfM,IAAI,SAAM,OAAO,CAAC,SAAS,IAAI,GAAG;4BACxC,IAAM,gBAAgB,SAAM,GAAG,IAAI,KAAE;4BACrC,IAAM,YAAY,SAAS,IAAI,CAAA,EAAA,UAAA,GAAA;gCAC/B;gCAAK,IAAI,YAAI,CAAC;gCAAd,MAAgB,EAAC,CAAA,CAAG,UAAU,MAAM;oCACnC,IAAM,OAAO,SAAS,CAAC,EAAE;oCACzB,IAAI,KAAI,EAAA,CAAY,eAAe;wCAClC,IAAM,SAAS,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,KAAK,CAAC;wCAC1B,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;4CACnB,eAAe,IAAI,CAAC;;sCAEf,IAEN,CAFM;wCACN,eAAe,IAAI,CAAC;;oCARgB;;;4BAWtC,2CAAgB;;;qBAOhB,OAAO,cAAG;wBACX,aAAoD,8BAA8B;wBAElF,2CAAgB,SAAS,IAAI;;oBAE7B,IAAM,qBAAM;wBACZ,IAAA,SAAQ,SAAS,MAAM;wBACvB,IAAA,OAAM;wBACN,IAAA,UAAS,SAAS,OAAO;wBACzB,IAAA,QAAO,SAAS,KAAK;wBACrB,IAAA,QAAO,SAAS,KAAK;wBACrB,IAAA,OAAM,SAAS,IAAI;wBACnB,IAAA,QAAO,SAAS,KAAK;wBACrB,IAAA,UAAS,SAAS,OAAO;wBACzB,IAAA,SAAQ,SAAS,MAAM;qBACvB;oBACD,SAAO;aACP;QAAD;;;AMzaK,IAAU,aAAa,QAAQ,MAAM,EAAA;INSrC,mBMPe,kBAAkB;IAGrC,IAAI;QACF,IAAI,KAAI,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,KAAK,MAAM,CAAA,EAAA,CAAI,IAAI,EAAE;YACvC,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG;;;KAE7B,OAAO,gBAAK;QACZ,cAAsC,4BAA4B;;AAEtE;AAMM,IAAU,oBAAoB,MAAM,CAAA;IACxC,IAAM,SAAS,ANJD,mBMIoB,kBAAiB,EAAA,CAAI,MAAM;IAC7D,IAAI,OAAM,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,OAAM,EAAA,CAAI,IAAI;QAClC,OAAO;;IAET,OAAO;AACT;AAiBM,IAAU,WAAW,OAAO,GAAG,EAAE,gBAAgB,MAAM,GAAG,MAAM,GAAG,SAAQ;IAE/E,IAAI,MAAK,EAAA,CAAY,UAAU;QAC7B,OAAO,MAAK,EAAA,CAAA;;IAEZ,IAAI,eAAe;IACrB,IAAI,oBAAY,CAAC,CAAC;IAElB,IAAI;QAEF,IAAI,MAAK,EAAA,CAAY,UAAO;YAC1B,eAAe,IAAA,CAAA,MAAK,EAAA,CAAA,QAAA,EAAC,OAAO,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,CAAA,MAAK,EAAA,CAAA,QAAA,EAAC,OAAO,CAAA,EAAA,CAAI,IAAK;gBAAA,CAAA,MAAK,EAAA,CAAA,QAAA,EAAC,OAAO;YAAP,EAAU,IAAc,CAAd;gBAAA;YAAA,CAAc;UAGzF,IA6DJ,CA7DI,IAAI,oBAAO,OAAK,GAAA,CAAK,UAAU;YAClC,eAAe,MAAK,EAAA,CAAA,MAAA;UAEjB,IA0DJ,CA1DI,IAAI,MAAK,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,oBAAO,OAAK,GAAA,CAAK,UAAU;YACnD,IAAM,WAAW,MAAK,EAAA,CAAI;YAC1B,IAAI,SAAS,MAAM,GAAG;YAGtB,IAAI,QAAQ,CAAC,UAAU,CAAA,EAAA,CAAI,IAAI,EAAE;gBAC/B,IAAM,WAAW,QAAQ,CAAC,UAAU;gBACpC,IAAI,oBAAO,UAAQ,GAAA,CAAK,UAAU;oBAChC,UAAU,SAAQ,EAAA,CAAA,MAAA;;cAEf,IAoBN,CApBM,IAAI,QAAQ,CAAC,SAAS,CAAA,EAAA,CAAI,IAAI,EAAE;gBACrC,IAAM,WAAW,QAAQ,CAAC,SAAS;gBACnC,IAAI,oBAAO,UAAQ,GAAA,CAAK,UAAU;oBAChC,UAAU,SAAQ,EAAA,CAAA,MAAA;;cAEf,IAeN,CAfM,IAAI,QAAQ,CAAC,QAAQ,CAAA,EAAA,CAAI,IAAI,EAAE;gBACpC,IAAM,WAAW,QAAQ,CAAC,QAAQ;gBAClC,IAAI,oBAAO,UAAQ,GAAA,CAAK,UAAU;oBAChC,UAAU,SAAQ,EAAA,CAAA,MAAA;;cAEf,IAUN,CAVM,IAAI,QAAQ,CAAC,UAAU,CAAA,EAAA,CAAI,IAAI,EAAE;gBACtC,IAAM,WAAW,QAAQ,CAAC,UAAU;gBACpC,IAAI,oBAAO,UAAQ,GAAA,CAAK,UAAU;oBAChC,UAAU,SAAQ,EAAA,CAAA,MAAA;;cAEf,IAKN,CALM,IAAI,QAAQ,CAAC,MAAM,CAAA,EAAA,CAAI,IAAI,EAAE;gBAClC,IAAM,WAAW,QAAQ,CAAC,MAAM;gBAChC,IAAI,oBAAO,UAAQ,GAAA,CAAK,UAAU;oBAChC,UAAU,SAAQ,EAAA,CAAA,MAAA;;;YAItB,IAAI,QAAO,EAAA,CAAI,IAAI;gBACjB,eAAe;;YAIjB,IAAI,MAAM,MAAM,GAAG,CAAC;YACpB,IAAI,QAAQ,CAAC,OAAO,CAAA,EAAA,CAAI,IAAI,EAAE;gBAC5B,IAAM,YAAY,QAAQ,CAAC,OAAO;gBAClC,IAAI,oBAAO,WAAS,GAAA,CAAK,UAAU;oBACjC,OAAO,UAAS,EAAA,CAAA,MAAA;;cAEb,IAUN,CAVM,IAAI,QAAQ,CAAC,UAAU,CAAA,EAAA,CAAI,IAAI,EAAE;gBACtC,IAAM,YAAY,QAAQ,CAAC,UAAU;gBACrC,IAAI,oBAAO,WAAS,GAAA,CAAK,UAAU;oBACjC,OAAO,UAAS,EAAA,CAAA,MAAA;;cAEb,IAKN,CALM,IAAI,QAAQ,CAAC,SAAS,CAAA,EAAA,CAAI,IAAI,EAAE;gBACrC,IAAM,YAAY,QAAQ,CAAC,SAAS;gBACpC,IAAI,oBAAO,WAAS,GAAA,CAAK,UAAU;oBACjC,OAAO,UAAS,EAAA,CAAA,MAAA;;;YAIpB,IAAI,KAAI,EAAA,CAAI,CAAC,EAAE;gBACb,YAAY;;;;KAGhB,OAAO,cAAG;QACV,cAAuC,iCAAiC;QACxE,eAAe;;IAGjB,IAAM,WAAW,AAAI,SAAS,YAAY,WAAW;IACrD,OAAO;AACT;ACjIiC,WAArB;IACX;2BAAe,MAAM,CAAC;IACtB;4BAAgB,MAAM,CAAC;IACvB;yBAAa,MAAM,CAAC;IACpB,eAAO,sBAAqB;IAC5B,qBAAc,MAAM,SAAC;IACrB,qBAAc,MAAM,SAAC;IACrB;kBAAM,cAAc;;;;;;UAIT,cAAc,MAAO;AAGC,WAAtB;IACX,gBAAS,MAAM,SAAC;IAChB,gBAAS,MAAM,SAAC;IAChB,mBAAY,MAAM,SAAC;IACnB,gBAAS,oBAAY;IACrB,eAAQ,OAAO,SAAC;IAChB,kBAAW,MAAM,SAAC;IAClB,iBAAU,OAAO,SAAC;IAClB,oBAAa,MAAM,SAAC;IACpB,kBAAW,MAAM,SAAC;;;;;;AAIQ,WAAf;IACX,oBAAa,OAAO,SAAC;;;;;;AAIU,WAApB;IACX,kBAAU,2BAA0B;IACpC,eAAO,sBAAqB;;;;;;AAKN,WAAlB;IACJ;oBAAQ,MAAM,CAAC;IACf;iBAAK,MAAM,CAAC;IACZ;oBAAQ,GAAG,CAAC;IACZ;oBAAQ,MAAM,CAAC;;;;;;AAGV,WAAO;;;;IACZ,YAAQ,OAAQ,MAAO;IACvB,YAAQ,QAAS,MAAM,AAAC;IACxB,YAAQ,SAAU,iBAAuB,IAAI,AAAC;IAC9C,YAAQ,UAAW,sBAAwB,qBAAC;IAC5C,YAAQ,gBAAwD,IAAI,AAAC;IACrE,YAAQ,SAAU,OAAO,GAAG,KAAK,AAAC;IAClC,YAAQ,aAAc,SAAM,mBAAmB,KAAE,AAAC;IAClD,YAAQ,YAAa,MAAM,GAAG,KAAM;IAEpC,YAAQ,SAAU,MAAQ,IAAmD,IAAI,AAAC;IAClF,YAAQ,WAAY,MAAM,IAAU,IAAI,AAAC;IACzC,YAAQ,cAAe,MAAM,IAAU,IAAI,AAAC;IAC5C,YAAQ,YAAa,iBAAuB,IAAI,AAAC;IACjD,YAAQ,OAAQ,MAAM,GAAG,CAAC,AAAC;IAE3B,YAAY,SAAO,MAAM,EAAE,OAAQ,MAAM,CAAA;QACxC,IAAI,CAAC,KAAK,GAAG;QACb,IAAI,CAAC,MAAM,GAAG;IACf;IAGA,SAAA,GAAG,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM;IAAQ;IACjG,SAAA,IAAI,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,OAAO;IAAQ;IACnG,SAAA,GAAG,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM;IAAQ;IACjG,SAAA,IAAI,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,OAAO;IAAQ;IACnG,SAAA,GAAG,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM;IAAQ;IACjG,SAAA,IAAI,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,OAAO;IAAQ;IACnG,SAAA,KAAK,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,QAAQ;IAAQ;IACrG,SAAA,MAAM,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,SAAS;IAAQ;IACvG,SAAA,KAAG,OAAQ,MAAM,EAAE,gBAAQ,GAAG,CAAE,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM;IAAQ;IACnG,SAAA,KAAG,OAAQ,MAAM,EAAE,OAAQ,GAAG,CAAO,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM;IAAQ;IACxG,SAAA,SAAS,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM;IAAQ;IACvG,SAAA,YAAY,OAAQ,MAAM,EAAE,OAAQ,GAAG,GAAI,mBAAkB;QAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM;IAAQ;IAC1G,SAAA,IAAI,OAAQ,MAAM,EAAE,WAAY,GAAG,EAAE,OAAO,GAAG,IAAU,IAAI,GAAI,mBAAkB;QAClF,IAAI,MAAK,EAAA,CAAI,IAAI,EAAE;YAGlB,IAAM,aAAa,OAAM,CAAA,CAAG;YAE5B,IAAI,YAAY;YAChB,IAAI,MAAK,EAAA,CAAK,IAAI,EAAE;gBACnB,YAAY;;YAEb,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,YAAY;UAClC,IAON,CAPM;YAEN,IAAI,YAAY;YAChB,IAAI,UAAS,EAAA,CAAK,IAAI,EAAE;gBACvB,YAAY;;YAEb,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,OAAO;;IAErC;IAEA,SAAA,OAAQ,mBAAkB;QAAG,IAAI,CAAC,UAAU,GAAG;QAAO,OAAO,IAAI;IAAE;IACnE,SAAA,GAAG,KAAO,MAAM,CAAA,GAAI,mBAAkB;QACrC,IAAI,oBAAO,KAAG,EAAA,CAAI,UAAU;YAC3B,IAAI,CAAC,SAAS,GAAG;UACX,IAEN,CAFM;YACN,IAAI,CAAC,UAAU,GAAG;;QAEnB,OAAO,IAAI;IACZ;IAEA,YAAQ,SAAS,QAAS,MAAM,EAAE,IAAK,MAAM,EAAE,OAAQ,GAAG,CAAO,GAAI,mBAAkB;QAEtF,IAAM,QAAQ,4BAAkB,CAAlB,mBAAmB;QAEjC,IAAI,YAAY;QAChB,IAAI,MAAK,EAAA,CAAK,IAAI,EAAE;YACnB,YAAY;;QAEb,IAAI,CAAC,WAAW,CAAC,IAAI,CAAwD,gBAArD,QAAA,OAAO,KAAA,IAAI,QAAO,WAAW,QAAO,IAAI,CAAC,UAAU;QAE3E,IAAI,CAAC,UAAU,GAAG;QAClB,OAAO,IAAI;IACZ;IAGA,SAAA,MAAM,QAAS,aAAa,GAAI,mBAAkB;QACjD,IAAI,CAAC,OAAO,GAAG;QACf,OAAO,IAAI;IACZ;IAEA,SAAA,KAAK,MAAO,MAAM,GAAI,mBAAkB;QACvC,IAAI,CAAC,KAAK,GAAG;QAEb,IAAI,gBAAQ,CAAC;QACb,IAAI,oBAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAA,EAAA,CAAI,UAAU;YAC3C,QAAQ,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAA,EAAA,CAAI,CAAC;;QAEjC,IAAI,MAAK,CAAA,CAAG,CAAC,EAAE;YACd,IAAM,OAAO,CAAC,KAAI,CAAA,CAAG,CAAC,EAAC,CAAA,CAAG;YAC1B,IAAM,KAAK,KAAI,CAAA,CAAG,MAAK,CAAA,CAAG,CAAC;YAC3B,IAAI,CAAC,KAAK,CAAC,MAAM;;QAElB,OAAO,IAAI;IACZ;IACA,SAAA,MAAM,OAAQ,MAAM,GAAI,mBAAkB;QACzC,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG;QAEtB,IAAM,OAAO,CAAC,IAAI,CAAC,KAAK,CAAA,CAAA,CAAG,CAAC,EAAC,CAAA,CAAG;QAChC,IAAM,KAAK,KAAI,CAAA,CAAG,MAAK,CAAA,CAAG,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,MAAM;QACjB,OAAO,IAAI;IACZ;IAEA,SAAA,MAAM,OAAQ,MAAM,EAAE,SAAW,aAAY,GAAI,mBAAkB;QAClE,IAAI,QAAO,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,QAAQ,SAAS,CAAA,EAAA,CAAI,KAAK,EAAE;YAClD,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,MAAK,CAAA,CAAG;UACxB,IAEN,CAFM;YACN,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,MAAK,CAAA,CAAG;;QAE/B,OAAO,IAAI;IACZ;IACA,SAAA,QAAQ,SAAU,MAAM,GAAI,mBAAkB;QAC7C,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG;QACxB,OAAO,IAAI;IACZ;IAGA,SAAA,MAAM,QAAS,cAAc,OAAO,GAAI,mBAAkB;QACzD,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG;QACtB,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI;QACzB,OAAO,IAAI;IACZ;IAGA,SAAA,cAAe,mBAAkB;QAChC,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB;IAEA,SAAA,kBAAmB,mBAAkB;QACpC,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB;IAEA,SAAA,gBAAiB,mBAAkB;QAClC,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB;IAGA,SAAA,KAAK,QAAS,OAAO,GAAG,IAAI,GAAI,mBAAkB;QACjD,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG;QACrB,OAAO,IAAI;IACZ;IAEA,SAAA,OAAO,QAAS,aAAa,GAAI,mBAAkB;QAClD,IAAI,CAAC,OAAO,GAAG;QACf,OAAO,IAAI;IACZ;IACA,SAAA,UAAW,mBAAkB;QAC5B,IAAI,CAAC,OAAO,GAAG,IAAI;QACnB,OAAO,IAAI;IACZ;IACA,SAAA,MAAM,MAAO,MAAM,EAAE,IAAK,MAAM,GAAI,mBAAkB;QACrD,IAAI,CAAC,QAAQ,CAAC,SAAS,GAAG;QAC1B,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG;QAExB,OAAO,IAAI;IACZ;IAEA,YAAQ,gBAAiB,MAAM,EAAO;QACrC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAA,EAAA,CAAI,CAAC,CAAA,EAAA,CAAI,CAAC,IAAI,CAAC,SAAS,CAAA,EAAA,CAAE,IAAI,CAAA,EAAA,CAAI,IAAI,CAAC,SAAS,CAAA,EAAA,CAAI,EAAE,GAAG;YAEnF,IAAI,IAAI,CAAC,OAAO,CAAA,EAAA,CAAI,IAAI;gBAAE,OAAO,IAAI;;YAErC,OAAO,yBAAyB,IAAI,CAAC,OAAO;;QAI7C,IAAM,eAAM,mBAAoB,KAAE;QAClC,IAAM,cAAK,mBAAoB,KAAE;QACjC,IAAW,6BAAK,IAAI,CAAC,WAAW,EAAE;YACjC,IAAI,EAAE,KAAK,CAAA,EAAA,CAAI,MAAM;gBACpB,IAAI,IAAI,CAAC;cACH,IAEN,CAFM;gBACN,KAAK,IAAI,CAAC;;;QAIZ,IAAM,iBAAQ,MAAM,IAAK,KAAE;QAE3B,IAAW,gCAAQ,MAAM;YACxB,IAAM,IAAI,KAAK,KAAK;YACpB,IAAM,KAAK,KAAK,EAAE;YAClB,IAAM,OAAM,KAAK,KAAK;YACtB,IAAI,CAAC,GAAE,EAAA,CAAI,KAAI,EAAA,CAAI,GAAE,EAAA,CAAI,QAAQ,EAAC,EAAA,CAAI,SAAM,OAAO,CAD7C,OACoD;gBACzD,OAAO,IAAI,CAAC,KAAG,IAAC,MAAI,KAAE,OAAK,CAFtB,KAAG,EAAG,UAEmB,GAAA,CAAA,EAAC,GAAG,CAAC,IAAA,IAAC,MAAA;2BAAI,IAAA,oBAAO,GAAC,EAAA,CAAI,UAAW;wBAAA,4BAAkB,CAAlB,mBAAmB,KAAK,SAAS,CAAC;oBAAlC,EAAwC,IAAkB,CAAlB;wBAAA,4BAAkB,CAAlB,mBAAmB,EAAE,QAAQ;oBAA7B,CAAgC;mBAAE,IAAI,CAAC,OAAI;cAC5I,IAKN,CALM,IAAI,CAAC,GAAE,EAAA,CAAI,KAAI,EAAA,CAAI,GAAE,EAAA,CAAI,QAAQ,EAAC,EAAA,CAAI,CAAC,AAHxC,KAG2C,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,AAHvD,KAG0D,EAAA,CAAI,MAAM,GAAG;gBAC5E,OAAO,IAAI,CAAC,KAAG,IAAC,MAAI,KAAE;cAChB,IAGN,CAHM;gBACN,IAAM,UAAU,MAAM,GAAG,IAAA,CAAC,oBANrB,MAM+B,EAAA,CAAI,QAAQ,GAAI;oBAAA,KAAK,SAAS,CAN7D;gBAMiE,EAAI,IAAe,CAAf;oBAAA,CANrE,KAAG,EAAG,CAMuE,MAAM;gBAAA;gBACxF,OAAO,IAAI,CAAC,KAAG,IAAC,MAAI,KAAE,MAAI,4BAAkB,CAAlB,mBAAmB;;;QAI/C,IAAI,IAAI,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;YACnB,IAAM,QAAQ,IAAI,GAAG,CAAC,IAAA,IAAC,MAAA,CAAG;gBACzB,IAAM,IAAI,EAAE,KAAK;gBACjB,IAAM,KAAK,EAAE,EAAE;gBACf,IAAM,OAAM,EAAE,KAAK;gBACnB,IAAI,GAAE,EAAA,CAAI,KAAI,EAAA,CAAI,SAAM,OAAO,CADzB,OACgC;oBACrC,OAAO,KAAG,IAAC,UAAQ,CAFd,KAAG,EAAG,UAEW,GAAA,CAAA,EAAC,GAAG,CAAC,IAAA,IAAC,MAAA;+BAAI,4BAAkB,CAAlB,mBAAmB,EAAC,EAAA,CAAI,MAAM;;sBAAG,IAAI,CAAC,OAAI;;gBAE3E,IAAI,GAAE,EAAA,CAAI,KAAI,EAAA,CAAI,CAAC,AAJb,KAIgB,EAAA,CAAI,IAAI,GAAG;oBAChC,OAAO,KAAG,IAAC;;gBAEZ,OAAO,KAAG,IAAC,MAAI,KAAE,MAAI,4BAAkB,CAAlB,mBAPf,KAAG,EAAG,CAOmC,MAAM;YACtD;cAAG,IAAI,CAAC;YACR,OAAO,IAAI,CAAC,SAAO,QAAK;;QAEzB,IAAI,IAAI,CAAC,SAAS,CAAA,EAAA,CAAE,IAAI,CAAA,EAAA,CAAI,IAAI,CAAC,SAAS,CAAA,GAAA,CAAK,IAAI;YAClD,OAAO,IAAI,CAAC,SAAO,4BAAkB,CAAlB,mBAAmB,IAAI,CAAC,SAAS,iDAAG;;QAExD,OAAO,IAAA,OAAO,MAAM,CAAA,CAAA,CAAG,CAAC,EAAG;YAAA,OAAO,IAAI,CAAC;QAAG,EAAI,IAAI,CAAJ;YAAA,IAAI;QAAJ;IAC/C;IAEA,SAAA,OAAO,SAAU,MAAM,GAAG,GAAG,EAAE,KAAM,iBAAuB,IAAI,GAAI,mBAAkB;QACrF,IAAI,CAAC,OAAO,GAAG;QACf,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;YACpB,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG;;QAEzB,IAAI,IAAG,EAAA,CAAI,IAAI,EAAE;YAEhB,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE;;QAE9B,OAAO,IAAI;IACZ;IACA,SAAA,OAAO,WAA6C,GAAI,mBAAkB;QACzE,IAAI,CAAC,OAAO,GAAG;QAEf,IAAI,SAAM,OAAO,CAAC,SAAS;YAC1B,IAAI,CAAA,OAAM,EAAA,UAAA,cAAA,EAAC,MAAM,CAAA,EAAA,CAAI,CAAC;gBAAE,MAAM,WAAW,4BAA4B,eAAgB;;UAC/E,IAEN,CAFM;YACN,IAAI,cAAc,IAAI,CAAC,OAAM,EAAA,CAAA,eAAE,MAAM,CAAA,EAAA,CAAI,CAAC;gBAAE,MAAM,WAAW,4BAA4B,eAAgB;;;QAE1G,IAAI,CAAC,OAAO,GAAG;QACf,OAAO,IAAI;IACZ;IACA,SAAA,OAAO,QAAS,aAAa,GAAI,mBAAkB;QAClD,IAAI,CAAC,OAAO,GAAG;QAEf,IAAI,cAAc,IAAI,CAAC,QAAQ,MAAM,CAAA,EAAA,CAAI,CAAC;YAAE,MAAM,WAAW,4BAA4B,WAAY;;QACrG,IAAI,CAAC,OAAO,GAAG;QAEf,OAAO,IAAI;IACZ;IACA,SAAA,YAAW,mBAAkB;QAC5B,IAAI,CAAC,OAAO,GAAG;QAEf,IAAM,SAAS,IAAI,CAAC,YAAY;QAEhC,IAAI,OAAM,EAAA,CAAI,IAAI;YAAE,MAAM,WAAW,4BAA4B,aAAc;;QAE/E,OAAO,IAAI;IACZ;IAEA,SAAA,IAAI,cAAe,MAAM,EAAE,QAAU,cAAa,GAAI,mBAAkB;QACvE,IAAI,CAAC,OAAO,GAAG;QACf,IAAI,CAAC,YAAY,GAAG;QACpB,IAAI,CAAC,UAAU,GAAG;QAClB,OAAO,IAAI;IACZ;IAEA,SAAM,WAAY,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBAE5C,IAAM,SAAS,IAAI,CAAC,YAAY;gBAEhC,IAAI,KAAM,GAAG;gBACb,MAAQ,IAAI,CAAC,OAAO;oBACd;wBAAU;4BAEd,IAAI,IAAI,CAAC,OAAO,EAAE;gCACjB,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI;gCAE3B,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;oCAChC,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC;;;4BAIzB,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;gCAChC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;oCAClE,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG;;;4BAGxB,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,CAAC,QAAQ;4BAEhE,IAAI,gBAAQ,CAAC;4BACb,IAAI,UAAU,KAAK;4BACnB,IAAM,OAAO,IAAI,CAAC,KAAK;4BACvB,IAAI,UAAU,IAAI,IAAI;4BACtB,IAAI,gBAAQ,CAAC;4BACb,IAAI,oBAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAA,EAAA,CAAI,UAAU;gCAC3C,QAAQ,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAA,EAAA,CAAI,CAAC;8BAC1B,IAEN,CAFM,IAAI,SAAM,OAAO,CAAC,UAAU;gCAClC,QAAQ,CAAA,QAAO,EAAA,UAAA,GAAA,CAAA,EAAC,MAAM;;4BAEvB,IAAI,cAAe,MAAM,IAAU,IAAI;4BACvC,IAAI,IAAI,OAAO,CAAA,EAAA,CAAI,IAAI,EAAE;gCACxB,IAAI,YAAY,IAAI,OAAO,CAAA,EAAA,CAAI;gCAC/B,IAAI,oBAAO,SAAS,CAAA,MAAI,EAAA,EAAA,CAAI,YAAY;oCAEvC,eAAe,UAAU,GAAG,CAAC,iBAAgB,EAAA,CAAI,MAAM;kCACjD,IAEN,CAFM,IAAI,oBAAO,SAAS,CAAC,gBAAgB,EAAA,EAAA,CAAI,UAAU;oCACzD,eAAe,SAAS,CAAC,gBAAgB,CAAA,EAAA,CAAI,MAAM;;;4BAGrD,IAAI,aAAY,EAAA,CAAI,IAAI,EAAE;gCACzB,IAAM,QAAQ,6BAAW,IAAI,CAAC;gCAC9B,IAAI,MAAK,EAAA,CAAI,IAAI,EAAE;oCAClB,QAAQ,SAAS,KAAK,CAAC,CAAC,CAAC,CAAA,EAAA,CAAI;oCAC7B,UAAU,CAAC,KAAI,CAAA,CAAG,KAAK,EAAC,CAAA,CAAG;;;4BAG7B,IAAI,MAAK,EAAA,CAAI,CAAC,EAAE;gCACf,IAAI,oBAAO,GAAG,CAAC,QAAQ,EAAA,EAAA,CAAI,UAAU;oCACpC,QAAQ,GAAG,CAAC,QAAQ,CAAA,EAAA,CAAI,MAAM,CAAA,EAAA,CAAI,CAAC;kCAC7B,IAIN,CAJM,IAAI,SAAM,OAAO,CAAC,UAAU;oCAClC,QAAQ,CAAA,QAAO,EAAA,UAAA,GAAA,CAAA,EAAC,MAAM;kCAChB,IAEN,CAFM;oCACN,QAAQ,CAAC;;;4BAGX,IAAI,CAAC;gCAAS,UAAU,CAAC,KAAI,CAAA,CAAG,KAAK,EAAC,CAAA,CAAG;;4BACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;gCAC/B,SAUK,cATJ,OAAM,IAAI,EACV,QAAA,OACA,OAAA,MACA,QAAA,OACA,UAAS,KAAK,EACd,SAAQ,KACR,SAAQ,IAAI,MAAM,EAClB,UAAS,IAAI,OAAO,EACpB,QAAO,IAAI,KAAK;;4BAIlB,SAUK,cATJ,OAAM,IAAI,IAAI,EACd,QAAA,OACA,OAAA,MACA,QAAA,OACA,UAAA,SACA,SAAQ,KACR,SAAQ,IAAI,MAAM,EAClB,UAAS,IAAI,OAAO,EACpB,QAAO,IAAI,KAAK;;oBAGb;wBAAU;4BACd,IAAM,eAAe,IAAI,CAAC,OAAO;4BACjC,IAAI,aAAY,EAAA,CAAI,IAAI;gCAAE,MAAM,WAAW,4BAA4B,WAAY;;4BACnF,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE;;oBAErC;wBAAU;4BAChB,IAAM,eAAe,IAAI,CAAC,OAAO;4BACjC,IAAI,aAAY,EAAA,CAAI,IAAI;gCAAE,MAAM,WAAW,4BAA4B,WAAY;;4BACnF,IAAI,OAAM,EAAA,CAAI,IAAI;gCAAE,MAAM,WAAW,4BAA4B,aAAc;;4BAE/E,IAAI,SAAM,OAAO,CAAC;gCAAe,MAAM,WAAW,wCAAwC,cAAe;;4BACzG,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,aAAY,EAAA,CAAI;;oBAG/D;;4BACJ,IAAI,OAAM,EAAA,CAAI,IAAI;gCAAE,MAAM,WAAW,4BAA4B,aAAc;;4BAC/E,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,QAAM,CAAC,IAAI,CAAC,MAAM,EAAE;;oBAGvC;;4BACJ,IAAI,IAAI,CAAC,YAAY,CAAA,EAAA,CAAI,IAAI;gCAAE,MAAM,WAAW,6BAA6B,aAAc;;4BAC3F,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAA,EAAA,CAAI,MAAM,EAAE,IAAI,CAAC,UAAU;;oBAGxE;wBACC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,CAAC,QAAQ;;gBAIlE,IAAI,GAAG,CAAC,OAAO,CAAA,EAAA,CAAI,IAAI;oBAAE,GAAG,CAAC,OAAO,GAAG,eAAE;;gBACzC,SAAO;SACP;IAAD;IACA,oBAAgB,GAAV,aAAuB,+BAAoC;QAAA,OAAA,eAAA;;;gBAChE,IAAM,SAAS,MAAM,IAAI,CAAC,OAAO;gBAGjC,IAAI,OAAO,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;oBACxB,IAAM,qBAAM;wBACX,IAAA,SAAQ,OAAO,MAAM;wBACrB,IAAA,OAAM,IAAI;wBACV,IAAA,UAAS,OAAO,OAAO;wBACvB,IAAA,QAAO,OAAO,KAAK;wBACnB,IAAA,QAAO,OAAO,KAAK;wBACnB,IAAA,OAAM,OAAO,IAAI;wBACjB,IAAA,QAAO,OAAO,KAAK;wBACnB,IAAA,UAAS,OAAO,OAAO;wBACvB,IAAA,SAAQ,OAAO,MAAM;qBACrB;oBACD,SAAO;;gBAIR,IAAI,sBAAsC,IAAI;gBAE9C,IAAI;oBACH,IAAI,SAAM,OAAO,CAAC,OAAO,IAAI,GAAG;wBAE/B,IAAM,YAAY,OAAO,IAAI,CAAA,EAAA,UAAA,GAAA;wBAC7B,IAAM,gBAAiB,SAAM,KAAK,KAAE;4BAEpC;4BAAK,IAAI,YAAI,CAAC;4BAAd,MAAgB,EAAC,CAAA,CAAG,UAAU,MAAM;gCACnC,IAAM,OAAO,SAAS,CAAC,EAAE;gCACzB,IAAI,KAAI,EAAA,CAAY,eAAe;oCAGlC,IAAM,SAAS,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,KAAK,CAAC;oCAM1B,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wCACnB,eAAe,IAAI,CAAC;sCACd,IAGN,CAHM;wCACN,aAAmD,gBAAgB;wCACnE,eAAe,IAAI,4BAAC;qCACpB;kCACK,IAgBN,CAhBM;oCAEN,IAAM,UAAU,AAAI,cAAc;oCAElC,IAAM,SAAS,QAAQ,KAAK,CAAC;oCAK7B,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wCACnB,eAAe,IAAI,CAAC;sCAEhB,IAGJ,CAHI;wCACJ,aAAmD,gBAAgB;wCACnE,eAAe,IAAI,4BAAC;qCACpB;iCACD;gCAjCoC;;;wBAmCtC,gBAAgB;sBAEV,IAuBN,CAvBM;wBAEN,IAAM,gBAAiB,SAAM,KAAK,KAAE;wBACpC,IAAI,OAAO,IAAI,CAAA,EAAA,CAAY,eAAe;4BACzC,IAAM,SAAS,CAAA,OAAO,IAAI,CAAA,EAAA,CAAA,aAAA,EAAC,KAAK,CAAC;4BAEjC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;gCACnB,eAAe,IAAI,CAAC;;0BAKf,IASN,CATM;4BACN,IAAM,UAAU,AAAI,cAAc,OAAO,IAAI;4BAC7C,IAAM,SAAS,QAAQ,KAAK,CAAC;4BAC7B,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;gCACnB,eAAe,IAAI,CAAC;;;wBAMtB,gBAAgB;;;iBAEhB,OAAO,cAAG;oBACX,aAAmD,oBAAoB;oBACvE,YAAkD,OAAO,IAAI;oBAE7D,2CAAgB,OAAO,IAAI;;gBAE5B,OAAO,IAAI,GAAG;gBACd,IAAM,MAAM,OAAM,EAAA;gBAYlB,SAAO;SAEP;IAAD;;AAkDD,WAAM;;;;IACL,YAAQ,MAAO,MAAO;IACtB,YAAQ,QAAS,MAAM,AAAC;IACxB,YAAY,SAAO,MAAM,EAAE,QAAS,MAAM,CAAA;QACzC,IAAI,CAAC,IAAI,GAAG;QACZ,IAAI,CAAC,MAAM,GAAG;IACf;IACA,SAAM,OAAO,MAAO,MAAM,EAAE,UAAW,MAAM,EAAE,SAAW,cAAa,GAAI,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBACrG,IAAM,MAAM,KAAG,IAAI,CAAC,IAAI,CAAC,OAAO,GAAA,wBAAsB,IAAI,CAAC,MAAM,GAAA,MAAI;gBACrE,IAAI,SAAU,gBAAgB,wGAAE,YAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;gBACxD,IAAM,UAAW,gBAAgB;iBAAE;gBACnC,IAAI,QAAO,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,oBAAO,SAAO,EAAA,CAAI,UAAU;oBAClD,IAAI,oBAAO,OAAO,CAAA,MAAI,EAAA,EAAA,CAAI,WAAU,EAAA,CAAI,QAAQ,GAAG,CAAC,YAAW,EAAA,CAAI,IAAI,EAAE;wBACxE,OAAO,CAAC,WAAW,GAAG,QAAQ,GAAG,CAAC;;oBAEnC,IAAM,OAAO,cAAc,IAAI,CAAC;wBAChC;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,KAAK,MAAM;4BAC9B,IAAM,IAAI,IAAI,CAAC,EAAE;4BACjB,IAAI,EAAC,EAAA,CAAI;gCAAY,QAAQ,CAAC,EAAE,GAAG,QAAQ,GAAG,CAAC;;4BAFf;;;;gBAKlC,IAAM,QAAQ,MAAM,QAAQ;gBAC5B,IAAI,MAAK,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,CAAC,CAAC,MAAK,EAAA,CAAI,EAAE,GAAG;oBACpC,OAAO,CAAC,gBAAgB,GAAG,YAAU;;gBAEtC,SAAO,MAAM,MAAM,MAAM,CAOxB,mBANA,MAAA,KACA,WAAA,UACA,OAAM,QACN,SAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,EACxB,WAAA,UACA,UAAA;SAED;IAAD;;AAGK,WAAO;;;;IACZ,YAAQ,OAAQ,MAAO;IACvB,YAAY,SAAO,MAAM,CAAA;QACxB,IAAI,CAAC,KAAK,GAAG;IACd;IACA,SAAA,KAAK,QAAS,MAAM,GAAI,oBAAmB;QAC1C,OAAO,AAAI,oBAAoB,IAAI,CAAC,KAAK,EAAE;IAC5C;;AAGK,WAAO;;;;IACZ,kBAAA,SAAU,MAAM,AAAC;IACjB,kBAAA,QAAS,MAAM,AAAC;IAChB,SAAA,SAAU,sBAA4B,IAAI,AAAC;IAC3C,SAAA,MAAO,iBAAuB,IAAI,AAAC;IACnC,kBAAA,SAAU,gBAAiB;IAE3B,YAAY,SAAU,MAAM,EAAE,QAAS,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG;QACf,IAAI,CAAC,MAAM,GAAG;QACd,IAAI,CAAC,OAAO,GAAG,AAAI,iBAAiB,IAAI;QAExC,IAAI;YACH,IAAI,CAAC,yBAAyB;;SAC7B,OAAO,cAAG;IAGb;IAGA,SAAM,6BAA8B,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACnD,IAAI;oBACH,IAAM,QAAQ,MAAM,QAAQ;oBAC5B,IAAI,MAAK,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,MAAK,EAAA,CAAI;wBAAI,SAAO,KAAK;;oBAC9C,IAAM,MAAM,MAAM,MAAM,OAAO,CAQ9B,aAPA,MAAK,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,iBACpB,SAAQ,OACR,UAAS,IACR,YAAQ,IAAI,CAAC,MAAM,EACnB,oBAAe,YAAU,QACzB,kBAAgB,sBAEf,KAAK;oBACR,IAAM,SAAS,IAAI,MAAM,CAAA,EAAA,CAAI,CAAC;oBAC9B,IAAI,CAAC,CAAC,OAAM,EAAA,CAAI,GAAG,CAAA,EAAA,CAAI,OAAM,CAAA,CAAG,GAAG,GAAG;wBACrC,SAAO,KAAK;;oBAEb,IAAI,MAAM,iBAAuB,IAAI;oBACrC,IAAI;wBACH,OAAO,AAAI,cAAc,IAAI,IAAI;;qBAChC,OAAO,cAAG;wBACX,OAAO,IAAI;;oBAEZ,IAAI,KAAI,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAC9B,IAAI,CAAC,IAAI,GAAG;oBAEZ,IAAI,IAAI,CAAC,OAAO,CAAA,EAAA,CAAI,IAAI,EAAE;wBACzB,IAAI,CAAC,OAAO,GAQP,mBAPJ,eAAc,OACd,gBAAe,MAAM,eAAe,GAAE,EAAA,CAAI,IAC1C,aAAY,MAAM,YAAY,GAAE,EAAA,CAAI,CAAC,EACrC,OAAM,MACN,aAAY,UACZ,aAAY,CAAC,EACb,MAAK;;oBAGP,SAAO,IAAI;;iBACV,OAAO,cAAG;oBACX,SAAO,KAAK;;SAEb;IAAD;IAEA,SAAM,cAAc,OAAQ,MAAM,GAAI,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACrD,IAAM,MAAM,MAAM,MAAM,OAAO,CAS9B,aARA,MAAK,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,oBACpB,SAAQ,QACR,UAAS,IACR,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,qBAEjB,OAAM,IAAE,WAAA,QACR,cAAa,qBACX,KAAK;gBAGR,SAAO,IAAI,MAAM,CAAA,EAAA,CAAI,GAAG;SACxB;IAAD;IACA,SAAM,WAAO,WAAA,IAAA,EAAA;QAAA,OAAA,eAAA;gBACZ,IAAI,CAAC,OAAO,GAAG,IAAI;gBACnB,IAAI,CAAC,IAAI,GAAG,IAAI;SAChB;IAAD;IACA,SAAM,OAAO,OAAQ,MAAM,EAAE,UAAW,MAAM,GAAI,WAAQ,oBAAmB;QAAA,OAAA,eAAA;gBAE5E,IAAI,IAAI,CAAC,MAAM,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,IAAI,CAAC,MAAM,CAAC,IAAI,GAAE,GAAA,CAAK,GAAE,EAAA,CAAI,IAAI,CAAC,MAAM,CAAA,GAAA,CAAK,iBAAiB;oBACxF,MAAM,AAAI,SAAM,sDAAuD;;gBAExE,IAAM,MAAM,MAAM,MAAM,OAAO,CAS9B,aARA,MAAK,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,sCACpB,SAAQ,QACR,UAAS,IACR,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,qBAEjB,OAAM,IAAE,WAAA,OAAO,cAAA,WACf,cAAa,qBACX,KAAK;gBAER,IAAM,SAAS,IAAI,MAAM,CAAA,EAAA,CAAI,CAAC;gBAC9B,IAAI,CAAC,CAAC,OAAM,EAAA,CAAI,GAAG,CAAA,EAAA,CAAI,OAAM,CAAA,CAAG,GAAG,GAAG;oBACrC,IAAI,MAAM;oBACV,IAAI;wBACH,IAAI,IAAI,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;4BACrB,IAAM,MAAM,AAAI,cAAc,IAAI,IAAI;4BACtC,MAAM,IAAI,SAAS,CAAC,WAAU,EAAA,CAAI,IAAI,SAAS,CAAC,SAAQ,EAAA,CAAI,IAAI,SAAS,CAAC,OAAM,EAAA,CAAI,IAAI,SAAS,CAAC,eAAc,EAAA,CAAI,IAAI,SAAS,CAAC,qBAAoB,EAAA,CAAI;;;qBAE1J,OAAO,cAAG;oBAGZ,MAAM,AAAI,SAAM,IAAK;;gBAGtB,IAAI,MAAM;gBACV,IAAI;oBACH,OAAO,AAAI,cAAc,IAAI,IAAI;;iBAChC,OAAO,cAAG;oBACX,OAAO,AAAI,cAAc,eAAE;;gBAE5B,IAAM,eAAe,KAAK,SAAS,CAAC,gBAAe,EAAA,CAAI;gBACvD,IAAM,gBAAgB,KAAK,SAAS,CAAC,iBAAgB,EAAA,CAAI;gBACzD,IAAM,aAAa,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;gBACpD,IAAM,OAAO,KAAK,OAAO,CAAC;gBAC1B,MAAM,QAAQ,CAAC,cAAc,eAAe;gBAC5C,IAAM,UAAU,mBACf,eAAc,cACd,gBAAe,eACf,aAAY,YACZ,OAAM,MACN,aAAY,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,IAC5C,aAAY,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC,EAC7C,MAAK;gBAEN,IAAI,CAAC,OAAO,GAAG;gBACf,IAAI,CAAC,IAAI,GAAG;gBACZ,SAAO;SACP;IAAD;IAKA,SAAA,cAAe,kBAAiB;QAC/B,OAGC,kBAFA,UAAS,IAAI,CAAC,OAAO,EACrB,OAAM,IAAI,CAAC,IAAI;IAEjB;IAEA,SAAM,OAAO,OAAQ,MAAM,EAAE,UAAW,MAAM,GAAI,WAAQ,eAAc;QAAA,OAAA,eAAA;gBACvE,IAAM,MAAM,MAAM,MAAM,OAAO,CAS9B,aARA,MAAK,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,mBACpB,SAAQ,QACR,UAAS,IACR,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,qBAEjB,OAAM,IAAE,WAAA,OAAO,cAAA,WACf,cAAa,qBACX,KAAK;gBACR,SAAO,IAAI,IAAI,CAAA,EAAA,CAAI;SACnB;IAAD;IASD,SAAM,OAAO,OAAQ,MAAM,EAAE,QAAU,MAAM,CAAO,EAAE,SAAW,oBAAmB,GAAI,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBAClH,IAAI,MAAM,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,YAAW,CAAA,CAAG;gBACvC,IAAI,yBAAU,wGACb,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,oBAChB,oBAAe,YAAU,CAAA,MAAM,QAAQ,GAAE,EAAA,CAAI,EAAA;gBAE9C,IAAI,iBAAS,MAAM,IAAK,KAAE;gBAC1B,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;oBACpB,IAAI,QAAQ,OAAO,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,CAAC,CAAC,QAAQ,OAAO,CAAA,EAAA,CAAI,EAAE;wBAAG,OAAO,IAAI,CAAC,UAAS,CAAA,CAAG,4BAAkB,CAAlB,mBAAmB,QAAQ,OAAO,CAAA,EAAA,CAAI;;oBACvH,IAAI,QAAQ,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,OAAO,IAAI,CAAC,SAAQ,CAAA,CAAG,QAAQ,KAAK;;oBAGrC,IAAI,QAAQ,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,CAAC,CAAC,QAAQ,KAAK,CAAA,EAAA,CAAI,EAAE;wBAAG,OAAO,IAAI,CAAC,SAAQ,CAAA,CAAG,4BAAkB,CAAlB,mBAAmB,QAAQ,KAAK,CAAA,EAAA,CAAI;;oBAChH,IAAI,QAAQ,SAAS,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,QAAQ,OAAO,CAAA,EAAA,CAAI,IAAI,EAAE;wBACzD,OAAO,CAAC,QAAQ,GAAG,KAAG,QAAQ,SAAS,KAAA,MAAI,QAAQ,OAAO;wBAC1D,OAAO,CAAC,aAAa,GAAG;;oBAKzB,IAAI,cAAc,QAAQ,KAAK,CAAA,EAAA,CAAI,QAAQ,QAAQ;oBACnD,IAAI,YAAW,EAAA,CAAI,IAAI,EAAE;wBACxB,OAAO,CAAC,SAAS,GAAG,WAAS;;oBAG9B,IAAI,QAAQ,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;wBAGzB,IAAI,OAAO,CAAC,SAAS,CAAA,EAAA,CAAI,IAAI,EAAE;4BAC9B,OAAO,CAAC,SAAS,GAAG,CAAC,OAAO,CAAC,SAAS,CAAA,EAAA,CAAI,MAAM,EAAC,CAAA,CAAG;0BAC9C,IAEN,CAFM;4BACN,OAAO,CAAC,SAAS,GAAG;;;oBAItB,IAAI,QAAQ,MAAM,CAAA,EAAA,CAAI,IAAI,EAAE;wBAE3B,IAAI,OAAO,CAAC,SAAS,CAAA,EAAA,CAAI,IAAI,EAAE;4BAC9B,OAAO,CAAC,SAAS,GAAG,CAAC,OAAO,CAAC,SAAS,CAAA,EAAA,CAAI,MAAM,EAAC,CAAA,CAAG;0BAC9C,IAEN,CAFM;4BACN,OAAO,CAAC,SAAS,GAAG;;;oBAKtB,IAAI,QAAQ,OAAO,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC5B,OAAO,IAAI,CAAC;sBACN,IAEN,CAFM,IAAI,QAAQ,OAAO,CAAA,EAAA,CAAI,IAAI;wBACjC,OAAO,IAAI,CAAC;;kBAEP,IAEN,CAFM;oBACN,OAAO,IAAI,CAAC;;gBAGb,IAAI,OAAM,EAAA,CAAE,IAAI,CAAA,EAAA,CAAI,OAAM,GAAA,CAAK,IAAI;oBAClC,OAAO,IAAI,CAAC;;gBAEb,IAAI,OAAO,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;oBACtB,OAAO,IAAG,CAAA,CAAG,OAAO,IAAI,CAAC;;gBAM1B,IAAI,YAAY,MAAK,GAAY;gBACjC,IAAI,QAAO,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,QAAQ,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;oBAC5C,aAAa;;gBAId,IAAI,aAAa,aAChB,MAAA,KACA,SAAQ,YACR,UAAA;gBAED,SAAO,MAAM,IAAI,CAAC,sBAAsB,CAAC;SACzC;IAAD;IAEA,SAAM,WAAW,OAAQ,MAAM,EAAE,QAAU,cAAoB,EAAE,SAAW,oBAAmB,GAAI,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBAC7H,IAAM,aAAa,yBAAyB;gBAC5C,SAAO,IAAI,CAAC,MAAM,CAAC,OAAM,YAAW;SACpC;IAAD;IAOC,SAAM,OAAO,OAAQ,MAAM,EAAE,QAA0C,GAAI,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBACrG,IAAM,MAAM,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,YAAW,CAAA,CAAG;gBACzC,IAAM,yBAAU,wGACf,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,oBAChB,oBAAe,YAAU,CAAA,MAAM,QAAQ,GAAE,EAAA,CAAI,EAAA,IAC7C,YAAQ;gBAKT,IAAI,aAAa,aAChB,MAAA,KACA,SAAQ,QACR,UAAA,SACA,OAAM,KACN,cAAa;gBAEd,SAAO,MAAM,IAAI,CAAC,sBAAsB,CAAC;SACzC;IAAD;IASD,SAAM,OAAO,OAAQ,MAAM,EAAE,QAAS,MAAM,CAAO,EAAE,QAAS,aAAa,GAAI,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBACzG,IAAI,MAAM,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,YAAW,CAAA,CAAG;gBACvC,IAAI,OAAM,EAAA,CAAE,IAAI,CAAA,EAAA,CAAI,OAAM,GAAA,CAAK,IAAI;oBAClC,OAAO,IAAG,CAAA,CAAG;;gBAEd,IAAM,yBAAU,wGACf,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,oBAChB,oBAAe,YAAU,CAAA,MAAM,QAAQ,GAAE,EAAA,CAAI,EAAA,IAC7C,YAAQ;gBAET,IAAI,aAAa,aAChB,MAAA,KACA,SAAQ,SACR,UAAA,SACA,OAAM,QACN,cAAa;gBAEd,SAAO,MAAM,IAAI,CAAC,sBAAsB,CAAC;SACzC;IAAD;IAQA,SAAM,SAAO,OAAQ,MAAM,EAAE,QAAS,MAAM,CAAO,GAAI,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBACjF,IAAI,MAAM,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,YAAW,CAAA,CAAG;gBACvC,IAAI,OAAM,EAAA,CAAE,IAAI,CAAA,EAAA,CAAI,OAAM,GAAA,CAAK,IAAI;oBAClC,OAAO,IAAG,CAAA,CAAG;;gBAEd,IAAM,yBAAU,wGACf,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,oBAChB,oBAAe,YAAU,CAAA,MAAM,QAAQ,GAAE,EAAA,CAAI,EAAA,IAC7C,YAAQ;gBAET,IAAI,aAAa,aAChB,MAAA,KACA,SAAQ,UACR,UAAA,SACA,cAAa;gBAEd,SAAO,MAAM,IAAI,CAAC,sBAAsB,CAAC;SACzC;IAAD;IAQC,SAAM,IAAI,cAAe,MAAM,EAAE,QAAU,cAAa,GAAI,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBACtF,IAAM,MAAM,KAAG,IAAI,CAAC,OAAO,GAAA,kBAAgB;gBAC3C,IAAM,yBAAU,wGACf,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,oBAChB,oBAAe,YAAU,CAAA,MAAM,QAAQ,GAAE,EAAA,CAAI,EAAA;gBAE9C,IAAI,aAAa,aAChB,MAAA,KACA,SAAQ,QACR,UAAA,SACA,OAAM,OAAM,EAAA,CAAI,eAAE,EAClB,cAAa;gBAEd,SAAO,MAAM,IAAI,CAAC,sBAAsB,CAAC;SACzC;IAAD;IAKA,SAAA,KAAK,WAAY,MAAM,GAAI,mBAAkB;QAC5C,OAAO,AAAI,mBAAmB,IAAI,EAAE;IACrC;IAMG,SAAA,QAAQ,OAAO,MAAM,GAAG,sBAAqB;QACzC,OAAO,AAAI,sBAAsB,IAAI,EAAE;IAC3C;IAKA,SAAA,cAAc,SAAS,qBAAqB,GAAG,WAAQ,MAAM,EAAC;QAC1D,QAAQ,WAAW;QACnB,OAAO,WAAQ,OAAO,CAAC;IAC3B;IAEH,SAAM,kBAAmB,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACxC,IAAI,IAAI,CAAC,OAAO,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,IAAI,CAAC,OAAO,EAAE,cAAa,EAAA,CAAI,IAAI;oBAAE,SAAO,KAAK;;gBAC7E,IAAI;oBACH,IAAM,MAAM,MAAM,MAAM,OAAO,CAS9B,aARA,MAAK,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,2CACpB,SAAQ,QACR,UAAS,IACR,YAAQ,IAAI,CAAC,MAAM,EACnB,kBAAgB,qBAEjB,OAAM,IAAE,mBAAe,IAAI,CAAC,OAAO,EAAE,gBACrC,cAAa,qBACX,KAAK;oBACR,IAAI,IAAI,MAAM,CAAA,EAAA,CAAI,GAAG,CAAA,EAAA,CAAI,CAAC,IAAI,IAAI,CAAA,EAAA,CAAI,IAAI,GAAG;wBAC5C,IAAM,OAAO,IAAI,IAAI,CAAA,EAAA,CAAI;wBACzB,IAAM,eAAe,KAAK,SAAS,CAAC,gBAAe,EAAA,CAAI;wBACvD,IAAM,gBAAgB,KAAK,SAAS,CAAC,iBAAgB,EAAA,CAAI;wBACzD,IAAM,aAAa,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;wBACpD,IAAM,OAAO,KAAK,OAAO,CAAC;wBAC1B,IAAI,CAAC,OAAO,GAQX,mBAPA,eAAA,cACA,gBAAA,eACA,aAAA,YACA,OAAA,MACA,aAAY,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,IAC5C,aAAY,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC,EAC7C,MAAK;wBAEN,IAAI,CAAC,IAAI,GAAG;wBAEZ,MAAM,QAAQ,CAAC,cAAc,eAAe;wBAC5C,SAAO,IAAI;;oBAEZ,SAAO,KAAK;;iBACX,OAAO,cAAG;oBACX,SAAO,KAAK;;SAEb;IAAD;IAGA,SAAM,uBAAuB,wBAAyB,EAAE,mBAAU,KAAK,GAAI,yBAAsB,GAAG,GAAE;QAAA,OAAA,eAAA;gBACrG,IAAI,MAAM,MAAM,MAAM,OAAO,CAAC,YAAY,KAAK;gBAE/C,IAAM,eAAe,CAAC,IAAI,MAAM,CAAA,EAAA,CAAI,GAAG;gBAEvC,IAAM,iBAAiB,CAAC,IAAI,MAAM,CAAA,EAAA,CAAI,GAAG;gBACzC,IAAI,CAAC,aAAY,EAAA,CAAI,cAAc,EAAC,EAAA,CAAI,CAAC,SAAS;oBACjD,IAAM,KAAK,MAAM,IAAI,CAAC,cAAc;oBACpC,IAAI,IAAI;wBACP,IAAI,UAAU,WAAW,OAAO;wBAChC,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;4BACpB,UAAU,AAAI;;wBAEf,IAAI,oBAAO,OAAO,CAAA,MAAI,EAAA,EAAA,CAAI,YAAY;4BACrC,QAAQ,GAAG,CAAC,iBAAiB,YAAU,CAAA,MAAM,QAAQ,GAAE,EAAA,CAAI,EAAA;4BAC3D,WAAW,OAAO,GAAG;;wBAGtB,MAAM,MAAM,MAAM,OAAO,CAAC,YAAY,KAAK;sBACrC,IAON,CAPM;wBPzgCJ,sBO0gCoB;wBP1gCpB,sBO2gCoB;wBAGV,YAAmD;wBAC/D,MAAM,WAAW,eAAe,SAAU;;;gBAG5C,SAAO;SACP;IAAD;;AAID,IAAS,yBAAyB,kBAAS,cAAoB,GAAI,MAAM,CAAA;IAAvC,IAAA,SAM/B;IAJF,IAAI,OAAM,EAAA,CAAI,IAAI;QAAE,OAAO;;IAE3B,IAAI,oBAAO,MAAM,CAAA,MAAI,EAAA,GAAA,CAAK,YAAY;QACrC,IAAI;YACH,SAAS,AAAI,cAAc,OAAM,EAAA,CAAI,GAAG;;SACvC,OAAO,cAAG;YACX,aAAoD,iCAAiC;YACrF,OAAO;;;IAGT,IAAM,iBAAS,MAAM,IAAK,KAAE;IAC5B,IAAM,eAAO,MAAM,IAAK,cAAc,IAAI,CAAC;QAC3C;QAAK,IAAI,YAAI,CAAC;QAAd,MAAgB,EAAC,CAAA,CAAG,KAAK,MAAM;YAC9B,IAAM,IAAI,IAAI,CAAC,EAAE;YACjB,IAAM,IAAI,OAAO,GAAG,CAAC;YACrB,IAAI,EAAC,EAAA,CAAI,KAAI,EAAA,CAAI,oBAAO,GAAC,EAAA,CAAI,UAAU;gBACtC,OAAO,IAAI,CAAC,SAAO,EAAC,EAAA,CAAA,MAAA,GAAA;gBAJW;gBAK/B,QAAS;;YAEV,IAAI,EAAC,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,oBAAO,GAAC,EAAA,CAAI,SAAQ,EAAA,CAAI,oBAAO,CAAC,EAAC,EAAA,CAAI,aAAa,CAAC,CAAA,MAAI,EAAA,EAAA,CAAI,YAAY;gBACvF,IAAM,OAAO,EAAC,EAAA,CAAI;gBAClB,IAAM,SAAS,cAAc,IAAI,CAAC;oBAClC;oBAAK,IAAI,YAAI,CAAC;oBAAd,MAAgB,EAAC,CAAA,CAAG,OAAO,MAAM;wBAChC,IAAM,KAAK,MAAM,CAAC,EAAE;wBACpB,IAAM,QAAQ,KAAK,GAAG,CAAC;wBACvB,IAAI,CAAC,GAAE,EAAA,CAAI,KAAI,EAAA,CAAI,GAAE,EAAA,CAAI,QAAQ,EAAC,EAAA,CAAI,SAAM,OAAO,CAAC,QAAQ;4BAC3D,OAAO,IAAI,CAAC,KAAG,IAAC,MAAI,KAAE,OAAK,CAAA,MAAK,EAAA,UAAA,GAAA,CAAA,EAAC,GAAG,CAAC,IAAA,IAAC,MAAA;uCAAI,IAAA,oBAAO,GAAC,EAAA,CAAI,UAAW;oCAAA,4BAAkB,CAAlB,mBAAmB,KAAK,SAAS,CAAC;gCAAlC,EAAwC,IAAkB,CAAlB;oCAAA,4BAAkB,CAAlB,mBAAmB,EAAE,QAAQ;gCAA7B,CAAgC;+BAAE,IAAI,CAAC,OAAI;0BAC9I,IAKN,CALM,IAAI,GAAE,EAAA,CAAI,KAAI,EAAA,CAAI,CAAC,MAAK,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,MAAK,EAAA,CAAI,MAAM,GAAG;4BAC5D,OAAO,IAAI,CAAC,KAAG,IAAC;0BACV,IAGN,CAHM;4BACN,IAAM,UAAW,MAAM,GAAG,IAAA,CAAC,oBAAO,OAAK,EAAA,CAAI,QAAQ,GAAI;gCAAA,KAAK,SAAS,CAAC;4BAAK,EAAI,IAAiB,CAAjB;gCAAA,CAAC,MAAK,EAAA,CAAI,MAAM;4BAAA,CAAC;4BAChG,OAAO,IAAI,CAAC,KAAG,IAAC,MAAI,KAAE,MAAI,4BAAkB,CAAlB,mBAAmB;yBAC7C;wBAViC;;;cAY7B,IAUN,CAVM,IAAI,EAAC,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,oBAAO,GAAC,EAAA,CAAI,UAAU;gBAC7C,IAAM,OAAO,EAAC,EAAA,CAAI;gBAClB,IAAM,SAAS,cAAc,IAAI,CAAC;oBAClC;oBAAK,IAAI,YAAI,CAAC;oBAAd,MAAgB,EAAC,CAAA,CAAG,OAAO,MAAM;wBAChC,IAAM,KAAK,MAAM,CAAC,EAAE;wBACpB,IAAM,QAAQ,KAAK,GAAG,CAAC;wBACvB,OAAO,IAAI,CAAC,KAAG,IAAC,MAAI,KAAE,MAAI,4BAAkB,CAAlB,mBAAmB,IAAA,CAAC,CAAC,MAAK,EAAA,CAAI,IAAI,GAAK;4BAAA,IAAA,oBAAO,OAAK,EAAA,CAAI,UAAW;gCAAA,KAAK,SAAS,CAAC;4BAAK,EAAI,IAAgB,CAAhB;gCAAA,MAAM,QAAQ;4BAAA,CAAE;wBAAF,EAAM,IAAE,CAAF;4BAAA;wBAAA,CAAE;wBAHxG;;;cAK7B,IAEN,CAFM;gBACN,OAAO,IAAI,CAAC,KAAG,IAAC,SAAO,4BAAkB,CAAlB,mBAAmB,IAAA,CAAC,CAAC,EAAC,EAAA,CAAI,IAAI,GAAI;oBAAA,EAAE,QAAQ;gBAAA,EAAK,IAAE,CAAF;oBAAA;gBAAA;gBAAE;;YA/B3C;;;IAkCjC,OAAO,OAAO,IAAI,CAAC;AACpB;AAOM,IAAU,aAAa,KAAM,MAAM,EAAE,KAAM,MAAM,GAAI,OAAM;IAChE,OAAO,AAAI,OAAO,KAAK;AACxB;AAGM,WAAO;;;;IACT,YAAQ,OAAO,MAAO;IACtB,YAAQ,QAAQ,MAAM,AAAC;IACvB,YAAQ,QAAQ,MAAM,GAAG,CAAC,AAAC;IAC3B,YAAQ,aAAa,SAAS,GAAG,KAAK,IAAI,KAAW,IAAI,AAAC;IAC1D,YAAQ,QAAQ,MAAM,GAAG,EAAG;IAC5B,YAAQ,WAAW,MAAM,GAAG,AAAI,OAAO,WAAW,EAAG;IACrD,YAAQ,eAAe,OAAO,GAAG,KAAK,AAAC;IAEvC,YAAY,SAAM,MAAM,EAAE,OAAO,MAAM,CAAA;QACnC,IAAI,CAAC,KAAK,GAAG;QACb,IAAI,CAAC,MAAM,GAAG;IAClB;IAGA,SAAA,GAAG,MAAM,MAAM,EAAE,QAAQ,aAAa,EAAE,WAAW,SAAS,GAAG,KAAK,IAAI,GAAG,sBAAqB;QAE5F,IAAM,QAAQ,OAAO,SAAS,CAAC;QAC/B,IAAI,MAAK,EAAA,CAAI,IAAI,EAAE;YACf,IAAI,CAAC,MAAM,GAAG;;QAElB,IAAI,CAAC,SAAS,GAAG;QACjB,OAAO,IAAI;IACf;IAGA,SAAA,UAAU,YAAY,QAAQ,MAAM,EAAE,KAAK,GAAG,MAAY,IAAI,EAAA,GAAG,sBAAqB;QAClF,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO,IAAI;;QACnC,IAAI,CAAC,aAAa,GAAG,IAAI;QAGzB,IAAI,SAAQ,EAAA,CAAI,IAAI,EAAE;YAClB,SAAS,cAAc,IAAI;;QAI/B,IAAI,IAAI,CAAC,MAAM,CAAA,EAAA,CAAI,IAAI;YACnB,aAAoD;YACpD,OAAO,IAAI;;QAIf,IAAI,CAAC,MAAM,GAAG,YAAY,KAAK;YAC1B,IAAI,CAAC,aAAa;QACvB;UAAG,IAAI;QAEP,OAAO,IAAI;IACf;IAGA,SAAA,cAAW;QACP,IAAI,IAAI,CAAC,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;YACjB,cAAc,IAAI,CAAC,MAAM;YACzB,IAAI,CAAC,MAAM,GAAG,CAAC;;QAEnB,IAAI,CAAC,aAAa,GAAG,KAAK;IAC9B;IAGA,YAAc,iBAAa,WAAA,IAAA,EAAA;QAAA,OAAA,eAAA;gBACvB,IAAI,CAAC,IAAI,CAAC,aAAa,CAAA,EAAA,CAAI,IAAI,CAAC,MAAM,CAAA,EAAA,CAAI;oBAAI;;gBAE9C,IAAI;oBACA,IAAM,MAAM,AAAI,OAAO,WAAW;oBAElC,IAAM,MAAM,MAAM,IAAI,CAAC,KAAK,CACvB,IAAI,CAAC,IAAI,CAAC,MAAM,EAChB,MAAM,CAAC,KACP,EAAE,CAAC,cAAc,IAAI,CAAC,SAAS,EAC/B,KAAK,CAAC,cAAiC,aAAjB,YAAW,IAAI,GACrC,OAAO;oBAEZ,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,IAAI,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;wBACvC,IAAI,eAAM,GAAG,IAAK,KAAE;wBACpB,IAAI,SAAM,OAAO,CAAC,IAAI,IAAI,GAAG;4BACxB,OAAO,IAAI,IAAI,CAAA,EAAA,UAAI,GAAG;;wBAG3B,IAAI,KAAK,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;4BAEjB,IAAM,WAAW,IAAI,CAAC,KAAK,MAAM,CAAA,CAAA,CAAG,CAAC,CAAC;4BACtC,IAAI,aAAa,MAAM,IAAU,IAAI;4BAErC,IAAI,SAAQ,EAAA,CAAY,eAAe;gCAClC,cAAc,CAAA,SAAQ,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;8BAC/B,IAIN,CAJM;gCAEF,IAAM,IAAI,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,qDAArB,EAAA,CAAmC;gCAClD,cAAc,EAAE,SAAS,CAAC;;4BAG/B,IAAI,YAAW,EAAA,CAAI,IAAI,EAAE;gCACrB,IAAI,CAAC,SAAS,GAAG;8BACd,IAEN,CAFM;gCACH,IAAI,CAAC,SAAS,GAAG;;4BAIrB,IAAI,IAAI,CAAC,SAAS,CAAA,EAAA,CAAI,IAAI,EAAE;gCAExB,KAAK,OAAO,CAAC,IAAA,KAAO;oCAChB,IAAM,yBAAU;wCACZ,IAAA,QAAK;wCACL,IAAA,YAAW;wCACX,IAAA,MAAK,IAAI;qCACZ;oCACD,IAAI,CAAC,SAAS,SAAG;gCACrB;;;;;;iBAId,OAAO,cAAG;oBACR,cAAqD,2BAA2B;;SAEvF;IAAD;;AC5uCJ,IAAM,eAAe;AAsBd,IAAM,YAAY,WAAQ,OAAO,CAAC,IAAI;ACzBtB,WAAX;IACX;iBAAI,MAAM,CAAA;IACV;oBAAO,MAAM,CAAA;IACb,gBAAO,MAAM,SAAO;IACpB,mBAAU,MAAM,SAAO;IACvB,qBAAY,MAAM,SAAO;IACzB;qBAAQ,MAAM,CAAA;IACd;wBAAW,MAAM,CAAA;IACjB;qBAAQ,MAAM,CAAA;IACd;yBAAY,MAAM,CAAA;;;;;;;;;MATP,yBAAA,uBAAA;;;;;+GACX,aAAA,IACA,gBAAA,OACA,gBAAA,OACA,mBAAA,UACA,qBAAA,YACA,iBAAA,QACA,oBAAA,WACA,iBAAA,QACA,qBAAA;;;;;;;eATW;;iBACX,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;AA8C0B,WAAf;IACX;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;wBAAW,MAAM,CAAA;IACjB,oBAAW,MAAM,SAAO;IACxB,sBAAa,MAAM,SAAO;IAC1B,2BAAkB,MAAM,SAAO;IAC/B;2BAAc,MAAM,CAAA;IACpB;4BAAe,MAAM,CAAA;IACrB;0BAAa,MAAM,CAAA;IACnB;qBAAQ,MAAM,CAAA;IACd;0BAAa,MAAM,CAAA;IACnB;yBAAY,MAAM,CAAA;;;;;;;;;MAZP,6BAAA,2BAAA;;;;;mHACX,aAAA,IACA,kBAAA,SACA,oBAAA,WACA,oBAAA,WACA,sBAAA,aACA,2BAAA,kBACA,uBAAA,cACA,wBAAA,eACA,sBAAA,aACA,iBAAA,QACA,sBAAA,aACA,qBAAA;;;;;;;eAZW;;iBACX,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,kBAAkB,MAAM;;6DAAxB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;AAIyB,WAAd;IACX;iBAAI,MAAM,CAAA;IACV;0BAAa,MAAM,CAAA;IACnB;0BAAa,MAAM,CAAA;IACnB;mBAAM,MAAM,CAAA;IACZ,sBAAa,MAAM,SAAO;IAC1B;qBAAQ,SAAM,MAAM,EAAC;IACrB;oBAAO,MAAM,CAAA;IACb,yBAAgB,MAAM,SAAO;IAC7B;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;IACb;qBAAQ,MAAM,CAAA;IACd;yBAAY,MAAM,CAAA;IAElB,wBAAgB,MAAM,SAAO;IAC7B,gBAAQ,MAAM,SAAO;IACrB,uBAAe,MAAM,SAAO;IAC5B,sBAAc,MAAM,SAAO;IAC3B,sBAAc,MAAM,SAAO;IAC3B,6BAAqB,MAAM,SAAO;IAClC,0BAAkB,MAAM,SAAO;IAC/B,eAAO,SAAM,MAAM,UAAQ;;;;;;;;;MArBhB,4BAAA,0BAAA;;;;;kHACX,aAAA,IACA,sBAAA,aACA,sBAAA,aACA,eAAA,MACA,sBAAA,aACA,iBAAA,QACA,gBAAA,OACA,yBAAA,gBACA,gBAAA,OACA,gBAAA,OACA,iBAAA,QACA,qBAAA,YAEA,wBAAA,eACA,gBAAA,OACA,uBAAA,cACA,sBAAA,aACA,sBAAA,aACA,6BAAA,oBACA,0BAAA,iBACA,eAAA;;;;;;;eArBW;;iBACX,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,SAAM,MAAM;;mDAApB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBAEA,eAAgB,MAAM;;0DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,OAAQ,MAAM;;kDAAd;;;;;;mCAAA;oBAAA;;;iBACA,cAAe,MAAM;;yDAArB;;;;;;mCAAA;oBAAA;;;iBACA,aAAc,MAAM;;wDAApB;;;;;;mCAAA;oBAAA;;;iBACA,aAAc,MAAM;;wDAApB;;;;;;mCAAA;oBAAA;;;iBACA,oBAAqB,MAAM;;+DAA3B;;;;;;mCAAA;oBAAA;;;iBACA,iBAAkB,MAAM;;4DAAxB;;;;;;mCAAA;oBAAA;;;iBACA,MAAO,SAAM,MAAM;;iDAAnB;;;;;;mCAAA;oBAAA;;;;AAI4B,WAAjB;IACX;iBAAI,MAAM,CAAA;IACV;yBAAY,MAAM,CAAA;IAClB;uBAAU,MAAM,CAAA;IAChB,yBAAgB,sBAAoB;IACpC;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;IACb,oBAAW,MAAM,SAAO;IACxB;qBAAQ,MAAM,CAAA;;;;;;;;;MARH,+BAAA,6BAAA;;;;;qHACX,aAAA,IACA,qBAAA,YACA,mBAAA,UACA,yBAAA,gBACA,gBAAA,OACA,gBAAA,OACA,oBAAA,WACA,iBAAA;;;;;;;eARW;;iBACX,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB;;2DAAhB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;;AAgBuB,WAAZ;IACX;iBAAI,MAAM,CAAA;IACV;uBAAU,MAAM,CAAA;IAChB;sBAAS,MAAM,CAAA;IACf;0BAAa,MAAM,CAAA;IACnB;qBAAQ,MAAM,CAAA;IACd;2BAAc,MAAM,CAAA;IACpB;8BAAiB,MAAM,CAAA;IACvB;2BAAc,MAAM,CAAA;IACpB;4BAAe,MAAM,CAAA;IACrB,yBAAgB,MAAM,SAAO;IAC7B;6BAAgB,MAAM,CAAA;IACtB;+BAAkB,cAAa;IAC/B;yBAAY,MAAM,CAAA;;;;;;;;;MAbP,0BAAA,wBAAA;;;;;gHACX,aAAA,IACA,mBAAA,UACA,kBAAA,SACA,sBAAA,aACA,iBAAA,QACA,uBAAA,cACA,0BAAA,iBACA,uBAAA,cACA,wBAAA,eACA,yBAAA,gBACA,yBAAA,gBACA,2BAAA,kBACA,qBAAA;;;;;;;eAbW;;iBACX,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,iBAAiB,MAAM;;4DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,kBAAkB;;6DAAlB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;AAuDgC,WAArB;IACX;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ,sBAAa,MAAM,SAAO;IAC1B;0BAAa,MAAM,CAAA;IACnB;4BAAe,MAAM,CAAA;IACrB;6BAAgB,MAAM,CAAA;IACtB;+BAAkB,MAAM,CAAA;IACxB,8BAAqB,MAAM,SAAO;IAClC,yBAAgB,MAAM,SAAO;IAC7B;6BAAgB,MAAM,CAAA;IACtB;0BAAa,MAAM,CAAA;IACnB,sBAAa,MAAM,SAAO;IAC1B;2BAAc,SAAM,MAAM,EAAC;IAC3B;0BAAa,SAAM,MAAM,EAAC;IAC1B,0BAAiB,MAAM,SAAO;IAC9B;yBAAY,MAAM,CAAA;IAClB;uBAAU,MAAM,CAAA;IAChB;qBAAQ,MAAM,CAAA;IACd;yBAAY,MAAM,CAAA;;;;;;;;;MAnBP,mCAAA,iCAAA;;;;;yHACX,aAAA,IACA,eAAA,MACA,sBAAA,aACA,sBAAA,aACA,wBAAA,eACA,yBAAA,gBACA,2BAAA,kBACA,8BAAA,qBACA,yBAAA,gBACA,yBAAA,gBACA,sBAAA,aACA,sBAAA,aACA,uBAAA,cACA,sBAAA,aACA,0BAAA,iBACA,qBAAA,YACA,mBAAA,UACA,iBAAA,QACA,qBAAA;;;;;;;eAnBW;;iBACX,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,kBAAkB,MAAM;;6DAAxB;;;;;;mCAAA;oBAAA;;;iBACA,qBAAqB,MAAM;;gEAA3B;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,SAAM,MAAM;;yDAA1B;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,SAAM,MAAM;;wDAAzB;;;;;;mCAAA;oBAAA;;;iBACA,iBAAiB,MAAM;;4DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;AAkCM,IAAM,8BAAe;IAC3B,IAAA,0BAAiB,CAAC;IAClB,IAAA,eAAM,CAAC;IACP,IAAA,kBAAS,CAAC;IACV,IAAA,oBAAW,CAAC;IACZ,IAAA,oBAAW,CAAC;IACZ,IAAA,oBAAW,CAAC;IACZ,IAAA,oBAAW,CAAC;IACZ,IAAA,mBAAU,CAAC;CACX;AAGM,IAAM,6BAAc;IAC1B,IAAA,0BAAiB,CAAC;IAClB,IAAA,2BAAkB,CAAC;IACnB,IAAA,wBAAe,CAAC;IAChB,IAAA,iBAAQ,CAAC;IACT,IAAA,iBAAQ,CAAC;IACT,IAAA,mBAAU,CAAC;IACX,IAAA,mBAAU,CAAC;IACX,IAAA,uBAAc,CAAC;CACf;AAGM,IAAM,gCAAiB;IAC7B,IAAA,iBAAQ,CAAC;IACT,IAAA,iBAAQ,CAAC;IACT,IAAA,mBAAU,CAAC;IACX,IAAA,kBAAS,CAAC;CACV;AAGM,IAAM,iCAAkB;IAC9B,IAAA,kBAAS,CAAC;IACV,IAAA,mBAAU,CAAC;IACX,IAAA,oBAAW,CAAC;IACZ,IAAA,qBAAY,CAAC;IACb,IAAA,oBAAW,CAAC;IACZ,IAAA,iBAAQ,CAAC;CACT;AAGM,IAAM,gCAAiB;IAC7B,IAAA,mBAAU,CAAC;IACX,IAAA,mBAAU,CAAC;IACX,IAAA,mBAAU,CAAC;IACX,IAAA,kBAAS,CAAC;IACV,IAAA,gBAAO,CAAC;CACR;AAWM,IAAM,qCAAsB;IAClC,IAAA,qBAAY,CAAC;IACb,IAAA,mBAAU,CAAC;IACX,IAAA,iBAAQ,CAAC;CACT;AAGM,IAAM,+BAAgB;IAC5B,IAAA,OAAM;IACN,IAAA,SAAQ;IACR,IAAA,SAAQ;IACR,IAAA,QAAO;CACP;AAGM,IAAM,+BAAgB;IAC5B,IAAA,UAAS;IACT,IAAA,OAAM;CACN;AAOM,IAAM,qCAAsB;IAClC,IAAA,UAAS;IACT,IAAA,SAAQ;CACR;AAGM,IAAM,qCAAsB;IAClC,IAAA,QAAO;IACP,IAAA,SAAQ;IACR,IAAA,WAAU;IACV,IAAA,WAAU;IACV,IAAA,UAAS;CACT;AAoCyB,WAAd;IACV,aAAK,MAAM,SAAC;IACZ;uBAAU,MAAM,CAAC;IACjB;oBAAO,MAAM,CAAC;IACd,iBAAS,MAAM,SAAC;IAChB,mBAAW,MAAM,SAAC;IAClB,oBAAY,MAAM,SAAC;IACnB,oBAAY,MAAM,SAAC;IACnB,cAAM,MAAM,SAAC;IACb,qBAAa,MAAM,SAAC;IACpB,6BAAqB,MAAM,SAAC;IAC5B,eAAO,MAAM,SAAC;IACd,oBAAY,MAAM,SAAC;IACnB,mBAAW,MAAM,SAAC;IAClB,mBAAW,MAAM,SAAC;IAClB,qBAAa,MAAM,SAAC;IACpB,qBAAa,MAAM,SAAC;;;;;;;;;MAhBV,+BAAA,6BAAA;;;;;qHACV,aAAA,IACA,mBAAA,UACA,gBAAA,OACA,iBAAA,QACA,mBAAA,UACA,oBAAA,WACA,oBAAA,WACA,cAAA,KACA,qBAAA,YACA,6BAAA,oBACA,eAAA,MACA,oBAAA,WACA,mBAAA,UACA,mBAAA,UACA,qBAAA,YACA,qBAAA;;;;;;;eAhBU;;iBACV,IAAK,MAAM;;+CAAX;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,QAAS,MAAM;;mDAAf;;;;;;mCAAA;oBAAA;;;iBACA,UAAW,MAAM;;qDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,KAAM,MAAM;;gDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,oBAAqB,MAAM;;+DAA3B;;;;;;mCAAA;oBAAA;;;iBACA,MAAO,MAAM;;iDAAb;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,UAAW,MAAM;;qDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,UAAW,MAAM;;qDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;;ACvYuB,WAAb;IACX;iBAAI,MAAM,CAAA;IACV,sBAAc,MAAM,SAAA;IACpB,iBAAS,MAAM,SAAA;IACf,kBAAU,MAAM,SAAA;;;;;;;;;MAJL,2BAAA,yBAAA;;;;;iHACX,aAAA,IACA,sBAAA,aACA,iBAAA,QACA,kBAAA;;;;;;;eAJW;;iBACX,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,aAAc,MAAM;;wDAApB;;;;;;mCAAA;oBAAA;;;iBACA,QAAS,MAAM;;mDAAf;;;;;;mCAAA;oBAAA;;;iBACA,SAAU,MAAM;;oDAAhB;;;;;;mCAAA;oBAAA;;;;ACGM,IAAe,kBAAkB,aAAa,aAAa,GAAG,4BAA2B;IAAA,OAAA,eAAA;YAC/F,IAAI;gBACH;gBAGA,IAAM,SAAS,YAAY,SAAS,CAAC;gBACrC,IAAM,QAAQ,YAAY,SAAS,CAAC,SAAQ,EAAA,CAAI;gBAEhD,IAAI,OAAM,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,OAAM,GAAA,CAAK,IAAI;oBACpC,cAAqC;oBACrC,SAAO,IAAI;;gBAIZ,IAAM,WAAW,MAAM,aAAS,IAAI,CAAC,YACnC,MAAM,CAAC,KAAK,eAAE,EACd,EAAE,CAAC,WAAW,QACd,MAAM,GACN,OAAO;gBAET,YAAmC,qCAAqC;oBACvE,IAAA,SAAQ,SAAS,MAAM;oBACvB,IAAA,UAAS,SAAS,IAAI,CAAA,EAAA,CAAI,IAAI;iBAC9B;gBAED,IAAI,SAAS,MAAM,CAAA,EAAA,CAAI,GAAG,CAAA,EAAA,CAAI,SAAS,MAAM,CAAA,CAAA,CAAG,GAAG,CAAA,EAAA,CAAI,SAAS,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;oBAE7E,IAAM,OAAO,SAAS,IAAI;oBAC1B,IAAI,cAAc;oBAClB,IAAI,KAAI,EAAA,CAAY,eAAe;wBAClC,eAAe,KAAI,EAAA,CAAA;sBACb,IAEN,CAFM;wBACN,eAAe,AAAI,cAAc;;oBAElC,SAcK,eAbJ,KAAI,aAAa,SAAS,CAAC,MAAK,EAAA,CAAI,IACpC,WAAU,aAAa,SAAS,CAAC,YAAW,EAAA,CAAI,IAChD,QAAO,aAAa,SAAS,CAAC,SAAQ,EAAA,CAAI,OAC1C,SAAQ,aAAa,SAAS,CAAC,WAC/B,WAAU,aAAa,SAAS,CAAC,aACjC,YAAW,aAAa,SAAS,CAAC,cAClC,YAAW,aAAa,SAAS,CAAC,cAClC,MAAK,aAAa,SAAS,CAAC,QAC5B,aAAY,aAAa,SAAS,CAAC,eACnC,qBAAoB,aAAa,SAAS,CAAC,uBAC3C,OAAM,aAAa,SAAS,CAAC,QAAO,EAAA,CAAI,YACxC,aAAY,aAAa,SAAS,CAAC,eACnC,aAAY,aAAa,SAAS,CAAC;;gBAKrC,IAAM,cAAc,AAAI;gBACxB,YAAY,GAAG,CAAC,MAAM;gBACtB,YAAY,GAAG,CAAC,SAAS;gBACzB,YAAY,GAAG,CAAC,YAAY,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA,EAAA,CAAI;gBAEnD,IAAM,YAAY,MAAM,aAAS,IAAI,CAAC,YACpC,MAAM,CAAC,aACP,MAAM,CAAC,KAAK,eAAE,EACd,MAAM,GACN,OAAO;gBAET,YAAmC,6CAA6C,UAAU,MAAM;gBAEhG,IAAI,UAAU,MAAM,CAAA,EAAA,CAAI,GAAG,CAAA,EAAA,CAAI,UAAU,MAAM,CAAA,CAAA,CAAG,GAAG,CAAA,EAAA,CAAI,UAAU,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;oBAChF,IAAM,UAAU,UAAU,IAAI;oBAC9B,IAAM,UAAU,IAAA,CAAC,QAAO,EAAA,CAAY,aAAa,GAC9C;wBAAA,CAAC,QAAO,EAAA,CAAI,aAAa;oBAAA,EACzB,IAA0B,CAA1B;wBAAI,cAAc;oBAAO,CAAC;oBAC7B,SAcK,eAbJ,KAAI,QAAQ,SAAS,CAAC,MAAK,EAAA,CAAI,IAC/B,WAAU,QAAQ,SAAS,CAAC,YAAW,EAAA,CAAI,IAC3C,QAAO,QAAQ,SAAS,CAAC,SAAQ,EAAA,CAAI,OACrC,SAAQ,QAAQ,SAAS,CAAC,WAC1B,WAAU,QAAQ,SAAS,CAAC,aAC5B,YAAW,QAAQ,SAAS,CAAC,cAC7B,YAAW,QAAQ,SAAS,CAAC,cAC7B,MAAK,QAAQ,SAAS,CAAC,QACvB,aAAY,QAAQ,SAAS,CAAC,eAC9B,qBAAoB,QAAQ,SAAS,CAAC,uBACtC,OAAM,QAAQ,SAAS,CAAC,QAAO,EAAA,CAAI,YACnC,aAAY,QAAQ,SAAS,CAAC,eAC9B,aAAY,QAAQ,SAAS,CAAC;kBAEzB,IAGN,CAHM;oBACN,cAAqC,aAAa,UAAU,MAAM;oBAClE,SAAO,IAAI;;;aAEX,OAAO,kBAAO;gBACf,cAAqC,yBAAyB;gBAC9D,SAAO,IAAI;;KAEZ;AAAD;AC7F0B,WAAd;IACX;sBAAU,qBAAiB;IAC3B,2CAAiC;IACjC;wBAAY,OAAO,SAAA;IACnB,sBAAc,MAAM,SAAO;;;;;;;;;MAJhB,4BAAA,0BAAA;;;;;kHACX,kBAAA,SACA,wBAAA,eACA,oBAAA,WACA,sBAAA;;;;;;;eAJW;;iBACX,SAAU;;oDAAV;;;;;;mCAAA;oBAAA;;;iBACA;;0DAAA;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,OAAO;;sDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,aAAc,MAAM;;wDAApB;;;;;;mCAAA;oBAAA;;;;AAImB,WAAR;IACX;wBAAY,MAAM,CAAA;IAClB,6CAA0B;IAC1B;yBAAa,OAAO,SAAA;IACpB;0BAAc,YAAW;;;;;;;;;MAJd,sBAAA,oBAAA;;;;;4GACX,oBAAA,WACA,sBAAA,aACA,qBAAA,YACA,sBAAA;;;;;;;eAJW;;iBACX,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA;;wDAAA;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,OAAO;;uDAApB;;;;;;mCAAA;oBAAA;;;iBACA,aAAc;;wDAAd;;;;;;mCAAA;oBAAA;;;;AAKM,IAAM,QAAQ,SAUhB,MATJ,YAAW,CAAC,EACZ,cAAwC,eAAzB,WAAU,IAAI,QAAO,KACpC,aAAY,KAAK,EACjB,cAKK,YAJJ,UAAS,KAAE,EACX,gBAAe,IAAI,EACnB,YAAW,KAAK,EAChB,cAAa,IAAI;AAYZ,IAAM,oBAAiB,IAAC,wBAAyB;IACvD,MAAM,WAAW,GAAG;AACrB;AAGO,IAAe,kBAAmB,4BAA2B;IAAA,OAAA,eAAA;YACnE,IAAI;gBACH;;aACC,OAAO,cAAG,CAAA;YAEZ,IAAM,cAAc,aAAK,UAAU;YACnC,IAAI,YAAY,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;gBAC7B,MAAM,WAAW,GAAkC,eAA7B,WAAU,IAAI,QAAO;gBAC3C,MAAM,UAAU,GAAG,KAAK;gBACxB,SAAO,IAAI;;YAEZ,IAAM,SAAS,YAAY,IAAI,EAAE,UAAU;YAC3C,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;gBACnB,MAAM,WAAW,GAAkC,eAA7B,WAAU,IAAI,QAAO;gBAC3C,MAAM,UAAU,GAAG,KAAK;gBACxB,SAAO,IAAI;;YAEZ,IAAM,MAAM,MAAM,aAAK,IAAI,CAAC,YAAY,MAAM,CAAC,KAAK,eAAE,EAAE,EAAE,CAAC,MAAM,QAAQ,OAAO;YAChF,YAAoC;YACpC,IAAI,IAAI,MAAM,CAAA,EAAA,CAAI,GAAG,CAAA,EAAA,CAAI,IAAI,MAAM,CAAA,CAAA,CAAG,GAAG,CAAA,EAAA,CAAI,CAAC,IAAI,IAAI,CAAA,EAAA,CAAI,IAAI,GAAG;gBAChE,IAAI,MAAO,iBAAuB,IAAI;gBACtC,IAAM,OAAO,IAAI,IAAI,CAAA,EAAA,CAAI,GAAG;gBAC5B,IAAI,SAAM,OAAO,CAAC,OAAO;oBACxB,IAAI,CAAA,KAAI,EAAA,UAAA,GAAA,CAAA,EAAC,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;wBACpB,OAAO,CAAA,KAAI,EAAA,UAAA,GAAA,CAAA,CAAA,CAAC,CAAC,CAAC,CAAA,EAAA,CAAI;;kBAEb,IAEN,CAFM,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;oBACxB,OAAO,KAAI,EAAA,CAAI;;gBACd,YAAoC;gBACtC,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;oBACjB,YAAoC;oBACpC,IAAM,cAAc,YAAY,IAAI;oBACpC,IAAI,YAAW,EAAA,CAAI,IAAI,EAAE;wBACxB,IAAM,iBAAiB,MAAM,kBAAkB;wBAC/C,IAAI,eAAc,EAAA,CAAI,IAAI,EAAE;4BAC3B,MAAM,WAAW,GAAG;4BACpB,MAAM,UAAU,GAAG,IAAI;4BACvB,SAAO;0BACD,IAKN,CALM;4BACN,cAAsC;4BACtC,MAAM,WAAW,GAAkC,eAA7B,WAAU,IAAI,QAAO;4BAC3C,MAAM,UAAU,GAAG,KAAK;4BACxB,SAAO,IAAI;yBACX;sBACK,IAKN,CALM;wBACN,cAAsC;wBACtC,MAAM,WAAW,GAAkC,eAA7B,WAAU,IAAI,QAAO;wBAC3C,MAAM,UAAU,GAAG,KAAK;wBACxB,SAAO,IAAI;;;gBAGb,YAAqC;gBAErC,IAAM,UAAU,eACf,KAAI,KAAK,SAAS,CAAC,OACnB,WAAU,KAAK,SAAS,CAAC,YAAW,EAAA,CAAI,IACxC,QAAO,KAAK,SAAS,CAAC,SAAQ,EAAA,CAAI,IAClC,SAAQ,KAAK,SAAS,CAAC,WACvB,WAAU,KAAK,SAAS,CAAC,aACzB,YAAW,KAAK,SAAS,CAAC,cAC1B,YAAW,KAAK,SAAS,CAAC,cAC1B,MAAK,KAAK,SAAS,CAAC,QACpB,aAAY,KAAK,SAAS,CAAC,eAC3B,qBAAoB,KAAK,SAAS,CAAC,uBACnC,OAAM,KAAK,SAAS,CAAC,SACrB,YAAW,KAAK,SAAS,CAAC,cAC1B,WAAU,KAAK,SAAS,CAAC,aACzB,WAAU,KAAK,SAAS,CAAC;gBAE1B,MAAM,WAAW,GAAG;gBACpB,MAAM,UAAU,GAAG,IAAI;gBACvB,SAAO;cACD,IAIN,CAJM;gBACN,MAAM,WAAW,GAAkC,eAA7B,WAAU,IAAI,QAAO;gBAC3C,MAAM,UAAU,GAAG,KAAK;gBACxB,SAAO,IAAI;;KAEZ;AAAD;AAGM,IAAU,SAAM;IACrB,aAAK,OAAO;IACZ,MAAM,WAAW,GAAkC,eAA7B,WAAU,IAAI,QAAO;IAC3C,MAAM,UAAU,GAAG,KAAK;AACzB;AAGM,IAAU,oBAAqB,MAAM,CAAA;IAC1C,IAAI;QACH,IAAM,UAAU,MAAM,WAAW;QACjC,IAAI,QAAO,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,QAAQ,EAAE,CAAA,EAAA,CAAI,IAAI,EAAE;YAC1C,IAAM,YAAY,QAAQ,EAAE;YAC5B,IAAI,UAAS,EAAA,CAAI,IAAI,EAAE;gBACtB,OAAO;;;;KAGR,OAAO,cAAG,CAAA;IACZ,IAAI;QACH,IAAM,UAAU,aAAK,UAAU;QAC/B,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;YACpB,IAAM,UAAU,QAAQ,IAAI;YAC5B,IAAM,SAAS,SAAS,UAAU;YAClC,IAAI,OAAM,EAAA,CAAI,IAAI;gBAAE,OAAO;;;;KAE3B,OAAO,cAAG,CAAA;IACZ,OAAO;AACR;;;6CC5JA,EAAA;;;;;;;;;;;;;;;;;gDAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;ACCyB,WAAb;IACV,aAAK,MAAM,SAAC;IACZ;uBAAU,MAAM,CAAC;IACjB;oBAAO,MAAM,CAAC;IACd,iBAAS,MAAM,SAAC;IAChB,mBAAW,MAAM,SAAC;IAClB,oBAAY,MAAM,SAAC;IACnB,oBAAY,MAAM,SAAC;IACnB,cAAM,MAAM,SAAC;IACb,qBAAa,MAAM,SAAC;IACpB,6BAAqB,MAAM,SAAC;IAC5B,eAAM,MAAM,SAAC;IACb,oBAAY,MAAM,SAAC;IACnB,mBAAW,MAAM,SAAC;IAClB,mBAAW,MAAM,SAAC;;;;;;;;;MAdR,+BAAA,6BAAA;;;;;qHACV,aAAA,IACA,mBAAA,UACA,gBAAA,OACA,iBAAA,QACA,mBAAA,UACA,oBAAA,WACA,oBAAA,WACA,cAAA,KACA,qBAAA,YACA,6BAAA,oBACA,eAAA,MACA,oBAAA,WACA,mBAAA,UACA,mBAAA;;;;;;;eAdU;;iBACV,IAAK,MAAM;;+CAAX;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,QAAS,MAAM;;mDAAf;;;;;;mCAAA;oBAAA;;;iBACA,UAAW,MAAM;;qDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,KAAM,MAAM;;gDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,oBAAqB,MAAM;;+DAA3B;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,UAAW,MAAM;;qDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,UAAW,MAAM;;qDAAjB;;;;;;mCAAA;oBAAA;;;;AAI2B,WAAjB;IACV;iBAAI,MAAM,CAAC;IACX;mBAAM,MAAM,CAAC;IACb;mBAAM,MAAM,CAAC;IACb;0BAAa,MAAM,CAAC;;;;;;;;;MAJV,+BAAA,6BAAA;;;;;qHACV,aAAA,IACA,eAAA,MACA,eAAA,MACA,sBAAA;;;;;;;eAJU;;iBACV,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;;AAGsB,WAAZ;IACV;wBAAW,MAAM,CAAC;IAClB;qBAAQ,MAAM,CAAC;IACf;qBAAQ,MAAM,CAAC;;;;;;;;;MAHL,0BAAA,wBAAA;;;;;gHACV,oBAAA,WACA,iBAAA,QACA,iBAAA;;;;;;;eAHU;;iBACV,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;;;;;;;;;;;;;;;;;;sDD7BF,EAAA;;;;;;;;;;iDAAA,EAAA;;;;;;;;;;iDAAA,EAAA;;;;;;;;AEOoB,WAAR;IACV;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;0BAAa,MAAM,CAAA;;;;;;;;;MAJT,sBAAA,oBAAA;;;;;4GACV,aAAA,IACA,eAAA,MACA,mBAAA,UACA,sBAAA;;;;;;;eAJU;;iBACV,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;;AAGqB,WAAX;IACV;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;mBAAM,MAAM,CAAA;IACZ;0BAAa,MAAM,CAAA;IACnB;oBAAO,MAAM,CAAA;IACb,gBAAQ,MAAM,SAAA;IACd,oBAAY,MAAM,SAAA;IAClB,eAAO,MAAM,SAAA;IACb,qBAAa,MAAM,SAAA;;;;;;;;;MATT,yBAAA,uBAAA;;;;;+GACV,aAAA,IACA,eAAA,MACA,eAAA,MACA,sBAAA,aACA,gBAAA,OACA,gBAAA,OACA,oBAAA,WACA,eAAA,MACA,qBAAA;;;;;;;eATU;;iBACV,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAQ,MAAM;;kDAAd;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,MAAO,MAAM;;iDAAb;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;;AAGoB,WAAV;IACV;iBAAI,MAAM,CAAA;IACV;0BAAa,MAAM,CAAA;IACnB;0BAAa,MAAM,CAAA;IACnB;mBAAM,MAAM,CAAA;IACZ,mBAAW,MAAM,SAAA;IACjB,sBAAc,MAAM,SAAA;IACpB;yBAAY,MAAM,CAAA;IAClB,uBAAe,MAAM,SAAA;IACrB,qBAAa,MAAM,SAAA;IACnB,yBAAiB,MAAM,SAAA;IACvB,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;IACnB,sBAAc,MAAM,SAAA;IACpB,0BAAkB,MAAM,SAAA;IACxB,iBAAS,OAAO,SAAA;IAChB,iBAAS,OAAO,SAAA;IAChB,sBAAc,OAAO,SAAA;IACrB,iBAAS,MAAM,SAAA;IACf,qBAAa,MAAM,SAAA;IACnB,uBAAe,MAAM,SAAA;IACrB,eAAO,MAAM,SAAA;IACb,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;IAEnB,gBAAQ,MAAM,SAAA;IACd,yBAAiB,MAAM,SAAA;IACvB,gBAAQ,MAAM,SAAA;IACd,gBAAQ,MAAM,SAAA;IACd,iBAAS,MAAM,SAAA;IACf,gBAAQ,MAAM,SAAA;IAEd,qBAAa,MAAM,SAAA;IACnB,wBAAgB,MAAM,SAAA;IACtB,oBAAY,MAAM,SAAA;IAClB,wBAAgB,MAAM,SAAA;;;;;;;;;MAtCZ,wBAAA,sBAAA;;;;;8GACV,aAAA,IACA,sBAAA,aACA,sBAAA,aACA,eAAA,MACA,mBAAA,UACA,sBAAA,aACA,qBAAA,YACA,uBAAA,cACA,qBAAA,YACA,yBAAA,gBACA,qBAAA,YACA,qBAAA,YACA,qBAAA,YACA,qBAAA,YACA,sBAAA,aACA,0BAAA,iBACA,iBAAA,QACA,iBAAA,QACA,sBAAA,aACA,iBAAA,QACA,qBAAA,YACA,uBAAA,cACA,eAAA,MACA,qBAAA,YACA,qBAAA,YACA,qBAAA,YAEA,gBAAA,OACA,yBAAA,gBACA,gBAAA,OACA,gBAAA,OACA,iBAAA,QACA,gBAAA,OAEA,qBAAA,YACA,wBAAA,eACA,oBAAA,WACA,wBAAA;;;;;;;eAtCU;;iBACV,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,UAAW,MAAM;;qDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,aAAc,MAAM;;wDAApB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,cAAe,MAAM;;yDAArB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAiB,MAAM;;2DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,aAAc,MAAM;;wDAApB;;;;;;mCAAA;oBAAA;;;iBACA,iBAAkB,MAAM;;4DAAxB;;;;;;mCAAA;oBAAA;;;iBACA,QAAS,OAAO;;mDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,QAAS,OAAO;;mDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,aAAc,OAAO;;wDAArB;;;;;;mCAAA;oBAAA;;;iBACA,QAAS,MAAM;;mDAAf;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,cAAe,MAAM;;yDAArB;;;;;;mCAAA;oBAAA;;;iBACA,MAAO,MAAM;;iDAAb;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBAEA,OAAQ,MAAM;;kDAAd;;;;;;mCAAA;oBAAA;;;iBACA,gBAAiB,MAAM;;2DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,OAAQ,MAAM;;kDAAd;;;;;;mCAAA;oBAAA;;;iBACA,OAAQ,MAAM;;kDAAd;;;;;;mCAAA;oBAAA;;;iBACA,QAAS,MAAM;;mDAAf;;;;;;mCAAA;oBAAA;;;iBACA,OAAQ,MAAM;;kDAAd;;;;;;mCAAA;oBAAA;;;iBAEA,YAAa,MAAM;;uDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,eAAgB,MAAM;;0DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,eAAgB,MAAM;;0DAAtB;;;;;;mCAAA;oBAAA;;;;AAGiB,WAAP;IACV;iBAAI,MAAM,CAAA;IACV;0BAAa,MAAM,CAAA;IACnB;wBAAW,MAAM,CAAA;IACjB,oBAAY,MAAM,SAAA;IAClB,sBAAc,MAAM,SAAA;IACpB,sBAAc,MAAM,SAAA;IACpB,uBAAe,MAAM,SAAA;IACrB,wBAAgB,MAAM,SAAA;IACtB,qBAAa,MAAM,SAAA;IACnB,sBAAc,MAAM,SAAA;IACpB,wBAAgB,MAAM,SAAA;IACtB,4BAAoB,MAAM,SAAA;IAC1B,qBAAa,MAAM,SAAA;;;;;;AAGE,WAAX;IACV;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;yBAAY,MAAM,CAAA;IAClB,iBAAS,MAAM,SAAA;IACf,sBAAc,MAAM,SAAA;IACpB;uBAAU,MAAM,CAAA;IAChB;uBAAU,OAAO,SAAA;IACjB,uBAAe,MAAM,SAAA;IACrB,wBAAgB,MAAM,SAAA;IACtB,wBAAgB,MAAM,SAAA;IACtB,gCAAwB,MAAM,SAAA;IAC9B,kBAAU,MAAM,SAAA;IAChB,oBAAY,MAAM,SAAA;IAClB,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;;;;;;AAGK,WAAd;IACV;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;6BAAgB,MAAM,CAAA;IACtB;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;6BAAgB,MAAM,CAAA;IACtB,sBAAc,MAAM,SAAA;IACpB;yBAAY,OAAO,SAAA;IACnB,gBAAQ,MAAM,SAAA;IACd,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;;;;;;AAGI,WAAb;IACV;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;0BAAa,MAAM,CAAA;IACnB;0BAAa,MAAM,CAAA;IACnB;qBAAQ,MAAM,CAAA;IACd;0BAAa,MAAM,CAAA;IACnB;wBAAW,MAAM,CAAA;IACjB,kBAAU,MAAM,SAAA;IAEhB,wBAAgB,MAAM,SAAA;IACtB,iBAAS,MAAM,SAAA;IACf,oBAAY,MAAM,SAAA;IAClB,eAAO,MAAM,SAAA;IACb,gBAAQ,MAAM,SAAA;;;;;;AAGO,WAAX;IACV;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;0BAAa,MAAM,CAAA;IACnB;wBAAW,MAAM,CAAA;IACjB,oBAAY,MAAM,SAAA;IAClB,uBAAe,MAAM,SAAA;IACrB,0BAAkB,MAAM,SAAA;IACxB;2BAAc,MAAM,CAAA;IACpB;qBAAQ,OAAO,SAAA;IACf,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;;;;;;AAGM,WAAf;IACV;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;sBAAS,MAAM,CAAA;IACf,mBAAW,MAAM,SAAA;IACjB,mBAAW,MAAM,SAAA;IACjB;sBAAS,OAAO,SAAA;IAChB,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;;;;;;AAGK,WAAd;IACV;iBAAI,MAAM,CAAA;IACV,qBAAa,MAAM,SAAA;IACnB,oBAAY,MAAM,SAAA;IAClB,sBAAc,MAAM,SAAA;IACpB;sBAAS,MAAM,CAAA;IACf;uBAAU,MAAM,CAAA;IAChB;sBAAS,OAAO,SAAA;IAChB;2BAAc,OAAO,SAAA;IACrB,qBAAa,MAAM,SAAA;IACnB,qBAAa,MAAM,SAAA;;;;;;AAGc,WAAvB,kBAAkB;IAC5B;4BAAM,GAAG;IACT;oBAAO,MAAM,CAAA;IACb;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;sBAAS,OAAO,SAAA;;;;;;AAGO,WAAb;IACV;iBAAI,MAAM,CAAA;IACV;yBAAY,MAAM,CAAA;IAClB;uBAAU,MAAM,CAAA;IAChB;6BAAgB,MAAM,CAAA;IACtB;oBAAO,MAAM,CAAA;IACb,uBAAe,MAAM,SAAA;IACrB,qBAAa,MAAM,SAAA;IACnB,gBAAQ,MAAM,SAAA;IACd,wBAAgB,MAAM,SAAA;IACtB,oBAAY,MAAM,SAAA;IAClB,iBAAS,MAAM,SAAA;IACf,iBAAS,MAAM,SAAA;IACf,qBAAa,MAAM,SAAA;;;;;;AAGU,WAAnB;IACV;6BAAgB,MAAM,CAAA;IACtB;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;6BAAgB,MAAM,CAAA;IACtB,sBAAc,MAAM,SAAA;IACpB,qBAAa,OAAO,SAAA;IACpB,gBAAQ,MAAM,SAAA;;;;;;AAGkB,WAAtB;IACV,yBAAiB,MAAM,SAAA;IACvB,gBAAQ,MAAM,SAAA;IACd,mBAAW,MAAM,SAAA;IACjB,eAAO,MAAM,SAAA;IACb,mBAAW,MAAM,SAAA;IACjB,yBAAiB,MAAM,SAAA;IACvB,sBAAc,MAAM,SAAA;IACpB,qBAAa,OAAO,SAAA;IACpB,gBAAQ,MAAM,SAAA;;;;;;AAGgB,WAApB;IACV;0BAAa,MAAM,CAAA;IACnB;6BAAgB,MAAM,CAAA;IACtB;2BAAc,MAAM,CAAA;IACpB;2BAAc,MAAM,CAAA;IACpB;+BAAkB,GAAG,CAAA;IACrB;6BAAO,GAAG,EAAE;;;;;;AAGgB,WAAlB;IACV;+BAAkB,GAAG,CAAA;IACrB;kCAAY,GAAG,EAAE;IACjB;0BAAa,MAAM,CAAA;IACnB;6BAAgB,MAAM,CAAA;;;;;;AAGQ,WAApB;IACV;sBAAS,OAAO,SAAA;IAChB;gCAAU,MAAM,EAAE;IAClB,gBAAQ,MAAM,SAAA;;;;;;AAGa,WAAjB;IACV;sBAAS,OAAO,SAAA;IAChB;sBAAS,MAAM,CAAA;;;;;;AAGoB,WAAzB;IACV;sBAAS,OAAO,SAAA;IAChB,gBAAQ,MAAM,SAAA;;;;;;AAGhB,WAAM;;;;IAEJ,gBAAO,oBAAoB,MAAM,EAAO;QACtC,IAAI;YAEF,IAAM,UAAU,aAAK,UAAU;YAC/B,IAAI,QAAO,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,QAAQ,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;gBAC3C,OAAO,QAAQ,IAAI,GAAC,SAAS,CAAC;;YAQhC,IAAM,SAAS,AfrPL,mBeqPwB;YAClC,OAAO,IAAA,OAAM,EAAA,CAAI,IAAI,EAAG;gBAAA,OAAM,EAAA,CAAI,MAAM;YAAN,EAAS,IAAI,CAAJ;gBAAA,IAAI;YAAJ;;SAC3C,OAAO,cAAG;YACV,cAAiD,aAAa;YAC9D,OAAO,IAAI;;IAEf;IAGA,SAAM,iBAAiB,WAAQ,MAAM,GAAQ;QAAA,OAAA,eAAA;gBACzC,IAAI,UAAU,aAAK,UAAU;gBAC7B,IAAI,QAAQ,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;oBACtB,YAA+C;oBAC/C,MAAM,aAAK,yBAAyB;oBACpC,UAAU,aAAK,UAAU;;gBAG7B,IAAI,QAAQ,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;oBAEtB,IAAM,MAAM,QAAQ,IAAI,KAAG,SAAS,CAAC;oBACrC,IAAI,IAAG,EAAA,CAAI,IAAI,EAAE;wBf/QrB,mBegR0B,WAAW;wBAC9B,SAAO;;;gBAGd,SAAO,IAAI,CAAC,gBAAgB;SAC/B;IAAD;IAGA,SAAM,iBAAiB,oBAAQ,WAAW;QAAA,OAAA,eAAA;gBACxC,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,iBACL,MAAM,CAAC,KACP,KAAK,CAAC,QAA2B,aAAjB,YAAW,IAAI,GAC/B,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,WAAW,SAAS,KAAK;wBAC1E,SAAO,KAAE;;oBAGX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,kBAAO;oBACd,cAAiD,WAAW;oBAC5D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,uBAAuB,oBAAQ,WAAW;QAAA,OAAA,eAAA;gBAC9C,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,iBACL,MAAM,CAAC,KACP,IAAE,CAAC,aAAa,IAAI,EACpB,KAAK,CAAC,cAAiC,aAAjB,YAAW,IAAI,GACrC,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,aAAa,SAAS,KAAK;wBAC5E,SAAO,KAAE;;oBAGX,IAAM,UAAU,SAAS,IAAI;oBAC7B,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;wBACnB,SAAO,KAAE;;oBAGX,IAAM,qBAAY,YAAa,KAAE;oBACjC,IAAM,UAAU,QAAO,EAAA,CAAI,SAAM;wBACjC;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,QAAQ,MAAM;4BAChC,IAAM,OAAO,OAAO,CAAC,EAAE;4BACvB,IAAM,OAAO,IAAI,CAAC,eAAe,CAAC;4BAClC,IAAM,MAAK,SACT,KAAI,IAAI,CAAC,KAAK,CAAA,EAAA,CAAI,MAAM,EACxB,OAAM,IAAI,CAAC,OAAO,CAAA,EAAA,CAAI,MAAM,EAC5B,OAAM,MACN,cAAa,CAAC,IAAI,CAAC,cAAc,CAAA,EAAA,CAAI,MAAM,EAAC,EAAA,CAAI,IAChD,QAAO,CAAC,IAAI,CAAC,QAAQ,CAAA,EAAA,CAAI,MAAM,EAAC,EAAA,CAAI,WACpC,QAAO,CAAC,EACR,OAAM,IAAI,CAAC,OAAO,CAAA,EAAA,CAAI,MAAM;4BAE9B,WAAW,IAAI,CAAC;4BAZkB;;;oBAcpC,SAAO;;iBACP,OAAO,kBAAO;oBACd,cAAiD,aAAa;oBAC9D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,iBAAiB,UAAU,MAAM,GAAG,oBAAQ,WAAW;QAAA,OAAA,eAAA;gBAC3D,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,iBACL,MAAM,CAAC,KACP,EAAE,CAAC,aAAa,UAChB,KAAK,CAAC,cAAiC,aAAjB,YAAW,IAAI,GACrC,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,YAAY,SAAS,KAAK;wBAC3E,SAAO,KAAE;;oBAGX,IAAM,UAAU,SAAS,IAAI;oBAC7B,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;wBACnB,SAAO,KAAE;;oBAGX,IAAM,qBAAY,YAAa,KAAE;oBACjC,IAAM,UAAU,QAAO,EAAA,CAAI,SAAM;wBACjC;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,QAAQ,MAAM;4BAChC,IAAM,OAAO,OAAO,CAAC,EAAE;4BACvB,IAAM,OAAO,IAAI,CAAC,eAAe,CAAC;4BAClC,IAAM,MAAK,SACT,KAAI,IAAI,CAAC,KAAK,CAAA,EAAA,CAAI,MAAM,EACxB,OAAM,IAAI,CAAC,OAAO,CAAA,EAAA,CAAI,MAAM,EAC5B,OAAM,MACN,cAAa,CAAC,IAAI,CAAC,cAAc,CAAA,EAAA,CAAI,MAAM,EAAC,EAAA,CAAI,IAChD,QAAO,CAAC,IAAI,CAAC,QAAQ,CAAA,EAAA,CAAI,MAAM,EAAC,EAAA,CAAI,WACpC,QAAO,CAAC,EACR,YAAW,IAAI,CAAC,YAAY,CAAA,EAAA,CAAI,MAAM,EACtC,OAAM,IAAI,CAAC,OAAO,CAAA,EAAA,CAAI,MAAM;4BAE9B,WAAW,IAAI,CAAC;4BAbkB;;;oBAepC,SAAO;;iBACP,OAAO,kBAAO;oBACd,cAAiD,YAAY;oBAC7D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAA,gBAAgB,MAAM,aAAa,GAAG,MAAM,CAAA;QAC1C,IAAM,UAAU,CAAC,IAAI,CAAC,WAAW,CAAA,EAAA,CAAI,MAAM,EAAC,EAAA,CAAI;QAChD,IAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAA,EAAA,CAAI,MAAM,EAAC,EAAA,CAAI;QAGzC,IAAI,QAAQ,MAAM,CAAA,CAAA,CAAG,CAAC,CAAA,EAAA,CAAI,QAAQ,MAAM,CAAA,EAAA,CAAI,CAAC,EAAE;YAC7C,OAAO;;QAIT,IAAI,KAAI,GAAA,CAAK;YAAW,OAAO;;QAC/B,IAAI,KAAI,GAAA,CAAK;YAAW,OAAO;;QAC/B,IAAI,KAAI,GAAA,CAAK;YAAQ,OAAO;;QAC5B,IAAI,KAAI,GAAA,CAAK;YAAQ,OAAO;;QAC5B,IAAI,KAAI,GAAA,CAAK;YAAU,OAAO;;QAC9B,IAAI,KAAI,GAAA,CAAK;YAAU,OAAO;;QAC9B,IAAI,KAAI,GAAA,CAAK;YAAS,OAAO;;QAC7B,IAAI,KAAI,GAAA,CAAK;YAAQ,OAAO;;QAC5B,IAAI,KAAI,GAAA,CAAK;YAAU,OAAO;;QAE9B,IAAI,KAAI,GAAA,CAAK;YAAU,OAAO;;QAC9B,IAAI,KAAI,GAAA,CAAK;YAAY,OAAO;;QAChC,IAAI,KAAI,GAAA,CAAK;YAAa,OAAO;;QACjC,IAAI,KAAI,GAAA,CAAK;YAAe,OAAO;;QACnC,IAAI,KAAI,GAAA,CAAK;YAAa,OAAO;;QACjC,IAAI,KAAI,GAAA,CAAK;YAAe,OAAO;;QACnC,IAAI,KAAI,GAAA,CAAK;YAAc,OAAO;;QAClC,IAAI,KAAI,GAAA,CAAK;YAAgB,OAAO;;QACpC,IAAI,KAAI,GAAA,CAAK;YAAa,OAAO;;QACjC,IAAI,KAAI,GAAA,CAAK;YAAc,OAAO;;QAClC,IAAI,KAAI,GAAA,CAAK;YAAW,OAAO;;QAC/B,IAAI,KAAI,GAAA,CAAK;YAAS,OAAO;;QAC7B,IAAI,KAAI,GAAA,CAAK;YAAU,OAAO;;QAC9B,IAAI,KAAI,GAAA,CAAK;YAAQ,OAAO;;QAC5B,IAAI,KAAI,GAAA,CAAK;YAAU,OAAO;;QAC9B,IAAI,KAAI,GAAA,CAAK;YAAU,OAAO;;QAE9B,OAAO;IACT;IAGA,SAAM,aAAa,oBAAQ,QAAQ;QAAA,OAAA,eAAA;gBACjC,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,aACL,MAAM,CAAC,KACP,EAAE,CAAC,aAAa,IAAI,EACpB,KAAK,CAAC,QAA2B,aAAjB,YAAW,IAAI,GAC/B,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,WAAW,SAAS,KAAK;wBAC1E,SAAO,KAAE;;oBAGX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,kBAAO;oBACd,cAAiD,WAAW;oBAC5D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,sBACJ,YAAY,MAAM,EAClB,MAAM,MAAM,GAAG,CAAC,EAChB,OAAO,MAAM,GAAG,EAAE,GACjB,WAAQ,kBAAkB,UAAS;QAAA,OAAA,eAAA;gBACpC,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,2BACL,MAAM,CAAC,KAAK;wBAAE,IAAA,QAAO;qBAAS,EAC9B,EAAE,CAAC,eAAe,YAClB,EAAE,CAAC,UAAU,CAAC,EACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,IAAI,CAAC,MACL,KAAK,CAAC,OACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,WAAW,SAAS,KAAK;wBAC1E,SAMC,kBALC,OAAM,IAAM,YACZ,QAAO,CAAC,EACR,OAAA,MACA,QAAA,OACA,UAAS,KAAK;;oBAIlB,SAMC,kBALC,OAAM,SAAS,IAAI,CAAA,EAAA,UAAI,UACvB,QAAO,SAAS,KAAK,CAAA,EAAA,CAAI,CAAC,EAC1B,OAAA,MACA,QAAA,OACA,UAAS,SAAS,OAAO,CAAA,EAAA,CAAI,KAAK;;iBAEpC,OAAO,kBAAO;oBACd,cAAiD,WAAW;oBAC5D,SAMC,kBALC,OAAM,IAAM,YACZ,QAAO,CAAC,EACR,OAAA,MACA,QAAA,OACA,UAAS,KAAK;;SAGnB;IAAD;IAGA,SAAM,eAAe,WAAW,MAAM,GAAG,oBAAQ,aAAa;QAAA,OAAA,eAAA;gBAC5D,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,mBACL,MAAM,CAAC,KACP,EAAE,CAAC,cAAc,WACjB,EAAE,CAAC,UAAU,CAAC,EACd,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,cAAc,SAAS,KAAK;wBAC7E,SAAO,KAAE;;oBAGX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,kBAAO;oBACd,cAAiD,cAAc;oBAC/D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,eACJ,SAAS,MAAM,EACf,MAAM,MAAM,GAAG,CAAC,EAChB,OAAO,MAAM,GAAG,EAAE,EAClB,QAAQ,MAAM,GAAG,OAAO,EACxB,WAAW,OAAO,GAAG,KAAK,GACzB,WAAQ,kBAAkB,UAAS;QAAA,OAAA,eAAA;gBACpC,IAAI;oBACF,IAAI,QAAQ,aACT,IAAI,CAAC,2BACL,MAAM,CAAC,KAAK;wBAAE,IAAA,QAAO;qBAAS,EAC9B,EAAE,CAAC,UAAU,CAAC,EACd,EAAE,CAAC,iBAAe,UAAO,0BAAwB,UAAO,uBAAqB,UAAO,yBAAuB,UAAO;oBAGrH,IAAI,OAAM,GAAA,CAAK,SAAS;wBACtB,QAAQ,MAAM,KAAK,CAAC,cAA2B,aAAX,YAAA;sBAC/B,IAKN,CALM,IAAI,OAAM,GAAA,CAAK,QAAO,EAAA,CAAI,OAAM,GAAA,CAAK,cAAc;wBACxD,QAAQ,MAAM,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK;sBAC/C,IAGN,CAHM;wBAEL,QAAQ,MAAM,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK;;oBAGtD,IAAM,WAAW,MAAM,MACpB,IAAI,CAAC,MACL,KAAK,CAAC,OACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,WAAW,SAAS,KAAK;wBAC1E,SAMC,kBALC,OAAM,IAAM,YACZ,QAAO,CAAC,EACR,OAAA,MACA,QAAA,OACA,UAAS,KAAK;;oBAIlB,SAMC,kBALC,OAAM,SAAS,IAAI,CAAA,EAAA,UAAI,UACvB,QAAO,SAAS,KAAK,CAAA,EAAA,CAAI,CAAC,EAC1B,OAAA,MACA,QAAA,OACA,UAAS,SAAS,OAAO,CAAA,EAAA,CAAI,KAAK;;iBAEpC,OAAO,kBAAO;oBACd,cAAiD,WAAW;oBAC5D,SAMC,kBALC,OAAM,IAAM,YACZ,QAAO,CAAC,EACR,OAAA,MACA,QAAA,OACA,UAAS,KAAK;;SAGnB;IAAD;IAGA,SAAM,YACJ,SAAS,MAAM,EACf,MAAM,MAAM,GAAG,CAAC,EAChB,OAAO,MAAM,GAAG,EAAE,GACjB,WAAQ,kBAAkB,OAAM;QAAA,OAAA,eAAA;gBACjC,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,YACL,MAAM,CAAC,KAAK;wBAAE,IAAA,QAAO;qBAAS,EAC9B,EAAE,CAAC,UAAU,CAAC,EACd,KAAK,CAAC,aAAa,MAAI,UAAO,KAC9B,KAAK,CAAC,iBAAqC,aAAlB,YAAW,KAAK,GACzC,IAAI,CAAC,MACL,KAAK,CAAC,OACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,WAAW,SAAS,KAAK;wBAC1E,SAAoE,kBAA3D,OAAM,IAAM,SAAQ,QAAO,CAAC,EAAE,OAAA,MAAM,QAAA,OAAO,UAAS,KAAK;;oBAIpE,IAAM,gBAAO,QAAS,KAAE;oBACxB,IAAM,WAAW,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG;wBACrC;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,SAAS,MAAM;4BAC/B,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAA,EAAA,CAAI;4BADO;;;oBAIrC,SAMC,kBALC,OAAM,OACN,QAAO,SAAS,KAAK,CAAA,EAAA,CAAI,CAAC,EAC1B,OAAA,MACA,QAAA,OACA,UAAS,SAAS,OAAO,CAAA,EAAA,CAAI,KAAK;;iBAEpC,OAAO,kBAAO;oBACb,cAAiD,WAAW;oBAC5D,SAAoE,kBAA3D,OAAM,IAAM,SAAQ,QAAO,CAAC,EAAE,OAAA,MAAM,QAAA,OAAO,UAAS,KAAK;;SAEtE;IAAD;IAGA,SAAM,eAAe,WAAW,MAAM,GAAG,WAAQ,UAAe;QAAA,OAAA,eAAA;gBAC9D,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,2BACL,MAAM,CAAC,KACP,EAAE,CAAC,MAAM,WACT,MAAM,GACN,SAAS,CAAC;oBAEb,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,aAAa,SAAS,KAAK;wBAC5E,SAAO,IAAI;;oBAGb,SAAO,SAAS,IAAI,CAAA,EAAA,CAAI;;iBACxB,OAAO,kBAAO;oBACd,cAAiD,aAAa;oBAC9D,SAAO,IAAI;;SAEd;IAAD;IAKA,SAAM,eAAe,QAAQ,MAAM,EAAE,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAClE,IAAI;oBACA,IAAM,MAAM,MAAM,aACf,IAAI,CAAC,mBACL,MAAM,CAAC,MAAM;wBAAE,IAAA,QAAO;qBAAS,EAC/B,EAAE,CAAC,WAAW,QACd,EAAE,CAAC,WAAW,QACd,KAAK,CAAC,CAAC,EACP,OAAO;oBAEV,SAAO,CAAC,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,IAAI,KAAK,GAAC,CAAA,CAAG,CAAC;;iBAC7C,OAAO,cAAG;oBACR,cAAiD,uBAAuB;oBACxE,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,WAAW,QAAQ,MAAM,EAAE,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAC9D,IAAI;oBACA,IAAM,MAAM,MAAM,aACf,IAAI,CAAC,mBACL,MAAM,CAAC;wBACJ,IAAA,UAAS;wBACT,IAAA,UAAS;qBACZ,EACA,OAAO;oBAEV,SAAO,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI;;iBAC1B,OAAO,cAAG;oBACR,cAAiD,sBAAsB;oBACvE,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,aAAa,QAAQ,MAAM,EAAE,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAChE,IAAI;oBACA,IAAM,MAAM,MAAM,aACf,IAAI,CAAC,mBACL,EAAE,CAAC,WAAW,QACd,EAAE,CAAC,WAAW,QACd,QAAM,GACN,OAAO;oBAEV,SAAO,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI;;iBAC1B,OAAO,cAAG;oBACR,cAAiD,wBAAwB;oBACzE,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,iBAAiB,QAAQ,MAAM,GAAG,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBAClD,IAAI;oBAEA,IAAM,MAAM,MAAM,aACd,IAAI,CAAC,mBACL,MAAM,CAAC,kBACP,EAAE,CAAC,WAAW,QACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEX,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnB,cAAiD,2BAA2B,IAAI,KAAK;wBACrF,SAAO,KAAE;;oBAGb,SAAO,IAAI,IAAI,CAAA,EAAA,UAAI,GAAG;;iBACxB,OAAO,cAAG;oBACR,cAAiD,+BAA+B;oBAChF,SAAO,KAAE;;SAEhB;IAAD;IAGA,SAAM,oBAAoB,YAAY,MAAM,GAAG,WAAQ,OAAY;QAAA,OAAA,eAAA;gBACjE,IAAI;oBAEF,IAAI,WAAW,MAAM,aAClB,IAAI,CAAC,YACL,MAAM,CAAC,KACP,EAAE,CAAC,eAAe,YAClB,KAAK,CAAC,CAAC,EACP,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,SAAS,IAAI,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,CAAC,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG,CAAE,EAAE,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;wBACzF,SAAO,CAAC,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG,CAAE,CAAC,CAAC,CAAC,CAAC,CAAA,EAAA,CAAI;;oBAIzC,YAA+C,4DAA4D;oBAC3G,WAAW,MAAM,aACd,IAAI,CAAC,YACL,MAAM,CAAC,KACP,EAAE,CAAC,MAAM,YACT,KAAK,CAAC,CAAC,EACP,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,SAAS,IAAI,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,CAAC,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG,CAAE,EAAE,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;wBACzF,YAA+C;wBAE/C,IAAM,OAAO,CAAC,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG,CAAE,CAAC,CAAC,CAAC,CAAC,CAAA,EAAA,CAAI;wBAC5C,SAAO;;oBAGV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,aAAa,SAAS,KAAK;;oBAE9E,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAiD,aAAa;oBAC9D,SAAO,IAAI;;SAEd;IAAD;IAGA,SAAM,wBAAwB,YAAY,MAAM,EAAE,MAAM,MAAM,GAAG,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,GAAG,WAAQ,kBAAkB,UAAS;QAAA,OAAA,eAAA;gBAC1H,IAAI;oBACF,YAA+C,yCAAyC;oBAGxF,IAAI,QAAQ,aACT,IAAI,CAAC,2BACL,MAAM,CAAC,KAAK;wBAAE,IAAA,QAAO;qBAAS,EAC9B,EAAE,CAAC,eAAe,YAElB,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,IAAI,CAAC,MACL,KAAK,CAAC;oBAET,IAAM,WAAW,MAAM,MAAM,OAAO;oBAGpC,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,CAAC,SAAS,IAAI,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,CAAC,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG,CAAE,EAAE,MAAM,CAAA,GAAA,CAAK,CAAC,GAAG;wBAC9F,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;4BACxB,cAAiD,oBAAoB,SAAS,KAAK;0BAChF,IAEN,CAFM;4BACH,YAA+C;;wBAInD,YAA+C;wBAC/C,IAAM,SAAS,aACV,IAAI,CAAC,eACL,MAAM,CAAC,KAAK;4BAAE,IAAA,QAAO;yBAAS,EAC9B,EAAE,CAAC,eAAe,YAElB,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,IAAI,CAAC,MACL,KAAK,CAAC;wBAEX,IAAM,OAAO,MAAM,OAAO,OAAO;wBACjC,IAAI,KAAK,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;4BACnB,cAAiD,mBAAmB,KAAK,KAAK;4BAC9E,SAAkE,kBAA1D,OAAK,IAAM,YAAW,QAAM,CAAC,EAAE,OAAA,MAAM,QAAA,OAAO,UAAQ,KAAK;;wBAGtE,YAA+C,2BAAyB,CAAC,KAAK,IAAI,CAAA,EAAA,UAAI,GAAG,CAAE,EAAE,MAAM,GAAA;wBAGnG,IAAM,qBAAY,WAAY,KAAE;wBAChC,IAAM,UAAU,KAAK,IAAI,CAAA,EAAA,UAAI,GAAG;4BAChC;4BAAI,IAAI,YAAI,CAAC;4BAAb,MAAe,EAAC,CAAA,CAAG,QAAQ,MAAM;gCAC7B,WAAW,IAAI,CAAC,OAAO,CAAC,EAAE,CAAA,EAAA,CAAI;gCADC;;;wBAInC,SAMC,kBALG,OAAM,YACN,QAAO,KAAK,KAAK,CAAA,EAAA,CAAI,CAAC,EACtB,OAAA,MACA,QAAA,OACA,UAAS,KAAK,OAAO,CAAA,EAAA,CAAI,KAAK;;oBAIpC,YAA+C,8BAA4B,CAAC,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG,CAAE,EAAE,MAAM;oBAC1G,SAMC,kBALC,OAAM,SAAS,IAAI,CAAA,EAAA,UAAI,UACvB,QAAO,SAAS,KAAK,CAAA,EAAA,CAAI,CAAC,EAC1B,OAAA,MACA,QAAA,OACA,UAAS,SAAS,OAAO,CAAA,EAAA,CAAI,KAAK;;iBAEpC,OAAO,kBAAO;oBACd,cAAiD,aAAa;oBAC9D,SAMC,kBALC,OAAM,IAAM,YACZ,QAAO,CAAC,EACR,OAAA,MACA,QAAA,OACA,UAAS,KAAK;;SAGnB;IAAD;IAGA,SAAM,eAAe,OAAO,MAAM,GAAG,EAAE,GAAG,oBAAQ,UAAU;QAAA,OAAA,eAAA;gBAC1D,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,2BACL,MAAM,CAAC,KACP,EAAE,CAAC,UAAU,IAAI,EACjB,EAAE,CAAC,UAAU,CAAC,EACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,OACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,aAAa,SAAS,KAAK;wBAC5E,SAAO,KAAE;;oBAGX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,kBAAO;oBACd,cAAiD,aAAa;oBAC9D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,mBAAmB,OAAO,MAAM,GAAG,EAAE,EAAE,WAAW,OAAO,GAAG,IAAI,GAAG,oBAAQ,UAAU;QAAA,OAAA,eAAA;gBACzF,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,2BACL,MAAM,CAAC,KACP,EAAE,CAAC,UAAU,CAAC,EACd,KAAK,CAAC,cAA2B,aAAX,YAAA,YACtB,KAAK,CAAC,OACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,eAAe,SAAS,KAAK;wBAC9E,SAAO,KAAE;;oBAGX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,kBAAO;oBACd,cAAiD,eAAe;oBAChE,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,oBAAoB,OAAO,MAAM,GAAG,EAAE,GAAG,oBAAQ,UAAU;QAAA,OAAA,eAAA;gBAC/D,IAAI;oBACF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,2BACL,MAAM,CAAC,KACP,EAAE,CAAC,UAAU,IAAI,EACjB,EAAE,CAAC,UAAU,CAAC,EACd,KAAK,CAAC,gBAAoC,aAAlB,YAAW,KAAK,GACxC,KAAK,CAAC,OACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,WAAW,SAAS,KAAK;wBAC1E,SAAO,KAAE;;oBAGX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,kBAAO;oBACd,cAAiD,WAAW;oBAC5D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,uBAAuB,OAAO,MAAM,GAAG,EAAE,GAAG,oBAAQ,UAAU;QAAA,OAAA,eAAA;gBAClE,IAAI;oBAEF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,2BACL,MAAM,CAAC,KACP,EAAE,CAAC,eAAe,IAAI,EACtB,EAAE,CAAC,UAAU,CAAC,EACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,OACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,aAAa,SAAS,KAAK;wBAC5E,SAAO,KAAE;;oBAGX,YAA+C,eAAe,CAAC,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG,CAAE,GAAG,OAAM,EAAA,CAAI,CAAC;oBACnG,IAAM,OAAO,SAAS,IAAI,CAAA,EAAA,UAAI;oBAC9B,SAAO,KAAI,EAAA,CAAI,KAAE;;iBACjB,OAAO,kBAAO;oBACd,cAAiD,aAAa;oBAC9D,SAAO,KAAE;;SAEZ;IAAD;IAIA,SAAM,oBAAoB,OAAO,MAAM,GAAG,EAAE,GAAG,oBAAQ,UAAU;QAAA,OAAA,eAAA;gBAC9D,SAAO,IAAM;SACf;IAAD;IAGA,SAAM,gBAAgB,oBAAQ,WAAW;QAAA,OAAA,eAAA;gBACvC,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,aAAgD;wBAChD,SAAO,KAAE;;oBAOX,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,QACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAiD,YAAY,SAAS,KAAK;wBAC3E,SAAO,KAAE;;oBAGX,IAAM,WAAW,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG;oBAGrC,IAAI,SAAQ,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,SAAS,MAAM,CAAA,GAAA,CAAK,CAAC,EAAE;wBAC3C,SAAO,KAAE;;oBAIb,IAAM,qBAAY,MAAM,IAAK,KAAE;oBAC/B,IAAM,iBAAQ,MAAM,IAAK,KAAE;wBAC3B;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,SAAS,MAAM;4BAC/B,IAAI,OAAO,QAAQ,CAAC,EAAE;4BACtB,IAAI,KAAK,MAAM,GAAG;4BAClB,IAAI,KAAK,MAAM,GAAG;4BAClB,IAAI,KAAI,EAAA,CAAY,eAAe;gCAC/B,MAAM,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI;gCACtC,MAAM,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,UAAS,EAAA,CAAI;8BAC/B,IAIN,CAJM;gCACH,IAAM,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,6CAArB,EAAA,CAA+B;gCACpD,MAAM,QAAQ,SAAS,CAAC,cAAa,EAAA,CAAI;gCACzC,MAAM,QAAQ,SAAS,CAAC,UAAS,EAAA,CAAI;;4BAEzC,IAAI,IAAG,GAAA,CAAK,GAAE,EAAA,CAAI,CAAC,WAAW,QAAQ,CAAC,MAAM;gCACzC,WAAW,IAAI,CAAC;;4BAEnB,IAAI,IAAG,GAAA,CAAK,GAAE,EAAA,CAAI,CAAC,OAAO,QAAQ,CAAC,MAAM;gCACtC,OAAO,IAAI,CAAC;;4BAhBiB;;;oBAqBrC,IAAM,aAAa,AAAI,IAAI,MAAM,EAAE,GAAG;oBAEtC,IAAI,WAAW,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;wBAEvB,IAAM,wBAAe,GAAG,IAAK,KAAE;4BAC/B;4BAAI,IAAI,YAAE,CAAC;4BAAX,MAAa,EAAC,CAAA,CAAC,WAAW,MAAM;gCAC9B,cAAc,IAAI,CAAC,UAAU,CAAC,EAAE;gCADA;;;wBAIlC,IAAM,aAAa,MAAM,aACtB,IAAI,CAAC,2BACL,MAAM,CAAC,sEACP,IAAE,CAAC,MAAM,eACT,OAAO;wBAEV,IAAI,WAAW,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,WAAW,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;4BACrD,IAAM,WAAW,WAAW,IAAI,CAAA,EAAA,UAAI,GAAG;gCACvC;gCAAK,IAAI,YAAI,CAAC;gCAAd,MAAgB,EAAC,CAAA,CAAG,SAAS,MAAM;oCAC/B,IAAI,IAAI,QAAQ,CAAC,EAAE;oCACnB,IAAI,KAAK,MAAM,GAAG;oCAElB,IAAI,EAAC,EAAA,CAAY,eAAe;wCAC5B,MAAM,CAAA,EAAC,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,MAAK,EAAA,CAAI;sCACxB,IAGN,CAHM;wCACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,0CAArB,EAAA,CAA4B;wCAC9C,MAAM,KAAK,SAAS,CAAC,MAAK,EAAA,CAAI;;oCAGlC,IAAI,IAAG,GAAA,CAAK,IAAI;wCACZ,WAAW,GAAG,CAAC,KAAK;;oCAZS;;;;;oBAmB7C,IAAM,SAAS,AAAI,IAAI,MAAM,EAAE,GAAG;oBAClC,IAAI,OAAO,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;wBACnB,IAAM,oBAAW,GAAG,IAAK,KAAE;4BAC3B;4BAAI,IAAI,YAAE,CAAC;4BAAX,MAAa,EAAC,CAAA,CAAC,OAAO,MAAM;gCACxB,UAAU,IAAI,CAAC,MAAM,CAAC,EAAE;gCADE;;;wBAG9B,IAAM,SAAS,MAAM,aAClB,IAAI,CAAC,mBACL,MAAM,CAAC,wCACP,IAAE,CAAC,MAAM,WACT,OAAO;wBAET,IAAI,OAAO,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,OAAO,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;4BAC9C,IAAM,OAAO,OAAO,IAAI,CAAA,EAAA,UAAI,GAAG;gCAC/B;gCAAK,IAAI,YAAI,CAAC;gCAAd,MAAgB,EAAC,CAAA,CAAG,KAAK,MAAM;oCAC3B,IAAI,IAAI,IAAI,CAAC,EAAE;oCACf,IAAI,KAAK,MAAM,GAAG;oCAClB,IAAI,EAAC,EAAA,CAAY,eAAe;wCAC5B,MAAM,CAAA,EAAC,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,MAAK,EAAA,CAAI;sCACxB,IAGN,CAHM;wCACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,2CAArB,EAAA,CAA4B;wCAC9C,MAAM,KAAK,SAAS,CAAC,MAAK,EAAA,CAAI;;oCAGlC,IAAI,IAAG,GAAA,CAAK,IAAI;wCACZ,OAAO,GAAG,CAAC,KAAK;;oCAXS;;;;;oBAkBzC,IAAM,oBAAW,YAAa,KAAE;oBAChC,IAAI,CAAC,SAAQ,EAAA,UAAI,GAAG,CAAE,EAAC,EAAA,CAAI,IAAI,EAAE;wBAC/B,IAAM,YAAY,SAAQ,EAAA,UAAI,GAAG;4BACjC;4BAAK,IAAI,YAAI,CAAC;4BAAd,MAAgB,EAAC,CAAA,CAAG,UAAU,MAAM;gCAClC,IAAI,OAAO,SAAS,CAAC,EAAE;gCACvB,IAAI,QAAQ,MAAM,GAAG;gCACrB,IAAI,WAAW,MAAM,GAAG;gCACxB,IAAI,WAAW,MAAM,GAAG;gCACxB,IAAI,OAAO,MAAM,GAAG;gCACpB,IAAI,UAAU,MAAM,GAAG,CAAC;gCACxB,IAAI,UAAU,OAAO,GAAG,KAAK;gCAC7B,IAAI,WAAW,MAAM,GAAG;gCACxB,IAAI,WAAW,MAAM,GAAG;gCACxB,IAAI,gBAAgB,MAAM,GAAG;gCAE7B,IAAI,KAAI,EAAA,CAAY,eAAe;oCAC/B,SAAS,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,MAAK,EAAA,CAAI;oCACjC,YAAY,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,WAAU,EAAA,CAAI;oCACzC,YAAY,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI;oCAC5C,QAAQ,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,UAAS,EAAA,CAAI;oCACpC,WAAW,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,YAAW,EAAA,CAAI,CAAC;oCAC1C,WAAW,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,UAAU,CAAC,YAAW,EAAA,CAAI,KAAK;oCAC/C,YAAY,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI;oCAC5C,YAAY,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI;oCAC5C,iBAAiB,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,eAAc,EAAA,CAAI;kCAC/C,IAWN,CAXM;oCACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;oCACjD,SAAS,KAAK,SAAS,CAAC,MAAK,EAAA,CAAI;oCACjC,YAAY,KAAK,SAAS,CAAC,WAAU,EAAA,CAAI;oCACzC,YAAY,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI;oCAC5C,QAAQ,KAAK,SAAS,CAAC,UAAS,EAAA,CAAI;oCACpC,WAAW,KAAK,SAAS,CAAC,YAAW,EAAA,CAAI,CAAC;oCAC1C,WAAW,KAAK,UAAU,CAAC,YAAW,EAAA,CAAI,KAAK;oCAC/C,YAAY,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI;oCAC5C,YAAY,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI;oCAC5C,iBAAiB,KAAK,SAAS,CAAC,eAAc,EAAA,CAAI;;gCAGtD,IAAM,UAAU,WAAW,GAAG,CAAC;gCAC/B,IAAM,MAAM,IAAA,CAAC,MAAK,GAAA,CAAK,GAAE,EAAA,CAAI,OAAO,GAAG,CAAC,MAAM,GAAI;oCAAA,OAAO,GAAG,CAAC;gCAAK,EAAI,IAAI,CAAJ;oCAAA,IAAI;gCAAJ;gCAEtE,IAAI,YAAY,MAAM,GAAG;gCACzB,IAAI,aAAa,MAAM,GAAG;gCAC1B,IAAI,cAAc,MAAM,GAAG;gCAC3B,IAAI,cAAc,MAAM,GAAG,CAAC;gCAC5B,IAAI,aAAa,MAAM,GAAG;gCAC1B,IAAI,aAAa,MAAM,GAAG;gCAE1B,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;oCACjB,IAAI,QAAO,EAAA,CAAY,eAAe;wCAElC,IAAI,WAAU,EAAA,CAAI,IAAI;4CAClB,aAAa,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,eAAc,EAAA,CAAI;;wCAErD,cAAc,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,QAAO,EAAA,CAAI;wCAC3C,eAAe,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,kBAAiB,EAAA,CAAI;wCACtD,eAAe,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;wCACnD,cAAc,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,aAAY,EAAA,CAAI;sCA8B7C,IAkCN,CAlCM;wCACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,iDAArB,EAAA,CAAkC;wCACpD,aAAa,KAAK,SAAS,CAAC,eAAc,EAAA,CAAI;wCAC9C,cAAc,KAAK,SAAS,CAAC,QAAO,EAAA,CAAI;wCACxC,eAAe,KAAK,SAAS,CAAC,kBAAiB,EAAA,CAAI;wCACnD,eAAe,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;wCAChD,cAAc,KAAK,SAAS,CAAC,aAAY,EAAA,CAAI;;;gCAgCrD,IAAI,IAAG,EAAA,CAAI,IAAI,EAAE;oCACZ,IAAI,IAAG,EAAA,CAAY,eAAe;wCAC7B,IAAM,WAAW,CAAA,IAAG,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;wCAC/B,IAAI,SAAQ,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,SAAQ,CAAA,CAAG,CAAC,EAAE;4CAClC,eAAe;;wCAEnB,IAAM,SAAS,CAAA,IAAG,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;wCAC7B,IAAI,OAAM,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,OAAM,GAAA,CAAK,IAAI;4CACjC,eAAe;;wCAGnB,IAAM,UAAU,CAAA,IAAG,EAAA,CAAA,aAAA,EAAC,GAAG,CAAC;wCACxB,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;4CAEjB,IAAI,oBAAO,SAAO,GAAA,CAAK,UAAU;gDAC7B,cAAc,QAAO,EAAA,CAAA,MAAA;8CAClB,IAeN,CAfM,IAAI,QAAO,EAAA,CAAY,eAAe;gDACzC,IAAM,OAAO,cAAc,IAAI,CAAC,QAAO,EAAA,CAAA;gDACvC,IAAM,gBAAO,MAAM,IAAK,KAAE;oDAC1B;oDAAI,IAAI,YAAI,CAAC;oDAAb,MAAe,EAAC,CAAA,CAAG,KAAK,MAAM;wDAC1B,IAAI,OAAM,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,GAAG,CAAC,IAAI,CAAC,EAAE;wDAC7B,IAAI,AADA,KACG,EAAA,CAAI,IAAI,EAAE;4DACb,MAAM,IAAI,CAAC,KAAG,IAAI,CAAC,EAAE,GAAA,OAFrB;;wDADwB;;;gDAMhC,cAAc,MAAM,IAAI,CAAC;8CACtB,IAKN,CALM;gDACH,IAAI;oDACA,IAAI,UAAU,KAAK,SAAS,CAAC;oDAC7B,cAAc,QAAQ,OAAO,CAAC,0BAAU,IAAI,OAAO,CAAC,qBAAM;;iDAC5D,OAAO,cAAG,CAAA;;;sCAGlB,IA8BN,CA9BM;wCACF,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,6CAArB,EAAA,CAA8B;wCAChD,IAAM,WAAW,KAAK,SAAS,CAAC,SAAQ,EAAA,CAAI,CAAC;wCAC7C,IAAI,SAAQ,CAAA,CAAG,CAAC;4CAAE,eAAe;;wCAEjC,IAAM,SAAS,KAAK,SAAS,CAAC,aAAY,EAAA,CAAI;wCAC9C,IAAI,OAAM,GAAA,CAAK;4CAAI,eAAe;;wCAElC,IAAM,UAAU,KAAK,GAAG,CAAC;wCACzB,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;4CAEjB,IAAI,oBAAO,SAAO,GAAA,CAAK,UAAU;gDAC7B,cAAc,QAAO,EAAA,CAAA,MAAA;8CAClB,IAeN,CAfM,IAAI,QAAO,EAAA,CAAY,eAAe;gDACzC,IAAM,OAAO,cAAc,IAAI,CAAC,QAAO,EAAA,CAAA;gDACvC,IAAM,gBAAO,MAAM,IAAK,KAAE;oDAC1B;oDAAI,IAAI,YAAI,CAAC;oDAAb,MAAe,EAAC,CAAA,CAAG,KAAK,MAAM;wDAC1B,IAAI,OAAM,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,GAAG,CAAC,IAAI,CAAC,EAAE;wDAC7B,IAAI,AADA,KACG,EAAA,CAAI,IAAI,EAAE;4DACb,MAAM,IAAI,CAAC,KAAG,IAAI,CAAC,EAAE,GAAA,OAFrB;;wDADwB;;;gDAMhC,cAAc,MAAM,IAAI,CAAC;8CACtB,IAKN,CALM;gDACH,IAAI;oDACA,IAAI,UAAU,KAAK,SAAS,CAAC;oDAC7B,cAAc,QAAQ,OAAO,CAAC,0BAAU,IAAI,OAAO,CAAC,qBAAM;;iDAC5D,OAAO,cAAG,CAAA;;;;;gCAQ9B,IAAI,YAAY,IAAA,WAAU,EAAA,CAAI,IAAK;oCAAA;gCAAA,EAAa,IAAc,CAAd;oCAAA;gCAAA;gCAGhD,UAAU,IAAI,CAgBT,SAfH,KAAI,QACJ,UAAS,WACT,aAAY,WACZ,SAAQ,OACR,cAAa,YACb,WAAU,UACV,WAAU,UACV,eAAc,aACd,gBAAe,cACf,gBAAe,cACf,wBAAuB,aACvB,UAAS,WACT,YAAW,aACX,aAAY,WACZ,aAAY;gCAhNsB;;;;oBAqNxC,SAAO;;iBACP,OAAO,kBAAO;oBACd,cAAkD,YAAY;oBAC9D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,qBAAqB,MAAM,MAAM,IAAU,IAAI,GAAG,oBAAQ,eAAe;QAAA,OAAA,eAAA;gBAC7E,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAE;;oBAE7B,IAAI,QAAQ,aACT,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW;oBAEjB,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBAChB,QAAQ,MAAM,EAAE,CAAC,QAAQ;;oBAG3B,IAAM,WAAW,MAAM,MAAM,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GAAI,OAAO;oBAE9E,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,WAAW,SAAS,KAAK;wBAC3E,SAAO,KAAE;;oBAEX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,cAAG;oBACV,cAAkD,WAAW;oBAC7D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,gBAAgB,oBAAQ,WAAW;QAAA,OAAA,eAAA;gBACvC,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAE;;oBAE7B,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,iBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,QACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,aAAa,SAAS,KAAK;wBAC7E,SAAO,KAAE;;oBAEX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,cAAG;oBACT,cAAkD,aAAa;oBAC/D,SAAO,KAAE;;SAEb;IAAD;IAGA,SAAM,uBAAuB,oBAAQ,cAAc;QAAA,OAAA,eAAA;gBACjD,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAE;;oBAE7B,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,EAAE,CAAC,kBAAgB,SAAM,qBAAmB,QAC5C,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,EAAE,EACR,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,aAAa,SAAS,KAAK;wBAC7E,SAAO,KAAE;;oBAEX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,cAAG;oBACT,cAAkD,aAAa;oBAC/D,SAAO,KAAE;;SAEb;IAAD;IAGA,SAAM,gBAAgB,YAAY,MAAM,GAAG,oBAAQ,cAAc;QAAA,OAAA,eAAA;gBAC/D,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAE;;oBAE7B,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,EAAE,CAAC,sBAAoB,SAAM,qBAAmB,aAAU,wBAAsB,aAAU,qBAAmB,SAAM,KACnH,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,EAAE,EACR,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,aAAa,SAAS,KAAK;wBAC7E,SAAO,KAAE;;oBAEX,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,cAAG;oBACT,cAAkD,aAAa;oBAC/D,SAAO,KAAE;;SAEb;IAAD;IAGA,SAAM,gBAAgB,SAAS,MAAM,EAAE,MAAM,MAAM,IAAU,IAAI,EAAE,MAAM,MAAM,GAAG,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACvG,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAEhC,IAAM,yBAAU,sGACZ,eAAW,QACX,aAAS,SACT,cAAU,MACV,kBAAc,IAAI,EAClB,gBAAY,AAAI,OAAO,WAAW;oBAEtC,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,QAAQ,GAAG,CAAC,eAAe;;oBAG/B,IAAM,WAAW,MAAM,aAClB,IAAI,CAAC,oBACL,MAAM,CAAC,SACP,OAAO;oBAEZ,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,cAAkD,WAAW,SAAS,KAAK;wBAC3E,SAAO,KAAK;;oBAEhB,SAAO,IAAI;;iBACb,OAAO,cAAG;oBACR,cAAkD,WAAW;oBAC7D,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,qBAAqB,SAAS,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACxD,IAAI;oBACD,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAEhC,IAAM,WAAW,MAAM,aAClB,IAAI,CAAC,oBACL,MAAM,CAAC,IACJ,iBAAa,QACb,aAAS,SACT,cAAU,QACV,kBAAc,KAAK,EACnB,gBAAY,AAAI,OAAO,WAAW,KAErC,OAAO;oBACX,SAAO,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI;;iBAC/B,OAAO,cAAG;oBACR,SAAO,KAAK;;SAEpB;IAAD;IAGA,SAAM,UAAU,WAAW,MAAM,EAAE,UAAU,MAAM,GAAG,CAAC,EAAE,OAAQ,MAAM,CAAA,EAAE,YAAa,MAAM,CAAA,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAC7G,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,KAAK;;oBAGd,IAAM,YAAY,IAAA,CAAC,MAAK,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,MAAM,MAAM,CAAA,CAAA,CAAG,CAAC,GAAI;wBAAA;oBAAA,EAAQ,IAAI,CAAJ;wBAAA,IAAI;oBAAJ;oBAChE,IAAM,iBAAiB,IAAA,CAAC,WAAU,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,WAAW,MAAM,CAAA,CAAA,CAAG,CAAC,GAAI;wBAAA;oBAAA,EAAa,IAAI,CAAJ;wBAAA,IAAI;oBAAJ;oBAIpF,IAAI,QAAQ,aACT,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,QACd,EAAE,CAAC,cAAc;oBAEpB,IAAI,UAAS,EAAA,CAAI,IAAI,EAAE;wBACrB,QAAQ,MAAM,EAAE,CAAC,UAAU;sBACtB,IAEN,CAFM;wBACL,QAAQ,MAAM,IAAE,CAAC,UAAU,IAAI;;oBAGjC,IAAM,mBAAmB,MAAM,MAAM,MAAM,GAAG,OAAO;oBAErD,IAAI,cAAc,GAAG,IAAU,IAAI;oBAEnC,IAAI,iBAAiB,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC/B,IAAM,UAAU,iBAAiB,IAAI,CAAA,EAAA,CAAI,GAAG;wBAC5C,IAAI,SAAM,OAAO,CAAC,UAAU;4BACxB,IAAI,CAAA,QAAO,EAAA,UAAA,GAAA,CAAA,EAAC,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;gCACpB,eAAe,CAAA,QAAO,EAAA,UAAA,GAAA,CAAA,CAAA,CAAC,CAAC,CAAC;;0BAE1B,IAEN,CAFM;4BACH,eAAe;;;oBAIvB,IAAI,wBAAwB,GAAG;oBAC/B,IAAI,aAAY,EAAA,CAAI,IAAI,EAAE;wBAExB,YAAgD,6BAA6B,KAAK,SAAS,CAAC;wBAG5F,IAAI,QAAQ,MAAM,IAAU,IAAI;wBAChC,IAAI,SAAS,GAAG,IAAU,IAAI;wBAE9B,IAAI,aAAY,EAAA,CAAY,eAAe;4BACvC,SAAS,CAAA,aAAY,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;4BAChC,UAAU,CAAA,aAAY,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;0BAC9B,IAIN,CAJM;4BACF,IAAM,MAAM,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,sDAArB,EAAA,CAAuC;4BACxD,SAAS,IAAI,SAAS,CAAC;4BACvB,UAAU,IAAI,SAAS,CAAC;yBAC5B;wBAED,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;4BAChB,IAAI,qBAAa,CAAC;4BAClB,IAAI,oBAAO,SAAO,GAAA,CAAK,UAAU;gCAC7B,aAAa,QAAO,EAAA,CAAI,MAAM;8BAC3B,IAGN,CAHM;gCACH,IAAM,OAAO,KAAG,CAAA,QAAO,EAAA,CAAI,CAAC,AAAD;gCAC3B,aAAa,SAAS;6BACzB;4BACD,IAAM,SAAS,WAAU,CAAA,CAAG;4BAE5B,WAAW,MAAM,aAChB,IAAI,CAAC,oBACL,MAAM,CAAC;gCACJ,IAAA,WAAU;gCACV,IAAA,cAAa;gCACb,IAAA,aAAY,AAAI,OAAO,WAAW;6BACrC,EACA,EAAE,CAAC,MAAM,QACT,OAAO;0BACL,IAGN,CAHM;4BACH,cAAkD,4BAA4B,KAAK,SAAS,CAAC;4BAC7F,SAAO,KAAK;yBACf;sBACI,IAkBN,CAlBM;wBAEL,IAAM,cAAc,AAAI;wBACxB,YAAY,GAAG,CAAC,WAAW;wBAC3B,YAAY,GAAG,CAAC,cAAc;wBAC9B,YAAY,GAAG,CAAC,UAAU;wBAC1B,YAAY,GAAG,CAAC,YAAY;wBAC5B,YAAY,GAAG,CAAC,YAAY,IAAI;wBAChC,YAAY,GAAG,CAAC,cAAc,AAAI,OAAO,WAAW;wBACpD,YAAY,GAAG,CAAC,cAAc,AAAI,OAAO,WAAW;wBACpD,IAAI,eAAc,EAAA,CAAI,IAAI,EAAE;4BACxB,YAAY,GAAG,CAAC,eAAe;;wBAGnC,WAAW,MAAM,aACd,IAAI,CAAC,oBACL,MAAM,CAAC,aACP,OAAO;;oBAGZ,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,eAAe,SAAS,KAAK;wBAC/E,SAAO,KAAK;;oBAGd,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAkD,eAAe;oBACjE,SAAO,KAAK;;SAEf;IAAD;IAGA,SAAM,uBAAuB,YAAY,MAAM,EAAE,UAAU,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAClF,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,KAAK;;oBAGd,IAAI,SAAQ,CAAA,CAAG,CAAC,EAAE;wBAEhB,SAAO,MAAM,IAAI,CAAC,cAAc,CAAC;;oBAGnC,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,IACN,cAAU,UACV,gBAAY,AAAI,OAAO,WAAW,KAEnC,EAAE,CAAC,MAAM,YACT,EAAE,CAAC,WAAW,QACd,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,gBAAgB,SAAS,KAAK;wBAChF,SAAO,KAAK;;oBAGd,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAkD,gBAAgB;oBAClE,SAAO,KAAK;;SAEf;IAAD;IAGA,SAAM,wBAAwB,YAAY,MAAM,EAAE,UAAU,OAAO,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACpF,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,KAAK;;oBAGd,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,IACN,cAAU,UACV,gBAAY,AAAI,OAAO,WAAW,KAEnC,EAAE,CAAC,MAAM,YACT,EAAE,CAAC,WAAW,QACd,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,kBAAkB,SAAS,KAAK;wBAClF,SAAO,KAAK;;oBAGd,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAkD,kBAAkB;oBACpE,SAAO,KAAK;;SAEf;IAAD;IAGA,SAAM,6BAA6B,sBAAa,MAAM,CAAE,EAAE,UAAU,OAAO,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAC5F,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,KAAK;;oBAGd,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,IACN,cAAU,UACV,gBAAY,AAAI,OAAO,WAAW,KAEnC,EAAE,CAAC,WAAW,QACd,IAAE,CAAC,MAAM,YAAW,EAAA,UAAI,GAAG,GAC3B,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,oBAAoB,SAAS,KAAK;wBACpF,SAAO,KAAK;;oBAGd,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAkD,oBAAoB;oBACtE,SAAO,KAAK;;SAEf;IAAD;IAGA,SAAM,eAAe,YAAY,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACxD,SAAO,IAAI;SA4BZ;IAAD;IAGA,SAAM,qBAAqB,sBAAa,MAAM,CAAE,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACjE,SAAO,IAAI;SA2BZ;IAAD;IAGA,SAAM,aAAa,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACjC,SAAO,IAAI;SA0BZ;IAAD;IAGA,SAAM,gBAAgB,oBAAQ,cAAc;QAAA,OAAA,eAAA;gBAC1C,IAAM,SAAS,IAAI,CAAC,gBAAgB;gBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;oBAClB,aAAiD;oBACjD,SAAO,KAAE;;gBAGX,IAAI;oBACF,YAAgD,gCAAgC;oBAEhF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,qBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,QACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEV,YAAgD,kCAAkC,SAAS,KAAK;oBAChG,YAAgD,iCAAiC,KAAK,SAAS,CAAC,SAAS,IAAI;oBAE7G,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,0BAA0B,SAAS,KAAK;wBAC1F,SAAO,KAAE;;oBAGX,IAAM,OAAO,SAAS,IAAI;oBAC1B,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBAChB,SAAO,KAAE;;oBAGX,IAAM,iBAAQ,eAAgB,KAAE;oBAChC,IAAM,UAAU,KAAI,EAAA,UAAI,GAAG;wBAC3B;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,QAAQ,MAAM;4BAChC,IAAM,OAAO,OAAO,CAAC,EAAE;4BACvB,IAAI,SAAS;4BACb,IAAI,KAAI,EAAA,CAAY,eAAe;gCACjC,UAAU,KAAI,EAAA,CAAI;8BACb,IAEN,CAFM;gCACL,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;;4BAGhD,IAAM,UAAU,AAAI;4BACpB,QAAQ,GAAG,CAAC,MAAM,QAAQ,SAAS,CAAC,MAAK,EAAA,CAAI;4BAC7C,QAAQ,GAAG,CAAC,WAAW,QAAQ,SAAS,CAAC,WAAU,EAAA,CAAI;4BACvD,QAAQ,GAAG,CAAC,kBAAkB,QAAQ,SAAS,CAAC,iBAAgB,EAAA,CAAI,QAAQ,SAAS,CAAC,kBAAiB,EAAA,CAAI;4BAC3G,QAAQ,GAAG,CAAC,SAAS,QAAQ,SAAS,CAAC,kBAAiB,EAAA,CAAI,QAAQ,SAAS,CAAC,SAAQ,EAAA,CAAI;4BAC1F,QAAQ,GAAG,CAAC,YAAY,QAAQ,SAAS,CAAC,YAAW,EAAA,CAAI;4BACzD,QAAQ,GAAG,CAAC,QAAQ,QAAQ,SAAS,CAAC,QAAO,EAAA,CAAI;4BACjD,QAAQ,GAAG,CAAC,YAAY,QAAQ,SAAS,CAAC,YAAW,EAAA,CAAI;4BACzD,QAAQ,GAAG,CAAC,kBAAkB,QAAQ,SAAS,CAAC,kBAAiB,EAAA,CAAI,QAAQ,SAAS,CAAC,kBAAiB,EAAA,CAAI;4BAC5G,QAAQ,GAAG,CAAC,cAAc,QAAQ,UAAU,CAAC,cAAa,EAAA,CAAI,KAAK;4BACnE,OAAO,IAAI,CAAC,QAAO,EAAA,CAAI;4BAnBW;;;oBAsBpC,YAAgD,0BAA0B,OAAO,MAAM;oBACvF,SAAO;;iBACP,OAAO,kBAAO;oBACd,cAAkD,0BAA0B;oBAC5E,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,eAAe,WAAW,MAAM,GAAG,WAAQ,cAAmB;QAAA,OAAA,eAAA;gBAClE,IAAM,SAAS,IAAI,CAAC,gBAAgB;gBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;oBAClB,aAAiD;oBACjD,SAAO,IAAI;;gBAGb,IAAI;oBACF,IAAM,QAAQ,aACX,IAAI,CAAC,qBACL,MAAM,CAAC,wFACP,EAAE,CAAC,MAAM,WACT,EAAE,CAAC,WAAW,QACd,MAAM;oBAET,IAAM,WAAW,MAAM,MAAM,OAAO;oBAEpC,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,aAAa,SAAS,KAAK;wBAC7E,SAAO,IAAI;;oBAGb,SAAO,SAAS,IAAI,CAAA,EAAA,CAAI;;iBACxB,OAAO,kBAAO;oBACd,cAAkD,aAAa;oBAC/D,SAAO,IAAI;;SAEd;IAAD;IAGA,SAAM,WAAW,SAAS,gBAAgB,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAC3D,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,KAAK;;oBAId,IAAI,QAAQ,UAAU,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC9B,MAAM,IAAI,CAAC,mBAAmB,CAAC;;oBAGjC,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,qBACL,MAAM,CAAC;wBACN,IAAA,UAAS;wBACT,IAAA,gBAAe,QAAQ,cAAc;wBACrC,IAAA,iBAAgB,QAAQ,KAAK;wBAC7B,IAAA,WAAU,QAAQ,QAAQ;wBAC1B,IAAA,OAAM,QAAQ,IAAI;wBAClB,IAAA,WAAU,QAAQ,QAAQ;wBAC1B,IAAA,iBAAgB,QAAQ,cAAc;wBACtC,IAAA,cAAa,QAAQ,WAAW,CAAA,EAAA,CAAI,IAAI;wBACxC,IAAA,aAAY,QAAQ,UAAU,CAAA,EAAA,CAAI,KAAK;wBACvC,IAAA,aAAY,AAAI,OAAO,WAAW;wBAClC,IAAA,aAAY,AAAI,OAAO,WAAW;qBACnC,EACA,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,WAAW,SAAS,KAAK;wBAC3E,SAAO,KAAK;;oBAGd,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAkD,WAAW;oBAC7D,SAAO,KAAK;;SAEf;IAAD;IAGA,SAAM,cAAc,WAAW,MAAM,EAAE,SAAS,mBAAmB,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACpF,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,KAAK;;oBAId,IAAI,QAAQ,UAAU,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC9B,MAAM,IAAI,CAAC,mBAAmB,CAAC;;oBAIjC,IAAM,4BAAa;qBAAE;oBACrB,IAAI,QAAQ,cAAc,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,gBAAgB,GAAG,QAAQ,cAAc;;oBACxF,IAAI,QAAQ,KAAK,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,iBAAiB,GAAG,QAAQ,KAAK;;oBACvE,IAAI,QAAQ,QAAQ,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,WAAW,GAAG,QAAQ,QAAQ;;oBACvE,IAAI,QAAQ,IAAI,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,OAAO,GAAG,QAAQ,IAAI;;oBAC3D,IAAI,QAAQ,QAAQ,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,WAAW,GAAG,QAAQ,QAAQ;;oBACvE,IAAI,QAAQ,cAAc,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,iBAAiB,GAAG,QAAQ,cAAc;;oBACzF,IAAI,QAAQ,WAAW,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,cAAc,GAAG,QAAQ,WAAW;;oBAChF,IAAI,QAAQ,UAAU,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,aAAa,GAAG,QAAQ,UAAU;;oBAC7E,IAAI,QAAQ,KAAK,CAAA,EAAA,CAAI,IAAI;wBAAE,UAAU,CAAC,QAAQ,GAAG,QAAQ,KAAK;;oBAC9D,UAAU,CAAC,aAAa,GAAG,AAAI,OAAO,WAAW;oBAEjD,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,qBACL,MAAM,CAAC,YACP,EAAE,CAAC,MAAM,WACT,EAAE,CAAC,WAAW,QACd,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,WAAW,SAAS,KAAK;wBAC3E,SAAO,KAAK;;oBAGd,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAkD,WAAW;oBAC7D,SAAO,KAAK;;SAEf;IAAD;IAGA,SAAM,eAAe,SAAS,MAAM,GAAG,WAAQ,wBAAuB;QAAA,OAAA,eAAA;gBACpE,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,SAAyC,uBAAhC,UAAS,KAAK,EAAE,QAAO;;oBAGlC,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,aACL,MAAM,CAAC;wBACN,IAAA,uBAAc,CAAC;wBACf,IAAA,eAAc,AAAI,OAAO,WAAW;wBACpC,IAAA,eAAc,AAAI,OAAO,WAAW;wBACpC,IAAA,aAAY,AAAI,OAAO,WAAW;qBACnC,EACA,EAAE,CAAC,MAAM,SACT,EAAE,CAAC,WAAW,QACd,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,SAAwD,uBAA/C,UAAS,KAAK,EAAE,QAAO,SAAS,KAAK,GAAC,OAAO;;oBAG1D,SAAwB,uBAAf,UAAS,IAAI;;iBACtB,OAAO,cAAQ;oBACb,SAA2C,uBAAlC,UAAS,KAAK,EAAE,QAAO,EAAE,OAAO;;SAE9C;IAAD;IAGA,SAAM,cAAc,WAAW,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACtD,IAAI;oBACF,YAAgD,gBAAgB;oBAChE,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,KAAK;;oBAGd,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,qBACL,EAAE,CAAC,MAAM,WACT,EAAE,CAAC,WAAW,QACd,QAAM,GACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,WAAW,SAAS,KAAK;wBAC3E,SAAO,KAAK;;oBAGd,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAkD,WAAW;oBAC7D,SAAO,KAAK;;SAEf;IAAD;IAGA,YAAc,oBAAoB,QAAQ,MAAM,GAAG,WAAQ,IAAI,EAAC;QAAA,OAAA,eAAA;gBAC9D,IAAI;oBACF,MAAM,aACH,IAAI,CAAC,qBACL,MAAM,CAAC;wBACN,IAAA,aAAY,KAAK;wBACjB,IAAA,aAAY,AAAI,OAAO,WAAW;qBACnC,EACA,EAAE,CAAC,WAAW,QACd,EAAE,CAAC,cAAc,IAAI,EACrB,OAAO;;iBACV,OAAO,kBAAO;oBACd,cAAkD,aAAa;;SAElE;IAAD;IAGA,SAAM,kBAAkB,WAAQ,GAAG,GAAQ;QAAA,OAAA,eAAA;gBACzC,IAAI;oBACD,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,IAAI;;oBAI/B,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,QACd,MAAM,GACN,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAEzB,SAAO,IAAI;;oBAEd,SAAO,SAAS,IAAI;;iBACrB,OAAO,cAAG;oBACT,SAAO,IAAI;;SAEf;IAAD;IAGA,SAAM,YAAY,WAAW,iBAAiB,GAAG,WAAQ,MAAM,GAAQ;QAAA,OAAA,eAAA;gBACnE,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,IAAI;;oBAGb,IAAM,UAAU,KAAI,CAAA,CAAG,KAAK,GAAG,GAAE,CAAA,CAAG,KAAK,KAAK,CAAC,KAAK,MAAM,GAAE,CAAA,CAAG,IAAI;oBAEnE,IAAI,aAAa,UAAU,WAAW;oBACtC,IAAI,WAAU,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,WAAU,EAAA,CAAI,GAAE,EAAA,CAAI,WAAU,EAAA,CAAI,WAAW;wBACrE,aAAa;;oBAGf,IAAI,kBAAkB;oBACtB,IAAI,UAAU,gBAAgB,CAAA,EAAA,CAAI,IAAI,EAAE;wBACtC,IAAI,oBAAO,UAAU,gBAAgB,EAAA,GAAA,CAAK,UAAU;4BAClD,kBAAkB,UAAU,gBAAgB,CAAA,EAAA,CAAA,MAAA;0BACvC,IAEN,CAFM;4BACL,kBAAkB,KAAK,SAAS,CAAC,UAAU,gBAAgB;;;oBAI/D,IAAM,eAAe,AAAI;oBACzB,aAAa,GAAG,CAAC,WAAW;oBAC5B,aAAa,GAAG,CAAC,eAAe;oBAChC,aAAa,GAAG,CAAC,YAAY;oBAC7B,aAAa,GAAG,CAAC,kBAAkB,UAAU,cAAc;oBAC3D,aAAa,GAAG,CAAC,gBAAgB,UAAU,YAAY;oBACvD,aAAa,GAAG,CAAC,gBAAgB,UAAU,YAAY;oBACvD,aAAa,GAAG,CAAC,eAAe,CAAC;oBACjC,aAAa,GAAG,CAAC,oBAAoB;oBACrC,aAAa,GAAG,CAAC,gBAAgB,CAAC;oBAClC,aAAa,GAAG,CAAC,kBAAkB,CAAC;oBACpC,aAAa,GAAG,CAAC,mBAAmB,CAAC;oBACrC,aAAa,GAAG,CAAC,cAAc,AAAI,OAAO,WAAW;oBACrD,aAAa,GAAG,CAAC,cAAc,AAAI,OAAO,WAAW;oBAErD,YAAgD,yBAAyB,KAAK,SAAS,CAAC;oBACxF,YAAgD,yBAAyB;oBAEzE,IAAM,gBAAgB,MAAM,aACvB,IAAI,CAAC,aACL,MAAM,CAAC,cACP,OAAO;oBAEZ,YAAgD;oBAChD,YAAgD,sCAAsC,cAAc,KAAK;oBAEzG,IAAI,cAAc,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC7B,cAAkD,yBAAyB,cAAc,KAAK;wBAC9F,SAAO,IAAI;;oBAGf,YAAgD,uCAAuC;oBAEvF,IAAM,gBAAgB,MAAM,aACvB,IAAI,CAAC,aACL,MAAM,CAAC,gBACP,EAAE,CAAC,YAAY,SACf,OAAO;oBAEZ,YAAgD,sCAAsC,cAAc,KAAK;oBACzG,YAAgD,qCAAqC,KAAK,SAAS,CAAC,cAAc,IAAI;oBAEtH,IAAI,cAAc,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC7B,cAAkD,yBAAyB,cAAc,KAAK;wBAC9F,SAAO,IAAI;;oBAGf,IAAM,YAAY,cAAc,IAAI,CAAA,EAAA,CAAI,GAAG;oBAC3C,IAAI,UAAU;oBAEd,IAAI,SAAM,OAAO,CAAC,WAAU,EAAA,CAAI,CAAA,UAAS,EAAA,UAAA,GAAA,CAAA,EAAC,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;wBAClD,IAAM,YAAY,CAAA,UAAS,EAAA,UAAA,GAAA,CAAA,CAAA,CAAC,CAAC,CAAC,CAAA,EAAA,CAAI,OAAO,MAAM,EAAE,GAAG;wBACpD,UAAU,SAAS,CAAC,KAAK,CAAA,EAAA,CAAI,MAAM;wBACnC,YAAgD,+BAA+B;sBAC5E,IAGN,CAHM;wBACH,cAAkD;wBAClD,SAAO,IAAI;;oBAGf,YAAgD,kCAAkC;oBAElF,IAAM,qBAAY,iBAAkB,KAAE;oBACtC,IAAM,WAAW,UAAU,KAAK,CAAA,EAAA,UAAI,GAAG;wBAEvC;wBAAI,IAAI,YAAI,CAAC;wBAAb,MAAe,EAAC,CAAA,CAAG,SAAS,MAAM;4BAC9B,IAAI,MAAM;4BACV,IAAM,UAAU,QAAQ,CAAC,EAAE;4BAC3B,OAAO,QAAO,EAAA,CAAI;4BAElB,IAAM,WAAW,AAAI;4BAErB,IAAI,MAAM,KAAK,GAAG,CAAC;4BACnB,IAAI,IAAG,EAAA,CAAI,IAAI,EAAE;gCACf,MAAM,KAAK,GAAG,CAAC;;4BAGjB,SAAS,GAAG,CAAC,YAAY;4BACzB,SAAS,GAAG,CAAC,cAAc;4BAE3B,IAAM,WAAW,KAAK,GAAG,CAAC;4BAC1B,IAAI,SAAQ,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,SAAQ,GAAA,CAAK,IAAI;gCACrC,SAAS,GAAG,CAAC,UAAU;;4BAG3B,SAAS,GAAG,CAAC,gBAAgB,KAAK,GAAG,CAAC,gBAAe,EAAA,CAAI;4BAEzD,IAAM,QAAQ,KAAK,GAAG,CAAC;4BACvB,SAAS,GAAG,CAAC,YAAY,MAAK,EAAA,CAAI;4BAElC,IAAM,UAAU,KAAK,GAAG,CAAC;4BACzB,IAAI,cAAc;4BAClB,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;gCACjB,IAAI,oBAAO,SAAO,GAAA,CAAK,UAAU;oCAC7B,cAAc,QAAO,EAAA,CAAI,MAAM;kCAC5B,IAEN,CAFM;oCACH,cAAc,KAAK,SAAS,CAAC;;;4BAGrC,SAAS,GAAG,CAAC,gBAAgB;4BAC7B,SAAS,GAAG,CAAC,kBAAkB;4BAE/B,IAAM,OAAO,KAAK,GAAG,CAAC;4BACtB,IAAM,OAAO,KAAK,GAAG,CAAC;4BACtB,IAAI,SAAS,CAAC,KAAI,EAAA,CAAI,KAAI,EAAA,CAAI,EAAE,EAAC,EAAA,CAAI,MAAM;4BAC3C,MAAO,OAAO,OAAO,CAAC,KAAI,EAAA,CAAI,CAAC,CAAE;gCAC7B,SAAS,OAAO,OAAO,CAAC,KAAK;;4BAEjC,SAAS,GAAG,CAAC,aAAa;4BAE1B,IAAM,SAAS,KAAK,SAAS,CAAC,SAAQ,EAAA,CAAI,CAAC;4BAC3C,IAAM,OAAO,KAAK,SAAS,CAAC,YAAW,EAAA,CAAI,CAAC;4BAC5C,SAAS,GAAG,CAAC,SAAS;4BACtB,SAAS,GAAG,CAAC,YAAY;4BACzB,SAAS,GAAG,CAAC,gBAAgB,OAAM,CAAA,CAAG;4BACtC,SAAS,GAAG,CAAC,cAAc,AAAI,OAAO,WAAW;4BAEjD,WAAW,IAAI,CAAC;4BApDgB;;;oBAuDpC,YAAgD,0BAA0B,WAAW,MAAM;oBAC3F,YAAgD,wBAAwB,KAAK,SAAS,CAAC;oBAEvF,IAAM,gBAAgB,MAAM,aACvB,IAAI,CAAC,kBACL,MAAM,CAAC,YACP,OAAO;oBAEZ,IAAI,cAAc,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC7B,cAAkD,0BAA0B,cAAc,KAAK;wBAC/F,cAAkD,uBAAuB,KAAK,SAAS,CAAC,cAAc,KAAK;wBAC3G,YAAgD;wBAChD,SAAO;;oBAGX,YAAgD;oBAEhD,IAAM,sBAAa,MAAM,IAAK,KAAE;wBAChC;wBAAI,IAAI,YAAI,CAAC;wBAAb,MAAe,EAAC,CAAA,CAAG,SAAS,MAAM;4BAC9B,IAAM,OAAO,QAAQ,CAAC,EAAE,CAAA,EAAA,CAAI;4BAC5B,IAAM,MAAM,KAAK,SAAS,CAAC;4BAC3B,IAAI,IAAG,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,IAAI,MAAM,CAAA,CAAA,CAAG,EAAE,EAAE;gCAChC,YAAY,IAAI,CAAC;;4BAJW;;;oBAQpC,IAAI,YAAY,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;wBACxB,MAAM,IAAI,CAAC,oBAAoB,CAAC;;oBAGpC,SAAO;;iBACP,OAAO,kBAAO;oBACZ,cAAkD,yBAAyB;oBAC3E,SAAO,IAAI;;SAElB;IAAD;IAGA,SAAM,mBAAmB,QAAQ,eAAe,GAAG,WAAQ,mBAAkB;QAAA,OAAA,eAAA;gBAC3E,IAAI;oBACA,IAAM,mBAAU,MAAM,IAAK,KAAE;oBAC7B,IAAM,SAAS,OAAO,UAAU,CAAA,EAAA,UAAI,GAAG;oBAEvC,IAAI,qBAAa,GAAG;wBACpB;wBAAI,IAAI,YAAI,CAAC;wBAAb,MAAe,EAAC,CAAA,CAAG,OAAO,MAAM;4BAC3B,IAAM,IAAI,MAAM,CAAC,EAAE,CAAA,EAAA,CAAI;4BAEvB,IAAM,YAAY,EAAE,GAAG,CAAC;4BACxB,IAAI,UAAS,EAAA,CAAI,IAAI;gCAJQ;gCAIN,QAAQ;;4BAC/B,IAAM,SAAS,UAAS,EAAA,UAAI,GAAG;gCAE/B;gCAAI,IAAI,aAAK,CAAC;gCAAd,MAAgB,GAAE,CAAA,CAAG,OAAO,MAAM;oCAC9B,IAAM,KAAK,MAAM,CAAC,GAAG,CAAA,EAAA,CAAI;oCACzB,IAAM,UAAU,GAAG,SAAS,CAAC,SAAQ,EAAA,CAAI,CAAC;oCAC1C,IAAM,QAAQ,GAAG,SAAS,CAAC,YAAW,EAAA,CAAI,CAAC;oCAC3C,cAAc,QAAO,CAAA,CAAG;oCAJQ;;;4BAPP;;;wBAgBlC;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,OAAO,MAAM;4BAC7B,IAAM,QAAQ,MAAM,CAAC,EAAE,CAAA,EAAA,CAAI;4BAC3B,IAAM,eAAe,MAAM,GAAG,CAAC;4BAC/B,IAAI,aAAY,EAAA,CAAI,IAAI;gCAHO;gCAGL,QAAQ;;4BAClC,IAAM,YAAY,aAAY,EAAA,UAAI,GAAG;4BAErC,IAAI,wBAAgB,GAAG;gCACvB;gCAAI,IAAI,YAAI,CAAC;gCAAb,MAAe,EAAC,CAAA,CAAG,UAAU,MAAM;oCAC/B,IAAM,QAAQ,SAAS,CAAC,EAAE,CAAA,EAAA,CAAI;oCAC9B,IAAM,UAAU,MAAM,SAAS,CAAC,SAAQ,EAAA,CAAI,CAAC;oCAC7C,IAAM,QAAQ,MAAM,SAAS,CAAC,YAAW,EAAA,CAAI,CAAC;oCAC9C,iBAAiB,QAAO,CAAA,CAAG;oCAJM;;;4BAQrC,IAAM,QAAQ,IAAA,WAAU,CAAA,CAAG,CAAC,EAAG;gCAAA,CAAC,cAAa,CAAA,CAAG,UAAU;4BAAA,EAAI,IAAC,CAAD;AAAA,iCAAC;4BAAD;4BAC9D,IAAM,kBAAkB,OAAO,WAAW,CAAA,CAAA,CAAG;4BAC7C,IAAM,eAAe,OAAO,cAAc,CAAA,CAAA,CAAG;4BAC7C,IAAM,YAAY,cAAa,CAAA,CAAG,gBAAe,CAAA,CAAG;4BAEpD,IAAM,MAAM,MAAM,SAAS,CAAC;4BAC5B,IAAM,MAAM,MAAM,SAAS,CAAC;4BAC5B,IAAM,WAAW,MAAM,SAAS,CAAC;4BAEjC,IAAM,UAAU,MAAM,IAAI,CAAC,WAAW,CAOrC,kBANG,cAAa,IAAA,CAAC,IAAG,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,IAAG,EAAA,CAAI,EAAE,GAAI;gCAAA;4BAAA,EAAM,IAAW,CAAX;gCAAA,CAAC,IAAG,EAAA,CAAI,EAAE;4BAAA;4BAAC,EAC3D,iBAAgB,eAChB,eAAc,iBACd,eAAc,WACd,mBAAkB,OAAO,gBAAgB,EACzC,QAAO;4BAGX,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;gCACjB,SAAS,IAAI,CAAC;8BACX,IAEN,CAFM;gCACH,SAAmE,kBAA1D,UAAS,KAAK,EAAE,WAAA,UAAU,QAAO,QAAM,WAAQ;;4BApC7B;;;oBAwCnC,SAAkC,kBAAzB,UAAS,IAAI,EAAE,WAAA;;iBAC1B,OAAO,cAAG;oBACR,cAAkD,aAAa;oBAC/D,SAAsD,kBAA7C,UAAS,KAAK,EAAE,WAAU,KAAE,EAAE,QAAO;;SAEnD;IAAD;IAGA,SAAM,UAAU,QAAQ,MAAM,GAAG,CAAC,GAAG,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBAC/C,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBACjB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGV,IAAI,QAAQ,aACT,IAAI,CAAC,aACL,MAAM,CAAC,0EAIP,EAAE,CAAC,WAAW,QACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK;oBAEzC,IAAI,OAAM,CAAA,CAAG,CAAC,EAAE;wBACZ,QAAQ,MAAM,EAAE,CAAC,gBAAgB;;oBAGrC,IAAM,WAAW,MAAM,MAAM,OAAO;oBAEpC,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,cAAkD,aAAa,SAAS,KAAK;wBAC7E,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,IAAM,OAAO,SAAS,IAAI;oBAC1B,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,SAAO,KAAI,EAAA,UAAI,GAAG;;iBACpB,OAAO,kBAAO;oBACZ,cAAkD,aAAa;oBAC/D,IAAM,gBAAO,GAAG,IAAK,KAAE;oBACvB,SAAO;;SAEd;IAAD;IAGA,SAAM,eAAe,SAAS,MAAM,GAAG,WAAQ,GAAG,GAAQ;QAAA,OAAA,eAAA;gBACtD,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,IAAI;;oBAE/B,IAAM,WAAW,MAAM,aACnB,IAAI,CAAC,aACL,MAAM,CAAC,6EAIP,EAAE,CAAC,MAAM,SACT,EAAE,CAAC,WAAW,QACd,MAAM,GACN,OAAO;oBAEX,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,SAAO,IAAI;;oBAEf,SAAO,SAAS,IAAI;;iBACtB,OAAO,cAAG;oBACR,SAAO,IAAI;;SAElB;IAAD;IAGA,SAAM,SAAS,SAAS,MAAM,EAAE,eAAe,MAAM,EAAE,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACpF,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,cAAkD;wBAClD,SAAO,KAAK;;oBAGhB,YAAgD,iCAAiC,SAAS,WAAW;oBAErG,IAAM,gBAAgB,AAAI;oBAC1B,cAAc,GAAG,CAAC,gBAAgB,CAAC;oBACnC,cAAc,GAAG,CAAC,kBAAkB,CAAC;oBACrC,cAAc,GAAG,CAAC,kBAAkB;oBACpC,cAAc,GAAG,CAAC,gBAAgB,AAAI,OAAO,WAAW;oBACxD,cAAc,GAAG,CAAC,eAAe;oBACjC,cAAc,GAAG,CAAC,cAAc,AAAI,OAAO,WAAW;oBAEtD,YAAgD,oBAAoB,KAAK,SAAS,CAAC;oBAEnF,IAAM,WAAW,MAAM,aACnB,IAAI,CAAC,aACL,MAAM,CAAC,eACP,EAAE,CAAC,MAAM,SACT,EAAE,CAAC,WAAW,QACd,OAAO;oBAEX,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,cAAkD,sBAAsB,SAAS,KAAK;wBACtF,SAAO,KAAK;;oBAGhB,YAAgD;oBAEhD,IAAI,cAAa,GAAA,CAAK,WAAW;wBAC5B,YAAgD;;oBAGrD,SAAO,IAAI;;iBACb,OAAO,cAAG;oBACR,cAAkD,oBAAoB;oBACtE,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,aAAa,SAAS,MAAM,GAAG,WAAQ,gBAAqB;QAAA,OAAA,eAAA;gBAC9D,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,cAAkD;wBAClD,SAAO,IAAI;;oBAGf,YAAgD,iCAAiC;oBAEjF,IAAM,WAAW,MAAM,aAClB,IAAI,CAAC,aACL,MAAM,CAAC,KACP,EAAE,CAAC,MAAM,SACT,EAAE,CAAC,WAAW,QACd,OAAO;oBAEZ,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,cAAkD,0BAA0B,SAAS,KAAK;wBAC1F,SAAO,IAAI;;oBAGf,IAAM,OAAO,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG;oBACjC,IAAI,KAAI,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,KAAK,MAAM,CAAA,GAAA,CAAK,CAAC,EAAE;wBACnC,YAAgD;wBAChD,SAAO,IAAI;;oBAGf,IAAM,WAAW,IAAI,CAAC,CAAC,CAAC;oBACxB,IAAI,UAAU;oBACd,IAAI,SAAQ,EAAA,CAAY,eAAe;wBACnC,WAAW,SAAQ,EAAA,CAAI;sBACpB,IAEN,CAFM;wBACH,WAAW,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,kDAArB,EAAA,CAAmC;;oBAGvD,YAAgD,wBAAwB,KAAK,SAAS,CAAC;oBACvF,SAAO;;iBACT,OAAO,cAAG;oBACR,cAAkD,wBAAwB;oBAC1E,SAAO,IAAI;;SAElB;IAAD;IAGA,SAAM,aAAa,MAAM,GAAG,GAAG,WAAQ,gBAAe;QAAA,OAAA,eAAA;gBACpD,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAA0C,eAAjC,UAAS,KAAK,EAAE,UAAS;;oBAEtD,IAAM,IAAI,KAAI,EAAA,CAAI;oBAClB,IAAM,UAAU,EAAE,SAAS,CAAC;oBAC5B,IAAM,aAAa,EAAE,SAAS,CAAC;oBAC/B,IAAM,eAAe,EAAE,SAAS,CAAC;oBACjC,IAAM,eAAe,EAAE,SAAS,CAAC;oBACjC,IAAM,cAAc,EAAE,SAAS,CAAC;oBAChC,IAAM,SAAS,EAAE,QAAQ,CAAC;oBAE1B,IAAM,yBAAU;wBACd,IAAA,UAAS;wBACT,IAAA,WAAU;wBACV,IAAA,YAAW,MAAK,CAAA,CAAG,KAAK,GAAG,GAAE,CAAA,CAAG,KAAK,KAAK,CAAC,KAAK,MAAM,GAAE,CAAA,CAAG,IAAI;wBAC/D,IAAA,cAAa;wBACb,IAAA,gBAAe;wBACf,IAAA,gBAAe;wBACf,IAAA,cAAa,YAAW,EAAA,CAAI;wBAC5B,IAAA,SAAQ,OAAM,EAAA,CAAI,CAAC,IAAM,GAAG,GAAE;wBAC9B,IAAA,iBAAQ,CAAC;qBACV;oBAED,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,cACL,MAAM,CAAC,SACP,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,WAAW,SAAS,KAAK;wBAC3E,SAAiF,eAAxE,UAAS,KAAK,EAAE,UAAS,SAAQ,CAAA,CAAG,CAAC,SAAS,KAAK,GAAC,OAAO,CAAA,EAAA,CAAI,MAAM;;oBAGhF,SAA2C,eAAlC,UAAS,IAAI,EAAE,UAAS;;iBACjC,OAAO,cAAG;oBACV,cAAkD,WAAW;oBAC7D,SAA0C,eAAjC,UAAS,KAAK,EAAE,UAAS;;SAErC;IAAD;IAGA,SAAM,WAAW,OAAO,GAAG,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAC1C,IAAI;oBAEA,IAAM,WAAW,MAAK,EAAA,CAAI;oBAE1B,IAAI,WAAW;oBACf,IAAI,WAAW,SAAS,GAAG,CAAC;oBAE5B,IAAI,SAAQ,EAAA,CAAI,IAAI,EAAE;wBAClB,WAAW;wBACX,WAAW,SAAS,GAAG,CAAC;;oBAG5B,IAAI,SAAQ,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAGlC,IAAM,QAAQ,SAAQ,EAAA,UAAI,GAAG;oBAC7B,IAAI,MAAM,MAAM,CAAA,GAAA,CAAK,CAAC;wBAAE,SAAO,KAAK;;wBAGpC;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,MAAM,MAAM;4BAE7B,IAAM,OAAO,KAAK,CAAC,EAAE,CAAA,EAAA,CAAI;4BACzB,IAAM,YAAY,KAAK,SAAS,CAAC;4BACjC,IAAM,QAAQ,KAAK,SAAS,CAAC;4BAE7B,IAAM,WAAW,KAAK,SAAS,CAAC,YAAW,EAAA,CAAI,CAAC;4BAEhD,IAAI,UAAS,EAAA,CAAI,IAAI,EAAE;gCACpB,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,UAAU,MAAK,EAAA,CAAI,IAAI;;4BAT3B;;;oBAYlC,SAAO,IAAI;;iBACb,OAAO,cAAG;oBACR,cAAkD,oBAAoB;oBACtE,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,YAAY,SAAS,MAAM,EAAE,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAChE,IAAI;oBAEC,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,aACL,MAAM,CAAC;wBACJ,IAAA,uBAAc,CAAC;wBACf,IAAA,gBAAe;wBACf,IAAA,aAAY,AAAI,OAAO,WAAW;qBACrC,EACA,EAAE,CAAC,MAAM,SACT,OAAO;oBAEV,SAAO,SAAS,KAAK,CAAA,EAAA,CAAK,IAAI;;iBACjC,OAAO,cAAG;oBACR,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,WAAW,qBAAY,MAAM,IAAK,KAAE,EAAE,MAAM,MAAM,GAAG,CAAC,EAAE,UAAU,MAAM,GAAG,EAAE,GAAG,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBAChG,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,IAAI,QAAQ,aACT,IAAI,CAAC,cACL,MAAM,CAAC,8UAYP,EAAE,CAAC,WAAW,QACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK;oBAEzC,IAAI,WAAW,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;wBAEvB,IAAM,UAAU,WAAU,EAAA,UAAI,GAAG;wBACjC,QAAQ,MAAM,IAAE,CAAC,UAAU;;oBAG/B,QAAQ,MAAM,KAAK,CAAC,CAAC,KAAI,CAAA,CAAG,CAAC,EAAC,CAAA,CAAG,UAAU,KAAI,CAAA,CAAG,SAAQ,CAAA,CAAG,CAAC;oBAE9D,IAAM,WAAW,MAAM,MAAM,OAAO;oBAEpC,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,cAAkD,aAAa,SAAS,KAAK;wBAC7E,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,IAAM,OAAO,SAAS,IAAI;oBAC1B,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,SAAO;;iBACT,OAAO,cAAG;oBACR,cAAkD,aAAa;oBAC/D,IAAM,gBAAO,GAAG,IAAK,KAAE;oBACvB,SAAO;;SAEd;IAAD;IAGA,SAAM,kBAAkB,WAAQ,MAAM,EAAC;QAAA,OAAA,eAAA;gBACnC,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,YAAgD,qCAAqC;oBACrF,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,CAAC;;oBAG5B,IAAM,YAAY,MAAM,aACpB,IAAI,CAAC,mBACL,MAAM,CAAC,WACP,EAAE,CAAC,WAAW,UACd,MAAM,GACN,OAAO;oBAEX,IAAI,UAAU,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACzB,cAAkD,oCAAoC,UAAU,KAAK;sBAClG,IAEN,CAFM;wBACH,YAAgD,mCAAmC,UAAU,IAAI;;oBAGrG,IAAI,UAAU,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,UAAU,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnD,IAAI,OAAO,UAAU,IAAI;wBAEzB,IAAI,SAAM,OAAO,CAAC,OAAO;4BACrB,IAAM,MAAM,KAAI,EAAA,UAAI,GAAG;4BACvB,IAAI,IAAI,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;gCAChB,OAAO,GAAG,CAAC,CAAC,CAAC;;;wBAIrB,IAAI,MAAI,MAAM,GAAG,CAAC;wBAClB,IAAI,KAAI,EAAA,CAAY,eAAe;4BAD/B,OAEM,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,WAAU,EAAA,CAAI,CAAC;4BAEpC,IAAI,AAJJ,KAIO,GAAA,CAAK,CAAC,CAAA,EAAA,CAAI,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,WAAU,EAAA,CAAI,IAAI,EAAE;gCAJpD,OAKU,WAAW,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;;4BAEpC,SAPA;0BAQG,IAQN,CARM;4BAEH,IAAM,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;4BAVpD,OAWM,QAAQ,SAAS,CAAC,WAAU,EAAA,CAAI,CAAC;4BACvC,IAAI,AAZJ,KAYO,GAAA,CAAK,CAAC,CAAA,EAAA,CAAI,QAAQ,SAAS,CAAC,WAAU,EAAA,CAAI,IAAI,EAAE;gCAZvD,OAaY,WAAW,QAAQ,SAAS,CAAC;;4BAEzC,SAfA;;;oBAmBR,YAAgD;oBAGhD,IAAM,UAAU,MAAM,IAAI,CAAC,cAAc;oBACzC,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;wBACjB,IAAI,QAAO,EAAA,CAAY,eAAe;4BAClC,SAAO,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,WAAU,EAAA,CAAI,CAAC;0BACrC,IAGN,CAHM;4BACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,iDAArB,EAAA,CAAkC;4BACpD,SAAO,KAAK,SAAS,CAAC,WAAU,EAAA,CAAI,CAAC;;;oBAG7C,SAAO,CAAC;;iBACV,OAAM,cAAG;oBACP,cAAkD,wCAAwC;oBAC1F,SAAO,CAAC;;SAEf;IAAD;IAGA,SAAM,iBAAiB,WAAQ,MAAM,EAAC;QAAA,OAAA,eAAA;gBAClC,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,YAAgD,oCAAoC;oBACpF,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,CAAC;;oBAG5B,IAAM,MAAM,MAAM,aACd,IAAI,CAAC,kBACL,MAAM,CAAC,UACP,EAAE,CAAC,WAAW,UACd,MAAM,GACN,OAAO;oBAEX,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnB,cAAkD,mCAAmC,IAAI,KAAK;sBAC3F,IAEN,CAFM;wBACH,YAAgD,kCAAkC,IAAI,IAAI;;oBAG9F,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,IAAI,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxC,IAAI,OAAO,IAAI,IAAI;wBAEnB,IAAI,SAAM,OAAO,CAAC,OAAO;4BACpB,IAAM,MAAM,KAAI,EAAA,UAAI,GAAG;4BACvB,IAAI,IAAI,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;gCAChB,OAAO,GAAG,CAAC,CAAC,CAAC;;;wBAItB,IAAI,KAAI,EAAA,CAAY,eAAe;4BAC/B,SAAO,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,UAAS,EAAA,CAAI,CAAC;0BACjC,IAON,CAPM;4BAEH,IAAM,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;4BACpD,IAAM,OAAM,QAAQ,SAAS,CAAC;4BAC9B,IAAI,AADE,KACC,EAAA,CAAI,IAAI;gCAAE,SADX;;4BAGN,SAAO,CAAC;;;oBAKf,IAAM,UAAU,MAAM,IAAI,CAAC,cAAc;oBACzC,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAI,QAAO,EAAA,CAAY,eAAe;4BAClC,SAAO,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,UAAS,EAAA,CAAI,CAAC;0BACpC,IAGN,CAHM;4BACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,iDAArB,EAAA,CAAkC;4BACpD,SAAO,KAAK,SAAS,CAAC,UAAS,EAAA,CAAI,CAAC;;;oBAI7C,SAAO,CAAC;;iBACV,OAAO,cAAG;oBACR,cAAkD,uCAAuC;oBACzF,SAAO,CAAC;;SAEf;IAAD;IAGA,SAAM,gBAAgB,MAAM,MAAM,GAAG,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,GAAG,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBACzE,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,IAAM,OAAO,CAAC,KAAI,CAAA,CAAG,CAAC,EAAC,CAAA,CAAG;oBAC1B,IAAM,KAAK,KAAI,CAAA,CAAG,MAAK,CAAA,CAAG,CAAC;oBAE3B,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,0BACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,UACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,MAAM,IACZ,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,aAAa,SAAS,KAAK;wBAC7E,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGT,IAAM,OAAO,SAAS,IAAI;oBAC1B,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,SAAO,KAAI,EAAA,UAAI,GAAG;;iBAClB,OAAO,cAAG;oBACV,cAAkD,aAAa;oBAC/D,IAAM,gBAAO,GAAG,IAAK,KAAE;oBACvB,SAAO;;SAEV;IAAD;IAGA,SAAM,mBAAmB,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBACnC,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,IAAM,MAAM,MAAM,aACd,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,UACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEX,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,IAAM,OAAO,IAAI,IAAI;oBACrB,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,SAAO,KAAI,EAAA,UAAI,GAAG;;iBACpB,OAAO,cAAG;oBACR,IAAM,gBAAO,GAAG,IAAK,KAAE;oBACvB,SAAO;;SAEd;IAAD;IAGA,SAAM,qBAAqB,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBACrC,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,IAAM,MAAM,MAAM,aACb,IAAI,CAAC,uBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,UACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEZ,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnB,cAAkD,WAAW,IAAI,KAAK;wBACtE,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,IAAM,OAAO,IAAI,IAAI;oBACrB,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,SAAO,KAAI,EAAA,UAAI,GAAG;;iBACpB,OAAO,cAAG;oBACR,cAAkD,WAAW;oBAC7D,IAAM,gBAAO,GAAG,IAAK,KAAE;oBACvB,SAAO;;SAEd;IAAD;IAGA,SAAM,oBAAoB,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBACpC,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,IAAM,MAAM,MAAM,aACb,IAAI,CAAC,sBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,UACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEZ,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnB,cAAkD,YAAY,IAAI,KAAK;wBACvE,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,IAAM,OAAO,IAAI,IAAI;oBACrB,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,SAAO,KAAI,EAAA,UAAI,GAAG;;iBACpB,OAAO,cAAG;oBACR,cAAkD,YAAY;oBAC9D,IAAM,gBAAO,GAAG,IAAK,KAAE;oBACvB,SAAO;;SAEd;IAAD;IAGA,SAAM,gBAAgB,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACnD,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAEhC,IAAM,MAAM,MAAM,aAAK,GAAG,CAAC,mBAAmB;wBAC1C,IAAA,YAAW;wBACX,IAAA,WAAU;qBACb;oBAED,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnB,cAAkD,YAAY,IAAI,KAAK;wBACvE,SAAO,KAAK;;oBAIhB,IAAM,OAAO,IAAI,IAAI;oBACrB,IAAI,KAAI,EAAA,CAAY,eAAe;wBAC9B,SAAO,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,UAAU,CAAC,WAAU,EAAA,CAAI,KAAK;;oBAG/C,SAAO,KAAK;;iBACd,OAAO,cAAG;oBACR,cAAkD,SAAS;oBAC3D,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,gBAAgB,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACnD,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAEhC,IAAM,MAAM,MAAM,aAAK,GAAG,CAAC,mBAAmB;wBAC1C,IAAA,YAAW;wBACX,IAAA,WAAU;qBACb;oBAED,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnB,cAAkD,YAAY,IAAI,KAAK;wBACvE,SAAO,KAAK;;oBAGhB,IAAM,OAAO,IAAI,IAAI;oBACrB,IAAI,KAAI,EAAA,CAAY,eAAe;wBAC9B,SAAO,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,UAAU,CAAC,WAAU,EAAA,CAAI,KAAK;;oBAE/C,SAAO,KAAK;;iBACd,OAAO,cAAG;oBACR,cAAkD,SAAS;oBAC3D,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,YAAY,MAAM,aAAa,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACpD,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAGhC,KAAK,GAAG,CAAC,WAAW;oBAEpB,IAAM,MAAM,MAAM,aACd,IAAI,CAAC,sBACL,MAAM,CAAC,MACP,OAAO;oBAEX,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACnB,cAAkD,YAAY,IAAI,KAAK;wBACvE,SAAO,KAAK;;oBAEhB,SAAO,IAAI;;iBACb,OAAO,cAAG;oBACR,cAAkD,YAAY;oBAC9D,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,eAAe,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAClD,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAEhC,IAAM,MAAM,MAAM,aACb,IAAI,CAAC,sBACL,EAAE,CAAC,MAAM,QACT,EAAE,CAAC,WAAW,UACd,QAAM,GACN,OAAO;oBAEZ,IAAI,IAAI,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD,YAAY,IAAI,KAAK;wBACvE,SAAO,KAAK;;oBAEjB,SAAO,IAAI;;iBACb,OAAO,cAAG;oBACP,cAAkD,YAAY;oBAC9D,SAAO,KAAK;;SAEpB;IAAD;IAGA,SAAM,cAAc,WAAW,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACrD,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,YAAgD,mCAAiC,SAAM,gBAAc;oBAErG,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAEhC,IAAM,WAAW,MAAM,aACnB,IAAI,CAAC,qBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,UACd,EAAE,CAAC,aAAa,WAChB,EAAE,CAAC,eAAe,CAAC,EACnB,KAAK,CAAC,CAAC,EACP,OAAO;oBAIX,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,cAAkD,uBAAqB,KAAK,SAAS,CAAC,SAAS,KAAK;wBACpG,SAAO,KAAK;;oBAGhB,IAAM,OAAO,SAAS,IAAI;oBAC1B,IAAI,SAAM,OAAO,CAAC,OAAO;wBACrB,IAAI,CAAC,KAAI,EAAA,UAAI,GAAG,CAAE,EAAE,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;4BAG5B,IAAM,OAAO,CAAA,KAAI,EAAA,UAAA,GAAA,CAAA,CAAA,CAAC,CAAC,CAAC;4BACpB,IAAI,WAAW;4BACf,IAAI,KAAI,EAAA,CAAY,eAAe;gCAC/B,WAAW,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,aAAY,EAAA,CAAI;8BACvC,IAGN,CAHM;gCACH,IAAM,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;gCACpD,WAAW,QAAQ,SAAS,CAAC,aAAY,EAAA,CAAI;;4BAGjD,IAAI,SAAQ,GAAA,CAAK,GAAE,EAAA,CAAI,SAAQ,GAAA,CAAK,WAAW;gCAC3C,cAAkD,mCAAiC,YAAS,WAAS;gCACrG,SAAO,KAAK;;4BAGhB,SAAO,IAAI;;sBAEZ,IAON,CAPM,IAAI,KAAI,EAAA,CAAY,eAAe;wBAEtC,IAAI,WAAW,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,aAAY,EAAA,CAAI;wBAC9C,IAAI,SAAQ,GAAA,CAAK,GAAE,EAAA,CAAI,SAAQ,GAAA,CAAK,WAAW;4BAC3C,SAAO,KAAK;;wBAEhB,SAAO,IAAI;;oBAGf,SAAO,KAAK;;iBACd,OAAM,cAAG;oBACP,cAAkD,2BAAyB;oBAC3E,SAAO,KAAK;;SAElB;IAAD;IAEA,SAAM,eAAe,WAAW,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACrD,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;oBAEhC,YAAgD,8BAA4B;oBAG5E,IAAM,SAAS,MAAM,IAAI,CAAC,aAAa,CAAC;oBACxC,YAAgD,iCAA+B;oBAE/E,IAAI,QAAQ;wBAER,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,qBACL,EAAE,CAAC,WAAW,UACd,EAAE,CAAC,aAAa,WAChB,EAAE,CAAC,eAAe,CAAC,EACnB,QAAM,GACN,OAAO;wBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;4BACxB,cAAkD,WAAW,SAAS,KAAK;4BAC3E,SAAO,IAAI;;wBAEf,SAAO,KAAK;sBACT,IAiBN,CAjBM;wBAEH,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,qBACL,MAAM,CAAC;4BACJ,IAAA,UAAS;4BACT,IAAA,YAAW;4BACX,IAAA,sBAAa,CAAC;4BACd,IAAA,aAAY,AAAI,OAAO,WAAW;yBACrC,EACA,OAAO;wBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;4BACxB,cAAkD,WAAW,SAAS,KAAK;4BAC3E,SAAO,KAAK;;wBAEhB,SAAO,IAAI;;;iBAEjB,OAAO,cAAG;oBACR,cAAkD,aAAa;oBAE/D,SAAO,MAAM,IAAI,CAAC,aAAa,CAAC;;SAEvC;IAAD;IAEA,SAAM,gBAAgB,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBACjC,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAIX,IAAM,WAAW,MAAM,aACnB,IAAI,CAAC,qBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,UACd,EAAE,CAAC,eAAe,CAAC,EACnB,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEX,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACzB,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEV,IAAM,YAAY,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG;oBACtC,IAAI,UAAS,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,UAAU,MAAM,CAAA,GAAA,CAAK,CAAC,EAAE;wBAC9C,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAIV,IAAM,qBAAY,MAAM,IAAK,KAAE;wBAC/B;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,UAAU,MAAM;4BAChC,IAAI,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE;4BAC5B,IAAI,MAAM;4BACV,IAAI,KAAI,EAAA,CAAY,eAAe;gCAC/B,MAAM,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,aAAY,EAAA,CAAI;8BAClC,IAGN,CAHM;gCACH,IAAM,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;gCACpD,MAAM,QAAQ,SAAS,CAAC,aAAY,EAAA,CAAI;;4BAE5C,IAAI,IAAG,GAAA,CAAK;gCAAI,WAAW,IAAI,CAAC;;4BATE;;;oBAYtC,IAAI,WAAW,MAAM,CAAA,GAAA,CAAK,CAAC;wBAAE,SAAO,KAAE;;oBAGtC,IAAM,gBAAgB,WAAU,EAAA,UAAI,GAAG;oBACvC,IAAM,aAAa,MAAM,aACrB,IAAI,CAAC,eACL,MAAM,CAAC,oDACP,IAAE,CAAC,MAAM,eACT,OAAO;oBAEX,IAAI,WAAW,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,IAAM,WAAW,WAAW,IAAI,CAAA,EAAA,UAAI,GAAG;oBACvC,IAAM,aAAa,AAAI,IAAI,MAAM,EAAE,GAAG;wBAEtC;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,SAAS,MAAM;4BAE/B,IAAI,GAAG,GAAG,GAAG,QAAQ,CAAC,EAAE;4BACxB,IAAI,MAAM;4BACV,IAAI,EAAC,EAAA,CAAY,eAAe;gCAC5B,MAAM,CAAA,EAAC,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,MAAK,EAAA,CAAI;8BACxB,IAGN,CAHM;gCACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,2CAArB,EAAA,CAA4B;gCAC9C,MAAM,KAAK,SAAS,CAAC,MAAK,EAAA,CAAI;;4BAElC,IAAI,IAAG,GAAA,CAAK;gCAAI,WAAW,GAAG,CAAC,KAAK;;4BAVH;;;oBAcrC,IAAM,iBAAQ,GAAG,IAAK,KAAE;wBACxB;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,UAAU,MAAM;4BAChC,IAAI,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE;4BAC5B,IAAI,SAAS;4BAEb,IAAI,KAAI,EAAA,CAAY,eAAe;gCAE/B,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;8BAC3C,IAEN,CAFM;gCACH,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;;4BAGlD,IAAI,WAAW,QAAQ,SAAS,CAAC;4BAEjC,IAAI,SAAQ,EAAA,CAAI,IAAI,EAAE;gCAClB,IAAM,UAAU,WAAW,GAAG,CAAC,SAAQ,EAAA,CAAI,MAAM;gCACjD,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;oCACjB,QAAQ,GAAG,CAAC,eAAe;oCAC3B,OAAO,IAAI,CAAC;;;4BAjBc;;;oBAsBtC,SAAO;;iBACT,OAAO,cAAG;oBACR,cAAkD,aAAa;oBAC/D,SAAO,KAAE;;SAEf;IAAD;IAGA,SAAM,iBAAiB,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBACnC,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,YAAgD;wBAChD,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,YAAgD,iCAAiC;oBAGjF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,sBACL,MAAM,CAAC,KACP,EAAE,CAAC,WAAW,UACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,EAAE,EACR,OAAO;oBAEV,YAAgD,+BAA+B,SAAS,KAAK;oBAC7F,YAAgD,8BAA8B,KAAK,SAAS,CAAC,SAAS,IAAI;oBAE1G,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,2BAA2B,SAAS,KAAK;wBAC3F,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGT,IAAM,aAAa,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG;oBACvC,IAAI,WAAU,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,WAAW,MAAM,CAAA,GAAA,CAAK,CAAC,EAAE;wBAC/C,YAAgD;wBAChD,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGX,YAAgD,2BAA2B,WAAW,MAAM;oBAG5F,IAAM,qBAAY,MAAM,IAAK,KAAE;wBAC/B;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,WAAW,MAAM;4BACjC,IAAI,OAAO,UAAU,CAAC,EAAE;4BACxB,IAAI,MAAM;4BACV,IAAI,KAAI,EAAA,CAAY,eAAe;gCAC/B,MAAM,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI;8BACnC,IAGN,CAHM;gCACH,IAAM,UAAU,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;gCACpD,MAAM,QAAQ,SAAS,CAAC,cAAa,EAAA,CAAI;;4BAE7C,IAAI,IAAG,GAAA,CAAK,GAAE,EAAA,CAAI,CAAC,WAAW,QAAQ,CAAC;gCAAM,WAAW,IAAI,CAAC;;4BAT1B;;;oBAYvC,IAAI,WAAW,MAAM,CAAA,GAAA,CAAK,CAAC;wBAAE,SAAO,KAAE;;oBAEtC,IAAM,wBAAe,GAAG,IAAK,KAAE;wBAC/B;wBAAI,IAAI,YAAE,CAAC;wBAAX,MAAa,EAAC,CAAA,CAAC,WAAW,MAAM;4BAC5B,cAAc,IAAI,CAAC,UAAU,CAAC,EAAE;4BADF;;;oBAKlC,IAAM,aAAa,MAAM,aACtB,IAAI,CAAC,2BACL,MAAM,CAAC,0FACP,IAAE,CAAC,MAAM,eACT,OAAO;oBAGV,IAAI,mBAAU,GAAG,IAAK,KAAE;oBACxB,IAAI,WAAW,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,WAAW,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;wBACrD,WAAW,WAAW,IAAI,CAAA,EAAA,UAAI,GAAG;sBAC9B,IAUN,CAVM;wBACH,aAAiD;wBACjD,IAAM,UAAU,MAAM,aAClB,IAAI,CAAC,eACL,MAAM,CAAC,+EACP,IAAE,CAAC,MAAM,eACT,OAAO;wBACX,IAAI,QAAQ,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;4BACvB,WAAW,QAAQ,IAAI,CAAA,EAAA,UAAI,GAAG;;;oBAItC,IAAM,aAAa,AAAI,IAAI,MAAM,EAAE,GAAG;wBACtC;wBAAI,IAAI,YAAE,CAAC;wBAAX,MAAa,EAAC,CAAA,CAAC,SAAS,MAAM;4BAC1B,IAAI,IAAI,QAAQ,CAAC,EAAE;4BACnB,IAAI,MAAM;4BACV,IAAI,EAAC,EAAA,CAAY,eAAe;gCAC5B,MAAM,CAAA,EAAC,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,MAAK,EAAA,CAAI;8BACxB,IAGN,CAHM;gCACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,2CAArB,EAAA,CAA4B;gCAC9C,MAAM,KAAK,SAAS,CAAC,MAAK,EAAA,CAAI;;4BAElC,IAAI,IAAG,GAAA,CAAK;gCAAI,WAAW,GAAG,CAAC,KAAK;;4BATR;;;oBAahC,IAAM,iBAAQ,GAAG,IAAK,KAAE;wBACxB;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,WAAW,MAAM;4BACnC,IAAI,KAAK,UAAU,CAAC,EAAE;4BACtB,IAAI,MAAM;4BACV,IAAI,mBAAW,CAAC;4BAEhB,IAAI,GAAE,EAAA,CAAY,eAAe;gCAC5B,MAAM,CAAA,GAAE,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI;gCACpC,IAAM,UAAU,CAAA,GAAE,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;gCAC7B,IAAI,QAAO,EAAA,CAAI,IAAI;oCAAE,WAAW,AAAI,KAAK,SAAS,OAAO;;8BACvD,IAKN,CALM;gCACF,IAAM,QAAQ,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,4CAArB,EAAA,CAA6B;gCAChD,MAAM,MAAM,SAAS,CAAC,cAAa,EAAA,CAAI;gCACvC,IAAM,UAAU,MAAM,SAAS,CAAC;gCAChC,IAAI,QAAO,EAAA,CAAI,IAAI;oCAAE,WAAW,AAAI,KAAK,SAAS,OAAO;;;4BAG9D,IAAM,UAAU,WAAW,GAAG,CAAC;4BAC/B,IAAI,QAAO,EAAA,CAAI,IAAI,EAAE;gCACjB,IAAI,QAAQ;gCACZ,IAAI,SAAS;gCACb,IAAI,iBAAS,CAAC;gCACd,IAAI,yBAAiB,CAAC;gCACtB,IAAI,iBAAS,CAAC;gCACd,IAAI,UAAU;gCACd,IAAI,YAAY;gCAEhB,IAAI,QAAO,EAAA,CAAY,eAAe;oCAClC,QAAQ,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,QAAO,EAAA,CAAI;oCACrC,SAAS,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,kBAAiB,EAAA,CAAI;oCAChD,SAAS,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;oCAC7C,iBAAiB,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,gBAAe,EAAA,CAAI,CAAC;oCACvD,SAAS,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;oCAC7C,UAAU,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,eAAc,EAAA,CAAI;oCAC9C,YAAY,CAAA,QAAO,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,aAAY,EAAA,CAAI;kCAC3C,IASN,CATM;oCACH,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,iDAArB,EAAA,CAAkC;oCACpD,QAAQ,KAAK,SAAS,CAAC,QAAO,EAAA,CAAI;oCAClC,SAAS,KAAK,SAAS,CAAC,kBAAiB,EAAA,CAAI;oCAC7C,SAAS,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;oCAC1C,iBAAiB,KAAK,SAAS,CAAC,gBAAe,EAAA,CAAI,CAAC;oCACpD,SAAS,KAAK,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;oCAC1C,UAAU,KAAK,SAAS,CAAC,eAAc,EAAA,CAAI;oCAC3C,YAAY,KAAK,SAAS,CAAC,aAAY,EAAA,CAAI;;gCAG/C,IAAM,QAAQ,AAAI;gCAClB,MAAM,GAAG,CAAC,MAAM;gCAChB,MAAM,GAAG,CAAC,QAAQ;gCAClB,MAAM,GAAG,CAAC,SAAS;gCACnB,MAAM,GAAG,CAAC,kBAAkB;gCAC5B,MAAM,GAAG,CAAC,SAAS;gCACnB,MAAM,GAAG,CAAC,SAAS;gCACnB,MAAM,GAAG,CAAC,UAAU;gCACpB,MAAM,GAAG,CAAC,YAAY;gCACtB,MAAM,GAAG,CAAC,YAAY;gCACtB,OAAO,IAAI,CAAC;;4BAvDqB;;;oBA2DvC,SAAO;;iBACP,OAAO,kBAAO;oBACd,cAAkD,WAAW;oBAC7D,SAAO,KAAE;;SAEZ;IAAD;IAGA,SAAM,aAAa,WAAW,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACnD,IAAI;oBACA,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,YAAgD;wBAChD,SAAO,KAAK;;oBAGd,YAAgD,gCAAgC,QAAQ,cAAc;oBAGtG,IAAM,WAAW,MAAM,aACnB,IAAI,CAAC,sBACL,MAAM,CAAC,MACP,EAAE,CAAC,WAAW,UACd,EAAE,CAAC,cAAc,WACjB,OAAO;oBAEX,YAAgD,8BAA8B,SAAS,KAAK;oBAC5F,YAAgD,6BAA6B,KAAK,SAAS,CAAC,SAAS,IAAI;oBAEzG,IAAM,YAAY,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG;oBACtC,IAAM,SAAS,UAAS,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,SAAM,OAAO,CAAC,WAAU,EAAA,CAAI,UAAU,MAAM,CAAA,CAAA,CAAG,CAAC;oBAEpF,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,QAAQ;wBAClC,YAAgD;wBAEhD,IAAM,YAAY,MAAM,aACpB,IAAI,CAAC,sBACL,MAAM,CAAC;4BAAE,IAAA,aAAY,AAAI,OAAO,WAAW;yBAAI,EAC/C,EAAE,CAAC,WAAW,UACd,EAAE,CAAC,cAAc,WACjB,OAAO;wBACX,YAAgD,8BAA8B,UAAU,KAAK;sBAC1F,IAcN,CAdM;wBACH,YAAgD;wBAEhD,IAAM,gBAAgB,AAAI;wBAC1B,cAAc,GAAG,CAAC,WAAW;wBAC7B,cAAc,GAAG,CAAC,cAAc;wBAChC,cAAc,GAAG,CAAC,cAAc,AAAI,OAAO,WAAW;wBACtD,cAAc,GAAG,CAAC,cAAc,AAAI,OAAO,WAAW;wBAEtD,IAAM,YAAY,MAAM,aACpB,IAAI,CAAC,sBACL,MAAM,CAAC,eACP,OAAO;wBACX,YAAgD,8BAA8B,UAAU,KAAK;;oBAEjG,SAAO,IAAI;;iBACb,OAAO,cAAG;oBACR,cAAkD,0BAA0B;oBAC5E,SAAO,KAAK;;SAEnB;IAAD;IAEA,SAAM,kBAAkB,oBAAQ,cAAc;QAAA,OAAA,eAAA;gBAC5C,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAM,gBAAO,eAAgB,KAAE;wBAC/B,SAAO;;oBAGX,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,qBACL,MAAM,CAAC,wFACP,EAAE,CAAC,WAAW,UACd,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,aAAa,SAAS,KAAK;wBAC7E,IAAM,gBAAO,eAAgB,KAAE;wBAC/B,SAAO;;oBAET,SAAO,SAAS,IAAI,CAAA,EAAA,UAAI;;iBACxB,OAAO,cAAG;oBACV,cAAkD,aAAa;oBAC/D,IAAM,gBAAO,eAAgB,KAAE;oBAC/B,SAAO;;SAEV;IAAD;IAGA,SAAM,kBAAkB,WAAW,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAC1D,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAClB,cAAkD;wBAClD,SAAO,KAAK;;oBAId,MAAM,IAAI,CAAC,mBAAmB,CAAC;oBAG/B,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,qBACL,MAAM,CAAC;wBACN,IAAA,aAAY,IAAI;wBAChB,IAAA,aAAY,AAAI,OAAO,WAAW;qBACnC,EACA,EAAE,CAAC,MAAM,WACT,EAAE,CAAC,WAAW,UACd,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,aAAa,SAAS,KAAK;wBAC7E,SAAO,KAAK;;oBAGd,SAAO,IAAI;;iBACX,OAAO,kBAAO;oBACd,cAAkD,aAAa;oBAC/D,SAAO,KAAK;;SAEf;IAAD;IAGA,SAAM,eAAe,QAAQ,MAAM,GAAG,CAAC,GAAG,oBAAQ,aAAa;QAAA,OAAA,eAAA;gBAC7D,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;wBAChB,IAAM,gBAAO,cAAe,KAAE;wBAC9B,SAAO;;oBAMX,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,mBACL,MAAM,CAAC,6FAIP,EAAE,CAAC,WAAW,UACd,EAAE,CAAC,UAAU,QACb,KAAK,CAAC,aAAgC,aAAjB,YAAW,IAAI,GACpC,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,YAAY,SAAS,KAAK;wBAC5E,IAAM,gBAAO,cAAe,KAAE;wBAC9B,SAAO;;oBAIT,IAAM,kBAAS,cAAe,KAAE;oBAChC,IAAM,UAAU,SAAS,IAAI,CAAA,EAAA,UAAI,GAAG;wBACpC;wBAAK,IAAI,YAAI,CAAC;wBAAd,MAAgB,EAAC,CAAA,CAAG,QAAQ,MAAM;4BAC9B,IAAM,OAAO,OAAO,CAAC,EAAE;4BACvB,IAAI,UAAU,GAAG,IAAU,IAAI;4BAC/B,IAAI,SAAS;4BACb,IAAI,aAAa;4BACjB,IAAI,aAAa;4BACjB,IAAI,WAAW;4BACf,IAAI,qBAAa,CAAC;4BAClB,IAAI,WAAW;4BACf,IAAI,aAAa;4BAEjB,IAAI,KAAI,EAAA,CAAY,eAAe;gCAChC,WAAW,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,GAAG,CAAC,YAAW,EAAA,CAAI,GAAG;gCACtC,SAAS,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,MAAK,EAAA,CAAI;gCACjC,aAAa,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,WAAU,EAAA,CAAI;gCAC1C,aAAa,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,eAAc,EAAA,CAAI;gCAC9C,WAAW,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,eAAc,EAAA,CAAI;gCAC5C,aAAa,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,UAAS,EAAA,CAAI,CAAC;gCAC1C,WAAW,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,eAAc,EAAA,CAAI;gCAC5C,aAAa,CAAA,KAAI,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,aAAY,EAAA,CAAI;8BACxC,IAUN,CAVM;gCACJ,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,8CAArB,EAAA,CAA+B;gCACjD,WAAW,KAAK,GAAG,CAAC,YAAW,EAAA,CAAI,GAAG;gCACtC,SAAS,KAAK,SAAS,CAAC,MAAK,EAAA,CAAI;gCACjC,aAAa,KAAK,SAAS,CAAC,WAAU,EAAA,CAAI;gCAC1C,aAAa,KAAK,SAAS,CAAC,eAAc,EAAA,CAAI;gCAC9C,WAAW,KAAK,SAAS,CAAC,eAAc,EAAA,CAAI;gCAC5C,aAAa,KAAK,SAAS,CAAC,UAAS,EAAA,CAAI,CAAC;gCAC1C,WAAW,KAAK,SAAS,CAAC,eAAc,EAAA,CAAI;gCAC5C,aAAa,KAAK,SAAS,CAAC,aAAY,EAAA,CAAI;;4BAG/C,IAAI,SAAQ,EAAA,CAAI,IAAI;gCAAE,WAAW,AAAI;;4BAErC,IAAI,QAAQ;4BACZ,IAAI,kBAAU,CAAC;4BACf,IAAI,eAAO,CAAC;4BAEZ,IAAI,SAAQ,EAAA,CAAY,eAAe;gCACnC,QAAQ,CAAA,SAAQ,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,QAAO,EAAA,CAAI;gCACtC,UAAU,CAAA,SAAQ,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,UAAS,EAAA,CAAI,CAAC;gCAC3C,OAAO,CAAA,SAAQ,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,aAAY,EAAA,CAAI,CAAC;8BACxC,IAKN,CALM;gCACJ,IAAM,OAAO,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,kDAArB,EAAA,CAAmC;gCACrD,QAAQ,KAAK,SAAS,CAAC,QAAO,EAAA,CAAI;gCAClC,UAAU,KAAK,SAAS,CAAC,UAAS,EAAA,CAAI,CAAC;gCACvC,OAAO,KAAK,SAAS,CAAC,aAAY,EAAA,CAAI,CAAC;;4BAG1C,IAAM,YAAY,AAAI;4BACtB,UAAU,GAAG,CAAC,MAAM;4BACpB,UAAU,GAAG,CAAC,WAAW;4BACzB,UAAU,GAAG,CAAC,eAAe;4BAC7B,UAAU,GAAG,CAAC,eAAe;4BAC7B,UAAU,GAAG,CAAC,UAAU;4BACxB,UAAU,GAAG,CAAC,eAAe;4BAC7B,UAAU,GAAG,CAAC,aAAa;4BAC3B,UAAU,GAAG,CAAC,iBAAiB;4BAC/B,UAAU,GAAG,CAAC,UAAU;4BACxB,UAAU,GAAG,CAAC,aAAa;4BAE3B,QAAQ,IAAI,CAAC,UAAS,EAAA,CAAI;4BA7DM;;;oBAgEpC,SAAO;;iBACP,OAAO,cAAG;oBACR,cAAkD,YAAY;oBAC9D,IAAM,gBAAO,cAAe,KAAE;oBAC9B,SAAO;;SAEZ;IAAD;IAGA,SAAM,sBAAsB,WAAQ,MAAM,EAAC;QAAA,OAAA,eAAA;gBACzC,IAAI;oBACF,IAAM,SAAS,IAAI,CAAC,gBAAgB;oBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;wBAAE,SAAO,CAAC;;oBAE5B,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,mBACL,MAAM,CAAC,MAAM;wBAAE,IAAA,QAAO;qBAAS,EAC/B,EAAE,CAAC,WAAW,UACd,EAAE,CAAC,UAAU,CAAC,EACd,EAAE,CAAC,aAAa,AAAI,OAAO,WAAW,IACtC,KAAK,CAAC,CAAC,EACP,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,SAAO,CAAC;;oBAEZ,SAAO,SAAS,KAAK,CAAA,EAAA,CAAI,CAAC;;iBAC1B,OAAO,cAAG;oBACR,SAAO,CAAC;;SAEb;IAAD;IAGA,SAAM,oBAAoB,YAAY,MAAM,GAAG,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBAC3D,SAAO,IAAI,CAAC,gBAAgB,CAAC;SAC9B;IAAD;IAGA,SAAM,iBAAiB,YAAY,MAAM,GAAG,oBAAQ,GAAG,GAAG;QAAA,OAAA,eAAA;gBACxD,IAAI;oBAGF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,uBACL,MAAM,CAAC,KACP,EAAE,CAAC,oBAAkB,aAAU,wBAC/B,EAAE,CAAC,UAAU,CAAC,EACd,EAAE,CAAC,YAAY,AAAI,OAAO,WAAW,IACrC,KAAK,CAAC,kBAAsC,aAAlB,YAAW,KAAK,GAC1C,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,yBAAyB,SAAS,KAAK;wBACzF,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAGT,IAAM,OAAO,SAAS,IAAI;oBAC1B,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,IAAM,gBAAO,GAAG,IAAK,KAAE;wBACvB,SAAO;;oBAEX,SAAO,KAAI,EAAA,UAAI,GAAG;;iBAClB,OAAO,cAAG;oBACV,cAAkD,wBAAwB;oBAC1E,IAAM,gBAAO,GAAG,IAAK,KAAE;oBACvB,SAAO;;SAEV;IAAD;IAGA,SAAM,YAAY,YAAY,MAAM,EAAE,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACpE,SAAO,IAAI,CAAC,eAAe,CAAC,YAAY;SAC1C;IAAD;IAGA,SAAM,gBAAgB,YAAY,MAAM,EAAE,QAAQ,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBACxE,IAAI;oBACF,YAAgD,+BAA+B,YAAY,WAAW;oBAGtG,IAAM,UAAU,MAAM,aACnB,IAAI,CAAC,uBACL,MAAM,CAAC,KACP,EAAE,CAAC,MAAM,YACT,KAAK,CAAC,CAAC,EACP,OAAO;oBAEV,IAAI,QAAQ,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACzB,cAAkD,sCAAsC,QAAQ,KAAK;wBACrG,SAAO,KAAK;;oBAId,IAAI,QAAQ,IAAI,CAAA,EAAA,CAAI,IAAI,EAAE;wBACtB,cAAkD;wBAClD,SAAO,KAAK;;oBAGhB,IAAM,WAAW,QAAQ,IAAI,CAAA,EAAA,UAAI,GAAG;oBACpC,IAAI,SAAS,MAAM,CAAA,GAAA,CAAK,CAAC,EAAE;wBACvB,cAAkD;wBAClD,SAAO,KAAK;;oBAGhB,IAAM,WAAW,QAAQ,CAAC,CAAC,CAAC;oBAG5B,IAAI,oBAAY,CAAC;oBACjB,IAAI,YAAY,MAAM,IAAU,IAAI;oBACpC,IAAI,YAAY,MAAM,IAAU,IAAI;oBAEpC,IAAI,SAAQ,EAAA,CAAY,eAAe;wBACnC,YAAY,CAAA,SAAQ,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;wBACjD,aAAa,CAAA,SAAQ,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;wBAChC,aAAa,CAAA,SAAQ,EAAA,CAAA,aAAA,EAAC,SAAS,CAAC;sBAC7B,IAKN,CALM;wBACH,IAAM,QAAQ,4BAAI,CAAJ,KAAK,KAAK,CAAC,KAAK,SAAS,CAAC,kDAArB,EAAA,CAAmC;wBACtD,YAAY,MAAM,SAAS,CAAC,cAAa,EAAA,CAAI,CAAC;wBAC9C,aAAa,MAAM,SAAS,CAAC;wBAC7B,aAAa,MAAM,SAAS,CAAC;;oBAIjC,IAAI,WAAW,AAAI,KAAK,KAAK,GAAG,GAAE,CAAA,CAAG,UAAwB,EAAE,WAAW;oBAC1E,IAAI,UAAS,CAAA,CAAG,CAAC,EAAE;wBACf,WAAW,AAAI,KAAK,KAAK,GAAG,GAAE,CAAA,CAAG,CAAC,UAAwB,CAAA,CAAxB,QAA+B,GAAG,WAAW;sBAC5E,IAEN,CAFM,IAAI,WAAU,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,WAAU,GAAA,CAAK,IAAI;wBAChD,WAAW;;oBAIf,IAAI,WAAU,EAAA,CAAI,IAAI,CAAA,EAAA,CAAI,WAAW,MAAM,CAAA,GAAA,CAAK,CAAC,EAAE;wBAChD,aAAa,IAAI;;oBAIpB,IAAM,4BAAa;wBACd,IAAA,UAAS;wBACT,IAAA,cAAa;wBACb,IAAA,cAAa;wBACb,IAAA,cAAa,IAAG,CAAA,CAAG,KAAK,GAAG,GAAE,CAAA,CAAG,KAAK,KAAK,CAAC,KAAK,MAAM,GAAE,CAAA,CAAG,IAAI;wBAC/D,IAAA,iBAAQ,CAAC;wBACT,IAAA,YAAW;wBACX,IAAA,cAAa,AAAI,OAAO,WAAW;qBACvC;oBAED,YAAgD,gCAAgC,KAAK,SAAS,CAAC;oBAE/F,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,mBACL,MAAM,CAAC,YACP,OAAO;oBAET,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,cAAkD,gCAAgC,KAAK,SAAS,CAAC,SAAS,KAAK;wBAE/G,IAAI,KAAK,SAAS,CAAC,SAAS,KAAK,EAAE,QAAQ,CAAC,gBAAgB;4BACvD,YAAgD;4BAChD,IAAM,8BAAe;gCAClB,IAAA,UAAS;gCACT,IAAA,cAAa;gCACb,IAAA,cAAa,IAAG,CAAA,CAAG,KAAK,GAAG,GAAE,CAAA,CAAG,KAAK,MAAM,GAAG,QAAQ,CAAA,EAAA,EAAG,SAAS,CAAC,CAAC,EAAC,CAAC;gCACtE,IAAA,iBAAQ,CAAC;gCACT,IAAA,YAAW;gCACX,IAAA,cAAa,AAAI,OAAO,WAAW;6BACrC;4BACD,IAAM,OAAO,MAAM,aAAK,IAAI,CAAC,mBAAmB,MAAM,CAAC,cAAc,OAAO;4BAC5E,IAAI,KAAK,KAAK,CAAA,EAAA,CAAI,IAAI;gCAAE,SAAO,IAAI;;;wBAExC,SAAO,KAAK;;oBAEhB,SAAO,IAAI;;iBACZ,OAAM,cAAG;oBACR,cAAkD,uBAAuB;oBACzE,SAAO,KAAK;;SAEjB;IAAD;IAOA,SAAM,gBAAgB,YAAY,MAAM,EAAE,MAAM,MAAM,GAAG,CAAC,EAAE,UAAU,MAAM,GAAG,EAAE,GAAG,oBAAQ,cAAc;QAAA,OAAA,eAAA;gBACxG,IAAM,SAAS,IAAI,CAAC,gBAAgB;gBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;oBAChB,IAAM,gBAAO,eAAgB,KAAE;oBAC/B,SAAO;;gBAIX,IAAM,YAAY,CAAC,KAAI,CAAA,CAAG,CAAC,EAAC,CAAA,CAAG;gBAC/B,IAAM,UAAU,UAAS,CAAA,CAAG,SAAQ,CAAA,CAAG,CAAC;gBAExC,IAAI;oBAIF,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,EAAE,CAAC,kBAAgB,aAAU,qBAAmB,YAChD,KAAK,CAAC,cAAkC,aAAlB,YAAW,KAAK,GACtC,KAAK,CAAC,WAAW,SACjB,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBAC1B,cAAkD,0BAA0B,SAAS,KAAK;wBAC1F,IAAM,gBAAO,eAAgB,KAAE;wBAC/B,SAAO;;oBAGT,IAAM,OAAO,SAAS,IAAI;oBAC1B,IAAI,KAAI,EAAA,CAAI,IAAI,EAAE;wBACd,IAAM,gBAAO,eAAgB,KAAE;wBAC/B,SAAO;;oBAGX,SAAO,KAAI,EAAA,UAAI;;iBACf,OAAO,cAAG;oBACV,cAAkD,8BAA8B;oBAChF,IAAM,gBAAO,eAAgB,KAAE;oBAC/B,SAAO;;SAEV;IAAD;IAGA,SAAM,YAAY,YAAY,MAAM,EAAE,SAAS,MAAM,EAAE,SAAS,MAAM,GAAG,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAE9F,IAAM,SAAS,IAAI,CAAC,gBAAgB;gBACpC,IAAI,OAAM,EAAA,CAAI,IAAI,EAAE;oBAChB,cAAkD;oBAClD,SAAO,KAAK;;gBAGhB,IAAI;oBAKA,IAAM,qBAAM,kGACR,eAAW,UACX,iBAAa,YACb,aAAS,SACT,cAAU,SACV,aAAS,KAAK,EACd,kBAAc,IAAI;oBAGtB,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC,KACP,OAAO;oBAEV,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI,EAAE;wBACxB,cAAkD,sBAAsB,SAAS,KAAK;wBACtF,SAAO,KAAK;;oBAEhB,SAAO,IAAI;;iBACb,OAAO,cAAG;oBACR,cAAkD,0BAA0B;oBAC5E,SAAO,KAAK;;SAEnB;IAAD;IAGA,SAAM,SAAS,YAAY,MAAM,GAAG,WAAQ,OAAO,EAAC;QAAA,OAAA,eAAA;gBAChD,IAAM,SAAS,IAAI,CAAC,gBAAgB;gBACpC,IAAI,OAAM,EAAA,CAAI,IAAI;oBAAE,SAAO,KAAK;;gBAChC,IAAI;oBACA,IAAM,WAAW,MAAM,aACpB,IAAI,CAAC,oBACL,MAAM,CAAC;wBAAE,IAAA,UAAS,IAAI;qBAAE,EACxB,EAAE,CAAC,aAAa,YAChB,EAAE,CAAC,eAAe,QAClB,EAAE,CAAC,WAAW,KAAK,EACnB,OAAO;oBAET,IAAI,SAAS,KAAK,CAAA,EAAA,CAAI,IAAI;wBAAE,SAAO,KAAK;;;iBAC3C,OAAO,cAAG;oBAAE,SAAO,KAAK;;gBAC1B,SAAO,IAAI;SACd;IAAD;;AAIK,IAAM,kBAAkB,AAAI;AC39GpB,WAAV;IACJ;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;;;oCAFE,WAAA,kCAAA,GAAA,EAAA,CAAA;;;;;qDHhVf,EAAA;;;;;;;;AIqHqB,WAAhB;IACJ;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;mBAAM,MAAM,CAAA;IACZ;0BAAa,MAAM,CAAA;IACnB;oBAAO,MAAM,CAAA;;;oCALO,iBAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAhB,8BAAA,4BAAA;;;;;oHACJ,aAAA,IACA,eAAA,MACA,eAAA,MACA,sBAAA,aACA,gBAAA;;;;;;;eALI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;;;;wDJ1HD,EAAA;;;;;;;;AK4NmB,WAAd;IACJ;iBAAI,MAAM,CAAC;IACX;oBAAO,MAAM,CAAC;IACd;sBAAS,MAAM,CAAC;IAChB;mBAAM,MAAM,CAAC;IACb;mBAAM,OAAO,SAAC;IACd;mBAAM,MAAM,CAAC;IACb,iBAAQ,MAAM,SAAQ;IACtB;wBAAW,OAAO,SAAC;IACnB;qBAAQ,MAAM,CAAC;IACf;qBAAQ,MAAM,CAAC;IACf;sBAAS,OAAO,SAAC;IACjB;uBAAU,MAAM,CAAC;IACjB;qBAAQ,MAAM,CAAC;IACf;yBAAY,MAAM,CAAC;IACnB;mBAAM,MAAM,CAAC;IACb;0BAAa,MAAM,CAAC;IACpB;qBAAQ,OAAO,SAAC;IAChB;0BAAa,MAAM,CAAC;IACpB;4BAAM,MAAM,EAAG;IACf;mBAAM,MAAM,CAAC;IACb;oBAAO,MAAM,CAAC;IACd;qBAAQ,OAAO,SAAA;;;oCAtBG,eAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAd,4BAAA,0BAAA;;;;;kHACJ,aAAA,IACA,gBAAA,OACA,kBAAA,SACA,eAAA,MACA,eAAA,MACA,eAAA,MACA,iBAAA,QACA,oBAAA,WACA,iBAAA,QACA,iBAAA,QACA,kBAAA,SACA,mBAAA,UACA,iBAAA,QACA,qBAAA,YACA,eAAA,MACA,sBAAA,aACA,iBAAA,QACA,sBAAA,aACA,eAAA,MACA,eAAA,MACA,gBAAA,OACA,iBAAA;;;;;;;eAtBI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,OAAO;;iDAAb;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,OAAO;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,OAAO;;oDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,OAAO;;mDAAf;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,eAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,OAAO;;mDAAf;;;;;;mCAAA;oBAAA;;;;AAIiB,WAAb;IACJ;iBAAI,MAAM,CAAC;IACX;mBAAM,MAAM,CAAC;IACb;qBAAQ,MAAM,CAAA;;;oCAHG,cAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAb,2BAAA,yBAAA;;;;;iHACJ,aAAA,IACA,eAAA,MACA,iBAAA;;;;;;;eAHI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;;;;wDLzPD,EAAA;;;;;;;;AM4KqB,WAAhB;IACD;iBAAI,MAAM,CAAA;IACV;qBAAQ,MAAM,CAAA;IACd;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;IACb;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;uBAAU,OAAO,SAAA;IACjB;wBAAW,MAAM,CAAA;IACjB;oBAAO,MAAM,CAAA;IACb;yBAAY,MAAM,CAAA;;;oCAZD,iBAAA,iCAAA,GAAA,EAAA,CAAA;;;;;;MAAhB,8BAAA,4BAAA;;;;;oHACD,aAAA,IACA,iBAAA,QACA,mBAAA,UACA,eAAA,MACA,gBAAA,OACA,gBAAA,OACA,eAAA,MACA,mBAAA,UACA,mBAAA,UACA,oBAAA,WACA,gBAAA,OACA,qBAAA;;;;;;;eAZC;;iBACD,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,OAAO;;qDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;AAGa,WAAZ;IACJ;qBAAQ,MAAM,CAAA;IACd;uBAAU,MAAM,CAAA;IAChB;yBAAY,MAAM,CAAA;IAClB;6BAAO,eAAe;;;oCAJN,aAAA,iCAAA,GAAA,EAAA,CAAA;;;AAOO,WAAnB;IACJ;iBAAI,MAAM,CAAA;IACV;qBAAQ,MAAM,CAAA;IACd;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;;;oCAPU,oBAAA,iCAAA,GAAA,EAAA,CAAA;;;;;;MAAnB,iCAAA,+BAAA;;;;;uHACJ,aAAA,IACA,iBAAA,QACA,mBAAA,UACA,eAAA,MACA,gBAAA,OACA,gBAAA,OACA,gBAAA;;;;;;;eAPI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;;;;oDNzMD,EAAA;;;;;;;;AO8PqB,WAAhB;IACH;qBAAQ,MAAK,CAAA;IACb;sBAAS,MAAK,CAAA;IACd;oBAAO,MAAK,CAAA;;;oCAHO,iBAAA,oCAAA,GAAA,EAAA,CAAA;;;;;;MAAhB,8BAAA,4BAAA;;;;;oHACH,iBAAA,QACA,kBAAA,SACA,gBAAA;;;;;;;eAHG;;iBACH,QAAQ,MAAK;;mDAAb;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAK;;oDAAd;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAK;;kDAAZ;;;;;;mCAAA;oBAAA;;;;AAUuB,WAApB;IACH;sBAAS,MAAK,CAAA;IACd;wBAAW,MAAK,CAAA;;;oCAFO,qBAAA,oCAAA,GAAA,EAAA,CAAA;;;;;;MAApB,kCAAA,gCAAA;;;;;wHACH,kBAAA,SACA,oBAAA;;;;;;;eAFG;;iBACH,SAAS,MAAK;;oDAAd;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAK;;sDAAhB;;;;;;mCAAA;oBAAA;;;;AAG0B,WAAvB;IACH;2BAAc,MAAK,CAAA;IACnB;0BAAa,MAAK,CAAA;IAClB;yBAAY,MAAK,CAAA;IACjB;0BAAa,MAAK,CAAA;;;oCAJQ,wBAAA,oCAAA,GAAA,EAAA,CAAA;;;;;;MAAvB,qCAAA,mCAAA;;;;;2HACH,uBAAA,cACA,sBAAA,aACA,qBAAA,YACA,sBAAA;;;;;;;eAJG;;iBACH,cAAc,MAAK;;yDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAK;;wDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAK;;uDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAK;;wDAAlB;;;;;;mCAAA;oBAAA;;;;AAGqB,WAAlB;IACH;kBAAK,MAAK,CAAA;IACV;oBAAO,MAAK,CAAA;;;oCAFS,mBAAA,oCAAA,GAAA,EAAA,CAAA;;;;;;MAAlB,gCAAA,8BAAA;;;;;sHACH,cAAA,KACA,gBAAA;;;;;;;eAFG;;iBACH,KAAK,MAAK;;gDAAV;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAK;;kDAAZ;;;;;;mCAAA;oBAAA;;;;;;;;;;;AC7Dc,WAAX;IACJ;iBAAI,MAAM,CAAA;IACV,gBAAO,MAAM,SAAO;IACpB,gBAAO,MAAM,SAAO;IACpB,mBAAU,MAAM,SAAO;IACvB,qBAAY,MAAM,SAAO;;;oCALV,YAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAX,4BAAA,0BAAA;;;;;kHACJ,aAAA,IACA,gBAAA,OACA,gBAAA,OACA,mBAAA,UACA,qBAAA;;;;;;;eALI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;AAGuB,WAAnB;IACJ;oBAAO,OAAO,SAAA;IACd;wBAAW,OAAO,SAAA;IAClB;qBAAQ,OAAO,SAAA;;;oCAHQ,oBAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAnB,iCAAA,+BAAA;;;;;uHACJ,gBAAA,OACA,oBAAA,WACA,iBAAA;;;;;;;eAHI;;iBACJ,OAAO,OAAO;;kDAAd;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,OAAO;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,OAAO;;mDAAf;;;;;;mCAAA;oBAAA;;;;AAGkB,WAAd;IACJ;2BAAc,OAAO,SAAA;IACrB;iCAAoB,OAAO,SAAA;IAC3B;iCAAoB,OAAO,SAAA;;;oCAHT,eAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAd,4BAAA,0BAAA;;;;;kHACJ,uBAAA,cACA,6BAAA,oBACA,6BAAA;;;;;;;eAHI;;iBACJ,cAAc,OAAO;;yDAArB;;;;;;mCAAA;oBAAA;;;iBACA,oBAAoB,OAAO;;+DAA3B;;;;;;mCAAA;oBAAA;;;iBACA,oBAAoB,OAAO;;+DAA3B;;;;;;mCAAA;oBAAA;;;;;;wDR7OD,EAAA;;;;;;;;ASuLuB,WAAlB;IACJ;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;4BAAe,MAAM,CAAA;IACrB;8BAAiB,MAAM,CAAA;IACvB;0BAAa,MAAM,CAAA;IACnB,qBAAY,MAAM,SAAO;IACzB,iBAAQ,MAAM,SAAO;IACrB;yBAAY,MAAM,CAAA;;;oCARI,mBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAlB,gCAAA,8BAAA;;;;;sHACJ,aAAA,IACA,kBAAA,SACA,wBAAA,eACA,0BAAA,iBACA,sBAAA,aACA,qBAAA,YACA,iBAAA,QACA,qBAAA;;;;;;;eARI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,iBAAiB,MAAM;;4DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;AAGgB,WAAZ;IACJ;4BAAe,MAAM,CAAA;IACrB;2BAAc,MAAM,CAAA;IACpB;4BAAe,MAAM,CAAA;;;oCAHL,aAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAZ,0BAAA,wBAAA;;;;;gHACJ,wBAAA,eACA,uBAAA,cACA,wBAAA;;;;;;;eAHI;;iBACJ,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;;;;sDTrMD,EAAA;;;;;;;;AU6EgB,WAAX;IACH;iBAAI,MAAM,CAAA;IACV;wBAAW,MAAM,CAAA;IACjB;0BAAa,MAAM,CAAA;;;oCAHL,YAAA,qCAAA,EAAA,EAAA,CAAA;;;;;;MAAX,yBAAA,uBAAA;;;;;+GACH,aAAA,IACA,oBAAA,WACA,sBAAA;;;;;;;eAHG;;iBACH,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;;;;wDVhFF,EAAA;;;;;;;;;;sDAAA,EAAA;;;;;;;;;;;;;;;;;0DAAA,EAAA;;;;;;;;AW4Bc,WAAT;IACH;oBAAO,MAAM,CAAA;IACb;qBAAQ,MAAM,CAAA;IACd;qBAAQ,MAAM,CAAA;IACd;iBAAI,MAAM,CAAA;;;oCAJE,UAAA,oCAAA,EAAA,EAAA,CAAA;;;;;;MAAT,uBAAA,qBAAA;;;;;6GACH,gBAAA,OACA,iBAAA,QACA,iBAAA,QACA,aAAA;;;;;;;eAJG;;iBACH,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;;;;uDXhCF,EAAA;;;;;;;;AYmCe,WAAV;IACH;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;IACb,iBAAS,MAAM,SAAA;IACf,mBAAW,MAAM,SAAA;;;oCAPJ,WAAA,sCAAA,EAAA,EAAA,CAAA;;;;;;MAAV,2BAAA,yBAAA;;;;;iHACH,aAAA,IACA,eAAA,MACA,gBAAA,OACA,gBAAA,OACA,gBAAA,OACA,iBAAA,QACA,mBAAA;;;;;;;eAPG;;iBACH,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,QAAS,MAAM;;mDAAf;;;;;;mCAAA;oBAAA;;;iBACA,UAAW,MAAM;;qDAAjB;;;;;;mCAAA;oBAAA;;;;;;yDZ1CF,EAAA;;;;;;;;Aa0EqB,WAAhB;IACJ;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;6BAAgB,MAAM,CAAA;IACtB;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;IACb;qBAAQ,MAAM,CAAA;IACd;uBAAU,MAAM,CAAA;IAChB;uBAAU,MAAM,CAAA;IAChB;uBAAU,OAAO,SAAA;;;oCAVG,iBAAA,sCAAA,EAAA,EAAA,CAAA;;;;;;MAAhB,8BAAA,4BAAA;;;;;oHACJ,aAAA,IACA,eAAA,MACA,gBAAA,OACA,yBAAA,gBACA,gBAAA,OACA,gBAAA,OACA,iBAAA,QACA,mBAAA,UACA,mBAAA,UACA,mBAAA;;;;;;;eAVI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,OAAO;;qDAAjB;;;;;;mCAAA;oBAAA;;;;AAGqB,WAAjB;IACJ;wBAAW,MAAM,CAAA;IACjB;sBAAS,MAAM,CAAA;IACf;6BAAO,eAAe;;;oCAHD,kBAAA,sCAAA,EAAA,EAAA,CAAA;;;AAMG,WAApB;IACJ;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;6BAAgB,MAAM,CAAA;IACtB;oBAAO,MAAM,CAAA;IACb;oBAAO,MAAM,CAAA;IACb;qBAAQ,MAAM,CAAA;IACd;uBAAU,MAAM,CAAA;IAChB;uBAAU,MAAM,CAAA;;;oCATQ,qBAAA,sCAAA,EAAA,EAAA,CAAA;;;;;yDb7FzB,EAAA;;;;;;;;AcwCe,WAAV;IACH;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;qBAAQ,MAAM,CAAA;IACd;wBAAW,OAAO,SAAA;IAClB,gBAAQ,MAAM,SAAA;;;oCATD,WAAA,yCAAA,EAAA,EAAA,CAAA;;;;;;MAAV,wBAAA,sBAAA;;;;;8GACH,aAAA,IACA,eAAA,MACA,gBAAA,OACA,mBAAA,UACA,eAAA,MACA,mBAAA,UACA,iBAAA,QACA,oBAAA,WACA,gBAAA;;;;;;;eATG;;iBACH,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,OAAO;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,OAAQ,MAAM;;kDAAd;;;;;;mCAAA;oBAAA;;;;;;2DdjDF,EAAA;;;;;;;;Ae2De,WAAV;IACH;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;qBAAQ,MAAM,CAAA;IACd;wBAAW,OAAO,SAAA;IAClB,gBAAQ,MAAM,SAAA;;;oCATD,WAAA,yCAAA,EAAA,EAAA,CAAA;;;AAkBI,WAAd;IACD;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;qBAAQ,MAAM,CAAA;IACd;wBAAW,OAAO,SAAA;IAClB;oBAAO,MAAM,CAAA;;;oCALE,eAAA,yCAAA,EAAA,EAAA,CAAA;;;;;;MAAd,4BAAA,0BAAA;;;;;kHACD,eAAA,MACA,gBAAA,OACA,iBAAA,QACA,oBAAA,WACA,gBAAA;;;;;;;eALC;;iBACD,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,OAAO;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;;;;2DflFJ,EAAA;;;;;;;;AgBySwB,WAAnB;IACJ;iBAAI,MAAM,CAAA;IACV;yBAAY,MAAM,CAAA;IAClB;qBAAQ,MAAM,CAAA;IACd;2BAAc,MAAM,CAAA;IACpB;4BAAe,MAAM,CAAA;IACrB;iCAAoB,GAAG,CAAA;IACvB;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IACb,kBAAU,MAAM,SAAA;IAChB,oBAAY,MAAM,SAAA;IAClB,sBAAc,MAAM,SAAA;;;oCAXA,oBAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAnB,iCAAA,+BAAA;;;;;uHACJ,aAAA,IACA,qBAAA,YACA,iBAAA,QACA,uBAAA,cACA,wBAAA,eACA,6BAAA,oBACA,gBAAA,OACA,mBAAA,UACG,kBAAA,SACA,oBAAA,WACA,sBAAA;;;;;;;eAXC;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,oBAAoB,GAAG;;+DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACG,SAAU,MAAM;;oDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,WAAY,MAAM;;sDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,aAAc,MAAM;;wDAApB;;;;;;mCAAA;oBAAA;;;;AAGsB,WAArB;IACJ;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;0BAAa,MAAM,CAAA;;;oCAJM,sBAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAArB,mCAAA,iCAAA;;;;;yHACJ,aAAA,IACA,eAAA,MACA,gBAAA,OACA,sBAAA;;;;;;;eAJI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;;AAGoB,WAAhB;IACJ;qBAAQ,MAAM,CAAA;IACd;uBAAU,MAAM,CAAA;IAChB;0BAAa,MAAM,CAAA;IACnB;oBAAO,SAAM,kBAAiB;;;oCAJV,iBAAA,qCAAA,GAAA,EAAA,CAAA;;;AAOK,WAArB;IACJ;mBAAM,MAAM,CAAA;IACZ;6BAAgB,MAAM,CAAA;IACtB;+BAAkB,MAAM,CAAA;;;oCAHC,sBAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAArB,sCAAA,oCAAA;;;;;4HACJ,eAAA,MACA,yBAAA,gBACA,2BAAA;;;;;;;eAHI;;iBACJ,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,kBAAkB,MAAM;;6DAAxB;;;;;;mCAAA;oBAAA;;;;AAGqB,WAAjB;IACJ;iBAAI,MAAM,CAAA;IACV,mBAAU,8BAAyB;;;oCAFd,kBAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAjB,+BAAA,6BAAA;;;;;qHACJ,aAAA,IACA,mBAAA;;;;;;;eAFI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,UAAU;;qDAAV;;;;;;mCAAA;oBAAA;;;;AAGkB,WAAd;IACJ;iBAAI,MAAM,CAAA;IACV;6BAAgB,MAAM,CAAA;IACtB;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;qBAAQ,MAAM,CAAA;IACd;yBAAY,OAAO,SAAA;;;oCARD,eAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAd,4BAAA,0BAAA;;;;;kHACJ,aAAA,IACA,yBAAA,gBACA,gBAAA,OACA,mBAAA,UACA,eAAA,MACA,mBAAA,UACA,iBAAA,QACA,qBAAA;;;;;;;eARI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,OAAO;;uDAAnB;;;;;;mCAAA;oBAAA;;;;AAGqB,WAAjB;IACH;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;qBAAQ,MAAM,CAAA;IACd;wBAAW,OAAO,SAAA;;;oCARE,kBAAA,qCAAA,GAAA,EAAA,CAAA;;;AAYA,WAAjB;IACJ;6BAAgB,MAAM,CAAA;IACtB;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;qBAAQ,MAAM,CAAA;IACd;yBAAY,OAAO,SAAA;;;oCAPE,kBAAA,qCAAA,GAAA,EAAA,CAAA;;;;;;MAAjB,+BAAA,6BAAA;;;;;qHACJ,yBAAA,gBACA,gBAAA,OACA,mBAAA,UACA,eAAA,MACA,mBAAA,UACA,iBAAA,QACA,qBAAA;;;;;;;eAPI;;iBACJ,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,OAAO;;uDAAnB;;;;;;mCAAA;oBAAA;;;;AAGkB,WAAd;IACJ;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB;mBAAM,MAAM,CAAA;IACZ;uBAAU,MAAM,CAAA;IAChB;qBAAQ,MAAM,CAAA;IACd;wBAAW,OAAO,SAAA;;;oCARA,eAAA,qCAAA,GAAA,EAAA,CAAA;;;;;wDhBjXnB,EAAA;;;;;;;;AiBmHyB,WAApB;IACJ;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;0BAAa,MAAM,CAAA;IACnB;mBAAM,MAAM,CAAA;IACZ;sBAAS,OAAO,SAAA;;;oCALQ,qBAAA,oCAAA,GAAA,EAAA,CAAA;;;;;;MAApB,kCAAA,gCAAA;;;;;wHACJ,aAAA,IACA,eAAA,MACA,sBAAA,aACA,eAAA,MACA,kBAAA;;;;;;;eALI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,OAAO;;oDAAhB;;;;;;mCAAA;oBAAA;;;;;;uDjBxHD,EAAA;;;;;;;;;;8DAAA,EAAA;;;;;;;;AkBuLoB,WAAf;IACD;iBAAI,MAAM,CAAC;IACX;mBAAM,MAAM,CAAC;IACb;oBAAO,MAAM,CAAA;;;oCAHG,gBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAf,6BAAA,2BAAA;;;;;mHACD,aAAA,IACA,eAAA,MACA,gBAAA;;;;;;;eAHC;;iBACD,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;;AAIgB,WAAf;IACD;iBAAI,MAAM,CAAC;IACX;mBAAM,MAAM,CAAC;IACb;oBAAO,MAAM,CAAC;IACd;oBAAO,MAAM,CAAC;IACd;mBAAM,MAAM,CAAC;IACb;uBAAU,MAAM,CAAA;;;oCANA,gBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAf,6BAAA,2BAAA;;;;;mHACD,aAAA,IACA,eAAA,MACA,gBAAA,OACA,gBAAA,OACA,eAAA,MACA,mBAAA;;;;;;;eANC;;iBACD,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;;AAIa,WAAZ;IACD;iBAAI,MAAM,CAAC;IACX;uBAAU,MAAM,CAAC;IACjB;qBAAQ,MAAM,CAAC;IACf;0BAAa,MAAM,CAAC;IACpB;6BAAgB,MAAM,CAAC;IACvB;2BAAc,MAAM,CAAC;IACrB;2BAAc,MAAM,CAAC;IACrB;gCAAU,cAAc;;;oCARX,aAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAZ,0BAAA,wBAAA;;;;;gHACD,aAAA,IACA,mBAAA,UACA,iBAAA,QACA,sBAAA,aACA,yBAAA,gBACA,uBAAA,cACA,uBAAA,cACA,mBAAA;;;;;;;eARC;;iBACD,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,mBAAU;;qDAAV;;;;;;mCAAA;oBAAA;;;;;;sDlBhNJ,EAAA;;;;;;;;AmB8IiB,WAAZ;IACJ;uBAAU,MAAM,CAAC;IACjB;2BAAc,MAAM,CAAC;IACrB;2BAAc,MAAM,CAAC;IACrB;6BAAgB,MAAM,CAAC;IACvB;2BAAc,MAAM,CAAC;IACrB;8BAAiB,MAAM,CAAC;IACxB;6BAAgB,MAAM,CAAC;IACvB;yBAAY,MAAM,CAAC;IACnB;sBAAS,MAAM,CAAC;IAChB;yBAAY,MAAM,CAAC;IACnB;2BAAc,MAAM,CAAC;IACrB;0BAAa,MAAM,CAAC;IACpB;+BAAkB,GAAG,CAAA;;;oCAbL,aAAA,yCAAA,GAAA,EAAA,CAAA;;;;;;MAAZ,6BAAA,2BAAA;;;;;mHACJ,mBAAA,UACA,uBAAA,cACA,uBAAA,cACA,yBAAA,gBACA,uBAAA,cACA,0BAAA,iBACA,yBAAA,gBACA,qBAAA,YACA,kBAAA,SACA,qBAAA,YACA,uBAAA,cACA,sBAAA,aACA,2BAAA;;;;;;;eAbI;;iBACJ,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,iBAAiB,MAAM;;4DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,MAAM;;2DAAtB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,kBAAkB,GAAG;;6DAArB;;;;;;mCAAA;oBAAA;;;;AAGoB,WAAhB;IACJ;iBAAI,MAAM,CAAC;IACX;yBAAY,MAAM,CAAC;IACnB;2BAAc,MAAM,CAAC;IACrB;wBAAW,MAAM,CAAC;IAClB;oBAAO,MAAM,CAAC;IACd;uBAAU,MAAM,CAAC;IACjB;6BAAgB,GAAG,CAAA;;;oCAPC,iBAAA,yCAAA,GAAA,EAAA,CAAA;;;;;;MAAhB,8BAAA,4BAAA;;;;;oHACJ,aAAA,IACA,qBAAA,YACA,uBAAA,cACA,oBAAA,WACA,gBAAA,OACA,mBAAA,UACA,yBAAA;;;;;;;eAPI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,gBAAgB,GAAG;;2DAAnB;;;;;;mCAAA;oBAAA;;;;AAGkB,WAAd;IACJ;mBAAM,MAAM,CAAC;IACb;oBAAO,MAAM,CAAC;IACd;uBAAU,MAAM,CAAC;IACjB;mBAAM,MAAM,CAAC;IACb;uBAAU,MAAM,CAAC;IACjB;qBAAQ,MAAM,CAAC;IACf;sBAAS,MAAM,CAAA;;;oCAPG,eAAA,yCAAA,GAAA,EAAA,CAAA;;;;;;MAAd,4BAAA,0BAAA;;;;;kHACJ,eAAA,MACA,gBAAA,OACA,mBAAA,UACA,eAAA,MACA,mBAAA,UACA,iBAAA,QACA,kBAAA;;;;;;;eAPI;;iBACJ,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;;AAGuB,WAAnB;IACJ;0BAAa,MAAM,CAAA;;;oCADI,oBAAA,yCAAA,GAAA,EAAA,CAAA;;;;;;MAAnB,iCAAA,+BAAA;;;;;uHACJ,sBAAA;;;;;;;eADI;;iBACJ,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;;;;2DnBnLD,EAAA;;;;;;;;AoB6CiB,WAAZ;IACH;mBAAM,MAAM,CAAA;IACZ;mBAAM,MAAM,CAAA;;;oCAFG,aAAA,sCAAA,EAAA,EAAA,CAAA;;;;;;MAAZ,0BAAA,wBAAA;;;;;gHACH,eAAA,MACA,eAAA;;;;;;;eAFG;;iBACH,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;;;;yDpB/CF,EAAA;;;;;;;;AqBqJqB,WAAhB;IACJ;iBAAI,MAAM,CAAA;IACV;yBAAY,MAAM,CAAA;IAClB;2BAAc,MAAM,CAAA;IACpB;4BAAe,MAAM,CAAA;IACrB;iCAAoB,GAAG,CAAA;IACvB;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;;;oCAPI,iBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAhB,iCAAA,+BAAA;;;;;uHACJ,aAAA,IACA,qBAAA,YACA,uBAAA,cACA,wBAAA,eACA,6BAAA,oBACA,gBAAA,OACA,mBAAA;;;;;;;eAPI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,oBAAoB,GAAG;;+DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;;AAGmB,WAAf;IACJ;iBAAI,MAAM,CAAA;IACV;wBAAW,MAAM,CAAA;IACjB;qBAAQ,MAAM,CAAA;;;oCAHK,gBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAf,gCAAA,8BAAA;;;;;sHACJ,aAAA,IACA,oBAAA,WACA,iBAAA;;;;;;;eAHI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;;;;sDrBlKD,EAAA;;;;;;;;AsByG+B,WAA1B;IACJ;qBAAQ,MAAM,CAAA;IACd;qBAAQ,MAAM,CAAA;IACd;yBAAY,MAAM,CAAA;;;oCAHY,2BAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAA1B,wCAAA,sCAAA;;;;;8HACJ,iBAAA,QACA,iBAAA,QACA,qBAAA;;;;;;;eAHI;;iBACJ,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;AAGwB,WAApB;IACJ;8BAAQ,MAAM,EAAE;;;oCADQ,qBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAApB,kCAAA,gCAAA;;;;;wHACJ,iBAAA;;;;;;;eADI;;iBACJ,iBAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;;AAGsB,WAAlB;IACJ;iBAAI,MAAM,CAAA;IACV;2BAAc,MAAM,CAAA;IACpB;iCAAoB,GAAG,CAAA;IACvB;oBAAO,MAAM,CAAA;IACb;uBAAU,MAAM,CAAA;IAChB,kBAAU,0BAAiB;;;oCANL,mBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAlB,gCAAA,8BAAA;;;;;sHACJ,aAAA,IACA,uBAAA,cACA,6BAAA,oBACA,gBAAA,OACA,mBAAA,UACA,kBAAA;;;;;;;eANI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,cAAc,MAAM;;yDAApB;;;;;;mCAAA;oBAAA;;;iBACA,oBAAoB,GAAG;;+DAAvB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,SAAU;;oDAAV;;;;;;mCAAA;oBAAA;;;;AAGsB,WAAlB;IACJ;iBAAI,MAAM,CAAA;IACV;uBAAU,MAAM,CAAA;IAChB;yBAAY,MAAM,CAAA;IAClB;mCAAa,iBAAiB;;;oCAJR,mBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAlB,gCAAA,8BAAA;;;;;sHACJ,aAAA,IACA,mBAAA,UACA,qBAAA,YACA,sBAAA;;;;;;;eAJI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,sBAAa;;wDAAb;;;;;;mCAAA;oBAAA;;;;AAGiB,WAAb;IACJ;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;uBAAU,MAAM,CAAA;IAChB;wBAAW,MAAM,CAAA;IACjB;0BAAa,MAAM,CAAA;IACnB;4BAAe,MAAM,CAAA;IACrB;4BAAe,MAAM,CAAA;IACrB;qBAAQ,MAAM,CAAA;IACd,kCAAgB,iCAAgC;IAChD;yBAAY,MAAM,CAAA;IAClB,gBAAQ,wBAAe;;;oCAXN,cAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAb,2BAAA,yBAAA;;;;;iHACJ,aAAA,IACA,kBAAA,SACA,mBAAA,UACA,oBAAA,WACA,sBAAA,aACA,wBAAA,eACA,wBAAA,eACA,iBAAA,QACA,yBAAA,gBACA,qBAAA,YACA,gBAAA;;;;;;;eAXI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,UAAU,MAAM;;qDAAhB;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,yBAAgB;;2DAAhB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,OAAQ;;kDAAR;;;;;;mCAAA;oBAAA;;;;AAGoB,WAAhB;IACJ;yBAAY,MAAM,CAAA;;;oCADE,iBAAA,mCAAA,GAAA,EAAA,CAAA;;;;;;MAAhB,8BAAA,4BAAA;;;;;oHACJ,qBAAA;;;;;;;eADI;;iBACJ,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;;;sDtBlJD,EAAA;;;;;;;;;;2DAAA,EAAA;;;;;;;;;;4DAAA,EAAA;;;;;;;;AuB+HqB,WAAhB;IACJ;iBAAI,MAAM,CAAA;IACV;mBAAM,MAAM,CAAA;IACZ;sBAAS,MAAM,CAAA;IACf;mBAAM,MAAM,CAAA;;;oCAJQ,iBAAA,iCAAA,GAAA,EAAA,CAAA;;;;;;MAAhB,8BAAA,4BAAA;;;;;oHACJ,aAAA,IACA,eAAA,MACA,kBAAA,SACA,eAAA;;;;;;;eAJI;;iBACJ,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;;;;oDvBnID,EAAA;;;;;;;;;;oEAAA,EAAA;;;;;;;;;;sEAAA,EAAA;;;;;;;;;;6EAAA,EAAA;;;;;;;;;;2EAAA,EAAA;;;;;;;;AwBoCoB,WAAf;IACD;iBAAI,MAAM,CAAA;IACV;0BAAa,MAAM,CAAA;IACnB;wBAAW,MAAM,CAAA;IACjB,oBAAW,MAAM,SAAO;IACxB,sBAAa,MAAM,SAAO;IAC1B;yBAAY,MAAM,CAAA;IAClB;0BAAa,MAAM,CAAA;;;oCAPH,gBAAA,wDAAA,EAAA,EAAA,CAAA;;;;;;MAAf,6BAAA,2BAAA;;;;;mHACD,aAAA,IACA,sBAAA,aACA,oBAAA,WACA,oBAAA,WACA,sBAAA,aACA,qBAAA,YACA,sBAAA;;;;;;;eAPC;;iBACD,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;;;;yExB3CJ,EAAA;;;;;;;;AyB4CmB,WAAd;IACH;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;qBAAQ,MAAM,CAAA;IACd;mBAAM,MAAM,CAAA;IACZ;0BAAa,MAAM,CAAA;IACnB;yBAAY,MAAM,CAAA;;;oCAND,eAAA,yCAAA,EAAA,EAAA,CAAA;;;;;;MAAd,4BAAA,0BAAA;;;;;kHACH,aAAA,IACA,kBAAA,SACA,iBAAA,QACA,eAAA,MACA,sBAAA,aACA,qBAAA;;;;;;;eANG;;iBACH,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;;;2DzBlDF,EAAA;;;;;;;;A0B6CiB,WAAZ;IACH;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;qBAAQ,MAAM,CAAA;IACd;mBAAM,MAAM,CAAA;IACZ;qBAAQ,MAAM,CAAA;IACd;wBAAW,MAAM,CAAA;IACjB;yBAAY,MAAM,CAAA;;;oCAPH,aAAA,8CAAA,EAAA,EAAA,CAAA;;;;;;MAAZ,0BAAA,wBAAA;;;;;gHACH,aAAA,IACA,kBAAA,SACA,iBAAA,QACA,eAAA,MACA,iBAAA,QACA,oBAAA,WACA,qBAAA;;;;;;;eAPG;;iBACH,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,MAAM,MAAM;;iDAAZ;;;;;;mCAAA;oBAAA;;;iBACA,QAAQ,MAAM;;mDAAd;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,MAAM;;uDAAlB;;;;;;mCAAA;oBAAA;;;;;;+D1BpDF,EAAA;;;;;;;;A2BgCgB,WAAX;IACH;iBAAI,MAAM,CAAA;IACV;sBAAS,MAAM,CAAA;IACf;wBAAW,MAAM,CAAA;IACjB;4BAAe,MAAM,CAAA;IACrB;wBAAW,MAAM,CAAA;IACjB;0BAAa,MAAM,CAAA;IACnB;yBAAY,OAAO,SAAA;;;oCAPL,YAAA,6CAAA,EAAA,EAAA,CAAA;;;;;;MAAX,4BAAA,0BAAA;;;;;kHACH,aAAA,IACA,kBAAA,SACA,oBAAA,WACA,wBAAA,eACA,oBAAA,WACA,sBAAA,aACA,qBAAA;;;;;;;eAPG;;iBACH,IAAI,MAAM;;+CAAV;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,eAAe,MAAM;;0DAArB;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,OAAO;;uDAAnB;;;;;;mCAAA;oBAAA;;;;;;8D3BvCF,EAAA;;;;;;;;A4BoCoB,WAAf;IACD;0BAAa,MAAM,CAAA;IACnB;sBAAS,MAAM,CAAA;IACf;wBAAW,MAAM,CAAA;IACjB;oBAAO,MAAM,CAAA;IACb;yBAAY,OAAO,SAAA;;;oCALH,gBAAA,2CAAA,EAAA,EAAA,CAAA;;;;;;MAAf,6BAAA,2BAAA;;;;;mHACD,sBAAA,aACA,kBAAA,SACA,oBAAA,WACA,gBAAA,OACA,qBAAA;;;;;;;eALC;;iBACD,aAAa,MAAM;;wDAAnB;;;;;;mCAAA;oBAAA;;;iBACA,SAAS,MAAM;;oDAAf;;;;;;mCAAA;oBAAA;;;iBACA,WAAW,MAAM;;sDAAjB;;;;;;mCAAA;oBAAA;;;iBACA,OAAO,MAAM;;kDAAb;;;;;;mCAAA;oBAAA;;;iBACA,YAAY,OAAO;;uDAAnB;;;;;;mCAAA;oBAAA;;;;;;4D5BzCJ,EAAA;;;;;;;;AAKM,IAAU,aAAS,cAAA;IACvB,IAAM,MAAM;IAGb,IAAI,MAAM,CAAC,gBAAgB,OAAG;IAa7B,OAAO,IAAE,SAAA;AACX;AAEM,IAAU,KAAK,KAAK,IAAI,EAAA;IAC1B;IACA;IACA,CAAC,WAAW,CAAC,MAAM,CAAA,EAAA,CAAI,MAAM,EAAE,KAAK,CAAC,KAAK;AAC9C;AAEM,WAAO,eAAqB,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS;IACjE,aAAS,MAAM,MAAM,GAAG,MAAM;IAC9B,aAAS,OAAO,MAAM,GAAG,sBAAsB;IAC/C,aAAS,aAAa,MAAM,GAAG,OAAO;IACtC,aAAS,aAAa,MAAM,GAAG,KAAK;IACpC,aAAS,oBAAoB,MAAM,GAAG,MAAM;IAE5C,gBAAgB,KAAK,GAArB,CAAwB;;AAiD5B,IAAS,mBAAgB;IACzB,YAAY,IAAI,CAAyL,aAAtL,OAAM,oBAAoB,oCAAmC,OAA0B,YAAlB,SAAQ,IAAI,GAAmB,QAAO,IAAM,4BAAyB,QAAS,qBAAkB;IACxL,YAAY,IAAI,CAAuJ,aAApJ,OAAM,mBAAmB,mCAAkC,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC5J,YAAY,IAAI,CAAiK,aAA9J,OAAM,uBAAuB,uCAAsC,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACpK,YAAY,IAAI,CAAgL,aAA7K,OAAM,8BAA8B,6CAA4C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACjL,YAAY,IAAI,CAAkK,aAA/J,OAAM,oBAAoB,oCAAmC,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC9J,YAAY,IAAI,CAA+J,aAA5J,OAAM,qBAAqB,qCAAoC,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAChK,YAAY,IAAI,CAAiK,aAA9J,OAAM,sBAAsB,sCAAqC,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAClK,YAAY,IAAI,CAAgL,aAA7K,OAAM,8BAA8B,6CAA4C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACjL,YAAY,IAAI,CAAsK,aAAnK,OAAM,yBAAyB,wCAAuC,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACvK,YAAY,IAAI,CAAsK,aAAnK,OAAM,yBAAyB,wCAAuC,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACvK,YAAY,IAAI,CAAyO,aAAtO,OAAM,6BAA6B,4CAA2C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB,MAAO,qBAAkB,UAAW,2BAAwB,KAAK;IAChP,YAAY,IAAI,CAA+M,aAA5M,OAAM,gCAAgC,+CAA8C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB,MAAO,qBAAkB;IAC9M,YAAY,IAAI,CAAiN,aAA9M,OAAM,gCAAgC,+CAA8C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB,MAAO,2BAAwB,IAAI;IACxN,YAAY,IAAI,CAA2K,aAAxK,OAAM,4BAA4B,2CAA0C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC7K,YAAY,IAAI,CAAgL,aAA7K,OAAM,+BAA+B,8CAA6C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACnL,YAAY,IAAI,CAAkL,aAA/K,OAAM,gCAAgC,+CAA8C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACrL,YAAY,IAAI,CAAgL,aAA7K,OAAM,8BAA8B,6CAA4C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACjL,YAAY,IAAI,CAAoL,aAAjL,OAAM,gCAAgC,+CAA8C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACrL,YAAY,IAAI,CAA2M,aAAxM,OAAM,8BAA8B,6CAA4C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB,MAAO,qBAAkB;IAC1M,YAAY,IAAI,CAA+L,aAA5L,OAAM,sCAAsC,oDAAmD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAChM,YAAY,IAAI,CAAyL,aAAtL,OAAM,mCAAmC,iDAAgD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC1L,YAAY,IAAI,CAAmL,aAAhL,OAAM,+BAA+B,8CAA6C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACnL,YAAY,IAAI,CAAsL,aAAnL,OAAM,iCAAiC,gDAA+C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACvL,YAAY,IAAI,CAAsL,aAAnL,OAAM,iCAAiC,gDAA+C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACvL,YAAY,IAAI,CAA2L,aAAxL,OAAM,oCAAoC,kDAAiD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC5L,YAAY,IAAI,CAA2L,aAAxL,OAAM,oCAAoC,kDAAiD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC5L,YAAY,IAAI,CAAoL,aAAjL,OAAM,gCAAgC,+CAA8C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACrL,YAAY,IAAI,CAAiL,aAA9K,OAAM,+BAA+B,8CAA6C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACnL,YAAY,IAAI,CAA8N,aAA3N,OAAM,uCAAuC,qDAAoD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB,QAAS,qBAAkB;IAC7N,YAAY,IAAI,CAA+M,aAA5M,OAAM,8BAA8B,6CAA4C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB,QAAS,2BAAwB,IAAI;IACtN,YAAY,IAAI,CAA2L,aAAxL,OAAM,oCAAoC,kDAAiD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC5L,YAAY,IAAI,CAAsL,aAAnL,OAAM,iCAAiC,gDAA+C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACvL,YAAY,IAAI,CAAgL,aAA7K,OAAM,8BAA8B,6CAA4C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACjL,YAAY,IAAI,CAAiL,aAA9K,OAAM,8BAA8B,6CAA4C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACjL,YAAY,IAAI,CAA2L,aAAxL,OAAM,oCAAoC,kDAAiD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC5L,YAAY,IAAI,CAA6L,aAA1L,OAAM,qCAAqC,mDAAkD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC9L,YAAY,IAAI,CAAyM,aAAtM,OAAM,4BAA4B,2CAA0C,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB,QAAS,qBAAkB;IACxM,YAAY,IAAI,CAA8M,aAA3M,OAAM,8CAA8C,2DAA0D,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC/M,YAAY,IAAI,CAAkN,aAA/M,OAAM,gDAAgD,6DAA4D,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACnN,YAAY,IAAI,CAAgO,aAA7N,OAAM,uDAAuD,oEAAmE,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACjO,YAAY,IAAI,CAA4N,aAAzN,OAAM,qDAAqD,kEAAiE,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC7N,YAAY,IAAI,CAAwN,aAArN,OAAM,mDAAmD,gEAA+D,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACzN,YAAY,IAAI,CAA2L,aAAxL,OAAM,oCAAoC,kDAAiD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IAC5L,YAAY,IAAI,CAAoM,aAAjM,OAAM,yCAAyC,sDAAqD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACrM,YAAY,IAAI,CAAmM,aAAhM,OAAM,wCAAwC,qDAAoD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;IACnM,YAAY,IAAI,CAA+L,aAA5L,OAAM,sCAAsC,mDAAkD,OAA2B,YAAnB,SAAQ,KAAK,GAAmB,QAAO,IAAM,4BAAyB;AAC/L;AACA,IAAM,aAAa,IAAI,MAAM,EAAE,GAAG,MAAkB,IAAM,WAAQ,WAAY,mBAAgB,WAAY,qBAAkB,WAAY,iBAAc,SAAU,UAAO;IAAC,IAAM,cAAW,6BAA8B,UAAO,MAAO,cAAW,0BAA2B,sBAAmB;IAAmC,IAAM,cAAW,gCAAiC,UAAO,MAAO,cAAW,8BAA+B,sBAAmB;IAAuC,IAAM,cAAW,gCAAiC,UAAO,MAAO,cAAW,8BAA+B,sBAAmB;IAAuC,IAAM,cAAW,4BAA6B,UAAO,OAAQ,cAAW,0BAA2B,sBAAmB;IAAmC,IAAM,cAAW,+BAAgC,UAAO,MAAO,cAAW,6BAA8B,sBAAmB;CAAsC;AACn8B,IAAM,iBAAiB,IAAI,MAAM,EAAE,GAAG,KAAW,IAAM,SAAM,oBAAqB,WAAQ,IAAM,4BAAyB,QAAS,qBAAkB;AACpJ,IAAS,kBAAe;IACtB,YAAY,aAAa,GAAG;IAC5B,YAAY,WAAW,GAAG,IAAM,4BAAyB,SAAU,4BAAyB,QAAS,kCAA+B,WAAY,qBAAkB;IAClK,YAAY,eAAe,GAAG,OAAG,IAAI,MAAM,EAAE,GAAG;eAAa,IAAM,WAAQ,WAAY,mBAAgB,WAAY,qBAAkB,WAAY,iBAAc,SAAU,UAAO;YAAC,IAAM,cAAW,6BAA8B,UAAO,MAAO,cAAW,0BAA2B,sBAAmB;YAAmC,IAAM,cAAW,gCAAiC,UAAO,MAAO,cAAW,8BAA+B,sBAAmB;YAAuC,IAAM,cAAW,gCAAiC,UAAO,MAAO,cAAW,8BAA+B,sBAAmB;YAAuC,IAAM,cAAW,4BAA6B,UAAO,OAAQ,cAAW,0BAA2B,sBAAmB;YAAmC,IAAM,cAAW,+BAAgC,UAAO,MAAO,cAAW,6BAA8B,sBAAmB;SAAsC;;IAC58B,YAAY,MAAM,GAAG,YAAY,eAAe;IAChD,YAAY,YAAY,GAAG;IAC3B,YAAY,WAAW,GAAG,AAAI;IAE9B,YAAY,KAAK,GAAG,IAAI;AAC1B;sBAzImC,KAAK,MAAM,EAAE,QAAS,GAAG,GAAE,QAAS,MAAM,IAAG,MAAM,CAAG;IACvF,IAAI,KAAK,MAAM,CAAA,EAAA,CAAI,IAAI,EAAE;QACxB,cAA+B;QAC/B,OAAO;;IAEF,IAAM,SAAS,OAAM,EAAA,CAAI;IAC/B,IAAM,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ;IACjC,IAAI,IAAI,MAAM,CAAA,CAAA,CAAG,CAAC,EAAE;QAChB,OAAO;;IAEX,OAAO;AACd;;;;8BApBD,EAAA;;;;8BAAA,EAAA;;;;uBAAA,EAAA"}