Files
medical-mall/doc_mall/consumer/backup_doc_full/UTS_ANDROID_GUIDE.md

164 KiB
Raw Blame History

UTS Android 兼容性开发规范指南

以下为 uni-app x (UTS) Android 端开发常见注意事项与踩坑点,建议所有开发成员遵循:

================================================================================ 一、基础语法规范

  1. 变量声明

    • 只能使用 let 和 const不能使用 var
    • 变量声明必须有显式类型或初始化值
    • 不支持 undefined 类型,变量未赋值就是 null
    • 不支持 undefined 关键字,判断是否存在要用 != null
  2. 类型定义

    • 只适合用 type不适合使用 interfaceinterface 在 kotlin/swift 中另有不同)
    • 不支持 Intersection Type交叉类型
    • 不支持 Index Signature索引签名
    • 类型推断严格,必要时用 as Type 明确类型
    • 不支持内联对象类型Object Literal Type需要单独定义 type
  3. 函数定义

    • 函数必须在使用前定义(不支持函数提升)
    • 在 setup 模式下,调用的函数必须在调用之前定义
    • 依赖关系需要明确:被调用的函数必须先定义
    • 这与 JavaScript 的函数提升行为不同UTS 更接近 C/Java 的编译方式
  4. 循环

    • for 循环的 i 必须写明类型let i: number = 0
    • 不要用 forEach、map数组遍历用 for 循环
    • 嵌套的数组方法调用可能导致类型推断失败,应改用 for 循环

================================================================================ 二、类型与对象访问

  1. any 类型访问

    • 不能直接访问 any 类型对象的属性
    • 需要将对象转换为 UTSJSONObject 类型后使用 getString()、getNumber() 等方法访问属性
    • any 类型属性访问需转换为 Record<string, any> 后用索引访问
    • 使用索引访问属性时,推荐使用方括号语法 obj['property'] 而非点语法 obj.property
    • any 类型不支持索引访问 obj['key'],必须先转换为 UTSJSONObject
  2. UTSJSONObject 使用

    • 用 utils/utis 下的 UTSJSONObject 做类型转换
    • 不要用 safeget只要 UTSJSONObject 就好
    • 需要创建动态对象时,应使用 new UTSJSONObject() 然后调用 .set() 方法
    • 对于 type 定义的对象类型,同样需要使用 UTSJSONObject
    • 使用 getString()、getNumber() 方法获取属性值
  3. 数组类型

    • 数组类型建议写成 Array不要用 Type[] 简写
    • 空数组需要明确指定类型,如 [] as string[]
    • 数组元素需要明确的类型定义才能在模板中正确访问属性
    • 对于 any[] 或 reactive 数组,访问元素属性时需要先转换为 Record<string, any> 或 any[]
  4. 对象操作

    • 不支持 Object.keys()、Object.values()、Object.entries()
    • 不支持 Record<K, V> 对象字面量语法
    • 对象字面量 {...} 只能用于构造类型class不能用于接口interface
    • reactive 对象在 UTS 中不支持索引器赋值操作

================================================================================ 三、条件判断与逻辑运算

  1. if 条件

    • if 判断只能接 boolean 类型,不能是其他类型的值
    • 判断空要用 !== null不能用 !变量uts android 不支持 !在变量前面的判断空方式)
    • 模板中的 || 运算符左边必须是 boolean 类型
    • 可空类型使用可选链 ?. 和空值合并 ??
    • 字符串判断空要用variable != null && variable !== ''
  2. 逻辑运算符

    • || 表示逻辑或
    • && 表示逻辑与
    • ! 表示逻辑非(但 !变量 不支持用于判断空)
    • ?? 表示空值合并运算符(当左侧为 null 时返回右侧值)
    • ts 的为空则使用默认值的语法在 uts 中不能用 ||,要用 ?? 来代替

================================================================================ 四、组件与模板

  1. 表单与输入

    • 表单优先用 form 组件
    • 不支持 uni-easyinput用 input 代替
    • 时间选择用 uni_modules/lime-date-time-picker
  2. 选择器

    • uts android 不支持 picker用 picker-view 或 uni.showActionSheet
    • 一维的优先用 uni.showActionSheet
    • picker-view 的事件用 UniPickerViewChangeEvent
  3. 导航与布局

    • 不支持 uni-nav-bar先删除
    • 不支持 uni-data-select用 picker-view 代替
    • 不支持 uni-datetime-picker用 components/picker-date 或 components/picker-time 代替
    • 不支持 uni-icons
  4. 模板注意事项

    • 跟 template 交互的变量尽量用一维变量(不要嵌套对象)
    • 模板中可空类型必须使用 ?. 安全访问
    • 模板中访问可空类型属性前必须先判空 v-if="order != null"

================================================================================ 五、CSS 样式限制

  1. 布局方式

    • 只支持 display: flex
    • 不支持 display: grid、display: block
    • 不支持 gap
    • 不支持 table、grid、grid-template-columns
  2. 单位与计算

    • 不支持 calc()
    • 不支持的单位: vh、vw
    • property value 100% is not supported for min-height (supported values are: number|pixel)
    • property value 60% is not supported for max-height (supported values are: number|pixel)
    • property value calc(33.33% - 10px) is not supported for min-width
    • height 不支持 vh 单位,需要使用具体的像素值或百分比
  3. 选择器

    • [APP-ANDROID] 不支持伪类选择器
    • [APP-IOS] 不支持伪类选择器
    • ERROR: Selector .login-button[disabled] is not supported. uvue only support classname selector
  4. font-weight 限制

    • font-weight 只支持: normal | bold | 400 | 700
    • 不支持: 100, 300, 500, 600, 900 等其他数值
  5. border-radius 限制

    • border-radius 不支持百分比单位 (如 50%)
    • 需要使用具体的像素值 (如 border-radius: 9px 实现圆形)
  6. line-height 限制

    • line-height 不支持 normal 值
    • 需要使用具体的数值或像素值 (如 line-height: 36px 或 line-height: 1.5)
  7. position 限制

    • position 不支持 sticky 值
    • 只支持: relative | absolute | fixed
  8. 不支持的 CSS 属性

    • outline 属性不支持
    • aspect-ratio 属性不支持,需要用具体的 width 和 height 值
    • text-decoration 属性不支持(如 line-through
    • align-items 不支持 baseline 值,只支持 center | flex-start | flex-end | stretch
    • 不支持后代选择器(如 .parent text只支持类名选择器
  9. 其他样式

    • WARNING: backdrop-filter is not a standard property name
    • style property white-space is only supported on <text>|<button>
    • ERROR: property value all is not supported for transition-property

================================================================================ 六、UTS 模板表达式限制

  1. 条件语句必须使用布尔类型

    • 错误码: UTS110111120
    • 不支持 || 运算符的隐式类型转换
    • 错误写法: {{ value || '默认值' }}
    • 正确写法: {{ value != null && value != '' ? value : '默认值' }}
  2. 模板中的类型判断

    • 所有条件表达式必须返回布尔值
    • 不能使用 truthy/falsy 值作为条件

================================================================================ 七、scroll-view 使用

  • scroll-view 在 uni-app-x 中不是用 scroll-y=true
  • 而是要用 direction="vertical"

================================================================================ 八、异步与回调

  • uni.showModal 的 success 回调不能是 async 函数
  • 解决方案:创建独立的 async 函数,在回调中调用
  • UTS 中箭头函数 () => {} 有时会导致 "Parenthesized expression cannot be empty" 错误
  • 解决方案:使用普通函数 function name(): Type {} 代替箭头函数

================================================================================ 九、响应式数据

  • 对于需要整体替换的数组,推荐使用 ref 而非 reactive
  • 使用 ref 时,通过 .value 进行整体替换可以正确触发响应式更新
  • ref 数组元素不能直接整体替换,需要修改元素属性
  • 对于可能为 null 的参数,需要显式检查后再传递给函数

================================================================================ 十、类型导入

  • 类型导入需要使用 type 关键字
  • 一般情况下,尽可能用强类型模式
  • uni_modules 的情况下,尽量把 type 定义到 interface 里面
  • 数据获取争夺都用强类型方式,查询或 rpc 查询用 supa.from.executeAs() 方式
  • 返回的是 resultresultdata 一般可以 as Array

================================================================================ 十一、常见错误速查

  1. "Unresolved reference" - 函数未定义或顺序错误
  2. "Cannot create an instance of an abstract class" - Record<K,V> 不能实例化
  3. "Assignment type mismatch" - 类型不匹配,需要显式类型转换
  4. "if condition must be boolean" - if 条件必须是 boolean 类型
  5. "Index Signature is not supported" - 不支持索引签名
  6. "Intersection Type is not supported" - 不支持交叉类型
  7. "Parenthesized expression cannot be empty" - 箭头函数问题,改用普通函数
  8. "找不到名称" - 属性访问问题,需转换为 UTSJSONObject
  9. "参数类型不匹配" - 参数类型错误,需要显式类型转换

================================================================================ 十二、简明速记100+条)

  1. 表单优先用 form 组件
  2. 跟 template 交互的变量尽量用一维变量
  3. 不要用 forEach、map、safeget数组遍历用 for 循环,类型转换用 UTSJSONObject
  4. 数组类型建议写成 Array不要用 Type[] 简写
  5. 不支持 undefined变量未赋值就是 null判断用 != null
  6. 变量声明只能用 let 或 const不能用 var
  7. 判断空要用 !== null不能用 !变量
  8. 只支持 type不建议用 interface
  9. for 循环的 i 必须写明类型let i: number = 0
  10. 逻辑或用 ||,空值合并用 ??,不能混用
  11. if 判断只能是 boolean 类型
  12. 不支持索引签名Index Signature
  13. 类型推断严格,必要时用 as Type 明确类型
  14. 不支持 Intersection Type
  15. picker 用 picker-view 或 uni.showActionSheet 替代
  16. 样式只支持 display: flex不支持 gap、grid、calc()、伪类选择器、display: block
  17. scroll-view 用 direction="vertical"
  18. 不支持 table、grid、vh/vw 单位、min-height/max-height 百分比等
  19. font-weight 只支持 normal/bold/400/700不支持 300/500/600 等数值
  20. border-radius 不支持百分比(50%),用具体像素值(9px)实现圆形
  21. line-height 不支持 normal用具体数值(36px)或倍数(1.5)
  22. position 不支持 sticky只支持 relative/absolute/fixed
  23. 不支持 outline、aspect-ratio、text-decoration 等 CSS 属性
  24. align-items 不支持 baseline只支持 center/flex-start/flex-end/stretch
  25. 组件事件如 picker-view 用 UniPickerViewChangeEvent
  26. 时间选择用 uni_modules/lime-date-time-picker
  27. 类型转换建议用 utils/utis 下的 UTSJSONObject
  28. 在 uts setup 的 android 模式下,调用的函数必须在调用之前定义
  29. 箭头函数不支持默认参数值,改用显式传参或普通函数定义
  30. 不支持 Number()、String() 构造函数,用 as 类型转换
  31. 不支持 Object.keys(),用 JSON.stringify() 或 for 循环
  32. 不支持 typeof xxx === 'function',用 try-catch 替代
  33. 不支持 as unknown as 语法
  34. parseInt() 参数必须是 string 类型
  35. decodeURIComponent() 返回可空类型,需要处理 null
  36. charCodeAt() 返回可空类型,需要处理 null
  37. 不支持内联对象类型,需要在 types 文件中单独定义
  38. UTSJSONObject.get() 返回可空类型 Any?,需要处理 null 并转换为具体类型
  39. Array 元素不能直接访问属性,需转换为 UTSJSONObject 或定义明确类型
  40. 类型定义中属性可能为 null 时,必须声明为可空类型(如 any | null
  41. switch 语句在某些版本可能有问题,建议用 if-else 替代
  42. 模板中可选链 ?.length 需要改为显式判断v-if="arr != null && arr.length > 0"
  43. 解构赋值 const { data, error } 在 UTS 中可能有问题,建议用 response.data 方式访问
  44. response.data 返回 Any?,赋值前需要判断 null 并类型转换
  45. 空数组 [] 无法推断类型需要显式声明let arr: Array = [] 或先判断 null 再转换
  46. ref 对象字面量需要定义类型const obj = ref({...} as MyType),否则属性访问会报错
  47. if 条件必须是 boolean可空类型要用 != null 判断if (obj != null) 而非 if (obj)
  48. throw 语句不能抛出 Any 类型,需要处理错误而非抛出
  49. 模板中可选链 ?.property 需要改为三元表达式obj != null ? obj.property : ''
  50. ref 在模板中无法访问属性必须定义明确类型ref<MyType | null>(null)
  51. 展开运算符 [...arr] 不支持,需要手动复制数组
  52. Array.from(new Set()) 不支持,需要手动去重
  53. Promise.all 可能有问题,建议改为顺序执行
  54. .sort(() => Math.random() - 0.5) 随机排序不支持,需要手动实现
  55. 数组索引访问 arr[index] 可能越界,建议用 if-else 替代数组查找
  56. 事件对象 e.detail.value 需要转换为 UTSJSONObject 后访问
  57. String() 构造函数不支持,用 as string 类型转换
  58. 数组类型简写 string[] 需要改为 Array
  59. any 类型参数不能直接访问属性,需要转换为 UTSJSONObject 后使用 get/getString/getNumber 方法
  60. 模板中 !变量 取反不支持改为显式判断v-if="str == ''" 或 v-if="bool == false"
  61. 模板中 :class="{ 'class': condition }" 对象语法可能有问题,改为三元表达式::class="condition ? 'class' : ''"
  62. supabase .update() 参数需要 UTSJSONObject 类型,用 new UTSJSONObject() 创建并用 .set() 设置属性
  63. ref<Array> 在模板中无法访问元素属性,必须定义明确的类型后才能访问
  64. 函数参数可以用联合类型func(item: TypeA | TypeB)
  65. JSON.stringify(UTSJSONObject) 可能有问题,需要手动拼接字符串
  66. UTSJSONObject.keys() 方法不存在,无法获取键列表
  67. 联合类型参数不能直接访问属性需要先类型转换const id = (item as TypeA).id
  68. 某些 uni API 可能不存在(如 navigateToMiniProgram需要检查或替换
  69. JSON.parse(JSON.stringify(obj)) 复杂转换可能有问题,简化处理
  70. showModal success 回调不能是 async 函数,需要改为同步或使用 Promise
  71. supa.auth.signOut() 等 supabase auth 方法可能不支持,需要简化处理
  72. 模板中可空字符串判断 userInfo.phone ? 改为 userInfo.phone != null && userInfo.phone != ''
  73. .then() 回调可能有问题,建议用 async/await 或直接调用
  74. let res: any = null 不支持,改为 let res: any = {} 或其他默认值
  75. 类型定义中没有的字段不能赋值,检查类型定义后移除多余字段
  76. ref<Array> 在模板中无法访问元素属性,必须定义明确的类型
  77. 模板中复杂表达式如 parseFloat(String(x)) 不支持,简化为直接比较
  78. forEach 不支持,改用 for 循环
  79. any 类型数组元素不能直接访问属性,需转换为 UTSJSONObject
  80. showModal success 回调不能是 async 函数,需要改为同步调用独立 async 函数
  81. 被生命周期钩子调用的函数必须在钩子之前定义,包括 onMounted、watch、onUnmounted 等
  82. 箭头函数不支持默认参数值,改用显式传参或普通函数定义
  83. 对象字面量赋值给 ref 需要显式类型声明const obj: Type = {...} as Type
  84. 模板中访问对象属性时,类型定义必须包含该属性,否则报 "找不到名称" 错误
  85. !variable 取反操作不支持,改为 variable == '' 或 variable == false
  86. supa.auth 方法不支持,需要简化处理或移除
  87. setInterval 回调中使用外部变量需要先声明let timer: number = 0然后在回调中赋值
  88. $t() 国际化函数在模板中可能有问题,建议使用硬编码文本或自定义翻译函数
  89. profile.username ?? $t('xxx') 混合表达式不支持改为条件判断profile != null && profile.username != null ? profile.username : '默认值'
  90. 可选链操作符 ?. 在某些场景不支持,如 currentPage?.options需要改为 if 判断
  91. as any[] 类型转换后无法访问属性,需要使用正确的类型如 UTSJSONObject
  92. 可空类型 string | null 传给需要 string 的函数需要显式类型转换redirect as string
  93. setInterval 回调中修改外部变量,需要用 ref 而不是 let 声明变量,避免 smart cast 问题
  94. 非空断言操作符 ! 在某些场景仍无法解决类型问题,建议简化逻辑避免复杂类型转换
  95. decodeURIComponent 函数参数类型严格,可空类型即便使用 ! 也可能报错,建议简化或避免使用
  96. getCurrentPages() 获取页面 options 复杂且容易出错,建议简化跳转逻辑
  97. 模板中内联箭头函数不支持类型注解,如 @input="(e: any) => ..." 会报错,改用 v-model
  98. :class="{ disabled: codeDisabled }" 对象语法可能有问题,改为三元表达式 :class="codeDisabled ? 'disabled' : ''"
  99. 使用外部类型定义时,确保所有属性都有默认值,避免 null 导致类型不匹配
  100. Supabase insert/update 在 .uvue 文件中直接调用可能报类型错误,建议封装到 .uts 服务文件中调用
  101. 可空类型属性在模板中使用时需要处理 nullprofile.gender ?? 'other'
  102. 可空数字类型比较前需要先检查 nullprofile.height_cm != null && profile.height_cm > 0
  103. CSS 伪类选择器 :last-child、:first-child、:nth-child() 不支持,需要移除
  104. Supabase 批量 insert 数组参数类型不匹配,需要改为循环逐条 insert
  105. CSS 中独立的属性没有选择器会导致编译错误Return type mismatch: expected 'Map<String, Map<String, Map<String, Any>>>'
  106. Supabase 查询整数字段用字符串形式:.eq('status', '1') 而非 .eq('status', 1)
  107. Supabase insert 整数字段也用字符串target_type: '1' 而非 target_type: 1
  108. 数据库返回的字段类型可能不符,需要用 typeof 检查后安全转换
  109. UUID 字段可能返回 Integer需要安全转换typeof val === 'number' ? val.toString() : val
  110. 使用辅助函数 safeGetString、safeGetNumber 处理数据库字段

================================================================================ 十三、构造函数限制

  1. 不支持 Number() 构造函数,使用 as number 类型转换
  2. 不支持 String() 构造函数,使用 as string 类型转换
  3. 示例Number(x) → x as numberString(i) → i as string

================================================================================ 十四、取反操作符限制

  1. 不支持 !变量 的取反操作符用于判断空
  2. 字符串判断空variable == null || variable === ''
  3. 示例:!this.selectedSkuId → (this.selectedSkuId == null || this.selectedSkuId === '')

================================================================================ 十五、parseInt/parseFloat 限制

  1. parseInt() 参数必须是 string 类型
  2. 如果变量是 number 类型,直接使用,不要调用 parseInt
  3. 示例parseInt(this.quantity) 错误quantity 是 number直接用 this.quantity

================================================================================ 十六、Object.keys() 不支持

  1. 不支持 Object.keys() 方法
  2. 替代方案:使用 JSON.stringify() 或 for 循环遍历
  3. UTSJSONObject 可用 .keys() 方法(但某些版本可能不支持)

================================================================================ 十七、typeof 函数检查不支持

  1. 不支持 typeof xxx === 'function' 语法
  2. 替代方案:使用 try-catch 包裹方法调用

================================================================================ 十八、as unknown as 语法不支持

  1. 不支持 as unknown as 双重类型转换
  2. 直接使用 as 目标类型obj as UTSJSONObject

================================================================================ 十九、可空类型方法返回值

  1. decodeURIComponent() 返回 String?,需要处理 null
  2. charCodeAt() 返回 Number?,需要处理 null
  3. 示例const code = str.charCodeAt(i); if (code != null) { ... }

================================================================================ 二十、内联对象类型不支持

  1. 不支持 Array<{id: string, name: string}> 这种内联类型定义
  2. 需要在 types 文件中单独定义 type
  3. 示例:定义 type ItemType = { id: string, name: string },然后使用 Array

================================================================================ 二十一、eventChannel 不支持

  1. uni.navigateTo 的 success 回调中 res.eventChannel 不支持
  2. 替代方案:使用 Storage 或全局变量传递数据

================================================================================ 二十二、链式调用问题

  1. .map().join() 链式调用可能导致类型推断失败
  2. 替代方案:使用 for 循环或分步处理

================================================================================ 二十三、v-model 类型限制

  1. input 的 v-model 期望 string 类型
  2. 如果变量是 number使用 :value="variable.toString()" 替代 v-model

================================================================================ 二十四、setup 模式函数定义顺序(重要)

在 <script setup lang="uts"> 中,函数定义顺序至关重要:

  1. 基本原则

    • 函数必须在调用之前定义(不支持 JavaScript 的函数提升)
    • 这与 JavaScript 的行为不同UTS 更接近 C/Java 的编译方式
  2. watch 和 onMounted 中的函数调用

    • watch() 和 onMounted() 是立即执行的
    • 它们内部调用的函数必须在 watch/onMounted 之前定义
    • 错误示例:
      // 错误resetData 和 loadRefunds 还没定义
      watch(activeTab, () => {
          resetData()
          loadRefunds()
      })
      
      const resetData = () => { ... }
      const loadRefunds = async () => { ... }
      
    • 正确示例:
      // 正确!先定义函数
      const resetData = () => { ... }
      const loadRefunds = async () => { ... }
      
      // 再调用 watch/onMounted
      watch(activeTab, () => {
          resetData()
          loadRefunds()
      })
      onMounted(() => {
          loadRefunds()
      })
      
  3. 推荐的代码组织顺序:

    <script setup lang="uts">
    // 1. 导入语句
    import { ref, watch, onMounted } from 'vue'
    import { supabaseService } from '@/utils/supabaseService'
    
    // 2. 类型定义
    type MyType = { id: string, name: string }
    
    // 3. 响应式变量声明
    const data = ref<Array<MyType>>([])
    const loading = ref<boolean>(false)
    
    // 4. 工具函数(不依赖其他函数的)
    const getUserId = (): string => { ... }
    
    // 5. 业务函数(可能依赖工具函数)
    const loadData = async () => { ... }
    const resetData = () => { ... }
    
    // 6. 事件处理函数
    const handleClick = () => { ... }
    
    // 7. 生命周期钩子和 watch放在最后
    watch(data, () => { ... })
    onMounted(() => { ... })
    </script>
    
  4. 编译错误提示

    • 错误信息:"找不到名称 xxx"
    • 原因:函数在调用之后定义
    • 解决:调整函数定义顺序,确保被调用的函数先定义

================================================================================ 二十五、箭头函数限制(重要)

  1. 箭头函数不支持默认参数值

    • 错误示例:
      const loadData = async (page: number = 1) => { ... }  // 错误!
      
    • 正确示例:
      // 方案1不使用默认参数调用时显式传参
      const loadData = async (page: number): Promise<void> => { ... }
      loadData(1)  // 调用时传参
      
      // 方案2使用普通函数定义
      function loadData(page: number = 1): Promise<void> { ... }
      
  2. 箭头函数需要明确返回类型

    • 建议显式声明返回类型:: Promise<void>: string
    • 示例:const getData = async (): Promise<string> => { ... }
  3. 编译错误提示

    • 错误信息:"Anonymous functions cannot specify default values for their parameters"
    • 原因:箭头函数使用了默认参数
    • 解决:移除默认参数,改用显式传参或普通函数定义

================================================================================ 二十六、数组元素属性访问(重要)

  1. Array 元素属性访问问题

    • 数组元素类型为 any 时,不能直接访问属性
    • 错误示例:
      const steps: Array<any> = [...]
      steps[1].time = 'xxx'  // 错误!找不到名称 "time"
      
    • 正确示例:
      const steps: Array<any> = [...]
      const step = steps[1] as UTSJSONObject
      step.set('time', 'xxx')
      
  2. 推荐方案:定义明确的类型

    type StepType = { status: number, title: string, time: string, desc: string }
    const steps: Array<StepType> = [...]
    steps[1].time = 'xxx'  // 正确!
    
  3. 编译错误提示

    • 错误信息:"找不到名称 xxx"
    • 原因any 类型数组元素无法直接访问属性
    • 解决:转换为 UTSJSONObject 或定义明确类型

================================================================================ 二十七、类型定义与可空类型(重要)

  1. 类型定义中可空类型的处理

    • 如果属性可能为 null必须显式声明为可空类型
    • 错误示例:
      type ItemType = {
          sku_specifications: any  // 错误!如果值可能为 null
      }
      const spec = getSpec()  // 返回 Any?
      item.sku_specifications = spec  // 类型不匹配
      
    • 正确示例:
      type ItemType = {
          sku_specifications: any | null  // 正确!
      }
      const specRaw = getSpec()
      const spec = (specRaw != null) ? (specRaw as any) : null
      item.sku_specifications = spec
      
  2. UTSJSONObject.get() 返回值处理

    • get() 方法返回 Any? 类型
    • 必须先判断 null再进行类型转换
    • 示例:
      const raw = obj.get('field')
      const value = (raw != null) ? (raw as string) : null
      
  3. 编译错误提示

    • 错误信息:"参数类型不匹配:实际类型为 'Any?',预期类型为 'Any'"
    • 原因:可空类型不能直接赋值给非空类型
    • 解决:修改类型定义为可空类型,或处理 null 情况

================================================================================ 二十八、模板中的可空类型处理(重要)

  1. 模板中可选链限制

    • 模板中 ?.length 等可选链操作可能报错
    • 错误示例:
      <view v-if="refund.status_history?.length > 0">
      
    • 正确示例:
      <view v-if="refund.status_history != null && refund.status_history.length > 0">
      
  2. 编译错误提示

    • 错误信息:"Operator call is prohibited on a nullable receiver of type 'Number?'"
    • 原因:可空类型不能直接调用操作符
    • 解决:显式判断 null 后再访问属性

================================================================================ 二十九、解构赋值限制(重要)

  1. UTS 中解构赋值可能有问题

    • 错误示例:
      const { data, error } = await supa.from('table').select('*')
      
    • 正确示例:
      const response = await supa.from('table').select('*').execute()
      const data = response.data
      const error = response.error
      
  2. 推荐使用 .execute() 获取完整响应

    • 使用 response.data 和 response.error 访问结果
    • 避免使用解构赋值

================================================================================ 三十、API 响应数据处理(重要)

  1. response.data 返回 Any? 类型

    • 错误示例:
      order.value = orderRes.data  // 错误Any? 不能赋值给 Any
      const itemsArray = itemsRes.data ?? []  // 错误!无法推断空数组类型
      
    • 正确示例:
      if (orderRes.data != null) {
          order.value = orderRes.data as UTSJSONObject
      }
      
      const rawData = itemsRes.data
      let itemsArray: Array<any> = []
      if (rawData != null) {
          itemsArray = rawData as Array<any>
      }
      
  2. 空数组类型推断问题

    • [] ?? []data ?? [] 无法推断类型
    • 必须显式声明数组类型
    • 示例:
      // 错误
      const arr = data ?? []
      
      // 正确
      let arr: Array<any> = []
      if (data != null) {
          arr = data as Array<any>
      }
      
  3. 编译错误提示

    • 错误信息:"Assignment type mismatch: actual type is 'Any?', but 'Any' was expected"

    • 原因:可空类型 Any? 不能直接赋值给非空类型

    • 解决:先判断 null再进行类型转换

    • 错误信息:"Cannot infer type for this parameter" 或 "Not enough information to infer type"

    • 原因:空数组无法推断类型

    • 解决:显式声明数组类型

================================================================================ 三十一、ref 对象字面量类型(重要)

  1. ref 对象字面量必须定义类型

    • 错误示例:
      const merchantRating = ref({
          description: 5,
          logistics: 5,
          service: 5
      })
      merchantRating.value.description = rating  // 错误!找不到名称 "description"
      
    • 正确示例:
      type MerchantRatingType = {
          description: number
          logistics: number
          service: number
      }
      
      const merchantRating = ref<MerchantRatingType>({
          description: 5,
          logistics: 5,
          service: 5
      } as MerchantRatingType)
      merchantRating.value.description = rating  // 正确!
      
  2. 编译错误提示

    • 错误信息:"找不到名称 xxx"
    • 原因ref 对象字面量没有显式类型定义
    • 解决:定义 type 并在 ref 中指定泛型类型

================================================================================ 三十二、if 条件与可空类型(重要)

  1. if 条件必须是 boolean 类型

    • 错误示例:
      if (merchant.value) { ... }  // 错误MerchantType? 不是 boolean
      
    • 正确示例:
      if (merchant.value != null) { ... }  // 正确!
      
  2. 可空类型不能直接用于 if 条件

    • 必须使用 != null 或 !== null 判断
    • 示例:
      // 错误
      if (obj) { ... }
      
      // 正确
      if (obj != null) { ... }
      
  3. 编译错误提示

    • 错误信息:"Condition type mismatch: inferred type is 'XXX?' but 'Boolean' was expected"
    • 原因:可空类型不能直接用于 if 条件
    • 解决:使用 != null 判断

================================================================================ 三十三、throw 语句限制(重要)

  1. throw 语句不能抛出 Any 类型

    • 错误示例:
      const { error } = await supa.from('table').insert(data)
      if (error !== null) {
          throw error  // 错误Any 类型不能抛出
      }
      
    • 正确示例:
      const res = await supa.from('table').insert(data).execute()
      if (res.error != null) {
          console.error('操作失败:', res.error)
          uni.showToast({ title: '操作失败', icon: 'none' })
          return  // 处理错误而非抛出
      }
      
  2. 推荐错误处理方式

    • 记录错误日志
    • 显示用户提示
    • 返回或终止操作
  3. 编译错误提示

    • 错误信息:"类型不匹配 推断类型是 'Any',但预期的是'Throwable'"
    • 原因UTS 中 throw 只能抛出 Throwable 类型
    • 解决:处理错误而非抛出,或创建 Error 对象

================================================================================ 三十四、模板中的可选链与属性访问(重要)

  1. 模板中可选链限制

    • 错误示例:
      <text>{{ order?.order_no }}</text>
      <text>{{ formatTime(order?.created_at) }}</text>
      
    • 正确示例:
      <text>{{ order != null ? order.order_no : '' }}</text>
      <text>{{ formatTime(order != null ? order.created_at : '') }}</text>
      
  2. ref 在模板中无法访问属性

    • 错误示例:
      const order = ref<any>({})
      
      <text>{{ order?.order_no }}</text>  <!-- 错误!找不到名称 -->
      
    • 正确示例:
      type OrderType = {
          id: string
          order_no: string
          created_at: string
      }
      const order = ref<OrderType | null>(null)
      
      <text>{{ order != null ? order.order_no : '' }}</text>
      
  3. 编译错误提示

    • 错误信息:"找不到名称 xxx"
    • 原因any 类型或可选链在模板中无法正确推断属性
    • 解决:定义明确类型,使用三元表达式代替可选链

================================================================================ 三十五、数组操作限制(重要)

  1. 展开运算符 [...arr] 不支持

    • 错误示例:
      const shuffled = [...allGuessItems.value]
      searchResults.value.push(...newItems)
      
    • 正确示例:
      // 复制数组
      const arr: Array<any> = []
      for (let i: number = 0; i < allGuessItems.value.length; i++) {
          arr.push(allGuessItems.value[i])
      }
      // 追加元素
      for (let i: number = 0; i < newItems.length; i++) {
          searchResults.value.push(newItems[i])
      }
      
  2. Array.from(new Set()) 不支持

    • 错误示例:
      const uniqueNames = Array.from(new Set(names))
      
    • 正确示例:
      // 手动去重
      const uniqueNames: Array<string> = []
      for (let i: number = 0; i < names.length; i++) {
          let found = false
          for (let j: number = 0; j < uniqueNames.length; j++) {
              if (uniqueNames[j] === names[i]) {
                  found = true
                  break
              }
          }
          if (found === false) {
              uniqueNames.push(names[i])
          }
      }
      
  3. Map.entries() 和 Map.keys() 不支持需使用forEach遍历

    • 错误示例:
      const keywordCount = new Map<string, number>()
      // ... 填充数据 ...
      
      // 错误entries() 不能作为函数调用
      const sortedKeywords = Array.from(keywordCount.entries())
          .sort((a, b) => b[1] - a[1])
          .slice(0, limit)
          .map(entry => entry[0])
      
      // 错误keys() 也不能作为函数调用
      const keySet = keywordCount.keys()
      for (let i = 0; i < keySet.length; i++) { ... }
      
    • 正确示例:
      const keywordCount = new Map<string, number>()
      // ... 填充数据 ...
      
      // 定义类型用于存储键值对
      type KeywordEntry = {
          keyword: string
          count: number
      }
      
      // 使用forEach遍历MapUTS支持
      const entryArray: KeywordEntry[] = []
      keywordCount.forEach((value: number, key: string) => {
          entryArray.push({
              keyword: key,
              count: value
          })
      })
      
      // 排序时需要明确类型注解
      entryArray.sort((a: KeywordEntry, b: KeywordEntry): number => {
          return b.count - a.count
      })
      
      // 提取结果
      const sortedKeywords: string[] = []
      const maxCount = Math.min(entryArray.length, limit)
      for (let i: number = 0; i < maxCount; i++) {
          sortedKeywords.push(entryArray[i].keyword)
      }
      
  4. Supabase upsert 方法不支持,需使用 insert

    • 错误示例:
      await supa
          .from('ml_browse_history')
          .upsert(browseRecord)
          .execute()
      
    • 正确示例:
      // 使用 insert 替代 upsert
      await supa
          .from('ml_browse_history')
          .insert(browseRecord)
          .execute()
      
  5. .sort() 带回调函数需要明确类型注解

    • 错误示例:
      const shuffled = arr.sort(() => Math.random() - 0.5)
      
    • 正确示例(自定义排序需要类型注解):
      // Fisher-Yates 洗牌算法(推荐用于随机排序)
      for (let i: number = arr.length - 1; i > 0; i--) {
          const j = Math.floor(Math.random() * (i + 1))
          const temp = arr[i]
          arr[i] = arr[j]
          arr[j] = temp
      }
      
    • 或者使用带类型注解的sort回调
      type SortableItem = {
          id: string
          count: number
      }
      
      const items: SortableItem[] = [...]
      
      // 必须明确参数类型和返回类型
      items.sort((a: SortableItem, b: SortableItem): number => {
          return b.count - a.count
      })
      

================================================================================ 三十五、异步操作限制(重要)

  1. Promise.all 可能有问题

    • 错误示例:
      const [prodResp, shopResp] = await Promise.all([
          supabaseService.searchProducts(keyword, page, limit),
          supabaseService.searchShops(keyword)
      ])
      
    • 正确示例:
      const prodResp = await supabaseService.searchProducts(keyword, page, limit)
      const shopResp = await supabaseService.searchShops(keyword)
      
  2. 解构赋值可能有问题

    • 错误示例:
      const [prodResp, shopResp] = await Promise.all([...])
      
    • 正确示例:
      const prodResp = await func1()
      const shopResp = await func2()
      

================================================================================ 三十六、事件对象处理(重要)

  1. 事件对象属性访问

    • 错误示例:
      const onInput = (e: any) => {
          const val = e.detail.value  // 错误!找不到名称 "detail"
      }
      
    • 正确示例:
      const onInput = (e: any) => {
          const eObj = e as UTSJSONObject
          const detailRaw = eObj.get('detail')
          const detail = detailRaw != null ? (detailRaw as UTSJSONObject) : (new UTSJSONObject())
          const val = detail.getString('value') ?? ''
      }
      
  2. 编译错误提示

    • 错误信息:"找不到名称 xxx"
    • 原因any 类型事件对象无法直接访问属性
    • 解决:转换为 UTSJSONObject 后使用 .get() 方法

================================================================================ 三十七、数组索引访问限制(重要)

  1. 数组索引访问可能越界

    • 错误示例:
      const texts = ['非常差', '差', '一般', '好', '非常好']
      return texts[rating - 1] ?? '未评价'  // 可能越界或类型推断失败
      
    • 正确示例:
      if (rating === 1) return '非常差'
      if (rating === 2) return '差'
      if (rating === 3) return '一般'
      if (rating === 4) return '好'
      if (rating === 5) return '非常好'
      return '未评价'
      
  2. 推荐使用 if-else 或 Map 替代数组索引查找

================================================================================ 三十八、函数可选参数限制(重要)

  1. 可选参数不能跳过传递

    • UTS Android 不支持跳过可选参数传递
    • 如果函数有多个可选参数,必须按顺序传递所有参数
    • 错误示例:
      // 函数定义
      async addToCart(productId: string, quantity: number = 1, skuId?: string, merchantId?: string): Promise<boolean>
      
      // 错误调用 - 跳过了 merchantId 参数
      await supabaseService.addToCart(productId, 1, '')
      // 编译错误No value passed for parameter 'merchantId'
      
    • 正确示例:
      // 方案1给可选参数添加默认值
      async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise<boolean>
      
      // 方案2调用时传递所有参数
      await supabaseService.addToCart(productId, 1, '', '')
      
  2. 可选参数定义规范

    • 推荐使用 param: Type = defaultValue 而非 param?: Type
    • param?: Type 在 Android 端调用时仍需传递参数
    • param: Type = defaultValue 可以在不传参时使用默认值
    • 示例:
      // 不推荐 - 调用时仍需传递参数
      function foo(a: string, b?: string, c?: string): void
      
      // 推荐 - 可以跳过参数使用默认值
      function foo(a: string, b: string = '', c: string = ''): void
      
  3. 编译错误提示

    • 错误信息:"No value passed for parameter 'xxx'"
    • 原因:可选参数在 Android 端不能跳过
    • 解决:
      1. 修改函数签名,使用默认值 param: Type = defaultValue
      2. 调用时传递所有参数
  4. 最佳实践

    • 对于有多个可选参数的函数,统一使用默认值语法
    • 调用时显式传递所有参数,避免依赖可选参数跳过
    • 在服务层函数定义中,优先使用 = ''= 0 等默认值

================================================================================ 三十九、模板中的非空断言限制(重要)

  1. 模板中不支持非空断言操作符 !

    • UTS Android 模板中不能使用 variable! 非空断言
    • 错误示例:
      <text v-if="product.original_price != null && product.original_price! > product.price">
      
    • 正确示例:
      <text v-if="product.original_price != null && product.original_price > product.price">
      
  2. 编译错误提示

    • 错误信息:"参数类型不匹配:实际类型为 'Number?',预期类型为 'Number'"
    • 原因:模板中使用非空断言 ! 不被支持
    • 解决:移除非空断言 !,直接使用变量进行比较
  3. 最佳实践

    • 在模板中,先用 != null 判断可空类型,然后直接使用变量
    • UTS 编译器会在 != null 判断后自动识别变量为非空类型

================================================================================ 四十、未导入类型的处理(重要)

  1. 未导入的类型不能直接使用

    • 在页面中使用的类型必须先导入或使用 UTSJSONObject 替代
    • 错误示例:
      // Shop 类型未导入
      const s = shopRespData[i] as Shop
      const id = s.id  // 找不到名称 "id"
      
    • 正确示例:
      // 使用 UTSJSONObject
      const s = shopRespData[i] as UTSJSONObject
      const id = s.getString('id') ?? ''
      const name = s.getString('shop_name') ?? ''
      
  2. 编译错误提示

    • 错误信息:"找不到名称 'XXX'"
    • 原因:类型未导入或类型定义不存在
    • 解决:
      1. 导入需要的类型:import { Shop } from '@/utils/supabaseService.uts'
      2. 使用 UTSJSONObject 替代:as UTSJSONObject 然后用 .getString().getNumber() 访问属性
  3. 最佳实践

    • 对于简单的数据转换,推荐使用 UTSJSONObject
    • 避免在多个文件中重复定义相同的类型
    • 如果需要类型安全,从服务层导入类型定义

================================================================================ 四十一、服务层数据字段完整性(重要)

  1. 服务层返回数据必须包含所有必要字段

    • 从数据库获取数据时,必须正确映射所有需要的字段
    • 错误示例:
      const product: Product = {
        id: prodObj.getString('id') ?? '',
        name: prodObj.getString('name') ?? '',
        // 错误merchant_id 硬编码为空字符串
        merchant_id: ''
      } as Product
      
    • 正确示例:
      const product: Product = {
        id: prodObj.getString('id') ?? '',
        name: prodObj.getString('name') ?? '',
        // 正确:从数据库获取 merchant_id
        merchant_id: prodObj.getString('merchant_id') ?? ''
      } as Product
      
  2. 调用服务层方法时必须传递完整参数

    • 页面调用服务层方法时,需要传递所有必要参数
    • 错误示例:
      // 错误merchant_id 传空字符串
      await supabaseService.addToCart(productId, 1, '', '')
      
    • 正确示例:
      // 正确:从商品对象获取 merchant_id
      const merchantId = product.merchant_id ?? ''
      await supabaseService.addToCart(productId, 1, '', merchantId)
      
  3. 编译错误提示

    • 问题表现:数据添加到数据库失败,或添加的数据不完整
    • 原因:服务层或页面层缺少必要字段的传递
    • 解决:
      1. 检查服务层数据映射是否完整
      2. 检查页面调用时是否传递了所有必要参数
  4. 最佳实践

    • 服务层方法返回的对象应包含数据库视图的所有字段
    • 页面调用服务层方法时,应从数据对象中获取并传递所有参数
    • 对于关联数据(如 merchant_id确保在数据加载时一并获取

================================================================================ 四十二、模板中的非运算符限制(重要)

  1. 模板中不支持 ! 非运算符

    • UTS Android 模板中不能使用 !variable 非运算符
    • 错误示例:
      <view v-if="!brand.logo_url">
      
    • 正确示例:
      <view v-if="brand.logo_url == null || brand.logo_url == ''">
      
  2. 编译错误提示

    • 错误信息:"找不到名称 not'"
    • 原因:模板中不支持非运算符 !
    • 解决:使用显式的比较表达式替代
  3. 最佳实践

    • 使用 == null== '' 检查空值
    • 使用 != null && != '' 检查非空值

================================================================================ 四十三、索引访问限制(重要)

  1. 不支持 (obj as any)['key'] 索引访问方式

    • UTS Android 不支持对 any 类型使用索引访问
    • 错误示例:
      const detail = (e as any)['detail']
      val = detail['value'] ?? ''
      
    • 正确示例:
      // 方案1使用 UTSJSONObject
      const eObj = JSON.parse(JSON.stringify(e)) as UTSJSONObject
      const detail = eObj.get('detail') as UTSJSONObject
      val = detail.getString('value') ?? ''
      
      // 方案2先判断类型再转换
      if (e instanceof UTSJSONObject) {
          const eObj = e as UTSJSONObject
          const detail = eObj.get('detail') as UTSJSONObject
          val = detail.getString('value') ?? ''
      }
      
  2. 编译错误提示

    • 错误信息:"Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch"
    • 原因any 类型不支持索引访问
    • 解决:转换为 UTSJSONObject 后使用 .get() 方法
  3. 最佳实践

    • 统一使用 UTSJSONObject 处理动态对象
    • 使用 .get().getString().getNumber() 方法访问属性
    • 对于复杂对象,先用 JSON.parse(JSON.stringify(obj)) 转换

================================================================================ 四十四、字符串不能直接作为布尔条件(重要)

  1. 字符串不能直接作 if 条件

    • UTS Android 不支持将字符串直接作为布尔条件判断
    • 错误示例:
      const paramId = '123'
      if (paramId) {  // 错误:字符串不能直接作为布尔条件
          // ...
      }
      
    • 正确示例:
      const paramId = '123'
      if (paramId != null && paramId != '') {  // 正确:显式判断
          // ...
      }
      
  2. 编译错误提示

    • 错误信息:"Condition type mismatch: inferred type is 'String' but 'Boolean' was expected"
    • 原因:字符串类型不能直接作为布尔条件
    • 解决:使用显式的比较表达式
  3. 最佳实践

    • 使用 != null && != '' 检查字符串非空
    • 使用 == null || == '' 检查字符串为空

================================================================================ 四十五、联合类型属性访问(重要)

  1. 联合类型不能直接访问属性

    • 当参数类型为联合类型(如 A | B)时,不能直接访问属性
    • 错误示例:
      type A = { id: string, name: string }
      type B = { id: string, title: string }
      
      const foo = (item: A | B) => {
          const id = item.id  // 错误:联合类型不能直接访问属性
      }
      
    • 正确示例:
      const foo = (item: A | B) => {
          // 方案1转换为 UTSJSONObject
          const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
          const id = obj.getString('id') ?? ''
      
          // 方案2使用类型守卫
          if ('name' in item) {
              const id = item.id  // 此时类型已收窄为 A
          }
      }
      
  2. 编译错误提示

    • 错误信息:"找不到名称 xxx'"
    • 原因:联合类型的属性访问受限
    • 解决:转换为 UTSJSONObject 或使用类型守卫
  3. 最佳实践

    • 对于联合类型参数,统一转换为 UTSJSONObject 处理
    • 使用 .getString().getNumber() 等方法安全访问属性

================================================================================ 四十六、any 类型变量不能赋值为 null重要

  1. any 类型变量不能赋值为 null

    • UTS Android 中 any 类型不能赋值为 null
    • 错误示例:
      let res: any = null  // 错误Null cannot be a value of a non-null type 'Any'
      
    • 正确示例:
      let res: any = {}  // 正确:使用空对象
      // 或者
      let res: any | null = null  // 使用联合类型
      
  2. 编译错误提示

    • 错误信息:"Null cannot be a value of a non-null type 'Any'"
    • 原因any 类型不允许 null 值
    • 解决:使用空对象 {} 或联合类型 any | null

================================================================================ 四十七、对象字面量类型推断问题(重要)

  1. 对象字面量直接赋值给 ref 可能类型不匹配

    • 当对象字面量直接赋值给特定类型的 ref 时,可能报类型不匹配错误
    • 错误示例:
      merchant.value = {
        id: shop.id,
        user_id: shop.merchant_id,
        // ...
      }  // 错误Assignment type mismatch
      
    • 正确示例:
      // 方案1显式声明类型
      const merchantData: MerchantType = {
        id: shop.id,
        user_id: shop.merchant_id,
        // ...
      }
      merchant.value = merchantData
      
      // 方案2使用 as 类型断言
      merchant.value = {
        id: shop.id,
        user_id: shop.merchant_id,
        // ...
      } as MerchantType
      
  2. 编译错误提示

    • 错误信息:"Assignment type mismatch: actual type is '', but 'XXX' was expected"
    • 原因:对象字面量被推断为匿名类型
    • 解决:显式声明类型或使用类型断言

================================================================================ 四十八、any 类型不能直接访问属性(重要)

  1. any 类型参数不能直接访问属性

    • 在 map、forEach 等回调中any 类型的参数不能直接访问属性
    • 错误示例:
      const list = rawList.map((item): ProductType => {
        const id = item.id  // 错误:找不到名称 "id"
        const name = item.name  // 错误:找不到名称 "name"
      })
      
    • 正确示例:
      const list = rawList.map((item: any): ProductType => {
        // 方案1转换为 UTSJSONObject
        const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
        const id = itemObj.getString('id') ?? ''
        const name = itemObj.getString('name') ?? ''
      
        // 方案2显式标注参数类型并使用索引
        // 注意:这种方式在 UTS Android 中也可能有问题
      })
      
  2. 编译错误提示

    • 错误信息:"找不到名称 xxx'"
    • 原因any 类型的属性访问受限
    • 解决:转换为 UTSJSONObject 后使用 .getString() 等方法

================================================================================ 四十九、类型断言不会添加方法(重要)

  1. as UTSJSONObject 不会给对象添加方法

    • 使用 as UTSJSONObject 只是类型断言,不会让普通对象获得 getString 等方法
    • 错误示例:
      const profileObj = profile as UTSJSONObject
      const id = profileObj.getString('user_id')  // 运行时错误getString is not a function
      
    • 正确示例:
      // 必须使用 JSON.parse(JSON.stringify()) 进行真正的转换
      const profileObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject
      const id = profileObj.getString('user_id') ?? ''
      
  2. 运行时错误提示

    • 错误信息:"XXX is not a function"
    • 原因:类型断言只是编译时行为,不会改变运行时对象的方法
    • 解决:使用 JSON.parse(JSON.stringify()) 进行真正的对象转换
  3. 最佳实践

    • 对于从 API 返回的数据,统一使用 JSON.parse(JSON.stringify()) 转换
    • 使用 instanceof UTSJSONObject 检查对象类型
    • 不要依赖 as 类型断言来添加方法

================================================================================ 五十、类型必须包含所有必填字段(重要)

  1. 创建类型实例时必须包含所有必填字段

    • UTS 类型定义中的非可选字段(不带 ?)都是必填的
    • 错误示例:
      export type ProductType = {
        id: string
        merchant_id: string  // 必填
        category_id: string  // 必填
        name: string
        // ...
      }
      
      // 错误:缺少 merchant_id、category_id 等必填字段
      return {
        id: item.id,
        name: item.name,
        price: item.price
      } as ProductType  // 运行时错误missing required property
      
    • 正确示例:
      return {
        id: itemObj.getString('id') ?? '',
        merchant_id: itemObj.getString('merchant_id') ?? '',
        category_id: itemObj.getString('category_id') ?? '',
        name: itemObj.getString('name') ?? '未知商品',
        description: itemObj.getString('description') ?? '',
        images: images,
        price: itemObj.getNumber('base_price') ?? 0,
        original_price: itemObj.getNumber('market_price') ?? 0,
        stock: itemObj.getNumber('total_stock') ?? 0,
        sales: itemObj.getNumber('sale_count') ?? 0,
        status: 1,
        created_at: itemObj.getString('created_at') ?? ''
      } as ProductType
      
  2. 运行时错误提示

    • 错误信息:"Failed to construct type, missing required property: xxx"
    • 原因:类型定义中有必填字段未提供
    • 解决:
      1. 检查类型定义,确认哪些字段是必填的(不带 ?
      2. 为所有必填字段提供值,即使是空字符串或默认值
  3. 最佳实践

    • 查看类型定义,确认哪些字段是必填的(不带 ?
    • 使用 ?? 运算符提供默认值
    • 对于可选字段,可以不提供或使用 null

================================================================================ 五十一、回调函数不能是 async重要

  1. API 回调函数不能使用 async 修饰

    • uni API 的回调函数(如 showModal 的 success不支持 async 函数
    • 错误示例:
      uni.showModal({
          title: '确认',
          content: '确定要删除吗?',
          success: async (res) => {  // 错误:回调函数不能是 async
              if (res.confirm) {
                  const result = await someAsyncFunction()
              }
          }
      })
      
    • 正确示例:
      uni.showModal({
          title: '确认',
          content: '确定要删除吗?',
          success: (res) => {
              if (res.confirm) {
                  // 使用 Promise.then() 代替 await
                  someAsyncFunction().then((result) => {
                      // 处理结果
                  })
              }
          }
      })
      
  2. 编译错误提示

    • 错误信息:"参数类型不匹配:实际类型为 'Function1<..., UTSPromise>',预期类型为 'Function1<..., Unit>?'"
    • 原因:回调函数返回 Promise 而非 void
    • 解决:使用 .then() 代替 await
  3. 最佳实践

    • 在回调函数中使用 .then() 处理异步操作
    • 将异步逻辑封装为单独的函数,在回调中调用

================================================================================ 五十二、类型转换前必须检查类型(重要)

  1. 使用 as 类型转换前必须检查实际类型

    • 直接使用 as string 转换可能导致运行时类型转换异常
    • 错误示例:
      const idVal = item['id']
      const id = idVal as string  // 错误:如果 idVal 是其他类型会崩溃
      
    • 正确示例:
      const idVal = item['id']
      const id = (idVal != null && typeof idVal == 'string') ? (idVal as string) : ''
      
  2. 运行时错误提示

    • 错误信息:"null cannot be cast to non-null type kotlin.String"
    • 错误信息:"java.lang.Boolean cannot be cast to java.lang.String"
    • 原因:直接类型转换时,实际类型与目标类型不匹配
    • 解决:使用 typeof 检查类型后再转换
  3. 最佳实践

    • 使用 typeof 检查类型
    • 使用 != null 检查空值
    • 提供默认值防止空指针异常

================================================================================ 五十三、UTSJSONObject 必须正确转换(重要)

  1. as UTSJSONObject 不会添加方法

    • 从数据库返回的数据需要正确转换为 UTSJSONObject
    • 错误示例:
      const item = rawList[i]
      const brandObj = item as UTSJSONObject  // 错误brandObj.getString 不存在
      
    • 正确示例:
      const item = rawList[i]
      const brandObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
      const id = brandObj.getString('id') ?? ''
      
  2. 运行时错误提示

    • 错误信息:"getString is not a function"
    • 原因:对象没有正确转换为 UTSJSONObject
    • 解决:使用 JSON.parse(JSON.stringify()) 进行转换
  3. 最佳实践

    • 对于从数据库/API 返回的数据,统一使用 JSON.parse(JSON.stringify()) 转换
    • 使用 .getString().getNumber() 等方法安全访问属性

================================================================================ 五十四、getBoolean 方法可能导致类型转换异常(重要)

  1. UTSJSONObject.getBoolean() 可能导致类型转换异常

    • 当数据库字段类型与预期不符时getBoolean() 可能抛出异常
    • 错误示例:
      const isFeatured = prodObj.getBoolean('is_featured') ?? false  // 可能抛出异常
      
    • 正确示例:
      const isFeaturedVal = prodObj.get('is_featured')
      const isFeatured = (isFeaturedVal != null && typeof isFeaturedVal == 'boolean') 
          ? (isFeaturedVal as boolean) 
          : false
      
  2. 运行时错误提示

    • 错误信息:"java.lang.Boolean cannot be cast to java.lang.String"
    • 原因:数据库返回的字段类型与 UTSJSONObject 方法期望的类型不匹配
    • 解决:使用 .get() 方法获取原始值,然后手动检查类型
  3. 最佳实践

    • 避免使用 .getBoolean(),改用 .get() + typeof 检查
    • 在 SQL 查询中明确指定需要的字段,避免 SELECT *
    • 对于布尔值,使用 typeof val == 'boolean' 检查类型

================================================================================ 五十五、SELECT * 可能导致类型转换问题(重要)

  1. 避免使用 SELECT * 查询所有字段

    • 数据库可能包含前端不需要的字段,导致类型转换异常
    • 错误示例:
      .select('*')  // 可能返回意外的字段类型
      
    • 正确示例:
      .select('id, name, description, base_price, market_price, main_image_url')
      
  2. 最佳实践

    • 只查询需要的字段
    • 参考数据库文档确认字段类型
    • 对于视图(如 ml_products_detail_view),注意字段名可能与基础表不同

================================================================================ 五十六、创建辅助函数处理数据转换(重要)

  1. 创建辅助函数统一处理数据类型转换

    • 避免在每个方法中重复写类型检查代码
    • 示例:
      // 辅助函数:安全获取字符串值
      function safeGetString(obj: UTSJSONObject, key: string): string {
        try {
          const val = obj.get(key)
          if (val == null) return ''
          if (typeof val == 'string') return val as string
          if (typeof val == 'number') return (val as number).toString()
          if (typeof val == 'boolean') return (val as boolean) ? 'true' : 'false'
          return ''
        } catch (e) {
          console.error('safeGetString error for key:', key, e)
          return ''
        }
      }
      
      // 辅助函数:安全获取数值
      function safeGetNumber(obj: UTSJSONObject, key: string): number {
        try {
          const val = obj.get(key)
          if (val == null) return 0
          if (typeof val == 'number') return val as number
          if (typeof val == 'string') {
            const parsed = parseFloat(val as string)
            return isNaN(parsed) ? 0 : parsed
          }
          return 0
        } catch (e) {
          console.error('safeGetNumber error for key:', key, e)
          return 0
        }
      }
      
      // 辅助函数:安全获取布尔值
      function safeGetBoolean(obj: UTSJSONObject, key: string): boolean {
        try {
          const val = obj.get(key)
          if (val == null) return false
          if (typeof val == 'boolean') return val as boolean
          if (typeof val == 'string') return (val as string) === 'true'
          if (typeof val == 'number') return (val as number) !== 0
          return false
        } catch (e) {
          console.error('safeGetBoolean error for key:', key, e)
          return false
        }
      }
      
      // 辅助函数:从原始数据解析商品
      function parseProductFromRaw(item: any): Product {
        const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
        return {
          id: safeGetString(prodObj, 'id'),
          name: safeGetString(prodObj, 'name'),
          base_price: safeGetNumber(prodObj, 'base_price'),
          is_featured: safeGetBoolean(prodObj, 'is_featured'),
          // ...
        } as Product
      }
      
  2. 优点

    • 代码复用,减少重复
    • 统一处理各种类型转换异常
    • 易于维护和修改
  3. 最佳实践

    • 将辅助函数放在文件顶部
    • 对所有从数据库获取的数据使用辅助函数
    • 处理所有可能的类型转换情况

================================================================================ 五十七、视图字段名可能与基础表不同(重要)

  1. 数据库视图的字段名可能与基础表不同

    • ml_products_detail_view 视图中没有 image_url 字段
    • 只有 main_image_urlimage_urls 字段
    • 错误示例:
      .select('id, name, image_url')  // 错误:视图没有 image_url 字段
      
    • 正确示例:
      .select('id, name, main_image_url, image_urls')  // 正确
      
  2. 运行时错误提示

    • 错误信息:"column ml_products_detail_view.image_url does not exist"
    • 提示:"Perhaps you meant to reference the column 'ml_products_detail_view.image_urls'"
    • 原因:查询了视图中不存在的字段
    • 解决:参考数据库文档确认视图字段名
  3. 最佳实践

    • 查询视图前先确认字段名
    • 使用明确字段列表而非 SELECT *

================================================================================ 五十八、辅助函数应使用 try-catch 包装(重要)

  1. 辅助函数应使用 try-catch 包装防止崩溃

    • 在 Android 端,类型转换可能抛出异常
    • 使用 try-catch 包装可以防止整个应用崩溃
    • 示例:
      function safeGetString(obj: UTSJSONObject, key: string): string {
        try {
          const val = obj.get(key)
          if (val == null) return ''
          if (typeof val == 'string') return val as string
          if (typeof val == 'number') return (val as number).toString()
          if (typeof val == 'boolean') return (val as boolean) ? 'true' : 'false'
          return ''
        } catch (e) {
          console.error('safeGetString error for key:', key, e)
          return ''
        }
      }
      
      function toUTSJSONObject(item: any): UTSJSONObject {
        if (item instanceof UTSJSONObject) {
          return item as UTSJSONObject
        }
        try {
          const str = JSON.stringify(item)
          return JSON.parse(str) as UTSJSONObject
        } catch (e) {
          console.error('toUTSJSONObject error:', e)
          return new UTSJSONObject()
        }
      }
      
  2. 优点

    • 防止单个字段解析失败导致整个应用崩溃
    • 提供详细的错误日志便于调试
    • 返回默认值保证应用继续运行
  3. 最佳实践

    • 所有辅助函数都应使用 try-catch 包装
    • 在 catch 中记录错误日志
    • 返回合理的默认值

================================================================================ 五十九、商品视图没有 shop_id 字段(重要)

  1. ml_products_detail_view 视图没有 shop_id 字段

    • 商品通过 merchant_id 关联商家/店铺
    • 错误示例:
      .select('id, name, shop_id')  // 错误:视图没有 shop_id 字段
      
    • 正确示例:
      .select('id, name, merchant_id')  // 正确:使用 merchant_id
      
  2. 数据库字段对应关系

    • ml_products 表:merchant_id 关联商家
    • ml_shops 表:user_id 等于商家的 merchant_id
    • 视图中通过 merchant_id JOIN ml_shops 获取店铺信息
  3. 最佳实践

    • 查询商品时使用 merchant_id 而非 shop_id

================================================================================ 六十、any 类型不支持索引访问(重要)

  1. UTS Android 中 any 类型不支持索引访问

    • 不能使用 obj[key] 语法访问 any 类型对象的属性
    • 错误示例:
      function safeGetString(obj: any, key: string): string {
        const val = obj[key]  // 错误unresolved reference
      }
      
    • 正确示例:
      function safeGetString(obj: UTSJSONObject, key: string): string {
        const val = obj.get(key)  // 正确:使用 UTSJSONObject 的 get 方法
      }
      
  2. 编译错误提示

    • 错误信息:"Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch"
    • 原因:any 类型不支持索引访问
    • 解决:先将对象转换为 UTSJSONObject,再使用 .get() 方法
  3. 最佳实践

    • 使用 toUTSJSONObject() 函数将 any 转换为 UTSJSONObject
    • 使用 .get().getString().getNumber() 等方法访问属性
    • 辅助函数参数类型应为 UTSJSONObject 而非 any

================================================================================ 六十一、使用 getString/getNumber/getBoolean 方法(重要)

  1. UTSJSONObject 提供了类型安全的访问方法

    • getString(key) - 直接返回字符串或 null
    • getNumber(key) - 直接返回数值或 null
    • getBoolean(key) - 直接返回布尔值或 null
    • getArray(key) - 直接返回数组或 null
    • 这些方法比 .get() 更安全,会自动进行类型转换
  2. 推荐用法:

    // 推荐使用
    const name = obj.getString('name') ?? ''
    const price = obj.getNumber('price') ?? 0
    const isActive = obj.getBoolean('is_active') ?? false
    const images = obj.getArray('images') as string[] ?? []
    
    // 不推荐使用 .get() 后手动类型检查
    const val = obj.get('key')
    if (typeof val == 'string') { ... }
    
  3. 最佳实践

    • 优先使用 getString()getNumber()getBoolean()getArray()
    • 使用 ?? 提供默认值
    • 在 catch 块中处理异常

================================================================================ 六十二、item as UTSJSONObject 不会添加方法(重要)

  1. item as UTSJSONObject 不会让对象获得 getString 等方法

    • 直接使用 as UTSJSONObject 只是类型断言,不会改变运行时对象
    • 错误示例:
      const item = rawList[i]
      const prodObj = item as UTSJSONObject  // 错误getString 不存在
      const id = prodObj.getString('id')  // 运行时错误
      
    • 正确示例:
      const item = rawList[i]
      const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
      const id = prodObj.getString('id') ?? ''  // 正确
      
  2. 运行时错误提示

    • 错误信息:"getString is not a function"
    • 原因:对象没有正确转换为 UTSJSONObject
    • 解决:使用 JSON.parse(JSON.stringify()) 进行转换
  3. 最佳实践

    • 对于从数据库/API 返回的数据,统一使用 JSON.parse(JSON.stringify()) 转换
    • 使用 .getString().getNumber() 等方法安全访问属性

================================================================================ 六十三、自定义导航栏 Android 端适配(重要)

  1. .smart-navbar 样式规范
    • 问题:在 Android 端沉浸式导航栏中,若 .smart-navbar 设置了 paddingTop: statusBarHeight且 flex-direction 未显式设置为 column内部元素标题、按钮可能会错位。
    • 修复:.smart-navbar 必须显式设置 flex-direction: column内部内容容器 .nav-container 设置为 flex-direction: row。
    • 正确示例:
      .smart-navbar {
          display: flex;
          flex-direction: column;
          justify-content: flex-start;
      }
      .nav-container {
          display: flex;
          flex-direction: row;
          align-items: center;
          justify-content: space-between;
          height: 44px;
      }
      

================================================================================ 六十四、常见修复模式速查

  1. 取反操作修复模式

    // 错误
    if (!variable) { ... }
    if (!isValid.value) { ... }
    if (!validate()) { ... }
    
    // 正确 - 根据类型选择
    if (variable == null || variable == '') { ... }  // 字符串判空
    if (isValid.value == false) { ... }              // 布尔值取反
    if (validate() == false) { ... }                 // 函数返回布尔值取反
    
  2. 解构赋值修复模式

    // 错误
    const { data, error } = await someAsyncCall()
    
    // 正确
    const result = await someAsyncCall()
    const data = result.data
    const error = result.error
    
  3. typeof 检查修复模式

    // 错误
    if (typeof err === 'object') { ... }
    if (typeof xxx === 'function') { ... }
    
    // 正确
    try {
      const e = err as Error
      // 使用 e
    } catch (e2) {
      // 处理转换失败
    }
    
  4. as unknown as 修复模式

    // 错误
    const timer = setInterval(...) as unknown as number
    
    // 正确
    const timer = setInterval(...) as number
    

================================================================================ 六十五、错误处理最佳实践

  1. 统一错误处理模式

    try {
      const result = await someAsyncCall()
      if (result.error != null) {
        const errorMsg = (result.error as Error).message
        uni.showToast({ title: errorMsg, icon: 'none' })
        return
      }
      // 处理成功结果
    } catch (e) {
      console.error('操作失败:', e)
      uni.showToast({ title: '操作失败', icon: 'none' })
    }
    
  2. 可空类型安全访问

    // 安全访问对象属性
    const value = obj != null ? obj.property : null
    
    // 安全调用方法
    const result = obj != null ? obj.method() : null
    
  3. 数组安全访问

    // 安全访问数组元素
    if (arr.length > index) {
      const item = arr[index]
      // 使用 item
    }
    

================================================================================ 六十六、scroll-view 正确使用方式(重要)

  1. scroll-view 属性语法

    • 在 uvue 中scroll-view 的滚动属性必须使用绑定语法
    • 错误示例:
      <scroll-view scroll-y class="content">
      <scroll-view direction="vertical" class="content">
      
    • 正确示例:
      <scroll-view :scroll-y="true" class="content">
      
  2. scroll-view 高度约束

    • scroll-view 必须有明确的高度约束才能正常滚动
    • 父容器需要设置 flex: 1height: 0px(或 height: 0
    • scroll-view 本身设置 flex: 1height: 100%
    • 正确示例:
      .parent-container {
          flex: 1;
          height: 0px;  /* 关键:配合 flex: 1 使用 */
          display: flex;
          flex-direction: row;
      }
      
      .scroll-content {
          flex: 1;
          height: 100%;  /* 继承父容器高度 */
      }
      
  3. 常见问题

    • 问题scroll-view 内容无法滚动
    • 原因:缺少高度约束或 height: 0px 未设置
    • 解决:确保父容器有 height: 0pxscroll-view 有 height: 100%

================================================================================ 六十七、switchTab 页面间参数传递(重要)

  1. switchTab 不支持 URL 参数

    • uni.switchTab 不能像 uni.navigateTo 那样传递 URL 参数
    • 错误示例:
      uni.switchTab({
          url: '/pages/category?categoryId=123'  // 参数会被忽略
      })
      
  2. 使用 Storage 传递参数

    • 正确示例:
      // 发送页面
      uni.setStorageSync('selectedCategory', categoryId)
      uni.switchTab({ url: '/pages/category' })
      
      // 接收页面
      onShow(() => {
          const savedCategoryId = uni.getStorageSync('selectedCategory')
          if (savedCategoryId != null && savedCategoryId != '') {
              uni.removeStorageSync('selectedCategory')  // 清除,避免重复使用
              // 处理参数
          }
      })
      
  3. 时序问题处理

    • 问题onShow 可能在数据加载完成前执行
    • 解决:使用 pending 变量暂存参数,等待数据加载完成后处理
    • 正确示例:
      const pendingCategoryId = ref('')
      
      onShow(() => {
          const savedCategoryId = uni.getStorageSync('selectedCategory')
          if (savedCategoryId != null && savedCategoryId != '') {
              uni.removeStorageSync('selectedCategory')
      
              if (primaryCategories.value.length > 0) {
                  // 数据已加载,直接处理
                  selectPrimaryCategory(savedCategoryId as string)
              } else {
                  // 数据未加载,暂存等待
                  pendingCategoryId.value = savedCategoryId as string
              }
          }
      })
      
      async function loadCategories(): Promise<void> {
          // ... 加载数据 ...
      
          // 检查是否有待处理的参数
          if (pendingCategoryId.value != '') {
              const id = pendingCategoryId.value
              pendingCategoryId.value = ''
              selectPrimaryCategory(id)
          }
      }
      

================================================================================ 六十八、函数定义顺序严格检查(重要)

  1. UTS 中函数必须先定义后使用

    • UTS 不支持 JavaScript 的函数提升
    • 被调用的函数必须在调用之前定义
    • 错误示例:
      // 错误selectPrimaryCategory 未定义
      async function loadCategories(): Promise<void> {
          // ...
          selectPrimaryCategory(categoryId)  // 编译错误
      }
      
      async function selectPrimaryCategory(id: string): Promise<void> {
          // ...
      }
      
    • 正确示例:
      // 正确:先定义被调用的函数
      async function selectPrimaryCategory(id: string): Promise<void> {
          // ...
      }
      
      async function loadCategories(): Promise<void> {
          // ...
          selectPrimaryCategory(categoryId)  // 正确
      }
      
  2. 变量使用前必须定义

    • 错误示例:
      if (systemInfo.windowWidth > 1025) {  // 错误systemInfo 未定义
          // ...
      }
      const systemInfo = uni.getSystemInfoSync()
      
    • 正确示例:
      const systemInfo = uni.getSystemInfoSync()  // 先定义
      if (systemInfo.windowWidth > 1025) {  // 正确
          // ...
      }
      
  3. 推荐的代码组织顺序

    <script setup lang="uts">
    // 1. 导入语句
    import { ref, onMounted, onShow } from 'vue'
    
    // 2. 类型定义
    type LocalCategory = { id: string, name: string }
    
    // 3. 响应式变量声明
    const pendingCategoryId = ref('')
    const primaryCategories = ref<LocalCategory[]>([])
    
    // 4. 工具函数(不依赖其他函数)
    const getUserId = (): string => { ... }
    
    // 5. 核心业务函数(被其他函数调用)
    async function selectPrimaryCategory(id: string): Promise<void> { ... }
    async function loadProducts(): Promise<void> { ... }
    
    // 6. 高层业务函数(调用核心函数)
    async function loadCategories(): Promise<void> {
        // 可以安全调用 selectPrimaryCategory
    }
    
    // 7. 生命周期钩子(最后)
    onMounted(() => { ... })
    onShow(() => { ... })
    </script>
    

================================================================================ 六十九、CSS 渐变背景兼容性(重要)

  1. linear-gradient 在 uvue 中可能不支持
    • 问题:background: linear-gradient(...) 在 Android 端可能不显示
    • 解决:使用纯色背景 background-color 替代
    • 错误示例:
      .navbar {
          background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
      }
      
    • 正确示例:
      .navbar {
          background-color: #4CAF50;
      }
      

================================================================================ 七十、避免重复定义生命周期钩子(重要)

  1. 不要重复定义相同的生命周期钩子
    • UTS 中如果定义了两个 onShow,它们都会被执行
    • 这会导致逻辑冲突和意外行为
    • 错误示例:
      onShow(() => {
          // 第一个 onShow
          selectPrimaryCategory(id1)
      })
      
      onShow(() => {
          // 第二个 onShow 也会执行
          // 可能覆盖第一个的处理结果
      })
      
    • 正确示例:
      onShow(() => {
          // 合并所有逻辑到一个 onShow 中
          // 处理 Storage 参数
          // 处理页面参数
          // 其他逻辑
      })
      

================================================================================ 七十一、二级分类ID与一级分类ID的处理重要

  1. 分类页可能收到二级分类ID

    • 首页点击二级分类(如"男装")跳转到分类页
    • 分类页左侧栏显示的是一级分类(如"服装鞋帽"
    • 需要将二级分类ID转换为其父级分类ID
  2. 处理方案

    async function selectPrimaryCategory(categoryId: string): Promise<void> {
        // 检查传入的是否是一级分类ID
        let targetCategoryId = categoryId
        const foundInPrimary = primaryCategories.value.find(c => c.id === categoryId)
    
        if (foundInPrimary == null) {
            // 传入的可能是二级分类ID查找父级分类
            const categoryInfo = await supabaseService.getCategoryById(categoryId)
            if (categoryInfo != null && categoryInfo.parent_id != null) {
                targetCategoryId = categoryInfo.parent_id
            }
        }
    
        activePrimary.value = targetCategoryId
        // ... 后续处理
    }
    
  3. 最佳实践

    • 在服务层添加 getCategoryById 方法获取单个分类信息
    • 通过 parent_id 字段确定父级分类
    • 如果找不到父级,回退到默认分类

================================================================================ 七十二、Supabase .single() 方法兼容性(重要)

  1. .single() 在 UTS 中可能返回不同格式

    • 问题:.single() 返回的数据可能导致类型转换异常
    • 错误信息:UTSArray cannot be cast to UTSJSONObject
    • 错误示例:
      const response = await supa
          .from('table')
          .select('*')
          .eq('id', id)
          .single()
          .execute()
      
      const rawData = response.data
      const obj = JSON.parse(JSON.stringify(rawData)) as UTSJSONObject  // 可能报错
      
  2. 解决方案:使用 .limit(1) 替代

    const response = await supa
        .from('table')
        .select('*')
        .eq('id', id)
        .limit(1)  // 使用 limit(1) 替代 single()
        .execute()
    
    const rawData = response.data
    if (rawData == null) {
        return null
    }
    
    // 作为数组处理
    const rawList = rawData as any[]
    if (rawList.length == 0) {
        return null
    }
    
    const item = rawList[0]
    const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
    
  3. 最佳实践

    • 避免使用 .single(),改用 .limit(1)
    • 始终将返回数据作为数组处理
    • 检查数组长度后再取第一个元素

================================================================================ 七十三、字符串比较必须使用 == 而不是 ===(重要)

  1. UTS Android 中 === 比较字符串可能失败

    • 问题:即使两个字符串内容相同,=== 也可能返回 false
    • 这会影响 findfindIndexfilter 等数组方法
    • 错误示例:
      const found = array.find((item) => item.id === targetId)  // 可能失败
      const index = array.findIndex((item) => item.id === targetId)  // 可能失败
      if (str1 === str2) { ... }  // 可能失败
      
  2. 解决方案:使用 == 或 for 循环

    // 方案1使用 == 替代 ===
    const found = array.find((item) => item.id == targetId)
    if (str1 == str2) { ... }
    
    // 方案2使用 for 循环(推荐,更可靠)
    let foundItem: Item | null = null
    for (let i = 0; i < array.length; i++) {
        if (array[i].id == targetId) {
            foundItem = array[i]
            break
        }
    }
    
  3. 受影响的场景

    • 数组查找:findfindIndexfiltersomeevery
    • 字符串比较:if (a === b)
    • 对象属性比较:if (obj.id === targetId)
  4. 最佳实践

    • 在 UTS Android 中,始终使用 == 而不是 === 进行字符串比较
    • 对于关键逻辑,使用 for 循环替代数组方法
    • 添加调试日志确认比较结果

================================================================================ 七十四、Supabase 查询应在数据库层面过滤(重要)

  1. 问题:先获取数据再手动过滤导致数据不足

    • 错误示例:
      // 错误:先获取 limit 条数据,再手动过滤
      const response = await supa
          .from('ml_products')
          .select('*')
          .limit(20)
          .execute()
      
      // 手动过滤 category_id
      for (let i = 0; i < rawList.length; i++) {
          if (itemCatId == categoryId) {
              products.push(item)
          }
      }
      // 结果:可能返回 0 个商品,因为匹配的商品不在前 20 条中
      
  2. 解决方案:在数据库层面进行过滤

    // 正确:在数据库层面过滤
    const response = await supa
        .from('ml_products')
        .select('*')
        .eq('category_id', categoryId)  // 数据库层面过滤
        .eq('status', '1')              // 使用字符串 '1'
        .limit(20)
        .execute()
    
  3. 需要手动过滤的场景

    • 某些字段(如 is_hotis_newis_featured)可能无法在数据库层面过滤
    • 解决方案获取更多数据limit * 5然后手动过滤
    const response = await supa
        .from('ml_products')
        .select('*')
        .eq('status', '1')
        .limit(limit * 5)  // 获取更多数据
        .execute()
    
    // 手动过滤 is_hot
    for (let i = 0; i < rawList.length; i++) {
        if (isHotBool) {
            products.push(item)
            if (products.length >= limit) break
        }
    }
    
  4. 最佳实践

    • 优先在数据库层面使用 .eq().in() 等方法过滤
    • 整数字段使用字符串:.eq('status', '1') 而不是 .eq('status', 1)
    • 无法在数据库层面过滤时,获取更多数据再手动过滤

================================================================================ 七十五、URL 参数类型转换(重要)

  1. 问题URL 参数可能是不同类型

    • 从 URL 传递的参数可能是 stringnumber 或其他类型
    • 错误示例:
      const priceOpt = opts['price'] as string  // 错误:可能是 number
      this.product.price = parseFloat(priceOpt)
      // 运行时错误java.lang.Double cannot be cast to java.lang.String
      
  2. 解决方案:检查类型后再转换

    const priceOpt = opts['price']
    if (typeof priceOpt == 'number') {
        this.product.price = priceOpt as number
    } else if (typeof priceOpt == 'string') {
        this.product.price = parseFloat(priceOpt as string)
    } else {
        this.product.price = 0
    }
    
  3. 通用类型安全获取函数

    function getNumberParam(opts: UTSJSONObject, key: string): number {
        const val = opts.get(key)
        if (typeof val == 'number') return val as number
        if (typeof val == 'string') return parseFloat(val as string)
        return 0
    }
    
    function getStringParam(opts: UTSJSONObject, key: string): string {
        const val = opts.get(key)
        if (typeof val == 'string') return val as string
        if (typeof val == 'number') return (val as number).toString()
        return ''
    }
    

================================================================================ 七十六、避免使用 .single() 和 .executeAs()(重要)

  1. 问题:.single().executeAs<T>() 在 UTS Android 中可能失败

    • 错误示例:
      const response = await supa
          .from('ml_products')
          .select('*')
          .eq('id', productId)
          .single()
          .executeAs<Product>()
      // 可能报错UTSArray cannot be cast to UTSJSONObject
      
  2. 解决方案:使用 .limit(1) 和手动解析

    const response = await supa
        .from('ml_products')
        .select('*')
        .eq('id', productId)
        .limit(1)  // 使用 limit(1) 替代 single()
        .execute()
    
    const rawData = response.data
    if (rawData == null) return null
    
    const rawList = rawData as any[]
    if (rawList.length == 0) return null
    
    const item = rawList[0]
    const product = parseProductFromRaw(item)  // 使用解析函数
    return product
    
  3. 最佳实践

    • 使用 .limit(1) 替代 .single()
    • 使用 .execute() 替代 .executeAs<T>()
    • 使用统一的解析函数(如 parseProductFromRaw)处理数据

================================================================================ 七十七、排序切换状态管理(重要)

  1. 问题:点击同一排序按钮时无法切换排序方向

    • 需求:二次点击"价格"时,切换升序/降序
  2. 解决方案:使用状态变量记录排序方向

    const priceAscending = ref(true)  // 价格排序方向
    
    const switchSort = (sortId: string) => {
        // 如果点击的是价格排序,切换升序/降序
        if (sortId === 'price' && activeSort.value === 'price') {
            priceAscending.value = !priceAscending.value
        } else {
            // 切换到其他排序时,重置价格排序为升序
            if (sortId !== 'price') {
                priceAscending.value = true
            }
            activeSort.value = sortId
        }
        // 重新加载数据
        loadData()
    }
    
  3. 在数据加载时使用排序方向

    switch (activeSort.value) {
        case 'price':
            products = await supabaseService.getProductsByPrice(limit, priceAscending.value)
            break
        // ...
    }
    

================================================================================ 七十八、加载更多逻辑(重要)

  1. 问题:加载更多时数据被替换而不是追加

    • 错误示例:
      const loadMore = async () => {
          const nextLimit = currentCount + 6
          await loadHotProducts(nextLimit)  // 这个函数会替换数据
      }
      
  2. 解决方案:根据当前排序方式调用对应函数

    const loadMore = async () => {
        const currentCount = hotProducts.value.length
        const additionalLimit = 6
    
        let newProducts: Product[] = []
        switch (activeSort.value) {
            case 'sales':
                newProducts = await supabaseService.getProductsBySales(currentCount + additionalLimit)
                break
            case 'price':
                newProducts = await supabaseService.getProductsByPrice(currentCount + additionalLimit, priceAscending.value)
                break
            // ... 其他排序方式
        }
    
        // 检查是否还有更多数据
        if (newProducts.length <= currentCount) {
            hasMore.value = false
        } else {
            hotProducts.value = newProducts
        }
    }
    
  3. 最佳实践

    • 每个排序方式对应独立的数据获取函数
    • 传递 currentCount + additionalLimit 作为新的 limit
    • 比较返回数量判断是否还有更多数据

================================================================================ 七十九、二级分类自适应宽度布局(重要)

  1. 问题:二级分类项宽度不一致,超出屏幕

  2. 解决方案:使用 flex 布局自适应宽度

    .sub-category-list {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        width: 100%;
        justify-content: space-between;
    }
    
    .sub-category-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 8px 4px;
        background: #f5f5f5;
        border-radius: 8px;
        flex: 1;           /* 自动扩展填充空间 */
        min-width: 50px;   /* 最小宽度保证可读性 */
    }
    
  3. 布局效果

    • 二级分类项均匀分布在一行内
    • 根据容器宽度自适应
    • 不会超出屏幕右边

================================================================================ 八十、Product 类型与服务层返回类型一致性(重要)

  1. 问题:页面期望的数据类型与服务层返回类型不匹配

    • 服务层 getProductById 返回 Product 类型
    • 页面可能期望 UTSJSONObject 类型
    • 导致数据无法正确映射
  2. 解决方案:统一使用服务层返回的类型

    // 正确:直接使用 Product 对象
    const dbProduct = await supabaseService.getProductById(productId)
    if (dbProduct != null) {
        this.product = {
            id: dbProduct.id,
            name: dbProduct.name,
            stock: dbProduct.stock ?? dbProduct.total_stock ?? 0,
            sales: dbProduct.sale_count ?? 0,
            // ...
        }
    }
    
  3. 类型定义需要包含所有使用到的字段

    • 确保 Product 类型包含所有必要字段
    • specificationusageexpiry_date

================================================================================ 八十一、parseProductFromRaw 辅助函数(重要)

  1. 使用统一的解析函数处理数据库返回数据

    function parseProductFromRaw(item: any): Product {
      const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
    
      const getSafeString = (key: string): string => {
        const val = itemObj.get(key)
        if (val == null) return ''
        if (typeof val == 'string') return val
        if (typeof val == 'number') return val.toString()
        return ''
      }
    
      const getSafeNumber = (key: string): number => {
        const val = itemObj.get(key)
        if (val == null) return 0
        if (typeof val == 'number') return val
        if (typeof val == 'string') return parseFloat(val) ?? 0
        return 0
      }
    
      return {
        id: getSafeString('id'),
        name: getSafeString('name'),
        // ...
      } as Product
    }
    
  2. 优点

    • 统一处理类型转换
    • 避免 ClassCastException 错误
    • 提供默认值防止空指针

================================================================================ 八十二、点击事件处理(重要)

  1. 问题:点击事件条件判断不完整导致无响应

    • 错误示例:
      goToShop() {
          if (this.merchant.user_id != null && this.merchant.user_id !== '') {
              uni.navigateTo({ url: '...' })
          }
          // 如果条件不满足,没有任何反馈
      }
      
  2. 解决方案:添加后备方案和用户反馈

    goToShop() {
        const merchantId = this.merchant.id ?? this.product.merchant_id ?? ''
        if (merchantId != '') {
            uni.navigateTo({ url: `/pages/shop?merchantId=${merchantId}` })
        } else {
            uni.showToast({ title: '店铺信息加载中', icon: 'none' })
        }
    }
    
  3. 最佳实践

    • 使用 ?? 提供后备值
    • 条件不满足时给用户反馈
    • 添加日志便于调试

================================================================================ 八十三、类型定义必须与实际数据类型一致(重要)

  1. 问题:类型定义与实际数据类型不匹配

    • ProductSku.specifications 定义为 stringJSON string
    • 但代码尝试传入 UTSJSONObject
    • 编译错误:参数类型不匹配:实际类型为 'UTSJSONObject',预期类型为 'String'
  2. 解决方案:确保类型一致

    // 错误specifications 是 string 类型,不能传 UTSJSONObject
    const sku: ProductSku = {
        specifications: specs as UTSJSONObject  // 错误
    }
    
    // 正确:转换为 JSON string
    let specsStr = ''
    if (rawSpecs != null) {
        if (typeof rawSpecs == 'string') {
            specsStr = rawSpecs as string
        } else {
            specsStr = JSON.stringify(rawSpecs)
        }
    }
    const sku: ProductSku = {
        specifications: specsStr  // 正确
    }
    
  3. 最佳实践

    • 检查类型定义,确认字段类型
    • JSON 数据存储为 string 类型时,需要序列化
    • 使用时再反序列化

================================================================================ 八十四、数组类型字段需要正确处理(重要)

  1. 问题:数据库存储的 JSON 字符串不能直接赋值给数组类型

    • Product.tags 在数据库中是 string 类型JSON 字符串)
    • ProductType.tagsArray<string> 类型
    • 直接赋值导致编译错误
  2. 解决方案:解析 JSON 字符串后赋值

    // 错误string 不能直接赋值给 Array<string>
    tags: dbProduct.tags ?? [] as string[]  // 错误
    
    // 正确:先初始化空数组,再解析 JSON
    tags: [] as string[]
    
    // 然后解析
    if (dbProduct.tags != null && dbProduct.tags != '') {
        try {
            const parsedTags = JSON.parse(dbProduct.tags)
            if (Array.isArray(parsedTags)) {
                this.product.tags = (parsedTags as any[]).map((t: any): string => t as string)
            }
        } catch(e) {}
    }
    
  3. 常见需要解析的 JSON 字段

    • tags - 标签数组
    • image_urls - 图片 URL 数组
    • specifications - 规格对象
    • attributes - 属性对象

================================================================================ 八十五、服务层返回类型必须显式解析(重要)

  1. 问题:直接使用 as Type 强制转换会失败

    • 错误信息:UTSJSONObject cannot be cast to Shop
    • 原因Supabase 返回的是 UTSJSONObject,不能直接转换为自定义类型
  2. 解决方案:使用解析函数显式转换

    // 错误:直接强制转换
    return (response.data as any[])[0] as Shop  // 运行时错误
    
    // 正确:使用解析函数
    const shopData = (response.data as any[])[0]
    const shop = this.parseShopFromRaw(shopData)
    return shop
    
    // 解析函数示例
    parseShopFromRaw(item: any): Shop {
      const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
    
      const getSafeString = (key: string): string => {
        const val = itemObj.get(key)
        if (val == null) return ''
        if (typeof val == 'string') return val
        return ''
      }
    
      return {
        id: getSafeString('id'),
        shop_name: getSafeString('shop_name'),
        // ...
      } as Shop
    }
    
  3. 受影响的场景

    • getShopByMerchantId - 返回 Shop 类型
    • fetchShopCoupons - 返回优惠券数组
    • getProductSkus - 返回 SKU 数组
    • 所有从数据库获取并返回自定义类型的函数

================================================================================ 八十六、数组遍历不要使用 map 方法(重要)

  1. 问题:map 方法中的类型转换可能失败

    • 错误信息:ProductSku cannot be cast to UTSJSONObject
    • 原因:map 回调中的类型推断可能不正确
  2. 解决方案:使用 for 循环替代 map

    // 错误:使用 map
    this.productSkus = skus.map((skuData): ProductSkuType => {
        const sku = skuData as UTSJSONObject  // 可能失败
        return { ... }
    })
    
    // 正确:使用 for 循环
    this.productSkus = []
    for (let i = 0; i < skus.length; i++) {
        const skuData = skus[i]
        // 直接使用 skuData 的属性
        const sku: ProductSkuType = {
            id: skuData.id,
            product_id: skuData.product_id,
            // ...
        }
        this.productSkus.push(sku)
    }
    
  3. 最佳实践

    • 避免在数组方法中使用类型转换
    • 使用 for 循环手动构建数组
    • 直接访问对象属性而不是强制转换

================================================================================ 八十七、类型定义字段必须完全匹配(重要)

  1. 问题:构建对象时字段名或类型不匹配

    • 错误信息:No parameter with name 'xxx' foundNo value passed for parameter 'xxx'
    • 原因:类型定义中的字段名与代码中使用的不一致
  2. 解决方案:检查类型定义,确保字段完全匹配

    // 类型定义
    export type CouponTemplateType = {
        id: string
        name: string
        discount_type: number      // 注意:是 discount_type 不是 coupon_type
        discount_value: number
        total_quantity: number     // 注意:是 total_quantity 不是 total_count
        usage_limit: number        // 必填字段
        category_ids: Array<string>
        product_ids: Array<string>
        // ...
    }
    
    // 错误:字段名不匹配
    const coupon: CouponTemplateType = {
        usage_scope: 1,        // 错误:没有这个字段
        total_count: 100,      // 错误:应该是 total_quantity
        // 缺少 usage_limit
    }
    
    // 正确:字段完全匹配
    const coupon: CouponTemplateType = {
        id: '...',
        name: '优惠券',
        discount_type: 1,
        discount_value: 10,
        total_quantity: 100,
        usage_limit: 1,
        category_ids: [] as string[],
        product_ids: [] as string[],
        // 所有必填字段都要提供
    }
    
  3. 常见错误

    • 字段名拼写错误:total_count vs total_quantity
    • 缺少必填字段:usage_limitdiscount_type
    • 类型不匹配:string vs numberArray<string> vs string
  4. 最佳实践

    • 在构建对象前,先查看类型定义
    • 确保所有必填字段都有值
    • 数组类型字段初始化为空数组:[] as string[]

================================================================================ 八十八、服务层返回对象直接使用属性(重要)

  1. 问题:尝试将服务层返回的类型对象转换为 UTSJSONObject

    • 错误信息:Shop cannot be cast to UTSJSONObject
    • 原因:服务层函数(如 getShopByMerchantId)已经返回解析后的类型对象
  2. 解决方案:直接使用返回对象的属性

    // 错误:尝试转换为 UTSJSONObject
    const shopResponse = await supabaseService.getShopByMerchantId(merchantId)
    const shop = shopResponse as UTSJSONObject  // 错误!
    this.merchant = {
        shop_name: shop['shop_name'] as string
    }
    
    // 正确:直接使用 Shop 对象的属性
    const shopResponse = await supabaseService.getShopByMerchantId(merchantId)
    if (shopResponse != null) {
        this.merchant = {
            id: shopResponse.id,
            shop_name: shopResponse.shop_name,
            shop_logo: shopResponse.shop_logo ?? '/static/default-shop.png',
            // ...
        } as MerchantType
    }
    
  3. 受影响的函数

    • getShopByMerchantId() - 返回 Shop 类型
    • getProductById() - 返回 Product 类型
    • getProductSkus() - 返回 ProductSku[] 类型
    • 所有使用 parseXxxFromRaw() 解析的函数
  4. 最佳实践

    • 检查服务层函数的返回类型
    • 如果返回类型是具体类型(如 Shop),直接使用属性
    • 如果返回类型是 anyUTSJSONObject,才需要手动解析

================================================================================ 八十九、数组类型返回值必须手动解析(重要)

  1. 问题:直接使用 as Type[] 转换数组会失败

    • 错误信息:UTSJSONObject cannot be cast to NotificationClassCastException
    • 原因Supabase 返回的是 UTSJSONObject[],不能直接转换为自定义类型数组
  2. 解决方案:使用 for 循环手动解析每个元素

    // 错误:直接转换数组
    return response.data as Notification[]  // 运行时错误
    
    // 正确:手动解析每个元素
    const rawData = response.data
    if (rawData == null) return []
    
    const notifications: Notification[] = []
    const rawList = rawData as any[]
    
    for (let i = 0; i < rawList.length; i++) {
        const item = rawList[i]
        const noteObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
    
        const note: Notification = {
            id: getSafeString('id'),
            title: getSafeString('title'),
            content: getSafeString('content'),
            is_read: getSafeBoolean('is_read'),
            // ...
        }
        notifications.push(note)
    }
    return notifications
    
  3. 受影响的函数

    • getUserNotifications() - 返回 Notification[]
    • getChatRooms() - 返回 ChatRoom[]
    • 所有返回自定义类型数组的服务层函数
  4. 最佳实践

    • 创建 getSafeStringgetSafeNumbergetSafeBoolean 辅助函数
    • 使用 for 循环而不是 map 方法
    • 确保类型定义中的所有字段都被正确赋值

================================================================================ 九十、reverse() + map() 组合在 UTS Android 中的问题(重要)

  1. 问题:array.reverse().map() 组合可能失败

    • reverse() 会修改原数组
    • map() 中的类型转换可能失败
    • 组合使用时可能导致数据丢失或类型错误
  2. 解决方案:使用 for 循环反向遍历

    // 错误:使用 reverse() + map()
    messages.value = rawMsgs.reverse().map((m: ChatMessage): UiChatMessage => {
        return {
            id: m.id,
            content: m.content
        }
    })
    
    // 正确:使用 for 循环反向遍历
    const uiMessages: UiChatMessage[] = []
    for (let i = rawMsgs.length - 1; i >= 0; i--) {
        const m = rawMsgs[i]
        const uiMsg: UiChatMessage = {
            id: m.id,
            content: m.content ?? ''
        }
        uiMessages.push(uiMsg)
    }
    messages.value = uiMessages
    
  3. 优点

    • 不修改原数组
    • 类型安全
    • 更好的性能

================================================================================ 九十一、实时订阅中的字符串比较(重要)

  1. 问题:实时订阅回调中使用 ===!== 比较字符串失败

    • 症状:收到消息但条件判断失败,消息不显示
    • 原因:===!== 在 UTS Android 中可能对字符串比较失败
  2. 解决方案:使用 ==!= 进行字符串比较

    // 错误:使用 === 和 !==
    if (senderId === currentUserId.value) {
        return  // 可能失败
    }
    if (receiverId === currentUserId.value) {
        if (senderId !== merchantId.value) {
            return  // 可能失败
        }
    }
    
    // 正确:使用 == 和 !=
    if (senderId == currentUserId.value) {
        return  // 正确工作
    }
    if (receiverId == currentUserId.value) {
        if (senderId != merchantId.value) {
            return  // 正确工作
        }
    }
    
  3. 调试技巧

    • 在条件判断前添加日志输出变量值
    • 比较字符串时使用 ==!=
    • 确保变量不为 null 或 undefined

================================================================================ 九十二、多端实时同步消息处理(重要)

  1. 问题:多端同时在线时,自己发送的消息在其他端不显示

    • 原因:实时订阅中跳过了自己发送的消息
    • 错误逻辑:if (senderId == currentUserId.value) return
  2. 解决方案:正确处理自己发送的消息

    // 错误:跳过自己发送的消息
    if (senderId == currentUserId.value) {
        return  // 导致其他端看不到自己发的消息
    }
    
    // 正确:判断消息类型并显示
    const isMyMessage = (senderId == currentUserId.value)
    const isForMe = (receiverId == currentUserId.value)
    
    if (isMyMessage || isForMe) {
        const incomingMsg: UiChatMessage = {
            id: msgId,
            type: isMyMessage ? 'sent' : 'received',  // 区分发送/接收
            content: newMsg.getString('content') ?? '',
            time: timeStr
        }
        messages.value.push(incomingMsg)
    }
    
  3. 避免重复消息

    • 发送消息时不要使用乐观更新
    • 等待实时订阅推送消息
    • 检查消息 ID 是否已存在
    // 错误:乐观更新 + 实时订阅 = 重复消息
    messages.value.push(newMessage)  // 乐观更新
    await supabaseService.sendMessage(...)  // 实时订阅会再推送一条
    
    // 正确:只依赖实时订阅
    await supabaseService.sendMessage(...)  // 实时订阅推送消息
    
    // 检查重复
    for (let i = 0; i < messages.value.length; i++) {
        if (messages.value[i].id == msgId) {
            return  // 已存在,跳过
        }
    }
    
  4. 最佳实践

    • 统一使用实时订阅处理所有消息(包括自己发送的)
    • 发送消息后不进行乐观更新
    • 通过消息 ID 去重

================================================================================ 九十三、避免使用 .single() 和 forEach重要

  1. 问题:.single()forEach 在 UTS Android 中可能失败

    • .single() 可能导致类型转换错误
    • forEach 中的类型判断可能失败
  2. 解决方案:使用 .limit(1)for 循环

    // 错误:使用 .single()
    const response = await supa
        .from('ml_orders')
        .select('*')
        .eq('id', orderId)
        .single()
        .execute()
    return response.data
    
    // 正确:使用 .limit(1)
    const response = await supa
        .from('ml_orders')
        .select('*')
        .eq('id', orderId)
        .limit(1)
        .execute()
    
    const rawList = response.data as any[]
    if (rawList.length == 0) return null
    return rawList[0]
    
  3. forEach 替换为 for 循环

    // 错误:使用 forEach
    notes.forEach((note: Notification) => {
        if (note.type === 'order') {  // === 可能失败
            orderMessages.push(item)
        }
    })
    
    // 正确:使用 for 循环
    for (let i = 0; i < notes.length; i++) {
        const note = notes[i]
        if (note.type == 'order') {  // 使用 ==
            orderMessages.push(item)
        }
    }
    

================================================================================ 九十四、嵌套对象数据解析(重要)

  1. 问题Supabase 返回的嵌套对象无法直接访问

    • dataObj['ml_order_items'] 可能返回 undefined
    • dataObj['shipping_address'] 类型转换失败
  2. 解决方案:使用 JSON.parse(JSON.stringify())UTSJSONObject.get()

    // 错误:直接访问属性
    const dataObj = data as Record<string, any>
    const items = dataObj['ml_order_items']  // 可能失败
    orderItems.value = items as OrderItemType[]
    
    // 正确:使用 UTSJSONObject 解析
    const dataObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject
    
    // 解析嵌套数组
    const itemsRaw = dataObj.get('ml_order_items')
    if (itemsRaw != null && Array.isArray(itemsRaw)) {
        const items = itemsRaw as any[]
        orderItems.value = []
        for (let i = 0; i < items.length; i++) {
            const itemObj = JSON.parse(JSON.stringify(items[i])) as UTSJSONObject
            const orderItem: OrderItemType = {
                id: (itemObj.get('id') ?? '') as string,
                product_name: (itemObj.get('product_name') ?? '') as string,
                // ...
            }
            orderItems.value.push(orderItem)
        }
    }
    
    // 解析嵌套对象
    const addressRaw = dataObj.get('shipping_address')
    if (addressRaw != null) {
        const addressObj = JSON.parse(JSON.stringify(addressRaw)) as UTSJSONObject
        deliveryAddress.value = {
            name: (addressObj.get('name') ?? '') as string,
            phone: (addressObj.get('phone') ?? '') as string,
            // ...
        } as AddressType
    }
    
  3. 最佳实践

    • 所有嵌套数据都需要显式解析
    • 使用 ?? 提供默认值
    • 添加日志确认数据解析成功

================================================================================ 九十五、避免使用 .single() 查询单条数据(重要)

  1. 问题:.single() 在 UTS Android 中可能导致类型转换错误

    • 错误信息:UTSArray cannot be cast to UTSJSONObject
    • 原因:.single() 返回的数据格式与预期不符
  2. 解决方案:使用 .limit(1) 替代

    // 错误:使用 .single()
    const response = await supa
        .from('ml_user_profiles')
        .select('*')
        .eq('user_id', userId)
        .single()
        .execute()
    return response.data
    
    // 正确:使用 .limit(1)
    const response = await supa
        .from('ml_user_profiles')
        .select('*')
        .eq('user_id', userId)
        .limit(1)
        .execute()
    
    const rawData = response.data
    if (rawData == null) return null
    
    const rawList = rawData as any[]
    if (rawList.length == 0) return null
    
    return rawList[0]
    
  3. 最佳实践

    • 所有查询单条数据的场景都应使用 .limit(1)
    • 始终将返回数据作为数组处理
    • 检查数组长度后再取第一个元素

================================================================================ 九十六、避免使用 .map() 处理数组转换(重要)

  1. 问题:.map() 中的类型转换可能在 Android 端失败

    • 错误信息:类型转换异常或数据丢失
    • 原因:.map() 回调中的类型推断可能不正确
  2. 解决方案:使用 for 循环替代

    // 错误:使用 .map()
    const products = rawData.map((item: any): Product => {
        const obj = item as UTSJSONObject
        return {
            id: obj.getString('id') ?? '',
            name: obj.getString('name') ?? ''
        } as Product
    })
    
    // 正确:使用 for 循环
    const products: Product[] = []
    for (let i = 0; i < rawData.length; i++) {
        const item = rawData[i]
        const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
        const product: Product = {
            id: obj.getString('id') ?? '',
            name: obj.getString('name') ?? ''
        } as Product
        products.push(product)
    }
    
  3. 最佳实践

    • 所有数组转换都应使用 for 循环
    • 使用 JSON.parse(JSON.stringify()) 确保正确转换为 UTSJSONObject
    • 显式声明结果数组类型

================================================================================ 九十七、scroll-view 必须正确配置(重要)

  1. 问题scroll-view 在 Android 端无法滚动

    • 原因1使用了错误的属性语法 scroll-y 而不是 :scroll-y="true"
    • 原因2缺少高度约束
  2. 解决方案

    <!-- 错误 -->
    <scroll-view scroll-y class="content">
    
    <!-- 正确 -->
    <scroll-view :scroll-y="true" class="content">
    
    /* 父容器 */
    .parent-container {
        flex: 1;
        height: 0px;  /* 关键:配合 flex: 1 使用 */
        display: flex;
        flex-direction: column;
    }
    
    /* scroll-view */
    .content {
        flex: 1;
        height: 0px;  /* 关键:确保有高度约束 */
    }
    
  3. 最佳实践

    • scroll-view 的 scroll-y 属性必须使用绑定语法 :scroll-y="true"
    • 父容器和 scroll-view 都需要设置 flex: 1height: 0px
    • 确保 scroll-view 有明确的高度约束

================================================================================ 九十八、Record<string, any> 访问属性(重要)

  1. 问题:any 类型无法直接访问属性

    • 错误信息:找不到名称 "xxx"
    • 原因:any 类型不支持直接属性访问
  2. 解决方案:转换为 Record<string, any> 后使用索引访问

    // 错误:直接访问 any 类型属性
    const order = orders[i] as any
    const status = order.status  // 错误!
    
    // 正确:转换为 Record<string, any>
    const order = orders[i] as Record<string, any>
    const status = order['status'] as number  // 正确
    
  3. 最佳实践

    • 对于需要访问属性的对象,使用 Record<string, any> 类型
    • 使用索引访问 obj['property'] 而非点访问 obj.property
    • 访问后使用 as Type 进行类型转换

================================================================================ 九十九、保留嵌套数据结构(重要)

  1. 问题:类型定义不包含嵌套字段导致数据丢失

    • 例如:OrderType 不包含 ml_order_items 字段
    • 强制转换后会丢失嵌套数据
  2. 解决方案:使用 anyRecord<string, any> 保留完整数据

    // 错误:强制转换丢失嵌套数据
    const order: OrderType = {
        id: o.getString('id') ?? '',
        // ... 其他字段
    } as OrderType
    // ml_order_items 数据丢失!
    
    // 正确:保留嵌套数据
    const orderItem: Record<string, any> = {
        id: o.getString('id') ?? '',
        // ... 其他字段
        ml_order_items: o.get('ml_order_items')  // 保留嵌套数据
    }
    
  3. 最佳实践

    • 如果需要访问嵌套数据,不要使用不包含这些字段的类型
    • 使用 Record<string, any>any 保留完整数据结构
    • 在访问嵌套数据时进行类型转换

================================================================================ 一百、Supabase 查询中整数字段处理(重要)

  1. 问题Supabase 查询时整数字段导致类型转换异常

    • 错误信息:java.lang.Integer cannot be cast to java.lang.String
    • 原因UTS Android 中 Supabase 查询的 .eq().in() 等方法对整数字段需要使用字符串形式
  2. 解决方案:整数字段使用字符串形式

    // 错误:直接使用数字
    .eq('target_type', 1)
    .eq('status', 1)
    .in('category_id', [1, 2, 3])
    
    // 正确:使用字符串形式
    .eq('target_type', '1')
    .eq('status', '1')
    .in('category_id', ['1', '2', '3'])
    
  3. insert 语句中同样需要使用字符串

    // 错误:直接使用数字
    .insert({
        target_type: 1,
        status: 1
    })
    
    // 正确:使用字符串形式
    .insert({
        target_type: '1',
        status: '1'
    })
    
  4. 常见需要处理的整数字段

    • target_type - 收藏类型1:商品, 2:店铺)
    • status - 各种状态字段
    • order_status - 订单状态
    • payment_status - 支付状态
    • shipping_status - 发货状态
  5. 最佳实践

    • 所有 Supabase 查询中的整数值都使用字符串形式
    • 使用 UTSJSONObject.set() 设置整数字段时也使用字符串
    • 从数据库读取的整数值需要安全转换后再使用

================================================================================ 一百零一、数据库字段类型安全转换(重要)

  1. 问题:数据库返回的字段类型可能与预期不符

    • 错误信息:java.lang.Integer cannot be cast to java.lang.String
    • 原因UUID 字段可能返回为 Integer需要安全转换
  2. 解决方案:使用类型检查后转换

    // 安全获取字符串类型的 ID
    const targetIdRaw = itemObj.get('target_id')
    let targetId = ''
    if (targetIdRaw != null) {
        if (typeof targetIdRaw === 'string') {
            targetId = targetIdRaw as string
        } else if (typeof targetIdRaw === 'number') {
            targetId = (targetIdRaw as number).toString()
        }
    }
    
  3. 通用辅助函数

    // 安全获取字符串值
    function safeGetString(obj: UTSJSONObject, key: string): string {
        const val = obj.get(key)
        if (val == null) return ''
        if (typeof val === 'string') return val as string
        if (typeof val === 'number') return (val as number).toString()
        return ''
    }
    
    // 安全获取数值
    function safeGetNumber(obj: UTSJSONObject, key: string): number {
        const val = obj.get(key)
        if (val == null) return 0
        if (typeof val === 'number') return val as number
        if (typeof val === 'string') return parseFloat(val as string) ?? 0
        return 0
    }
    
  4. 最佳实践

    • 所有从数据库获取的字段都进行类型检查
    • 不要假设字段类型,使用 typeof 检查
    • 提供默认值防止空指针异常

================================================================================ 一百零二、JSON.parse() 返回值处理(重要)

  1. 问题JSON.parse() 返回 Any? 类型

    • 错误信息:参数类型不匹配:实际类型为 'Any?',预期类型为 'Any'
    • 原因JSON.parse() 返回的是可空类型,不能直接传给需要 Any 类型的函数
  2. 解决方案:检查 null 后再使用

    // 错误:直接传递 JSON.parse() 结果
    const parsed = JSON.parse(specs)
    return formatSpecs(parsed)  // 错误parsed 是 Any? 类型
    
    // 正确:先检查 null
    const parsed = JSON.parse(specs)
    if (parsed != null) {
        return formatSpecs(parsed)
    }
    return ''
    
  3. 完整示例

    const getSpecText = (specs: any): string => {
        if (specs == null) return ''
        if (typeof specs === 'string') {
            if (specs == '') return ''
            try {
                const parsed = JSON.parse(specs)
                if (parsed != null) {
                    return formatSpecs(parsed)
                }
                return specs
            } catch (e) {
                return specs
            }
        }
        return formatSpecs(specs)
    }
    
  4. 最佳实践

    • JSON.parse() 返回值必须检查 null
    • 使用 try-catch 包裹 JSON.parse() 防止解析异常
    • 提供默认值或后备方案

================================================================================ 一百零三、响应式对象不能直接转换为 Map/Record重要

  1. 问题ref 对象的值不能直接转换为 Record<string, any>

    • 错误信息:AddressTypeReactiveObject cannot be cast to Map
    • 原因Vue 的响应式对象在 UTS Android 中是特殊的代理类型,不能直接转换
  2. 错误示例

    const deliveryAddress = ref<AddressType | null>(null)
    
    const getFullAddress = (addr: any): string => {
        if (addr == null) return ''
        if (typeof addr === 'string') return addr
    
        // 错误:响应式对象不能直接转换为 Record
        const addrObj = addr as Record<string, any>
        return addrObj['province'] as string  // 运行时错误
    }
    
    // 模板中调用
    {{ getFullAddress(deliveryAddress as any) }}
    
  3. 正确解决方案:使用 JSON.parse(JSON.stringify()) 转换

    const getFullAddress = (addr: any): string => {
        if (addr == null) return ''
        if (typeof addr === 'string') return addr
    
        try {
            // 正确:先序列化再解析为 UTSJSONObject
            const addrObj = JSON.parse(JSON.stringify(addr)) as UTSJSONObject
    
            const addressField = addrObj.getString('address')
            if (addressField != null && addressField != '') return addressField
    
            const province = addrObj.getString('province') ?? ''
            const city = addrObj.getString('city') ?? ''
            const district = addrObj.getString('district') ?? ''
            const detail = addrObj.getString('detail') ?? ''
            return province + city + district + detail
        } catch (e) {
            console.error('[getFullAddress] 解析地址失败:', e)
            return ''
        }
    }
    
  4. 最佳实践

    • 响应式对象传递给函数时,必须使用 JSON.parse(JSON.stringify()) 转换
    • 转换后使用 UTSJSONObject 的 getString()、getNumber() 方法访问属性
    • 使用 try-catch 包裹防止转换异常
    • 对于简单场景,可以直接在模板中访问响应式对象的属性

================================================================================ 一百零四、数据库关联查询可能返回 null重要

  1. 问题Supabase 关联查询可能返回 null

    • 症状:ml_order_items: null 即使数据库有关联数据
    • 原因:
      1. 关联表中确实没有数据
      2. 外键关系配置不正确
      3. 查询语法问题
  2. 检查步骤

    // 1. 检查原始数据
    console.log('[loadOrderDetail] 订单商品数据:', itemsRaw)
    
    // 2. 正确处理 null 情况
    if (itemsRaw != null && Array.isArray(itemsRaw)) {
        // 处理数据
    } else {
        console.warn('[loadOrderDetail] 订单商品数据为空')
    }
    
  3. 确保 Supabase 查询正确

    // 正确的关联查询语法
    const response = await supa
        .from('ml_orders')
        .select(`
            *,
            ml_order_items (
                id,
                product_id,
                product_name,
                price,
                quantity,
                image_url,
                specifications
            )
        `)
        .eq('id', orderId)
        .limit(1)
        .execute()
    
  4. 最佳实践

    • 始终检查关联数据是否为 null
    • 添加日志输出原始数据便于调试
    • 给用户友好的提示(如"暂无商品数据"

================================================================================ 一百零五、as UTSJSONObject 不会添加方法(重要)

  1. 问题:直接使用 as UTSJSONObject 类型断言不会让对象获得 getString/getNumber 等方法

    • 错误信息:UTSJSONObject cannot be cast to io.dcloud.uts.Map
    • 原因:类型断言只是编译时行为,不会改变运行时对象的方法
  2. 错误示例

    const rawItems = orderData.items as any[]
    
    for(let i = 0; i < rawItems.length; i++) {
        // 错误:直接 as UTSJSONObject 不会添加方法
        const item = rawItems[i] as UTSJSONObject
    
        // 运行时错误getString is not a function
        const productId = item.getString('product_id')
    }
    
  3. 正确解决方案:使用 JSON.parse(JSON.stringify()) 进行真正的转换

    const rawItems = orderData.items as any[]
    
    for(let i = 0; i < rawItems.length; i++) {
        // 正确:使用 JSON.parse(JSON.stringify()) 转换
        const item = JSON.parse(JSON.stringify(rawItems[i])) as UTSJSONObject
    
        // 正确工作
        const productId = item.getString('product_id')
    }
    
  4. 常见场景

    • 从 Supabase 查询返回的数组元素
    • 从 API 响应获取的数据
    • 从页面传递的参数对象
    • 嵌套在父对象中的子对象
  5. 最佳实践

    • 所有需要调用 getString/getNumber/getArray 方法的地方,必须先用 JSON.parse(JSON.stringify()) 转换
    • 不要依赖 as UTSJSONObject 类型断言来添加方法
    • 对于复杂对象,统一使用转换函数处理

================================================================================ 一百零六、函数间传递对象需要序列化(重要)

  1. 问题:在函数间传递对象或数组时,可能遇到类型转换错误

    • 错误信息:UTSJSONObject cannot be cast to io.dcloud.uts.Map
    • 原因Vue 响应式对象、UTSJSONObject 等特殊类型在传递过程中可能无法正确序列化
  2. 错误示例

    // checkout.uvue 中构建商品数组
    const items: any[] = []
    for (let j = 0; j < group.items.length; j++) {
        const item = group.items[j]  // Vue 响应式对象
        items.push({
            id: item.id,
            product_id: item.product_id,
            // ...
        })
    }
    
    // 传递给服务层
    await supabaseService.createOrder({
        items: items  // 可能包含响应式代理对象
    })
    
    // 服务层处理
    const rawItems = orderData.items as any[]
    for(let i = 0; i < rawItems.length; i++) {
        const item = JSON.parse(JSON.stringify(rawItems[i])) as UTSJSONObject
        // 仍然可能失败,因为 rawItems[i] 可能是特殊对象
    }
    
  3. 正确解决方案:在传递前完全序列化

    // 在服务层接收参数时,先将整个数组序列化
    const plainItems: any[] = []
    for(let k = 0; k < shopItems.length; k++) {
        const plainItem = JSON.parse(JSON.stringify(shopItems[k]))
        plainItems.push(plainItem)
    }
    
    // 然后传递给内部函数
    const orderId = await this.createOrder({
        items: plainItems  // 现在是普通对象数组
    })
    
    // 在 createOrder 中处理
    for(let i = 0; i < rawItems.length; i++) {
        const itemStr = JSON.stringify(rawItems[i])
        const item = JSON.parse(itemStr) as UTSJSONObject
        // 现在可以正确使用 getString/getNumber
    }
    
  4. 最佳实践

    • 在函数边界处,将复杂对象序列化为普通对象
    • 使用 JSON.parse(JSON.stringify(obj)) 确保对象是普通 JavaScript 对象
    • 添加日志输出对象类型便于调试
    • 对于嵌套对象,每一层都需要序列化

================================================================================ 一百零七、CSS 属性值限制(重要)

  1. 问题UTS Android 不支持某些 CSS 属性值

    • gap 属性不支持
    • calc() 函数不支持
    • font-weight: 800 等数值不支持(只支持 normal/bold/400/700
    • env()constant() 函数不支持
  2. 错误示例

    /* 错误gap 不支持 */
    .container {
        display: flex;
        gap: 20px;
    }
    
    /* 错误calc() 不支持 */
    .card {
        width: calc(50% - 10px);
    }
    
    /* 错误font-weight 数值不支持 */
    .title {
        font-weight: 800;
    }
    
    /* 错误env()/constant() 不支持 */
    .bottom-bar {
        padding-bottom: env(safe-area-inset-bottom);
    }
    
  3. 正确解决方案

    /* 正确:使用 margin 替代 gap */
    .container {
        display: flex;
        flex-wrap: wrap;
    }
    .item {
        margin: 10px;
    }
    
    /* 正确:使用百分比和 margin 替代 calc() */
    .card {
        width: 48%;
        margin: 0 1%;
    }
    
    /* 正确:使用 bold 或 700 */
    .title {
        font-weight: bold;
    }
    
    /* 正确:使用固定像素值 */
    .bottom-bar {
        padding-bottom: 30px;
    }
    
  4. font-weight 支持的值

    • normal (等同于 400)
    • bold (等同于 700)
    • 400
    • 700
  5. 最佳实践

    • 避免使用 gap,改用 margin
    • 避免使用 calc(),改用百分比和 margin 组合
    • font-weight 只使用 normalbold400700
    • 安全区域适配使用固定像素值(如 30px
  6. 其他不支持的 CSS 属性

    • overflow-wrap 不支持
    • cursor 不支持
    • max-width 不支持百分比值,只支持 number|pixel

================================================================================ 一百零八、模板中 || 运算符限制(重要)

  1. 问题:模板中使用 || 运算符可能导致编译错误

    • 错误信息:Conditional statements must use boolean types
    • 原因UTS 模板中 || 左边必须是 boolean 类型
  2. 错误示例

    <!-- 错误:|| 左边不是 boolean 类型 -->
    <text>{{ order.shop_name || '自营店铺' }}</text>
    <text>{{ item.name || '默认名称' }}</text>
    
  3. 正确解决方案:使用三元表达式

    <!-- 正确:使用三元表达式 -->
    <text>{{ order.shop_name != null && order.shop_name != '' ? order.shop_name : '自营店铺' }}</text>
    <text>{{ item.name != null && item.name != '' ? item.name : '默认名称' }}</text>
    
  4. 最佳实践

    • 模板中避免使用 || 运算符处理默认值
    • 使用三元表达式:condition ? value : default
    • 对于字符串,使用 != null && != '' 判断

================================================================================ 一百零九、JSON.stringify 返回 Any? 类型(重要)

  1. 问题JSON.stringify() 返回 Any? 类型

    • 错误信息:参数类型不匹配:实际类型为 'Any?',预期类型为 'Any'
    • 原因JSON.stringify() 返回可空类型,不能直接使用
  2. 错误示例

    const plainItems: any[] = []
    for(let k = 0; k < shopItems.length; k++) {
        const plainItem = JSON.parse(JSON.stringify(shopItems[k]))
        plainItems.push(plainItem)  // 错误plainItem 是 Any? 类型
    }
    
  3. 正确解决方案:检查 null 后再使用

    const plainItems: any[] = []
    for(let k = 0; k < shopItems.length; k++) {
        const plainItemRaw = JSON.parse(JSON.stringify(shopItems[k]))
        if (plainItemRaw != null) {
            plainItems.push(plainItemRaw as any)
        }
    }
    
  4. 最佳实践

    • JSON.parse() 返回值必须检查 null
    • 使用 as any 或其他具体类型进行类型转换

================================================================================ 一百一十、类型定义必须包含所有字段(重要)

  1. 问题:创建类型实例时缺少必填字段

    • 错误信息:No value passed for parameter 'xxx'
    • 原因:类型定义中的字段都是必填的,必须提供所有字段值
  2. 错误示例

    type OrderItem = {
        id: string,
        order_no: string,
        merchant_id: string,  // 必填字段
        shop_name: string,    // 必填字段
        products: OrderProduct[]
    }
    
    // 错误:缺少 merchant_id 和 shop_name
    typedOrders.push({
        id: mo['id'] as string,
        order_no: mo['order_no'] as string,
        products: mo['products'] as OrderProduct[]
    })
    
  3. 正确解决方案:提供所有必填字段

    typedOrders.push({
        id: mo['id'] as string,
        order_no: mo['order_no'] as string,
        merchant_id: (mo['merchant_id'] ?? '') as string,
        shop_name: (mo['shop_name'] ?? '自营店铺') as string,
        products: mo['products'] as OrderProduct[]
    })
    
  4. 最佳实践

    • 检查类型定义,确认所有字段
    • 使用 ?? 提供默认值
    • 对于可能为空的字段,在类型定义中使用 string | null 或提供默认值

================================================================================ 一百一十一、UTSJSONObject.get() 返回值需要类型转换(重要)

  1. 问题UTSJSONObject.get() 返回 Any? 类型,不能直接传给需要具体类型的函数

    • 错误信息:UTSJSONObject cannot be cast to io.dcloud.uts.Map
    • 原因get() 返回的是 Any? 类型,需要先转换为具体类型
  2. 错误示例

    const item = JSON.parse(itemStr) as UTSJSONObject
    
    // 错误get() 返回 Any?,不能直接传给 set()
    itemJson.set('product_id', item.get('product_id'))
    itemJson.set('product_name', item.get('product_name') ?? '')
    
  3. 正确解决方案:先转换为具体类型

    const itemParsed = JSON.parse(itemStr)
    if (itemParsed == null) {
        console.error('解析失败')
        continue
    }
    const item = itemParsed as UTSJSONObject
    
    // 正确:先获取值,再转换类型
    let pId = item.get('product_id')
    if (pId == null) {
        pId = item.get('id')
    }
    const productId = (pId ?? '') as string
    itemJson.set('product_id', productId)
    
    const productName = (item.get('product_name') ?? '') as string
    itemJson.set('product_name', productName)
    
  4. 最佳实践

    • JSON.parse() 返回值先检查 null再 as UTSJSONObject
    • get() 返回值使用 ?? 提供默认值,再 as string 或其他类型
    • 对于可选字段,始终提供默认值

================================================================================ 一百一十二、onBackPress 生命周期问题(重要)

  1. 问题onBackPress 在 <script setup> 中可能导致编译失败

    • 错误信息:Failed to fetch dynamically imported module
    • 原因UTS 中 onBackPress 的调用方式可能与标准 Vue 不同
  2. 错误示例

    <script setup lang="uts">
    import { onBackPress } from '@dcloudio/uni-app'
    
    // 错误:在 setup 顶层直接调用可能导致问题
    onBackPress((options) => {
        // ...
        return false
    })
    </script>
    
  3. 解决方案

    • 移除 onBackPress 钩子,或使用其他方式处理返回事件
    • 如果需要拦截返回,考虑在页面跳转逻辑中处理
  4. 最佳实践

    • 避免在 <script setup> 中使用 onBackPress
    • 使用页面跳转参数或状态管理处理导航逻辑

================================================================================ 一百一十三、UTSJSONObject 不能直接用于 Supabase insert重要

  1. 问题UTSJSONObject 不能直接传给 Supabase 的 insert 方法

    • 错误信息:UTSJSONObject cannot be cast to io.dcloud.uts.Map
    • 原因Supabase insert 需要普通 JavaScript 对象,不是 UTSJSONObject
  2. 错误示例

    const itemJson = new UTSJSONObject()
    itemJson.set('order_id', orderId)
    itemJson.set('product_id', productId)
    // ...
    
    // 错误:直接传 UTSJSONObject
    const response = await supa
        .from('ml_order_items')
        .insert(itemJson)  // 错误!
        .execute()
    
  3. 正确解决方案:转换为普通对象

    const itemJson = new UTSJSONObject()
    itemJson.set('order_id', orderId)
    itemJson.set('product_id', productId)
    // ...
    
    // 正确:先转换为普通对象
    const itemObjStr = JSON.stringify(itemJson)
    const itemObj = JSON.parse(itemObjStr) as Record<string, any>
    
    const response = await supa
        .from('ml_order_items')
        .insert(itemObj)  // 正确
        .execute()
    
  4. 最佳实践

    • Supabase insert/update 操作必须使用普通对象
    • 使用 JSON.stringify + JSON.parse 转换 UTSJSONObject
    • 转换后使用 as Record<string, any> 类型
    • 重要JSON.parse 返回 Any?,必须先检查 null 再转换
  5. 完整示例

    // 错误JSON.parse 返回 Any?,不能直接 as
    const itemObj = JSON.parse(itemObjStr) as Record<string, any>
    
    // 正确:先检查 null
    const itemObjParsed = JSON.parse(itemObjStr)
    if (itemObjParsed == null) {
        console.error('转换失败')
        continue
    }
    const itemObj = itemObjParsed as Record<string, any>
    

================================================================================ 一百一十四、数组元素不能直接 as Record<string, any>(重要)

  1. 问题:从 Supabase 查询返回的数组元素不能直接转换为 Record<string, any>

    • 错误信息:UTSJSONObject cannot be cast to io.dcloud.uts.Map
    • 原因:数组元素是特殊的 UTS 对象,不能直接类型断言
  2. 错误示例

    const queryData = queryResponse.data as any
    if (Array.isArray(queryData) && queryData.length > 0) {
        // 错误:不能直接 as Record<string, any>
        const firstItem = queryData[0] as Record<string, any>
        const orderId = firstItem['id'] as string
    }
    
  3. 正确解决方案:使用 JSON 序列化转换

    const queryData = queryResponse.data as any
    if (Array.isArray(queryData) && queryData.length > 0) {
        const firstItemRaw = queryData[0]
    
        // 正确:使用 JSON 序列化转换
        const firstItemStr = JSON.stringify(firstItemRaw)
        const firstItemParsed = JSON.parse(firstItemStr)
        if (firstItemParsed == null) {
            console.error('解析失败')
            return null
        }
        const firstItem = firstItemParsed as UTSJSONObject
        const orderId = (firstItem.getString('id') ?? '') as string
    }
    
  4. 最佳实践

    • 从 Supabase 返回的数组元素必须先序列化再访问
    • 使用 UTSJSONObject 的 getString/getNumber 方法访问属性
    • 始终检查 JSON.parse 返回值是否为 null

================================================================================ 一百一十五、currentPage.options 不能直接类型转换(重要)

  1. 问题currentPage.options 不能直接 as Record<string, any>

    • 错误信息:OnLoadOptions cannot be cast to Map
    • 原因:页面选项是特殊的 UTS 类型,不能直接转换
  2. 错误示例

    import { onMounted } from 'vue'
    
    onMounted(() => {
        const pages = getCurrentPages()
        const currentPage = pages[pages.length - 1]
        // 错误:不能直接 as Record<string, any>
        const options = currentPage.options as Record<string, any>
        const orderId = options['orderId']
    })
    
  3. 正确解决方案:使用 onLoad 生命周期获取页面参数

    import { onLoad } from '@dcloudio/uni-app'
    
    // 正确:使用 onLoad 获取页面参数
    onLoad((options) => {
        if (options != null) {
            const orderIdValue = options['orderId']
            if (orderIdValue != null) {
                orderId.value = orderIdValue as string
            }
        }
    })
    
  4. 最佳实践

    • 使用 onLoad 生命周期获取页面参数,而不是 getCurrentPages()
    • onLoad 的 options 参数可以直接通过 key 访问
    • 始终检查 options 和参数值是否为 null

================================================================================ 一百一十六、uni.getStorageSync 返回值不能直接类型转换(重要)

  1. 问题uni.getStorageSync 返回的存储数据不能直接 as Record<string, any>

    • 错误信息:UTSJSONObject cannot be cast to io.dcloud.uts.Map
    • 原因:存储数据可能是特殊的 UTS 类型,不能直接转换
  2. 错误示例

    const userStore = uni.getStorageSync('userInfo')
    if (userStore != null) {
        // 错误:不能直接 as Record<string, any>
        const userObj = userStore as Record<string, any>
        const id = userObj['id']
    }
    
  3. 正确解决方案:使用 JSON 序列化转换

    const userStore = uni.getStorageSync('userInfo')
    if (userStore != null) {
        // 正确:使用 JSON 序列化转换
        const userStr = JSON.stringify(userStore)
        const userParsed = JSON.parse(userStr)
        if (userParsed != null) {
            const userObj = userParsed as UTSJSONObject
            const id = userObj.getString('id')
            if (id != null) {
                return id
            }
        }
    }
    
  4. 最佳实践

    • uni.getStorageSync 返回值必须先序列化再访问
    • 使用 UTSJSONObject 的 getString/getNumber 方法访问属性
    • 始终检查 JSON.parse 返回值是否为 null

================================================================================ 一百一十七、JSON.parse 空字符串会报错(重要)

  1. 问题JSON.parse 空字符串会抛出异常

    • 错误信息:JSON.parse error: input text is empty
    • 原因:空字符串不是有效的 JSON
  2. 错误示例

    const ordersStr = uni.getStorageSync('orders')
    if (ordersStr != null) {
        // 错误ordersStr 可能是空字符串 ''
        const parsed = JSON.parse(ordersStr as string)
    }
    
  3. 正确解决方案:检查空字符串

    const ordersStr = uni.getStorageSync('orders')
    if (ordersStr != null && ordersStr !== '') {
        const parsed = JSON.parse(ordersStr as string)
        // ...
    }
    
  4. 最佳实践

    • JSON.parse 前检查字符串是否为空
    • 使用 != null && !== '' 双重检查
    • uni.getStorageSync 可能返回空字符串

================================================================================ 一百一十八、自定义类型不能转换为 Record<string, any>(重要)

  1. 问题:自定义 type 类型不能直接转换为 Record<string, any>

    • 错误信息:OrderItem cannot be cast to io.dcloud.uts.Map
    • 原因UTS 中的自定义类型是独立的类,不是 Map
  2. 错误示例

    type OrderItem = {
        id: string,
        order_no: string,
        create_time: string
    }
    
    const orders: OrderItem[] = [...]
    
    // 错误:尝试将 OrderItem 转换为 Record
    orders.sort((a: any, b: any) => {
        const aObj = a as Record<string, any>  // 错误!
        const bObj = b as Record<string, any>  // 错误!
        return new Date(aObj['create_time']).getTime() - new Date(bObj['create_time']).getTime()
    })
    
  3. 正确解决方案:直接使用类型属性

    type OrderItem = {
        id: string,
        order_no: string,
        create_time: string
    }
    
    const orders: OrderItem[] = [...]
    
    // 正确:直接使用 OrderItem 类型访问属性
    orders.sort((a: OrderItem, b: OrderItem) => {
        return new Date(a.create_time).getTime() - new Date(b.create_time).getTime()
    })
    
  4. 最佳实践

    • 自定义类型数组排序时,直接使用类型声明参数
    • 不要尝试将自定义类型转换为 Record<string, any>
    • 使用点号访问属性,而不是方括号

================================================================================ 一百一十九、函数参数类型必须精确匹配(重要)

  1. 问题:函数参数类型必须与传入的类型精确匹配

    • 错误信息:参数类型不匹配:实际类型为 'UTSArray<OrderItem>',预期类型为 'UTSArray<Any>'
    • 原因UTS 是强类型语言,不支持隐式类型转换
  2. 错误示例

    type OrderItem = {
        id: string,
        status: number
    }
    
    // 错误:参数声明为 any[]
    const updateTabsCounts = (allOrders: any[]) => {
        const countPending = allOrders.filter((o: any) => {
            const item = o as OrderItem  // 多余的转换
            return item.status === 1
        }).length
    }
    
    const orders: OrderItem[] = [...]
    updateTabsCounts(orders)  // 错误!类型不匹配
    
  3. 正确解决方案:使用精确的类型声明

    type OrderItem = {
        id: string,
        status: number
    }
    
    // 正确:参数声明为 OrderItem[]
    const updateTabsCounts = (allOrders: OrderItem[]) => {
        const countPending = allOrders.filter((o: OrderItem) => o.status === 1).length
    }
    
    const orders: OrderItem[] = [...]
    updateTabsCounts(orders)  // 正确!类型匹配
    
  4. 最佳实践

    • 函数参数类型必须与实际传入类型一致
    • 避免使用 any[] 作为自定义类型数组的参数
    • 在 filter/map/sort 等回调中直接使用具体类型

================================================================================ 一百二十、Object.keys() 不支持 UTSJSONObject重要

  1. 问题Object.keys() 不能用于 UTSJSONObject

    • 错误信息:找不到名称"keys"
    • 原因UTSJSONObject 有自己的 keys() 方法
  2. 错误示例

    const obj = orderParsed as UTSJSONObject
    // 错误Object.keys() 不支持 UTSJSONObject
    const keys = Object.keys(obj)
    
  3. 正确解决方案:使用 UTSJSONObject.keys() 方法

    const obj = orderParsed as UTSJSONObject
    // 正确:使用 UTSJSONObject 的 keys() 方法
    const keys = obj.keys()
    
    for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        const value = obj.get(key)
        console.log(key, value)
    }
    
  4. 最佳实践

    • UTSJSONObject 使用 obj.keys() 获取键列表
    • 使用 obj.get(key) 获取值
    • 使用 obj.getString(key) / obj.getNumber(key) 获取特定类型的值

================================================================================ 一百二十一、Supabase 关联查询语法(重要)

  1. 问题:多行模板字符串可能导致关联查询失败

    • 现象:关联表数据返回 null
    • 原因Supabase JavaScript 客户端处理多行字符串时可能有问题
  2. 错误示例

    // 可能失败的多行语法
    .select(`
        *,
        ml_order_items (*)
    `)
    
  3. 正确解决方案:使用单行字符串

    // 正确:单行字符串
    .select('*, ml_order_items(*)')
    
  4. 最佳实践

    • Supabase 查询使用单行字符串
    • 关联查询格式:'*, related_table(*)'
    • 多个关联:'*, table1(*), table2(*)'

================================================================================ 一百二十二、UTSJSONObject.keys() 方法在 Android 端不可用(重要)

  1. 问题:UTSJSONObject.keys() 方法在 Android 端编译失败

    • 错误信息:找不到名称"keys"
    • 原因Android 端 UTSJSONObject 可能没有 keys() 方法
  2. 错误示例

    const specObj = objParsed as UTSJSONObject
    // 错误keys() 方法不可用
    const keys = specObj.keys()
    for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        // ...
    }
    
  3. 正确解决方案:直接获取已知字段或使用 JSON 字符串处理

    const specObj = objParsed as UTSJSONObject
    
    // 方案一:直接获取已知字段
    const colorVal = specObj.getString('Color')
    const sizeVal = specObj.getString('Size')
    const defaultVal = specObj.getString('默认')
    
    // 方案二:使用 JSON 字符串处理
    const specObjStr = JSON.stringify(specObj)
    const formatted = specObjStr.replace(/[{}"]/g, '').replace(/:/g, ': ').replace(/,/g, ' | ')
    
  4. 最佳实践

    • 避免使用 UTSJSONObject.keys() 方法
    • 如果知道字段名,直接使用 getString() 获取
    • 如果需要遍历,使用 JSON 字符串处理

================================================================================ 一百二十三、函数声明顺序必须正确(重要)

  1. 问题:使用 const 声明的函数在调用前必须已定义

    • 错误信息:找不到名称"xxx"
    • 原因:const 箭头函数在定义前无法被调用
  2. 错误示例

    // 错误formatSpecObj 在定义前被调用
    const parseSpecText = (specs: any): string => {
        return formatSpecObj(specs)  // 错误formatSpecObj 还未定义
    }
    
    const formatSpecObj = (obj: any): string => {
        return '...'
    }
    
  3. 正确解决方案:使用 function 声明或调整顺序

    // 方案一:使用 function 声明(推荐)
    function formatSpecObj(obj: any): string {
        return '...'
    }
    
    function parseSpecText(specs: any): string {
        return formatSpecObj(specs)  // 正确function 声明会提升
    }
    
    // 方案二:调整定义顺序
    const formatSpecObj = (obj: any): string => {
        return '...'
    }
    
    const parseSpecText = (specs: any): string => {
        return formatSpecObj(specs)  // 正确formatSpecObj 已定义
    }
    
  4. 最佳实践

    • 优先使用 function 声明,避免顺序问题
    • 如果使用 const 箭头函数,确保定义在被调用之前

================================================================================ 一百二十四、String() 构造函数不支持任意类型(重要)

  1. 问题:String(value) 不支持任意类型转换

    • 错误信息:None of the following candidates is applicable
    • 原因UTS 中 String() 只接受特定类型
  2. 错误示例

    const value: any = obj.get(key)
    // 错误String() 不接受 any 类型
    const str = String(value)
    
  3. 正确解决方案:使用类型判断

    const value: any = obj.get(key)
    let valueStr = ''
    if (typeof value === 'string') {
        valueStr = value
    } else if (typeof value === 'number') {
        valueStr = value.toString()
    } else {
        valueStr = JSON.stringify(value)
    }
    
  4. 最佳实践

    • 使用 typeof 判断类型后再转换
    • 数字使用 .toString() 方法
    • 对象使用 JSON.stringify()

================================================================================ 一百二十五、scroll-view 必须设置确定高度(重要)

  1. 问题scroll-view 无法滚动

    • 现象:页面内容超出屏幕但无法向下滚动
    • 原因scroll-view 没有确定的高度
  2. 错误示例

    .orders-content {
        flex: 1;
    }
    
  3. 正确解决方案:添加 height: 0 配合 flex: 1

    .orders-content {
        flex: 1;
        height: 0;  /* 重要:配合 flex: 1 使用 */
    }
    
  4. 最佳实践

    • scroll-view 必须有确定的高度
    • 使用 flex: 1; height: 0; 组合
    • 或者直接设置具体高度如 height: 500px

================================================================================ 一百二十六、Supabase 多表关联查询(重要)

  1. 问题:需要从关联表获取数据

    • 场景:订单表需要获取商家店铺名称
    • 前提:必须在 Supabase 中设置正确的外键关系
  2. 外键设置要求

    • 在 Supabase 控制台设置外键关系
    • 例如:ml_orders.merchant_id -> ml_merchants.id
    • 没有外键关系会导致 400 Bad Request 错误
  3. 示例:订单关联商家和订单项

    // 关联查询多个表(需要先设置外键)
    const response = await supa
        .from('ml_orders')
        .select('*, ml_order_items(*), ml_merchants(shop_name)')
        .eq('user_id', userId)
        .execute()
    
  4. 如果没有外键关系的替代方案

    // 方案一:使用默认值
    let shopName = '自营店铺'
    if (merchantId != null && merchantId != '') {
        shopName = '商家店铺'
    }
    
    // 方案二:单独查询商家表
    const merchantResponse = await supa
        .from('ml_merchants')
        .select('shop_name')
        .eq('id', merchantId)
        .single()
        .execute()
    
  5. 最佳实践

    • 关联查询前确保外键关系已设置
    • 使用单行字符串格式:'*, table1(*), table2(field1, field2)'
    • 如果关联查询失败,使用替代方案

================================================================================ 一百二十七、Android 特有 UI 与交互规范(最新补充)

  1. 图标兼容性Emoji 风险)

    • 现象:在 Android 真机上直接使用 Emoji🛒❤️)可能导致显示不全、模糊或样式不统一。
    • 规范:所有功能图标必须使用本地 PNG 图片
    • 路径:底部导航图标放在 static/tabbar/,业务小图标放在 pages/mall/consumer/icons/
    • TabBar 特别强调pages.json 中的 tabBar 图标路径严禁使用 .svg,必须使用 .png 以确保安卓稳定性。
  2. 布局高度计算修复

    • 场景Chat 页面或带有固定底部的详情页。
    • 规范scroll-view 的父级必须是 flex: column,且调用 scroll-view 时应配合 flex: 1; height: 0;
    • 双重滚动校准Android 初始渲染可能导致滚动不到底。
      setTimeout(() => { this.scrollTop = 99999 }, 200); // 首次快速定位
      setTimeout(() => { this.scrollTop = 99999 + Math.random() }, 500); // 二次精准校准
      
  3. 空状态展示(去行业化文案)

    • 规范:在数据加载中或为空时,严禁使用行业特定干扰词(如“暂无药品”)。
    • 方案:统一使用“正在加载商品...”或“该分类下暂无商品”,并辅以 loading-state 动画,提升加载感知。
  4. SKU 解析与展示规范

    • 规范:从数据库返回的 JSON 规格数据(如 sku_spec_attr)必须通过格式化函数转换为易读文本。
    • 示例[{"key":"规格","value":"10mg*7片"}] -> 规格: 10mg*7片
    • 技巧:在 uts 中处理 UTSJSONObject 时,推荐使用 getString("value") 进行容错处理。

================================================================================ 文档结束


最后更新2026-03-04