diff --git a/App.uvue b/App.uvue index f93b1651..d2dd69f3 100644 --- a/App.uvue +++ b/App.uvue @@ -1,13 +1,88 @@ diff --git a/pages.json b/pages.json index f1cc6064..fa41ce4d 100644 --- a/pages.json +++ b/pages.json @@ -87,19 +87,22 @@ "path": "pages/mall/consumer/messages", "style": { "navigationBarTitleText": "消息", + "navigationStyle": "custom", "enablePullDownRefresh": true } }, { "path": "pages/mall/consumer/cart", "style": { - "navigationBarTitleText": "购物车" + "navigationBarTitleText": "购物车", + "navigationStyle": "custom" } }, { "path": "pages/mall/consumer/profile", "style": { - "navigationBarTitleText": "我的" + "navigationBarTitleText": "我的", + "navigationStyle": "custom" } } ], @@ -911,32 +914,32 @@ { "pagePath": "pages/mall/consumer/index", "text": "首页", - "iconPath": "static/tabbar/home.png", - "selectedIconPath": "static/tabbar/home-active.png" + "iconPath": "static/tabbar/home.svg", + "selectedIconPath": "static/tabbar/home-active.svg" }, { "pagePath": "pages/mall/consumer/category", "text": "分类", - "iconPath": "static/tabbar/category.png", - "selectedIconPath": "static/tabbar/category-active.png" + "iconPath": "static/tabbar/category.svg", + "selectedIconPath": "static/tabbar/category-active.svg" }, { "pagePath": "pages/mall/consumer/messages", "text": "消息", - "iconPath": "static/tabbar/messages.png", - "selectedIconPath": "static/tabbar/messages-active.png" + "iconPath": "static/tabbar/messages.svg", + "selectedIconPath": "static/tabbar/messages-active.svg" }, { "pagePath": "pages/mall/consumer/cart", "text": "购物车", - "iconPath": "static/tabbar/cart.png", - "selectedIconPath": "static/tabbar/cart-active.png" + "iconPath": "static/tabbar/cart.svg", + "selectedIconPath": "static/tabbar/cart-active.svg" }, { "pagePath": "pages/mall/consumer/profile", "text": "我的", - "iconPath": "static/tabbar/profile.png", - "selectedIconPath": "static/tabbar/profile-active.png" + "iconPath": "static/tabbar/profile.svg", + "selectedIconPath": "static/tabbar/profile-active.svg" } ] }, diff --git a/pages/mall/consumer/address-list.uvue b/pages/mall/consumer/address-list.uvue index 4fcbd06c..11417c28 100644 --- a/pages/mall/consumer/address-list.uvue +++ b/pages/mall/consumer/address-list.uvue @@ -59,8 +59,10 @@ const loadAddresses = async () => { const supabaseAddresses = await supabaseService.getAddresses() // 转换数据格式以匹配前端界面 - const transformedAddresses = supabaseAddresses.map((item: SupabaseUserAddress): Address => { - return { + const transformedAddresses: Address[] = [] + for (let i = 0; i < supabaseAddresses.length; i++) { + const item = supabaseAddresses[i] + const addr: Address = { id: item.id, name: item.recipient_name, phone: item.phone, @@ -69,9 +71,10 @@ const loadAddresses = async () => { district: item.district, detail: item.detail_address, isDefault: item.is_default, - label: '' // Supabase表没有label字段 + label: '' } as Address - }) + transformedAddresses.push(addr) + } addresses.value = transformedAddresses diff --git a/pages/mall/consumer/cart.uvue b/pages/mall/consumer/cart.uvue index 17f4e715..7c86d44d 100644 --- a/pages/mall/consumer/cart.uvue +++ b/pages/mall/consumer/cart.uvue @@ -14,11 +14,17 @@ - - + + - + 🛒 @@ -726,28 +732,28 @@ const goToCheckout = () => { diff --git a/pages/mall/consumer/category copy.uvue b/pages/mall/consumer/category copy.uvue new file mode 100644 index 00000000..0aa14b02 --- /dev/null +++ b/pages/mall/consumer/category copy.uvue @@ -0,0 +1,1246 @@ + + + + + diff --git a/pages/mall/consumer/category.uvue b/pages/mall/consumer/category.uvue index c03ab4a1..1f964416 100644 --- a/pages/mall/consumer/category.uvue +++ b/pages/mall/consumer/category.uvue @@ -25,19 +25,24 @@ - - + + - + {{ item.icon }} {{ item.name }} @@ -46,7 +51,7 @@ {{ currentCategoryDesc }} + + + + + {{ sub.icon }} + {{ sub.name }} + + + + import { ref, onMounted } from 'vue' +import { onLoad, onShow } from '@dcloudio/uni-app' import supabaseService from '@/utils/supabaseService.uts' import type { Product } from '@/utils/supabaseService.uts' @@ -111,15 +132,20 @@ type LocalCategory = { // 响应式数据 const statusBarHeight = ref(0) -const headerHeight = ref(44) // 默认头部高度 +const headerHeight = ref(44) const primaryCategories = ref([]) +const subCategories = ref([]) // 二级分类列表 const productList = ref([]) const activePrimary = ref('') +const activeSubCategory = ref('') // 当前选中的二级分类 +const selectedParentId = ref('') // 当前选中的一级分类ID(用于高亮显示) const cartCount = ref(3) const hasMore = ref(true) -const hasLoadedFromParams = ref(false) // 标记是否已通过参数加载 +const hasLoadedFromParams = ref(false) const currentPage = ref(1) const loading = ref(false) +const scrollTop = ref(0) +const pendingCategoryId = ref('') // 待处理的分类ID(从其他页面跳转过来时暂存) // 获取当前分类信息 const currentCategoryName = ref('') @@ -147,7 +173,7 @@ async function loadProducts(): Promise { page: currentPage.value }) - if (currentPage.value === 1) { + if (currentPage.value == 1) { productList.value = response.data } else { productList.value.push(...response.data) @@ -155,75 +181,303 @@ async function loadProducts(): Promise { hasMore.value = response.hasmore - // 更新当前分类信息 - const category = primaryCategories.value.find((cat: LocalCategory): boolean => cat.id === activePrimary.value) - if (category != null) { - currentCategoryName.value = category.name - currentCategoryDesc.value = category.description + // 更新当前分类信息 - 先在一级分类中查找,再在二级分类中查找 + let foundCat: LocalCategory | null = null + for (let i = 0; i < primaryCategories.value.length; i++) { + if (primaryCategories.value[i].id == activePrimary.value) { + foundCat = primaryCategories.value[i] + break + } + } + if (foundCat == null) { + for (let i = 0; i < subCategories.value.length; i++) { + if (subCategories.value[i].id == activePrimary.value) { + foundCat = subCategories.value[i] + break + } + } + } + if (foundCat != null) { + currentCategoryName.value = foundCat.name + currentCategoryDesc.value = foundCat.description } console.log('商品列表加载完成,当前总数量:', productList.value.length) } catch (error) { console.error('加载商品数据失败:', error) - if (currentPage.value === 1) { + if (currentPage.value == 1) { productList.value = [] } } finally { - loading.value = false + loading.value = false } } +// 加载二级分类 +async function loadSubCategories(parentId: string): Promise { + console.log('加载二级分类,父级ID:', parentId) + try { + const subCats = await supabaseService.getSubCategories(parentId) + console.log('获取到二级分类数量:', subCats.length) + + const categories: LocalCategory[] = [] + for (let i = 0; i < subCats.length; i++) { + const cat = subCats[i] + categories.push({ + id: cat.id, + name: cat.name, + icon: cat.icon, + description: cat.description, + color: cat.color + }) + } + subCategories.value = categories + } catch (e) { + console.error('加载二级分类失败:', e) + subCategories.value = [] + } +} + +// 判断一级分类是否选中 +function isPrimaryActive(categoryId: string): boolean { + return selectedParentId.value == categoryId +} + +// 判断二级分类是否选中 +function isSubActive(subCategoryId: string): boolean { + return activeSubCategory.value == subCategoryId || activePrimary.value == subCategoryId +} + +// 获取一级分类的背景色 +function getPrimaryItemBgColor(item: LocalCategory): string { + if (isPrimaryActive(item.id)) { + return item.color + } + return 'transparent' +} + +// 选择二级分类 +async function selectSubCategory(subCategoryId: string): Promise { + console.log('选择二级分类:', subCategoryId) + activeSubCategory.value = subCategoryId + + // 使用二级分类ID加载商品 + currentPage.value = 1 + hasMore.value = true + activePrimary.value = subCategoryId // 临时设置为二级分类ID用于加载商品 + await loadProducts() +} + +// 选择一级分类 - 必须在 loadCategories 之前定义 +// originalCategoryId: 可能是一级分类ID,也可能是二级分类ID +async function selectPrimaryCategory(originalCategoryId: string): Promise { + console.log('=== selectPrimaryCategory函数开始执行 ===') + console.log('传入的categoryId:', originalCategoryId) + + if (originalCategoryId == '') { + console.error('categoryId为空,尝试使用第一个分类') + if (primaryCategories.value.length > 0) { + originalCategoryId = primaryCategories.value[0].id + } else { + console.error('没有可用的分类') + return + } + } + + // 检查传入的是否是一级分类ID + let targetParentId = originalCategoryId + let targetSubId = '' + console.log('当前一级分类列表长度:', primaryCategories.value.length) + let foundInPrimary: LocalCategory | null = null + for (let i = 0; i < primaryCategories.value.length; i++) { + if (primaryCategories.value[i].id == originalCategoryId) { + foundInPrimary = primaryCategories.value[i] + break + } + } + console.log('在一级分类中查找结果:', foundInPrimary != null) + + if (foundInPrimary == null) { + // 传入的可能是二级分类ID,需要查找其父级分类 + console.log('传入的ID不在一级分类中,可能是二级分类ID,尝试查找父级分类') + + // 从服务器获取分类信息以确定父级 + try { + const categoryInfo = await supabaseService.getCategoryById(originalCategoryId) + if (categoryInfo != null && categoryInfo.parent_id != null && categoryInfo.parent_id != '') { + console.log('找到父级分类ID:', categoryInfo.parent_id) + + // 检查父级分类ID是否在一级分类列表中 + console.log('查找父级分类ID:', categoryInfo.parent_id) + let parentInPrimary: LocalCategory | null = null + for (let i = 0; i < primaryCategories.value.length; i++) { + if (primaryCategories.value[i].id == categoryInfo.parent_id) { + parentInPrimary = primaryCategories.value[i] + break + } + } + console.log('父级分类查找结果:', parentInPrimary != null) + if (parentInPrimary != null) { + console.log('父级分类在列表中找到:', parentInPrimary.name) + targetParentId = categoryInfo.parent_id! + targetSubId = originalCategoryId // 记住要选中的二级分类 + } else { + console.log('父级分类不在列表中,使用第一个分类') + // 打印当前列表中的所有分类ID + for (let i = 0; i < primaryCategories.value.length; i++) { + console.log('列表中的分类:', primaryCategories.value[i].id, primaryCategories.value[i].name) + } + if (primaryCategories.value.length > 0) { + targetParentId = primaryCategories.value[0].id + } + } + } else { + console.log('未找到父级分类,使用第一个分类') + if (primaryCategories.value.length > 0) { + targetParentId = primaryCategories.value[0].id + } + } + } catch (e) { + console.error('获取分类信息失败:', e) + if (primaryCategories.value.length > 0) { + targetParentId = primaryCategories.value[0].id + } + } + } + + console.log('最终选中的一级分类ID:', targetParentId) + console.log('需要选中的二级分类ID:', targetSubId) + + // 设置一级分类高亮 + selectedParentId.value = targetParentId + activePrimary.value = targetParentId + + // 加载二级分类 + await loadSubCategories(targetParentId) + + // 如果有要选中的二级分类 + if (targetSubId != '') { + activeSubCategory.value = targetSubId + } else { + // 如果没有指定二级分类,但有二级分类列表,默认选中第一个 + if (subCategories.value.length > 0) { + activeSubCategory.value = subCategories.value[0].id + targetSubId = subCategories.value[0].id + console.log('默认选中第一个二级分类:', subCategories.value[0].name) + } else { + activeSubCategory.value = '' + } + } + + // 自动滚动到选中位置 + let foundIndex = -1 + for (let i = 0; i < primaryCategories.value.length; i++) { + if (primaryCategories.value[i].id == targetParentId) { + foundIndex = i + break + } + } + if (foundIndex != -1) { + // 获取系统信息 + const systemInfo = uni.getSystemInfoSync() + + let itemHeight = 70 + if (systemInfo.windowWidth > 1025) { + itemHeight = 80 + } + + const scrollViewHeight = systemInfo.windowHeight - systemInfo.statusBarHeight - 44 + const targetScrollTop = (foundIndex * itemHeight) - (scrollViewHeight / 2) + (itemHeight / 2) + scrollTop.value = Math.max(0, targetScrollTop) + console.log(`滚动左侧菜单: index=${foundIndex}, target=${scrollTop.value}`) + } + + // 查找分类信息 + let foundCategory: LocalCategory | null = null + for (let i = 0; i < primaryCategories.value.length; i++) { + if (primaryCategories.value[i].id == targetParentId) { + foundCategory = primaryCategories.value[i] + break + } + } + if (foundCategory != null) { + currentCategoryName.value = foundCategory.name + currentCategoryDesc.value = foundCategory.description + } else { + console.log('分类信息未找到,使用第一个分类的信息') + if (primaryCategories.value.length > 0) { + const firstCategory = primaryCategories.value[0] + currentCategoryName.value = firstCategory.name + currentCategoryDesc.value = firstCategory.description + } + } + + currentPage.value = 1 + hasMore.value = true + + // 如果有选中的二级分类,使用二级分类ID加载商品;否则使用一级分类ID + const categoryIdForProducts = (targetSubId != '') ? targetSubId : targetParentId + activePrimary.value = categoryIdForProducts // 临时设置为要加载的分类ID + await loadProducts() +} + async function loadCategories(): Promise { try { - const categoriesData = await supabaseService.getCategories() - console.log('加载分类数据成功,数量:', categoriesData.length) + // 只获取一级分类(parent_id 为 null 的分类) + const categoriesData = await supabaseService.getParentCategories() + console.log('加载一级分类数据成功,数量:', categoriesData.length) // 映射数据并添加默认颜色,防止选中时背景透明导致文字看不清 // 过滤掉医药健康相关分类 const categories: LocalCategory[] = [] - const rawList = categoriesData as any[] - for (let i = 0; i < rawList.length; i++) { - const raw = rawList[i] - const catObj = (raw instanceof UTSJSONObject) ? (raw as UTSJSONObject) : (JSON.parse(JSON.stringify(raw)) as UTSJSONObject) - const name = catObj.getString('name') ?? '' + for (let i = 0; i < categoriesData.length; i++) { + const cat = categoriesData[i] + const name = cat.name + console.log('一级分类:', cat.id, name) if (name.includes('医药') || name.includes('健康')) { + console.log('过滤掉分类:', name) continue } - const id = catObj.getString('id') ?? '' - const description = catObj.getString('description') ?? '' - const icon = catObj.getString('icon') ?? catObj.getString('icon_url') ?? '📦' - const color = catObj.getString('color') ?? '#4CAF50' categories.push({ - id, - name, - icon, - description, - color + id: cat.id, + name: cat.name, + icon: cat.icon, + description: cat.description, + color: cat.color }) } + + console.log('最终一级分类列表数量:', categories.length) if (categories.length > 0) { primaryCategories.value = categories - // 如果没有通过参数设置分类,则设置默认选中一个分类 - if (activePrimary.value == '') { - // 优先查找"厨具"相关的分类作为默认 - const defaultCategory = categories.find((c: LocalCategory): boolean => c.name.includes('厨具')) ?? categories[0] - - activePrimary.value = defaultCategory.id - console.log('设置默认分类为:', defaultCategory.name, 'ID:', defaultCategory.id) - currentCategoryName.value = defaultCategory.name - currentCategoryDesc.value = defaultCategory.description - } else { - // 如果已经选中了分类(可能来自Storage),更新显示信息 - const current = categories.find((c: LocalCategory): boolean => c.id == activePrimary.value) - if (current != null) { - currentCategoryName.value = current.name - currentCategoryDesc.value = current.description - // 如果此时没有商品列表(且没有正在加载),可能需要加载 - if (productList.value.length === 0 && !loading.value) { - loadProducts() - } - } + + // 检查是否有待处理的分类ID(从其他页面跳转过来时暂存) + if (pendingCategoryId.value != '') { + console.log('发现待处理的分类ID:', pendingCategoryId.value) + // 直接调用 selectPrimaryCategory,它会处理一级或二级分类ID + const idToSelect = pendingCategoryId.value + pendingCategoryId.value = '' // 清除暂存 + selectPrimaryCategory(idToSelect) + return + } + + // 检查是否有预设的分类ID + if (activePrimary.value != '') { + console.log('有预设的分类ID:', activePrimary.value) + const target = categories.find((c: LocalCategory): boolean => c.id == activePrimary.value) + if (target != null) { + console.log('找到目标分类,执行选中:', target.name) + selectPrimaryCategory(activePrimary.value) + return + } + } + + // 默认选中第一个分类或"厨具"分类 + const defaultCategory = categories.find((c: LocalCategory): boolean => c.name.includes('厨具')) ?? categories[0] + if (defaultCategory != null) { + console.log('设置默认分类:', defaultCategory.name) + selectPrimaryCategory(defaultCategory.id) } } else { console.warn('从Supabase获取的分类数据为空') @@ -241,71 +495,6 @@ function loadMore(): void { } } -// 选择一级分类 -async function selectPrimaryCategory(categoryId: string): Promise { - console.log('=== selectPrimaryCategory函数开始执行 ===') - console.log('传入的categoryId:', categoryId) - console.log('当前时间:', Date.now()) - - // 验证categoryId是否有效 - if (categoryId == '') { - console.error('categoryId为空,尝试使用第一个分类') - if (primaryCategories.value.length > 0) { - categoryId = primaryCategories.value[0].id - } else { - console.error('没有可用的分类') - return - } - } - - console.log('验证后的categoryId:', categoryId) - console.log('当前activePrimary的值:', activePrimary.value) - - // 更新活动分类 - activePrimary.value = categoryId - console.log('更新后的activePrimary:', activePrimary.value) - - // 更新当前分类信息 - const category = primaryCategories.value.find((cat: LocalCategory): boolean => cat.id === categoryId) - if (category != null) { - currentCategoryName.value = category.name - currentCategoryDesc.value = category.description - console.log('✅ 找到分类:', category.name, '描述:', category.description) - } else { - console.error('❌ 未找到分类ID:', categoryId, ',使用第一个分类') - // 如果找不到对应的分类,使用第一个分类 - if (primaryCategories.value.length > 0) { - const firstCategory = primaryCategories.value[0] - currentCategoryName.value = firstCategory.name - currentCategoryDesc.value = firstCategory.description - activePrimary.value = firstCategory.id - categoryId = firstCategory.id - console.log('使用默认分类:', firstCategory.name) - } - } - - console.log('准备加载商品数据...') - - // 重置分页并加载 - currentPage.value = 1 - hasMore.value = true - await loadProducts() - - console.log('✅ 加载商品数据成功') - console.log('分类:', categoryId) - console.log('商品数量:', productList.value.length) - console.log('商品列表:', productList.value) - - // 验证数据是否已正确更新 - console.log('数据更新验证:') - console.log('activePrimary:', activePrimary.value) - console.log('currentCategoryName:', currentCategoryName.value) - console.log('currentCategoryDesc:', currentCategoryDesc.value) - console.log('productList长度:', productList.value.length) - - console.log('=== selectPrimaryCategory函数执行完成 ===') -} - // 生命周期 onMounted(() => { loadCategories().then(() => { @@ -317,12 +506,42 @@ onMounted(() => { }) }) -// 页面加载时处理参数 - 这是处理分类切换的主要入口 +// 页面显示时检查是否有参数传递过来 +onShow(() => { + console.log('=== category页面onShow被调用 ===') + + // 检查是否有存储的分类选择 + const savedCategoryId = uni.getStorageSync('selectedCategory') + console.log('onShow检查Storage:', savedCategoryId) + + if (savedCategoryId != null && savedCategoryId != '') { + const targetId = savedCategoryId as string + console.log('onShow发现存储的分类ID:', targetId) + + // 清除存储,避免下次进入默认选中 + uni.removeStorageSync('selectedCategory') + + // 确保分类数据已加载 + if (primaryCategories.value.length > 0) { + // 如果当前未选中或选中的不是目标分类,则切换 + if (activePrimary.value != targetId) { + console.log('onShow执行切换分类:', targetId) + selectPrimaryCategory(targetId) + } else { + console.log('当前已是目标分类:', targetId) + } + } else { + // 如果分类数据未加载,暂存ID,等待loadCategories完成后处理 + console.log('分类数据尚未加载,暂存ID等待加载') + pendingCategoryId.value = targetId + } + } +}) + // 页面加载时处理参数 - 这是处理分类切换的主要入口 onLoad((options: any) => { - console.log('=== category页面onLoad被调用 ===') - console.log('页面加载时间:', Date.now()) - console.log('传入的options参数:', options) - console.log('当前活动分类:', activePrimary.value) + const systemInfo = uni.getSystemInfoSync() + statusBarHeight.value = systemInfo.statusBarHeight + console.log('=== category页面onLoad被调用 ===') let categoryId = '' let categoryName = '' @@ -381,72 +600,6 @@ onLoad((options: any) => { console.log('=== category页面onLoad执行完成 ===') }) -// 页面显示时也检查参数,确保从其他页面返回时能正确显示 -onShow(() => { - console.log('=== category页面onShow被调用 ===') - console.log('页面显示时间:', Date.now()) - console.log('当前活动分类:', activePrimary.value) - - // 1. 优先检查 Storage 中的参数 (由首页传入) - const storageCategoryId = (uni.getStorageSync('selectedCategory') as string) ?? '' - if (storageCategoryId !== '') { - console.log('✅ onShow中找到Storage分类参数:', storageCategoryId) - hasLoadedFromParams.value = true - // 清除Storage,防止下次误读 - uni.removeStorageSync('selectedCategory') - - if (activePrimary.value !== storageCategoryId) { - selectPrimaryCategory(storageCategoryId) - } - // 如果分类还没加载完,这里设置了ID,等loadCategories完成后会自动匹配信息 - return - } - - // 在onShow中,我们也需要检查是否有新的参数 - // 因为当从主页再次点击分类跳转过来时,可能不会触发onLoad - // 而是触发onShow - - // 获取当前页面实例和参数 - const pages = getCurrentPages() - if (pages.length > 0) { - const currentPage = pages[pages.length - 1] - const rawPageOptions = currentPage.options ?? {} - console.log('onShow中获取参数:', rawPageOptions) - const pageOptObj = (rawPageOptions instanceof UTSJSONObject) ? (rawPageOptions as UTSJSONObject) : (JSON.parse(JSON.stringify(rawPageOptions)) as UTSJSONObject) - - // 检查是否有分类参数 - const pageCategoryId = pageOptObj.getString('categoryId') ?? '' - if (pageCategoryId !== '') { - hasLoadedFromParams.value = true - const categoryId = pageCategoryId - const categoryName = pageOptObj.getString('name') ?? '' - - console.log('✅ onShow中找到分类参数:', categoryId, categoryName) - console.log('URL中的时间戳参数:', pageOptObj.getString('timestamp') ?? '') - console.log('URL中的随机参数:', pageOptObj.getString('random') ?? '') - - // 检查是否需要更新分类 - if (activePrimary.value !== categoryId) { - console.log('当前分类:', activePrimary.value, '与目标分类:', categoryId, '不同,需要更新') - console.log('准备调用selectPrimaryCategory函数...') - selectPrimaryCategory(categoryId) - } else { - console.log('当前分类已经是目标分类,但可能用户想要刷新页面') - console.log('当前分类:', activePrimary.value, '目标分类:', categoryId) - // 即使分类相同,也重新加载数据,确保数据是最新的 - // 添加一个小的延迟,确保页面完全显示后再更新数据 - setTimeout(() => { - selectPrimaryCategory(categoryId) - }, 100) - } - } else { - console.log('⚠️ onShow中未找到分类参数') - } - } - - console.log('=== category页面onShow执行完成 ===') -}) - // 添加到购物车 async function addToCart(product: Product): Promise { @@ -574,7 +727,7 @@ function onScan(): void { top: 0; left: 0; right: 0; - background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%); + background-color: #4CAF50; z-index: 1000; box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15); } @@ -714,32 +867,31 @@ function onScan(): void { /* 左侧一级分类 */ .primary-category { - width: 120px; - height: 100%; /* 占满父容器高度 */ - margin-right: 20px; /* gap replacement */ + width: 63px; + height: 100%; + margin-right: 6px; background: white; - border-radius: 12px; - padding: 12px 0; + border-radius: 8px; + padding: 6px 0; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); flex-shrink: 0; } .primary-item { display: flex; - flex-direction: column; /* 图标和文字垂直排列 */ + flex-direction: column; align-items: center; justify-content: center; - padding: 12px 8px; - margin: 4px 8px; + padding: 12px 4px; + margin: 4px 6px; border-radius: 8px; - /* cursor: pointer; removed for uniapp-x support */ transition: all 0.2s ease; color: #666; text-align: center; } .primary-item:hover { - transform: translateY(-2px); /* 悬停时向上浮动 */ + transform: translateY(-2px); } .primary-item.active { @@ -748,24 +900,22 @@ function onScan(): void { } .primary-icon { - font-size: 24px; - margin-bottom: 6px; - margin-right: 0; /* 移除右边距 */ + font-size: 20px; + margin-bottom: 4px; + margin-right: 0; text-align: center; - /* display: block; removed for uniapp-x support */ } .primary-name { - font-size: 13px; - line-height: 1.4; - /* display: block; removed for uniapp-x support */ + font-size: 11px; + line-height: 1.25; } /* 右侧内容区 */ .product-content { flex: 1; - height: 100%; /* 占满父容器高度 */ - padding: 0; /* 移除内边距,交给内部元素 */ + height: 100%; + padding: 0; } .category-header { @@ -789,6 +939,56 @@ function onScan(): void { color: #666; } +/* 二级分类 */ +.sub-category-section { + padding: 8px 8px; + background: white; + margin-bottom: 8px; +} + +.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; +} + +.sub-category-item.active { + background: #4CAF50; +} + +.sub-category-item.active .sub-category-name { + color: white; +} + +.sub-category-icon { + font-size: 16px; + margin-bottom: 4px; +} + +.sub-category-name { + font-size: 11px; + color: #333; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + width: 100%; +} + /* 商品网格 */ .product-grid { display: flex; @@ -954,52 +1154,47 @@ function onScan(): void { } .category-content { - padding: 0 8px; + padding: 0 4px; } .primary-category { - width: 80px; /* 减小宽度 */ - /* display: flex; 移除flex布局,保持默认 */ - /* flex-wrap: wrap; 移除换行 */ - padding: 8px 0; - margin-right: 10px; /* Gap replacement */ + width: 56px; + padding: 4px 0; + margin-right: 4px; + border-radius: 8px; } .primary-item { - /* width: calc(25% - 8px); 移除百分比宽度 */ - width: auto; /* 恢复自动宽度 */ - margin: 4px; - padding: 8px 4px; - /* text-align: center; 已经在通用样式中设置 */ + margin: 2px 2px; + padding: 4px 2px; + border-radius: 6px; } .primary-icon { margin-right: 0; - margin-bottom: 4px; - font-size: 20px; + margin-bottom: 2px; + font-size: 16px; } .primary-name { - font-size: 11px; + font-size: 9px; + line-height: 1.1; } .product-grid { - /* grid-template-columns: repeat(2, 1fr); REMOVED */ - /* gap: 8px; REMOVED */ - padding: 0 4px 20px 4px; /* 增加底部内边距 */ + padding: 0 4px 20px 4px; } .product-card { - width: 48%; /* 2 columns for mobile */ + width: 48%; margin: 1%; } - /* 手机端商品卡片极简模式 - 仿照主页样式 */ .product-spec, .manufacturer, .original-price, .sales-info, - .product-badge { /* 分类页也隐藏角标,保持整洁 */ + .product-badge { display: none; } @@ -1008,7 +1203,7 @@ function onScan(): void { } .product-image { - height: 100px; /* 由于分类页右侧空间更窄,图片高度设得更小一点 */ + height: 100px; } .product-name { @@ -1018,10 +1213,7 @@ function onScan(): void { margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; - /* display: -webkit-box; REMOVED for support */ - /* -webkit-line-clamp: 2; REMOVED for support */ - /* -webkit-box-orient: vertical; REMOVED for support */ - lines: 2; /* UTS text truncation */ + lines: 2; } .price-section { @@ -1040,30 +1232,52 @@ function onScan(): void { } .product-meta { - display: none; /* 隐藏整个元数据行 */ - } - - .search-container { - padding: 0 12px; - height: 44px; - } - - .search-box { - padding: 8px 16px; + display: none; } .category-header { - padding: 12px 4px 0 4px; + padding: 8px 4px 0 4px; + } + + .category-title { + font-size: 14px; + } + + .category-desc { + font-size: 11px; } } -/* 中屏手机/小平板 (415px-768px) */ +/* 中屏手机 (415px-768px) */ @media screen and (min-width: 415px) and (max-width: 768px) { .search-container { padding: 0 16px; height: 44px; } + .primary-category { + width: 62px; + padding: 6px 0; + margin-right: 6px; + border-radius: 8px; + } + + .primary-item { + margin: 2px 2px; + padding: 5px 2px; + border-radius: 6px; + } + + .primary-icon { + font-size: 18px; + margin-bottom: 3px; + } + + .primary-name { + font-size: 10px; + line-height: 1.1; + } + .product-card { width: 46%; margin: 2%; @@ -1077,6 +1291,26 @@ function onScan(): void { height: 44px; } + .primary-category { + width: 100px; + padding: 10px 0; + margin-right: 16px; + } + + .primary-item { + margin: 4px 4px; + padding: 10px 6px; + } + + .primary-icon { + font-size: 22px; + margin-bottom: 5px; + } + + .primary-name { + font-size: 12px; + } + .product-card { width: 30%; margin: 1.5%; @@ -1097,7 +1331,7 @@ function onScan(): void { .primary-category { width: 160px; padding: 16px 0; - margin-right: 30px; /* Gap replacement */ + margin-right: 30px; } .primary-item { @@ -1132,14 +1366,9 @@ function onScan(): void { font-size: 15px; } - .product-grid { - /* grid-template-columns: repeat(4, 1fr); REMOVED */ - /* gap: 24px; REMOVED */ - } - .product-card { border-radius: 14px; - width: 22%; /* 4 columns */ + width: 22%; margin: 1.5%; } @@ -1164,12 +1393,11 @@ function onScan(): void { @media screen and (min-width: 1400px) { .category-content { max-width: 1600px; - /* gap: 40px; REMOVED */ padding: 0 32px; } .primary-category { - margin-right: 40px; /* Gap replacement */ + margin-right: 40px; } .primary-category { @@ -1209,14 +1437,9 @@ function onScan(): void { font-size: 16px; } - .product-grid { - /* grid-template-columns: repeat(5, 1fr); REMOVED */ - /* gap: 28px; REMOVED */ - } - .product-card { border-radius: 16px; - width: 17%; /* 5 columns */ + width: 17%; margin: 1.5%; } diff --git a/pages/mall/consumer/chat.uvue b/pages/mall/consumer/chat.uvue index cff3cc15..6b551cc5 100644 --- a/pages/mall/consumer/chat.uvue +++ b/pages/mall/consumer/chat.uvue @@ -165,6 +165,8 @@ function getCurrentTime(): string { function setupRealtimeSubscription(): void { console.log('开始建立聊天实时订阅...') + console.log('当前用户ID:', currentUserId.value, '商家ID:', merchantId.value) + const filter = ({ event: 'INSERT', schema: 'public', @@ -173,41 +175,80 @@ function setupRealtimeSubscription(): void { realtimeChannel = supa.channel('public:ml_chat_messages') .on('postgres_changes', filter, (payload: any) => { + console.log('=== 收到实时订阅回调 ===') const payloadObj = (payload instanceof UTSJSONObject) ? (payload as UTSJSONObject) : (JSON.parse(JSON.stringify(payload ?? {})) as UTSJSONObject) const newMsgAny = payloadObj.get('new') - if (newMsgAny == null) return + if (newMsgAny == null) { + console.log('newMsgAny 为空,跳过') + return + } const newMsg = (newMsgAny instanceof UTSJSONObject) ? (newMsgAny as UTSJSONObject) : (JSON.parse(JSON.stringify(newMsgAny)) as UTSJSONObject) console.log('收到新消息:', newMsg) const senderId = newMsg.getString('sender_id') ?? '' const receiverId = newMsg.getString('receiver_id') ?? '' + const msgId = newMsg.getString('id') ?? '' + const content = newMsg.getString('content') ?? '' + + console.log('=== 消息详情 ===') + console.log('消息ID:', msgId) + console.log('发送者ID:', senderId) + console.log('接收者ID:', receiverId) + console.log('当前用户ID:', currentUserId.value) + console.log('商家ID:', merchantId.value) + console.log('消息内容:', content) - if (senderId === currentUserId.value) { + // 检查消息是否已经在列表中(避免重复) + for (let i = 0; i < messages.value.length; i++) { + if (messages.value[i].id == msgId) { + console.log('消息已存在,跳过') + return + } + } + + // 判断消息类型 + const isMyMessage = (senderId == currentUserId.value) + const isForMe = (receiverId == currentUserId.value) + const isRelatedToCurrentChat = (senderId == merchantId.value || receiverId == merchantId.value) + + console.log('=== 条件判断 ===') + console.log('isMyMessage:', isMyMessage) + console.log('isForMe:', isForMe) + console.log('isRelatedToCurrentChat:', isRelatedToCurrentChat) + + // 如果消息与当前聊天无关,跳过 + if (!isRelatedToCurrentChat) { + console.log('消息与当前聊天无关,跳过') return } - if (receiverId === currentUserId.value) { - if (merchantId.value != '' && senderId !== merchantId.value) { - return - } - + // 如果是自己发送的消息,或者是发给自己的消息,都显示 + if (isMyMessage || isForMe) { const createdAt = newMsg.getString('created_at') ?? new Date().toISOString() const date = new Date(createdAt) const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` const incomingMsg: UiChatMessage = { - id: newMsg.getString('id') ?? Date.now().toString(), - type: 'received', - content: newMsg.getString('content') ?? '', + id: msgId, + type: isMyMessage ? 'sent' : 'received', + content: content, time: timeStr } + console.log('=== 添加新消息到列表 ===') + console.log('消息类型:', incomingMsg.type) + console.log('消息内容:', incomingMsg.content) messages.value.push(incomingMsg) scrollToBottom() + } else { + console.log('条件不满足,不添加消息') } }) .subscribe((status: string, err: any | null) => { console.log('订阅状态:', status) + if (err != null) { + console.log('订阅错误:', err) + } }) } @@ -221,21 +262,27 @@ async function loadChatHistory(): Promise { return } - messages.value = rawMsgs.reverse().map((m: ChatMessage): UiChatMessage => { + // 使用 for 循环替代 map + const uiMessages: UiChatMessage[] = [] + for (let i = rawMsgs.length - 1; i >= 0; i--) { + const m = rawMsgs[i] const date = new Date(m.created_at ?? new Date().toISOString()) const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` const sender = m.sender_id ?? '' const msgType = (currentUserId.value != '' && sender == currentUserId.value) ? 'sent' : 'received' const rawId = (m.id ?? '').toString() - const msgId = rawId !== '' ? rawId : Date.now().toString() - return { + const msgId = rawId != '' ? rawId : Date.now().toString() + i.toString() + + const uiMsg: UiChatMessage = { id: msgId, type: msgType, content: m.content ?? '', time: timeStr } - }) + uiMessages.push(uiMsg) + } + messages.value = uiMessages setTimeout(() => { scrollToBottom() @@ -288,22 +335,13 @@ const sendMessage = async () => { const content = inputMessage.value.trim() if (content == '') return - // 添加发送的消息 (乐观更新) - const newMessage: UiChatMessage = { - id: Date.now().toString(), - type: 'sent', - content: content, - time: getCurrentTime() - } - - messages.value.push(newMessage) + // 清空输入框 inputMessage.value = '' - // 滚动到底部 - scrollToBottom() - // 发送到 Supabase if (merchantId.value != '') { + // 不使用乐观更新,等待实时订阅推送 + // 这样可以确保多端同步 const success = await supabaseService.sendMessage(merchantId.value, content) if (!success) { uni.showToast({ diff --git a/pages/mall/consumer/coupons.uvue b/pages/mall/consumer/coupons.uvue index 8cd67c72..c08112d7 100644 --- a/pages/mall/consumer/coupons.uvue +++ b/pages/mall/consumer/coupons.uvue @@ -39,18 +39,22 @@ const loadCoupons = async () => { uni.showLoading({ title: '加载中...' }) try { const userCoupons = await supabaseService.getUserCoupons(1) - coupons.value = userCoupons.map((item: UserCoupon): Coupon => { + const couponList: Coupon[] = [] + for (let i = 0; i < userCoupons.length; i++) { + const item = userCoupons[i] const amountVal = item.amount ?? 0 const expiryVal = (item.expire_at != null && item.expire_at !== '') ? item.expire_at.substring(0, 10) : '长期有效' - return { + const coupon: Coupon = { id: item.id, title: (item.template_name != null && item.template_name !== '') ? item.template_name : '优惠券', amount: `¥${amountVal}`, expiry: expiryVal } as Coupon - }) + couponList.push(coupon) + } + coupons.value = couponList } catch (e) { console.error('加载优惠券失败', e) coupons.value = [] diff --git a/pages/mall/consumer/doc/SMART_RECOMMENDATION.md b/pages/mall/consumer/doc/SMART_RECOMMENDATION.md new file mode 100644 index 00000000..3bb2d713 --- /dev/null +++ b/pages/mall/consumer/doc/SMART_RECOMMENDATION.md @@ -0,0 +1,235 @@ +# 智能推荐系统文档 + +## 一、系统概述 + +智能推荐系统基于用户行为数据,为用户提供个性化的商品推荐。系统综合分析用户的搜索历史、浏览历史,结合全站热销商品数据,生成智能推荐列表。 + +## 二、推荐逻辑架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 智能推荐系统 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 用户搜索历史 │ │ 用户浏览历史 │ │ 全站热销商品 │ │ +│ │ (权重高) │ │ (权重中) │ │ (权重低) │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 关键词匹配 │ │ 分类匹配 │ │ 销量排序 │ │ +│ │ 相关商品 │ │ 相似商品 │ │ 热门商品 │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ │ +│ └────────────────┼────────────────┘ │ +│ ▼ │ +│ ┌─────────────────────┐ │ +│ │ 去重 & 合并结果 │ │ +│ └──────────┬──────────┘ │ +│ ▼ │ +│ ┌─────────────────────┐ │ +│ │ 返回推荐商品列表 │ │ +│ └─────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 三、权重分配策略 + +### 3.1 数据源权重 + +| 数据源 | 权重 | 说明 | +|--------|------|------| +| 用户搜索历史 | 50% | 用户主动搜索的关键词最能反映购买意向 | +| 用户浏览历史 | 30% | 用户浏览过的商品分类反映兴趣偏好 | +| 热销商品 | 20% | 全站热销商品作为兜底和补充 | + +### 3.2 推荐优先级 + +``` +优先级 1: 用户搜索历史匹配的商品(最多占推荐列表的50%) +优先级 2: 用户浏览过的分类下的商品(最多占推荐列表的30%) +优先级 3: 全站热销商品(填充剩余位置) +``` + +## 四、核心算法详解 + +### 4.1 智能推荐主流程 + +```typescript +async getSmartRecommendations(limit: number): Promise { + const products: Product[] = [] + const addedIds = new Set() // 用于去重 + + // 步骤1: 根据用户搜索历史推荐(权重最高) + const searchHistory = await getUserSearchHistory(5) + if (searchHistory.length > 0) { + const keywordProducts = await searchProductsByKeywords(searchHistory, limit) + // 添加到结果列表,去重 + } + + // 步骤2: 根据用户浏览历史推荐(权重中) + if (products.length < limit) { + const browseCategories = await getUserBrowseCategories(3) + if (browseCategories.length > 0) { + const categoryProducts = await getProductsByCategories(browseCategories, limit - products.length) + // 添加到结果列表,去重 + } + } + + // 步骤3: 补充热销商品(权重低) + if (products.length < limit) { + const hotProducts = await getHotProducts(limit - products.length + 5) + // 添加到结果列表,去重 + } + + return products.slice(0, limit) +} +``` + +### 4.2 搜索历史匹配算法 + +```typescript +// 根据用户搜索关键词匹配商品 +async searchProductsByKeywords(keywords: string[], limit: number): Promise { + // 1. 获取商品数据 + // 2. 遍历商品,检查名称和描述是否包含关键词 + // 3. 匹配成功的商品加入推荐列表 + + for (product in products) { + for (keyword in keywords) { + if (product.name.contains(keyword) || product.description.contains(keyword)) { + matched = true + break + } + } + } +} +``` + +### 4.3 浏览历史分类匹配算法 + +```typescript +// 根据用户浏览过的商品分类推荐 +async getProductsByCategories(categoryIds: string[], limit: number): Promise { + // 1. 获取用户浏览过的商品分类ID列表 + // 2. 查询这些分类下的商品 + // 3. 按销量排序返回 +} +``` + +## 五、热搜词系统 + +### 5.1 热搜词计算 + +```typescript +async getHotKeywords(limit: number): Promise { + // 1. 获取最近100条搜索记录 + // 2. 统计每个关键词的出现频率 + // 3. 按频率降序排序 + // 4. 返回前N个高频关键词 +} +``` + +### 5.2 热搜词展示规则 + +| 排名 | 样式 | 说明 | +|------|------|------| +| 1-3名 | 红色背景 | 热度最高,突出显示 | +| 4-10名 | 灰色背景 | 普通热度 | + +## 六、用户行为记录 + +### 6.1 搜索行为记录 + +```typescript +// 在用户执行搜索时调用 +async recordSearch(keyword: string, resultCount: number): Promise { + // 记录字段: + // - user_id: 用户ID(可选,支持匿名) + // - keyword: 搜索关键词 + // - result_count: 搜索结果数量 + // - created_at: 搜索时间 +} +``` + +### 6.2 浏览行为记录 + +```typescript +// 在用户查看商品详情时调用 +async recordBrowse(productId: string, duration: number): Promise { + // 记录字段: + // - user_id: 用户ID + // - product_id: 商品ID + // - browse_duration: 浏览时长(秒) + // - created_at/updated_at: 时间戳 +} +``` + +## 七、数据表结构 + +### 7.1 搜索历史表 (ml_search_history) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID | 主键 | +| user_id | UUID | 用户ID(可空) | +| keyword | VARCHAR(200) | 搜索关键词 | +| result_count | INTEGER | 搜索结果数量 | +| created_at | TIMESTAMP | 搜索时间 | + +### 7.2 浏览历史表 (ml_browse_history) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID | 主键 | +| user_id | UUID | 用户ID | +| product_id | UUID | 商品ID | +| browse_duration | INTEGER | 浏览时长(秒) | +| created_at | TIMESTAMP | 首次浏览时间 | +| updated_at | TIMESTAMP | 最近浏览时间 | + +## 八、API接口列表 + +| API | 方法 | 说明 | +|-----|------|------| +| getSmartRecommendations | GET | 获取智能推荐商品 | +| getHotKeywords | GET | 获取热搜词列表 | +| getUserSearchHistory | GET | 获取用户搜索历史 | +| getUserBrowseCategories | GET | 获取用户浏览分类 | +| recordSearch | POST | 记录搜索行为 | +| recordBrowse | POST | 记录浏览行为 | + +## 九、性能优化建议 + +### 9.1 缓存策略 + +- 热搜词列表:缓存5分钟,减少数据库查询 +- 用户搜索历史:缓存10分钟 +- 推荐结果:缓存3分钟 + +### 9.2 数据量控制 + +- 搜索历史:每个用户最多保留100条 +- 浏览历史:每个用户最多保留50条 +- 热搜词统计:只统计最近30天的数据 + +## 十、扩展方向 + +### 10.1 短期优化 + +1. **时间衰减因子**:近期行为权重更高 +2. **购买行为加权**:已购买商品的相关商品权重提升 +3. **收藏行为加权**:收藏商品的相关商品权重提升 + +### 10.2 长期规划 + +1. **协同过滤**:基于相似用户的行为推荐 +2. **商品相似度**:基于商品属性计算相似度 +3. **机器学习**:使用推荐算法模型 + +--- + +*文档版本:1.0* +*最后更新:2024年* diff --git a/pages/mall/consumer/doc/UTS_ANDROID_GUIDE.md b/pages/mall/consumer/doc/UTS_ANDROID_GUIDE.md new file mode 100644 index 00000000..00feca29 --- /dev/null +++ b/pages/mall/consumer/doc/UTS_ANDROID_GUIDE.md @@ -0,0 +1,3364 @@ +# UTS Android 兼容性开发规范指南 + +> 以下为 uni-app x (UTS) Android 端开发常见注意事项与踩坑点,建议所有开发成员遵循: + +================================================================================ +一、基础语法规范 +================================================================================ + +1. 变量声明 + - 只能使用 let 和 const,不能使用 var + - 变量声明必须有显式类型或初始化值 + - 不支持 undefined 类型,变量未赋值就是 null + - 不支持 undefined 关键字,判断是否存在要用 != null + +2. 类型定义 + - 只适合用 type,不适合使用 interface(interface 在 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 后用索引访问 + - 使用索引访问属性时,推荐使用方括号语法 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 或 any[] + +4. 对象操作 + - 不支持 Object.keys()、Object.values()、Object.entries() + - 不支持 Record 对象字面量语法 + - 对象字面量 {...} 只能用于构造类型(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 + - 不支持 gap + - 不支持 table、grid、grid-template-columns + +2. 单位与计算 + - 不支持 calc() + - 不支持的单位: vh + - property value `100%` is not supported for min-height (supported values are: number|pixel) + - property value `calc(33.33% - 10px)` is not supported for min-width + +3. 选择器 + - [APP-ANDROID] 不支持伪类选择器 + - [APP-IOS] 不支持伪类选择器 + - ERROR: Selector `.login-button[disabled]` is not supported. uvue only support classname selector + +4. 其他样式 + - WARNING: `backdrop-filter` is not a standard property name + - style property `white-space` is only supported on `| + + - - + + ``` -3. Map 类型访问限制 - - UTS Android 不支持 Map 类型的 get() 方法访问属性 - - 应统一使用 UTSJSONObject - - 示例: +3. Map 绫诲瀷璁块棶闄愬埗 + - UTS Android 涓嶆敮鎸?Map 绫诲瀷鐨?get() 鏂规硶璁块棶灞炴€? + - 搴旂粺涓€浣跨敤 UTSJSONObject + - 绀轰緥锛? ```typescript - // 错误 + // 閿欒 const m = item as Map const idVal = m.get('id') - // 正确 + // 姝g‘ const itemObj = item as UTSJSONObject const id = itemObj.getString('id') ?? '' ``` -4. 函数内取反操作符 - - if (!isValid.value) 不支持取反 - - 使用 if (isValid.value === false) - - 示例: +4. 鍑芥暟鍐呭彇鍙嶆搷浣滅 + - if (!isValid.value) 涓嶆敮鎸佸彇鍙? + - 浣跨敤 if (isValid.value === false) + - 绀轰緥锛? ```typescript - // 错误 + // 閿欒 if (!isValid.value) return - // 正确 + // 姝g‘ if (isValid.value === false) return ``` ================================================================================ -二十一、2026-02-25 user 目录页面修复记录 +浜屽崄涓€銆?026-02-25 user 鐩綍椤甸潰淇璁板綍 ================================================================================ -1. change-password.uvue 修复 - - 问题:!oldPassword.value 取反操作不支持 - - 修复:改为 oldPassword.value == '' 显式判断 - - 问题:const { error } = await ... 解构赋值不支持 - - 修复:改为 const result = await ... 然后 result.error 访问 +1. change-password.uvue 淇 + - 闂锛?oldPassword.value 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?oldPassword.value == '' 鏄惧紡鍒ゆ柇 + - 闂锛歝onst { error } = await ... 瑙f瀯璧嬪€间笉鏀寔 + - 淇锛氭敼涓?const result = await ... 鐒跺悗 result.error 璁块棶 -2. login.uvue 修复 - - 问题:as unknown as number 双重类型转换不支持 - - 修复:改为 as number 单一类型转换 - - 问题:typeof err === 'object' 不支持 - - 修复:使用 try-catch 包裹类型转换 +2. login.uvue 淇 + - 闂锛歛s unknown as number 鍙岄噸绫诲瀷杞崲涓嶆敮鎸? + - 淇锛氭敼涓?as number 鍗曚竴绫诲瀷杞崲 + - 闂锛歵ypeof err === 'object' 涓嶆敮鎸? + - 淇锛氫娇鐢?try-catch 鍖呰9绫诲瀷杞崲 -3. forgot-password.uvue 修复 - - 问题:!emailRegex.test(this.email) 取反操作不支持 - - 修复:改为 emailRegex.test(this.email) == false - - 问题:typeof err === 'object' 不支持 - - 修复:使用 try-catch 包裹类型转换 +3. forgot-password.uvue 淇 + - 闂锛?emailRegex.test(this.email) 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?emailRegex.test(this.email) == false + - 闂锛歵ypeof err === 'object' 涓嶆敮鎸? + - 淇锛氫娇鐢?try-catch 鍖呰9绫诲瀷杞崲 -4. register.uvue 修复 - - 问题:!protocol.value 取反操作不支持 - - 修复:改为 protocol.value == false - - 问题:!validateEmail() 等取反操作不支持 - - 修复:改为 validateEmail() == false +4. register.uvue 淇 + - 闂锛?protocol.value 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?protocol.value == false + - 闂锛?validateEmail() 绛夊彇鍙嶆搷浣滀笉鏀寔 + - 淇锛氭敼涓?validateEmail() == false ================================================================================ -二十二、常见修复模式速查 +浜屽崄浜屻€佸父瑙佷慨澶嶆ā寮忛€熸煡 ================================================================================ -1. 取反操作修复模式 +1. 鍙栧弽鎿嶄綔淇妯″紡 ```typescript - // 错误 + // 閿欒 if (!variable) { ... } if (!isValid.value) { ... } if (!validate()) { ... } - // 正确 - 根据类型选择 - if (variable == null || variable == '') { ... } // 字符串判空 - if (isValid.value == false) { ... } // 布尔值取反 - if (validate() == false) { ... } // 函数返回布尔值取反 + // 姝g‘ - 鏍规嵁绫诲瀷閫夋嫨 + if (variable == null || variable == '') { ... } // 瀛楃涓插垽绌? + if (isValid.value == false) { ... } // 甯冨皵鍊煎彇鍙? + if (validate() == false) { ... } // 鍑芥暟杩斿洖甯冨皵鍊煎彇鍙? ``` -2. 解构赋值修复模式 +2. 瑙f瀯璧嬪€间慨澶嶆ā寮? ```typescript - // 错误 + // 閿欒 const { data, error } = await someAsyncCall() - // 正确 + // 姝g‘ const result = await someAsyncCall() const data = result.data const error = result.error ``` -3. typeof 检查修复模式 +3. typeof 妫€鏌ヤ慨澶嶆ā寮? ```typescript - // 错误 + // 閿欒 if (typeof err === 'object') { ... } if (typeof xxx === 'function') { ... } - // 正确 + // 姝g‘ try { const e = err as Error - // 使用 e + // 浣跨敤 e } catch (e2) { - // 处理转换失败 + // 澶勭悊杞崲澶辫触 } ``` -4. as unknown as 修复模式 +4. as unknown as 淇妯″紡 ```typescript - // 错误 + // 閿欒 const timer = setInterval(...) as unknown as number - // 正确 + // 姝g‘ const timer = setInterval(...) as number ``` ================================================================================ -二十三、错误处理最佳实践 +浜屽崄涓夈€侀敊璇鐞嗘渶浣冲疄璺? ================================================================================ -1. 统一错误处理模式 +1. 缁熶竴閿欒澶勭悊妯″紡 ```typescript try { const result = await someAsyncCall() @@ -1366,235 +1366,235 @@ getCurrentUserId 函数 - 将可选链替换为显式 null 检查和 UTSJSONObje uni.showToast({ title: errorMsg, icon: 'none' }) return } - // 处理成功结果 + // 澶勭悊鎴愬姛缁撴灉 } catch (e) { - console.error('操作失败:', e) - uni.showToast({ title: '操作失败', icon: 'none' }) + console.error('鎿嶄綔澶辫触:', e) + uni.showToast({ title: '鎿嶄綔澶辫触', icon: 'none' }) } ``` -2. 可空类型安全访问 +2. 鍙┖绫诲瀷瀹夊叏璁块棶 ```typescript - // 安全访问对象属性 + // 瀹夊叏璁块棶瀵硅薄灞炴€? const value = obj != null ? obj.property : null - // 安全调用方法 + // 瀹夊叏璋冪敤鏂规硶 const result = obj != null ? obj.method() : null ``` -3. 数组安全访问 +3. 鏁扮粍瀹夊叏璁块棶 ```typescript - // 安全访问数组元素 + // 瀹夊叏璁块棶鏁扮粍鍏冪礌 if (arr.length > index) { const item = arr[index] - // 使用 item + // 浣跨敤 item } ``` ================================================================================ -文档结束 +鏂囨。缁撴潫 ================================================================================ ================================================================================ -二十四、2026-02-27 函数可选参数限制(重要) +浜屽崄鍥涖€?026-02-27 鍑芥暟鍙€夊弬鏁伴檺鍒讹紙閲嶈锛? ================================================================================ -1. 可选参数不能跳过传递 - - UTS Android 不支持跳过可选参数传递 - - 如果函数有多个可选参数,必须按顺序传递所有参数 - - 错误示例: +1. 鍙€夊弬鏁颁笉鑳借烦杩囦紶閫? + - UTS Android 涓嶆敮鎸佽烦杩囧彲閫夊弬鏁颁紶閫? + - 濡傛灉鍑芥暟鏈夊涓彲閫夊弬鏁帮紝蹇呴』鎸夐『搴忎紶閫掓墍鏈夊弬鏁? + - 閿欒绀轰緥锛? ```typescript - // 函数定义 + // 鍑芥暟瀹氫箟 async addToCart(productId: string, quantity: number = 1, skuId?: string, merchantId?: string): Promise - // 错误调用 - 跳过了 merchantId 参数 + // 閿欒璋冪敤 - 璺宠繃浜?merchantId 鍙傛暟 await supabaseService.addToCart(productId, 1, '') - // 编译错误:No value passed for parameter 'merchantId' + // 缂栬瘧閿欒锛歂o value passed for parameter 'merchantId' ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript - // 方案1:给可选参数添加默认值 + // 鏂规1锛氱粰鍙€夊弬鏁版坊鍔犻粯璁ゅ€? async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise - // 方案2:调用时传递所有参数 + // 鏂规2锛氳皟鐢ㄦ椂浼犻€掓墍鏈夊弬鏁? await supabaseService.addToCart(productId, 1, '', '') ``` -2. 可选参数定义规范 - - 推荐使用 `param: Type = defaultValue` 而非 `param?: Type` - - `param?: Type` 在 Android 端调用时仍需传递参数 - - `param: Type = defaultValue` 可以在不传参时使用默认值 - - 示例: +2. 鍙€夊弬鏁板畾涔夎鑼? + - 鎺ㄨ崘浣跨敤 `param: Type = defaultValue` 鑰岄潪 `param?: Type` + - `param?: Type` 鍦?Android 绔皟鐢ㄦ椂浠嶉渶浼犻€掑弬鏁? + - `param: Type = defaultValue` 鍙互鍦ㄤ笉浼犲弬鏃朵娇鐢ㄩ粯璁ゅ€? + - 绀轰緥锛? ```typescript - // 不推荐 - 调用时仍需传递参数 + // 涓嶆帹鑽?- 璋冪敤鏃朵粛闇€浼犻€掑弬鏁? 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. 调用时传递所有参数 +3. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?No value passed for parameter 'xxx'" + - 鍘熷洜锛氬彲閫夊弬鏁板湪 Android 绔笉鑳借烦杩? + - 瑙e喅锛? + 1. 淇敼鍑芥暟绛惧悕锛屼娇鐢ㄩ粯璁ゅ€?`param: Type = defaultValue` + 2. 璋冪敤鏃朵紶閫掓墍鏈夊弬鏁? -4. 最佳实践 - - 对于有多个可选参数的函数,统一使用默认值语法 - - 调用时显式传递所有参数,避免依赖可选参数跳过 - - 在服务层函数定义中,优先使用 `= ''` 或 `= 0` 等默认值 +4. 鏈€浣冲疄璺? + - 瀵逛簬鏈夊涓彲閫夊弬鏁扮殑鍑芥暟锛岀粺涓€浣跨敤榛樿鍊艰娉? + - 璋冪敤鏃舵樉寮忎紶閫掓墍鏈夊弬鏁帮紝閬垮厤渚濊禆鍙€夊弬鏁拌烦杩? + - 鍦ㄦ湇鍔″眰鍑芥暟瀹氫箟涓紝浼樺厛浣跨敤 `= ''` 鎴?`= 0` 绛夐粯璁ゅ€? ================================================================================ -二十五、2026-02-27 模板中的非空断言限制(重要) +浜屽崄浜斻€?026-02-27 妯℃澘涓殑闈炵┖鏂█闄愬埗锛堥噸瑕侊級 ================================================================================ -1. 模板中不支持非空断言操作符 `!` - - UTS Android 模板中不能使用 `variable!` 非空断言 - - 错误示例: +1. 妯℃澘涓笉鏀寔闈炵┖鏂█鎿嶄綔绗?`!` + - UTS Android 妯℃澘涓笉鑳戒娇鐢?`variable!` 闈炵┖鏂█ + - 閿欒绀轰緥锛? ```html ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```html ``` -2. 编译错误提示 - - 错误信息:"参数类型不匹配:实际类型为 'Number?',预期类型为 'Number'" - - 原因:模板中使用非空断言 `!` 不被支持 - - 解决:移除非空断言 `!`,直接使用变量进行比较 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鍙傛暟绫诲瀷涓嶅尮閰嶏細瀹為檯绫诲瀷涓?'Number?'锛岄鏈熺被鍨嬩负 'Number'" + - 鍘熷洜锛氭ā鏉夸腑浣跨敤闈炵┖鏂█ `!` 涓嶈鏀寔 + - 瑙e喅锛氱Щ闄ら潪绌烘柇瑷€ `!`锛岀洿鎺ヤ娇鐢ㄥ彉閲忚繘琛屾瘮杈? -3. 最佳实践 - - 在模板中,先用 `!= null` 判断可空类型,然后直接使用变量 - - UTS 编译器会在 `!= null` 判断后自动识别变量为非空类型 +3. 鏈€浣冲疄璺? + - 鍦ㄦā鏉夸腑锛屽厛鐢?`!= null` 鍒ゆ柇鍙┖绫诲瀷锛岀劧鍚庣洿鎺ヤ娇鐢ㄥ彉閲? + - UTS 缂栬瘧鍣ㄤ細鍦?`!= null` 鍒ゆ柇鍚庤嚜鍔ㄨ瘑鍒彉閲忎负闈炵┖绫诲瀷 ================================================================================ -二十六、2026-02-27 未导入类型的处理(重要) +浜屽崄鍏€?026-02-27 鏈鍏ョ被鍨嬬殑澶勭悊锛堥噸瑕侊級 ================================================================================ -1. 未导入的类型不能直接使用 - - 在页面中使用的类型必须先导入或使用 UTSJSONObject 替代 - - 错误示例: +1. 鏈鍏ョ殑绫诲瀷涓嶈兘鐩存帴浣跨敤 + - 鍦ㄩ〉闈腑浣跨敤鐨勭被鍨嬪繀椤诲厛瀵煎叆鎴栦娇鐢?UTSJSONObject 鏇夸唬 + - 閿欒绀轰緥锛? ```typescript - // Shop 类型未导入 + // Shop 绫诲瀷鏈鍏? const s = shopRespData[i] as Shop - const id = s.id // 找不到名称 "id" + const id = s.id // 鎵句笉鍒板悕绉?"id" ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript - // 使用 UTSJSONObject + // 浣跨敤 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()` 访问属性 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?'XXX'" + - 鍘熷洜锛氱被鍨嬫湭瀵煎叆鎴栫被鍨嬪畾涔変笉瀛樺湪 + - 瑙e喅锛? + 1. 瀵煎叆闇€瑕佺殑绫诲瀷锛歚import { Shop } from '@/utils/supabaseService.uts'` + 2. 浣跨敤 UTSJSONObject 鏇夸唬锛歚as UTSJSONObject` 鐒跺悗鐢?`getString()`銆乣getNumber()` 璁块棶灞炴€? -3. 最佳实践 - - 对于简单的数据转换,推荐使用 UTSJSONObject - - 避免在多个文件中重复定义相同的类型 - - 如果需要类型安全,从服务层导入类型定义 +3. 鏈€浣冲疄璺? + - 瀵逛簬绠€鍗曠殑鏁版嵁杞崲锛屾帹鑽愪娇鐢?UTSJSONObject + - 閬垮厤鍦ㄥ涓枃浠朵腑閲嶅瀹氫箟鐩稿悓鐨勭被鍨? + - 濡傛灉闇€瑕佺被鍨嬪畨鍏紝浠庢湇鍔″眰瀵煎叆绫诲瀷瀹氫箟 ================================================================================ -二十七、2026-02-27 服务层数据字段完整性(重要) +浜屽崄涓冦€?026-02-27 鏈嶅姟灞傛暟鎹瓧娈靛畬鏁存€э紙閲嶈锛? ================================================================================ -1. 服务层返回数据必须包含所有必要字段 - - 从数据库获取数据时,必须正确映射所有需要的字段 - - 错误示例: +1. 鏈嶅姟灞傝繑鍥炴暟鎹繀椤诲寘鍚墍鏈夊繀瑕佸瓧娈? + - 浠庢暟鎹簱鑾峰彇鏁版嵁鏃讹紝蹇呴』姝g‘鏄犲皠鎵€鏈夐渶瑕佺殑瀛楁 + - 閿欒绀轰緥锛? ```typescript const product: Product = { id: prodObj.getString('id') ?? '', name: prodObj.getString('name') ?? '', - // 错误:merchant_id 硬编码为空字符串 + // 閿欒锛歮erchant_id 纭紪鐮佷负绌哄瓧绗︿覆 merchant_id: '' } as Product ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript const product: Product = { id: prodObj.getString('id') ?? '', name: prodObj.getString('name') ?? '', - // 正确:从数据库获取 merchant_id + // 姝g‘锛氫粠鏁版嵁搴撹幏鍙?merchant_id merchant_id: prodObj.getString('merchant_id') ?? '' } as Product ``` -2. 调用服务层方法时必须传递完整参数 - - 页面调用服务层方法时,需要传递所有必要参数 - - 错误示例: +2. 璋冪敤鏈嶅姟灞傛柟娉曟椂蹇呴』浼犻€掑畬鏁村弬鏁? + - 椤甸潰璋冪敤鏈嶅姟灞傛柟娉曟椂锛岄渶瑕佷紶閫掓墍鏈夊繀瑕佸弬鏁? + - 閿欒绀轰緥锛? ```typescript - // 错误:merchant_id 传空字符串 + // 閿欒锛歮erchant_id 浼犵┖瀛楃涓? await supabaseService.addToCart(productId, 1, '', '') ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript - // 正确:从商品对象获取 merchant_id + // 姝g‘锛氫粠鍟嗗搧瀵硅薄鑾峰彇 merchant_id const merchantId = product.merchant_id ?? '' await supabaseService.addToCart(productId, 1, '', merchantId) ``` -3. 编译错误提示 - - 问题表现:数据添加到数据库失败,或添加的数据不完整 - - 原因:服务层或页面层缺少必要字段的传递 - - 解决: - 1. 检查服务层数据映射是否完整 - 2. 检查页面调用时是否传递了所有必要参数 +3. 缂栬瘧閿欒鎻愮ず + - 闂琛ㄧ幇锛氭暟鎹坊鍔犲埌鏁版嵁搴撳け璐ワ紝鎴栨坊鍔犵殑鏁版嵁涓嶅畬鏁? + - 鍘熷洜锛氭湇鍔″眰鎴栭〉闈㈠眰缂哄皯蹇呰瀛楁鐨勪紶閫? + - 瑙e喅锛? + 1. 妫€鏌ユ湇鍔″眰鏁版嵁鏄犲皠鏄惁瀹屾暣 + 2. 妫€鏌ラ〉闈㈣皟鐢ㄦ椂鏄惁浼犻€掍簡鎵€鏈夊繀瑕佸弬鏁? -4. 最佳实践 - - 服务层方法返回的对象应包含数据库视图的所有字段 - - 页面调用服务层方法时,应从数据对象中获取并传递所有参数 - - 对于关联数据(如 merchant_id),确保在数据加载时一并获取 +4. 鏈€浣冲疄璺? + - 鏈嶅姟灞傛柟娉曡繑鍥炵殑瀵硅薄搴斿寘鍚暟鎹簱瑙嗗浘鐨勬墍鏈夊瓧娈? + - 椤甸潰璋冪敤鏈嶅姟灞傛柟娉曟椂锛屽簲浠庢暟鎹璞′腑鑾峰彇骞朵紶閫掓墍鏈夊弬鏁? + - 瀵逛簬鍏宠仈鏁版嵁锛堝 merchant_id锛夛紝纭繚鍦ㄦ暟鎹姞杞芥椂涓€骞惰幏鍙? ================================================================================ -二十八、2026-02-27 模板中的非运算符限制(重要) +浜屽崄鍏€?026-02-27 妯℃澘涓殑闈炶繍绠楃闄愬埗锛堥噸瑕侊級 ================================================================================ -1. 模板中不支持 `!` 非运算符 - - UTS Android 模板中不能使用 `!variable` 非运算符 - - 错误示例: +1. 妯℃澘涓笉鏀寔 `!` 闈炶繍绠楃 + - UTS Android 妯℃澘涓笉鑳戒娇鐢?`!variable` 闈炶繍绠楃 + - 閿欒绀轰緥锛? ```html ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```html ``` -2. 编译错误提示 - - 错误信息:"找不到名称'not'" - - 原因:模板中不支持非运算符 `!` - - 解决:使用显式的比较表达式替代 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?not'" + - 鍘熷洜锛氭ā鏉夸腑涓嶆敮鎸侀潪杩愮畻绗?`!` + - 瑙e喅锛氫娇鐢ㄦ樉寮忕殑姣旇緝琛ㄨ揪寮忔浛浠? -3. 最佳实践 - - 使用 `== null` 或 `== ''` 检查空值 - - 使用 `!= null && != ''` 检查非空值 +3. 鏈€浣冲疄璺? + - 浣跨敤 `== null` 鎴?`== ''` 妫€鏌ョ┖鍊? + - 浣跨敤 `!= null && != ''` 妫€鏌ラ潪绌哄€? ================================================================================ -二十九、2026-02-27 索引访问限制(重要) +浜屽崄涔濄€?026-02-27 绱㈠紩璁块棶闄愬埗锛堥噸瑕侊級 ================================================================================ -1. 不支持 `(obj as any)['key']` 索引访问方式 - - UTS Android 不支持对 any 类型使用索引访问 - - 错误示例: +1. 涓嶆敮鎸?`(obj as any)['key']` 绱㈠紩璁块棶鏂瑰紡 + - UTS Android 涓嶆敮鎸佸 any 绫诲瀷浣跨敤绱㈠紩璁块棶 + - 閿欒绀轰緥锛? ```typescript const detail = (e as any)['detail'] val = detail['value'] ?? '' ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript - // 方案1:使用 UTSJSONObject + // 鏂规1锛氫娇鐢?UTSJSONObject const eObj = JSON.parse(JSON.stringify(e)) as UTSJSONObject const detail = eObj.get('detail') as UTSJSONObject val = detail.getString('value') ?? '' - // 方案2:先判断类型再转换 + // 鏂规2锛氬厛鍒ゆ柇绫诲瀷鍐嶈浆鎹? if (e instanceof UTSJSONObject) { const eObj = e as UTSJSONObject const detail = eObj.get('detail') as UTSJSONObject @@ -1602,160 +1602,160 @@ getCurrentUserId 函数 - 将可选链替换为显式 null 检查和 UTSJSONObje } ``` -2. 编译错误提示 - - 错误信息:"Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch" - - 原因:any 类型不支持索引访问 - - 解决:转换为 UTSJSONObject 后使用 `.get()` 方法 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch" + - 鍘熷洜锛歛ny 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鍚庝娇鐢?`.get()` 鏂规硶 -3. 最佳实践 - - 统一使用 UTSJSONObject 处理动态对象 - - 使用 `.get()`、`.getString()`、`.getNumber()` 方法访问属性 - - 对于复杂对象,先用 `JSON.parse(JSON.stringify(obj))` 转换 +3. 鏈€浣冲疄璺? + - 缁熶竴浣跨敤 UTSJSONObject 澶勭悊鍔ㄦ€佸璞? + - 浣跨敤 `.get()`銆乣.getString()`銆乣.getNumber()` 鏂规硶璁块棶灞炴€? + - 瀵逛簬澶嶆潅瀵硅薄锛屽厛鐢?`JSON.parse(JSON.stringify(obj))` 杞崲 ================================================================================ -三十、2026-02-27 字符串不能直接作为布尔条件(重要) +涓夊崄銆?026-02-27 瀛楃涓蹭笉鑳界洿鎺ヤ綔涓哄竷灏旀潯浠讹紙閲嶈锛? ================================================================================ -1. 字符串不能直接作为 if 条件 - - UTS Android 不支持将字符串直接作为布尔条件判断 - - 错误示例: +1. 瀛楃涓蹭笉鑳界洿鎺ヤ綔涓?if 鏉′欢 + - UTS Android 涓嶆敮鎸佸皢瀛楃涓茬洿鎺ヤ綔涓哄竷灏旀潯浠跺垽鏂? + - 閿欒绀轰緥锛? ```typescript const paramId = '123' - if (paramId) { // 错误:字符串不能直接作为布尔条件 + if (paramId) { // 閿欒锛氬瓧绗︿覆涓嶈兘鐩存帴浣滀负甯冨皵鏉′欢 // ... } ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript const paramId = '123' - if (paramId != null && paramId != '') { // 正确:显式判断 + if (paramId != null && paramId != '') { // 姝g‘锛氭樉寮忓垽鏂? // ... } ``` -2. 编译错误提示 - - 错误信息:"Condition type mismatch: inferred type is 'String' but 'Boolean' was expected" - - 原因:字符串类型不能直接作为布尔条件 - - 解决:使用显式的比较表达式 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Condition type mismatch: inferred type is 'String' but 'Boolean' was expected" + - 鍘熷洜锛氬瓧绗︿覆绫诲瀷涓嶈兘鐩存帴浣滀负甯冨皵鏉′欢 + - 瑙e喅锛氫娇鐢ㄦ樉寮忕殑姣旇緝琛ㄨ揪寮? -3. 最佳实践 - - 使用 `!= null && != ''` 检查字符串非空 - - 使用 `== null || == ''` 检查字符串为空 +3. 鏈€浣冲疄璺? + - 浣跨敤 `!= null && != ''` 妫€鏌ュ瓧绗︿覆闈炵┖ + - 浣跨敤 `== null || == ''` 妫€鏌ュ瓧绗︿覆涓虹┖ ================================================================================ -三十一、2026-02-27 函数定义顺序(重要) +涓夊崄涓€銆?026-02-27 鍑芥暟瀹氫箟椤哄簭锛堥噸瑕侊級 ================================================================================ -1. 函数必须在调用前定义 - - UTS Android 要求函数在调用之前完成定义 - - 这与 JavaScript 的函数提升不同 - - 错误示例: +1. 鍑芥暟蹇呴』鍦ㄨ皟鐢ㄥ墠瀹氫箟 + - UTS Android 瑕佹眰鍑芥暟鍦ㄨ皟鐢ㄤ箣鍓嶅畬鎴愬畾涔? + - 杩欎笌 JavaScript 鐨勫嚱鏁版彁鍗囦笉鍚? + - 閿欒绀轰緥锛? ```typescript onMounted(() => { - loadData() // 错误:loadData 还未定义 + loadData() // 閿欒锛歭oadData 杩樻湭瀹氫箟 }) const loadData = async () => { // ... } ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript const loadData = async () => { // ... } onMounted(() => { - loadData() // 正确:loadData 已定义 + loadData() // 姝g‘锛歭oadData 宸插畾涔? }) ``` -2. 编译错误提示 - - 错误信息:"找不到名称'xxx'" - - 原因:函数在调用点之后定义 - - 解决:将函数定义移到调用之前 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?xxx'" + - 鍘熷洜锛氬嚱鏁板湪璋冪敤鐐逛箣鍚庡畾涔? + - 瑙e喅锛氬皢鍑芥暟瀹氫箟绉诲埌璋冪敤涔嬪墠 -3. 最佳实践 - - 将所有函数定义放在生命周期钩子(onMounted、onShow 等)之前 - - 按依赖关系排序函数定义顺序 +3. 鏈€浣冲疄璺? + - 灏嗘墍鏈夊嚱鏁板畾涔夋斁鍦ㄧ敓鍛藉懆鏈熼挬瀛愶紙onMounted銆乷nShow 绛夛級涔嬪墠 + - 鎸変緷璧栧叧绯绘帓搴忓嚱鏁板畾涔夐『搴? ================================================================================ -三十二、2026-02-27 联合类型属性访问(重要) +涓夊崄浜屻€?026-02-27 鑱斿悎绫诲瀷灞炴€ц闂紙閲嶈锛? ================================================================================ -1. 联合类型不能直接访问属性 - - 当参数类型为联合类型(如 `A | B`)时,不能直接访问属性 - - 错误示例: +1. 鑱斿悎绫诲瀷涓嶈兘鐩存帴璁块棶灞炴€? + - 褰撳弬鏁扮被鍨嬩负鑱斿悎绫诲瀷锛堝 `A | B`锛夋椂锛屼笉鑳界洿鎺ヨ闂睘鎬? + - 閿欒绀轰緥锛? ```typescript type A = { id: string, name: string } type B = { id: string, title: string } const foo = (item: A | B) => { - const id = item.id // 错误:联合类型不能直接访问属性 + const id = item.id // 閿欒锛氳仈鍚堢被鍨嬩笉鑳界洿鎺ヨ闂睘鎬? } ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript const foo = (item: A | B) => { - // 方案1:转换为 UTSJSONObject + // 鏂规1锛氳浆鎹负 UTSJSONObject const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const id = obj.getString('id') ?? '' - // 方案2:使用类型守卫 + // 鏂规2锛氫娇鐢ㄧ被鍨嬪畧鍗? if ('name' in item) { - const id = item.id // 此时类型已收窄为 A + const id = item.id // 姝ゆ椂绫诲瀷宸叉敹绐勪负 A } } ``` -2. 编译错误提示 - - 错误信息:"找不到名称'xxx'" - - 原因:联合类型的属性访问受限 - - 解决:转换为 UTSJSONObject 或使用类型守卫 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?xxx'" + - 鍘熷洜锛氳仈鍚堢被鍨嬬殑灞炴€ц闂彈闄? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鎴栦娇鐢ㄧ被鍨嬪畧鍗? -3. 最佳实践 - - 对于联合类型参数,统一转换为 UTSJSONObject 处理 - - 使用 `.getString()`、`.getNumber()` 等方法安全访问属性 +3. 鏈€浣冲疄璺? + - 瀵逛簬鑱斿悎绫诲瀷鍙傛暟锛岀粺涓€杞崲涓?UTSJSONObject 澶勭悊 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? ================================================================================ -三十三、2026-02-27 any 类型变量不能赋值为 null(重要) +涓夊崄涓夈€?026-02-27 any 绫诲瀷鍙橀噺涓嶈兘璧嬪€间负 null锛堥噸瑕侊級 ================================================================================ -1. any 类型变量不能赋值为 null - - UTS Android 中 `any` 类型不能赋值为 `null` - - 错误示例: +1. any 绫诲瀷鍙橀噺涓嶈兘璧嬪€间负 null + - UTS Android 涓?`any` 绫诲瀷涓嶈兘璧嬪€间负 `null` + - 閿欒绀轰緥锛? ```typescript - let res: any = null // 错误:Null cannot be a value of a non-null type 'Any' + let res: any = null // 閿欒锛歂ull cannot be a value of a non-null type 'Any' ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript - let res: any = {} // 正确:使用空对象 - // 或者 - let res: any | null = null // 使用联合类型 + let res: any = {} // 姝g‘锛氫娇鐢ㄧ┖瀵硅薄 + // 鎴栬€? + let res: any | null = null // 浣跨敤鑱斿悎绫诲瀷 ``` -2. 编译错误提示 - - 错误信息:"Null cannot be a value of a non-null type 'Any'" - - 原因:any 类型不允许 null 值 - - 解决:使用空对象 `{}` 或联合类型 `any | null` +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Null cannot be a value of a non-null type 'Any'" + - 鍘熷洜锛歛ny 绫诲瀷涓嶅厑璁?null 鍊? + - 瑙e喅锛氫娇鐢ㄧ┖瀵硅薄 `{}` 鎴栬仈鍚堢被鍨?`any | null` ================================================================================ -三十四、2026-02-27 对象字面量类型推断问题(重要) +涓夊崄鍥涖€?026-02-27 瀵硅薄瀛楅潰閲忕被鍨嬫帹鏂棶棰橈紙閲嶈锛? ================================================================================ -1. 对象字面量直接赋值给 ref 可能类型不匹配 - - 当对象字面量直接赋值给特定类型的 ref 时,可能报类型不匹配错误 - - 错误示例: +1. 瀵硅薄瀛楅潰閲忕洿鎺ヨ祴鍊肩粰 ref 鍙兘绫诲瀷涓嶅尮閰? + - 褰撳璞″瓧闈㈤噺鐩存帴璧嬪€肩粰鐗瑰畾绫诲瀷鐨?ref 鏃讹紝鍙兘鎶ョ被鍨嬩笉鍖归厤閿欒 + - 閿欒绀轰緥锛? ```typescript merchant.value = { id: shop.id, user_id: shop.merchant_id, // ... - } // 错误:Assignment type mismatch + } // 閿欒锛欰ssignment type mismatch ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript - // 方案1:显式声明类型 + // 鏂规1锛氭樉寮忓0鏄庣被鍨? const merchantData: MerchantType = { id: shop.id, user_id: shop.merchant_id, @@ -1763,7 +1763,7 @@ getCurrentUserId 函数 - 将可选链替换为显式 null 检查和 UTSJSONObje } merchant.value = merchantData - // 方案2:使用 as 类型断言 + // 鏂规2锛氫娇鐢?as 绫诲瀷鏂█ merchant.value = { id: shop.id, user_id: shop.merchant_id, @@ -1771,100 +1771,100 @@ getCurrentUserId 函数 - 将可选链替换为显式 null 检查和 UTSJSONObje } as MerchantType ``` -2. 编译错误提示 - - 错误信息:"Assignment type mismatch: actual type is '', but 'XXX' was expected" - - 原因:对象字面量被推断为匿名类型 - - 解决:显式声明类型或使用类型断言 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Assignment type mismatch: actual type is '', but 'XXX' was expected" + - 鍘熷洜锛氬璞″瓧闈㈤噺琚帹鏂负鍖垮悕绫诲瀷 + - 瑙e喅锛氭樉寮忓0鏄庣被鍨嬫垨浣跨敤绫诲瀷鏂█ ================================================================================ -三十五、2026-02-27 any 类型不能直接访问属性(重要) +涓夊崄浜斻€?026-02-27 any 绫诲瀷涓嶈兘鐩存帴璁块棶灞炴€э紙閲嶈锛? ================================================================================ -1. any 类型参数不能直接访问属性 - - 在 map、forEach 等回调中,any 类型的参数不能直接访问属性 - - 错误示例: +1. any 绫诲瀷鍙傛暟涓嶈兘鐩存帴璁块棶灞炴€? + - 鍦?map銆乫orEach 绛夊洖璋冧腑锛宎ny 绫诲瀷鐨勫弬鏁颁笉鑳界洿鎺ヨ闂睘鎬? + - 閿欒绀轰緥锛? ```typescript const list = rawList.map((item): ProductType => { - const id = item.id // 错误:找不到名称"id" - const name = item.name // 错误:找不到名称"name" + const id = item.id // 閿欒锛氭壘涓嶅埌鍚嶇О"id" + const name = item.name // 閿欒锛氭壘涓嶅埌鍚嶇О"name" }) ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript const list = rawList.map((item: any): ProductType => { - // 方案1:转换为 UTSJSONObject + // 鏂规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锛氭樉寮忔爣娉ㄥ弬鏁扮被鍨嬪苟浣跨敤绱㈠紩 + // 娉ㄦ剰锛氳繖绉嶆柟寮忓湪 UTS Android 涓篃鍙兘鏈夐棶棰? }) ``` -2. 编译错误提示 - - 错误信息:"找不到名称'xxx'" - - 原因:any 类型的属性访问受限 - - 解决:转换为 UTSJSONObject 后使用 `.getString()` 等方法 +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?xxx'" + - 鍘熷洜锛歛ny 绫诲瀷鐨勫睘鎬ц闂彈闄? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鍚庝娇鐢?`.getString()` 绛夋柟娉? ================================================================================ -三十六、2026-02-27 类型断言不会添加方法(重要) +涓夊崄鍏€?026-02-27 绫诲瀷鏂█涓嶄細娣诲姞鏂规硶锛堥噸瑕侊級 ================================================================================ -1. `as UTSJSONObject` 不会给对象添加方法 - - 使用 `as UTSJSONObject` 只是类型断言,不会让普通对象获得 `getString` 等方法 - - 错误示例: +1. `as UTSJSONObject` 涓嶄細缁欏璞℃坊鍔犳柟娉? + - 浣跨敤 `as UTSJSONObject` 鍙槸绫诲瀷鏂█锛屼笉浼氳鏅€氬璞¤幏寰?`getString` 绛夋柟娉? + - 閿欒绀轰緥锛? ```typescript const profileObj = profile as UTSJSONObject - const id = profileObj.getString('user_id') // 运行时错误:getString is not a function + const id = profileObj.getString('user_id') // 杩愯鏃堕敊璇細getString is not a function ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript - // 必须使用 JSON.parse(JSON.stringify()) 进行真正的转换 + // 蹇呴』浣跨敤 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())` 进行真正的对象转换 +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?XXX is not a function" + - 鍘熷洜锛氱被鍨嬫柇瑷€鍙槸缂栬瘧鏃惰涓猴紝涓嶄細鏀瑰彉杩愯鏃跺璞$殑鏂规硶 + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜鐪熸鐨勫璞¤浆鎹? -3. 最佳实践 - - 对于从 API 返回的数据,统一使用 `JSON.parse(JSON.stringify())` 转换 - - 使用 `instanceof UTSJSONObject` 检查对象类型 - - 不要依赖 `as` 类型断言来添加方法 +3. 鏈€浣冲疄璺? + - 瀵逛簬浠?API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `instanceof UTSJSONObject` 妫€鏌ュ璞$被鍨? + - 涓嶈渚濊禆 `as` 绫诲瀷鏂█鏉ユ坊鍔犳柟娉? ================================================================================ -三十七、2026-02-27 类型必须包含所有必填字段(重要) +涓夊崄涓冦€?026-02-27 绫诲瀷蹇呴』鍖呭惈鎵€鏈夊繀濉瓧娈碉紙閲嶈锛? ================================================================================ -1. 创建类型实例时必须包含所有必填字段 - - UTS 类型定义中的非可选字段(不带 `?`)都是必填的 - - 错误示例: +1. 鍒涘缓绫诲瀷瀹炰緥鏃跺繀椤诲寘鍚墍鏈夊繀濉瓧娈? + - UTS 绫诲瀷瀹氫箟涓殑闈炲彲閫夊瓧娈碉紙涓嶅甫 `?`锛夐兘鏄繀濉殑 + - 閿欒绀轰緥锛? ```typescript export type ProductType = { id: string - merchant_id: string // 必填 - category_id: string // 必填 + merchant_id: string // 蹇呭~ + category_id: string // 蹇呭~ name: string // ... } - // 错误:缺少 merchant_id、category_id 等必填字段 + // 閿欒锛氱己灏?merchant_id銆乧ategory_id 绛夊繀濉瓧娈? return { id: item.id, name: item.name, price: item.price - } as ProductType // 运行时错误:missing required property + } as ProductType // 杩愯鏃堕敊璇細missing required property ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript return { id: itemObj.getString('id') ?? '', merchant_id: itemObj.getString('merchant_id') ?? '', category_id: itemObj.getString('category_id') ?? '', - name: itemObj.getString('name') ?? '未知商品', + name: itemObj.getString('name') ?? '鏈煡鍟嗗搧', description: itemObj.getString('description') ?? '', images: images, price: itemObj.getNumber('base_price') ?? 0, @@ -1876,116 +1876,459 @@ getCurrentUserId 函数 - 将可选链替换为显式 null 检查和 UTSJSONObje } as ProductType ``` -2. 运行时错误提示 - - 错误信息:"Failed to construct type, missing required property: xxx" - - 原因:类型定义中有必填字段未提供 - - 解决: - 1. 检查类型定义,确认所有必填字段 - 2. 为所有必填字段提供值,即使是空字符串或默认值 +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?Failed to construct type, missing required property: xxx" + - 鍘熷洜锛氱被鍨嬪畾涔変腑鏈夊繀濉瓧娈垫湭鎻愪緵 + - 瑙e喅锛? + 1. 妫€鏌ョ被鍨嬪畾涔夛紝纭鎵€鏈夊繀濉瓧娈? + 2. 涓烘墍鏈夊繀濉瓧娈垫彁渚涘€硷紝鍗充娇鏄┖瀛楃涓叉垨榛樿鍊? -3. 最佳实践 - - 查看类型定义,确认哪些字段是必填的(不带 `?`) - - 使用 `??` 运算符提供默认值 - - 对于可选字段,可以不提供或使用 `null` +3. 鏈€浣冲疄璺? + - 鏌ョ湅绫诲瀷瀹氫箟锛岀‘璁ゅ摢浜涘瓧娈垫槸蹇呭~鐨勶紙涓嶅甫 `?`锛? + - 浣跨敤 `??` 杩愮畻绗︽彁渚涢粯璁ゅ€? + - 瀵逛簬鍙€夊瓧娈碉紝鍙互涓嶆彁渚涙垨浣跨敤 `null` ================================================================================ -三十八、2026-02-27 回调函数不能是 async(重要) +涓夊崄鍏€?026-02-27 鍥炶皟鍑芥暟涓嶈兘鏄?async锛堥噸瑕侊級 ================================================================================ -1. API 回调函数不能使用 async 修饰 - - uni API 的回调函数(如 showModal 的 success)不支持 async 函数 - - 错误示例: +1. API 鍥炶皟鍑芥暟涓嶈兘浣跨敤 async 淇グ + - uni API 鐨勫洖璋冨嚱鏁帮紙濡?showModal 鐨?success锛変笉鏀寔 async 鍑芥暟 + - 閿欒绀轰緥锛? ```typescript uni.showModal({ - title: '确认', - content: '确定要删除吗?', - success: async (res) => { // 错误:回调函数不能是 async + title: '纭', + content: '纭畾瑕佸垹闄ゅ悧锛?, + success: async (res) => { // 閿欒锛氬洖璋冨嚱鏁颁笉鑳芥槸 async if (res.confirm) { const result = await someAsyncFunction() } } }) ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript uni.showModal({ - title: '确认', - content: '确定要删除吗?', + title: '纭', + content: '纭畾瑕佸垹闄ゅ悧锛?, success: (res) => { if (res.confirm) { - // 使用 Promise.then() 代替 await + // 浣跨敤 Promise.then() 浠f浛 await someAsyncFunction().then((result) => { - // 处理结果 + // 澶勭悊缁撴灉 }) } } }) ``` -2. 编译错误提示 - - 错误信息:"参数类型不匹配:实际类型为 'Function1<..., UTSPromise>',预期类型为 'Function1<..., Unit>?'" - - 原因:回调函数返回 Promise 而非 void - - 解决:使用 `.then()` 代替 `await` +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鍙傛暟绫诲瀷涓嶅尮閰嶏細瀹為檯绫诲瀷涓?'Function1<..., UTSPromise>'锛岄鏈熺被鍨嬩负 'Function1<..., Unit>?'" + - 鍘熷洜锛氬洖璋冨嚱鏁拌繑鍥?Promise 鑰岄潪 void + - 瑙e喅锛氫娇鐢?`.then()` 浠f浛 `await` -3. 最佳实践 - - 在回调函数中使用 `.then()` 处理异步操作 - - 将异步逻辑封装为单独的函数,在回调中调用 +3. 鏈€浣冲疄璺? + - 鍦ㄥ洖璋冨嚱鏁颁腑浣跨敤 `.then()` 澶勭悊寮傛鎿嶄綔 + - 灏嗗紓姝ラ€昏緫灏佽涓哄崟鐙殑鍑芥暟锛屽湪鍥炶皟涓皟鐢? ================================================================================ -三十九、2026-02-27 类型转换前必须检查类型(重要) +涓夊崄涔濄€?026-02-27 绫诲瀷杞崲鍓嶅繀椤绘鏌ョ被鍨嬶紙閲嶈锛? ================================================================================ -1. 使用 `as` 类型转换前必须检查实际类型 - - 直接使用 `as string` 转换可能导致运行时类型转换异常 - - 错误示例: +1. 浣跨敤 `as` 绫诲瀷杞崲鍓嶅繀椤绘鏌ュ疄闄呯被鍨? + - 鐩存帴浣跨敤 `as string` 杞崲鍙兘瀵艰嚧杩愯鏃剁被鍨嬭浆鎹㈠紓甯? + - 閿欒绀轰緥锛? ```typescript const idVal = item['id'] - const id = idVal as string // 错误:如果 idVal 是其他类型会崩溃 + const id = idVal as string // 閿欒锛氬鏋?idVal 鏄叾浠栫被鍨嬩細宕╂簝 ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript 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` 检查类型后再转换 +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?null cannot be cast to non-null type kotlin.String" + - 閿欒淇℃伅锛?java.lang.Boolean cannot be cast to java.lang.String" + - 鍘熷洜锛氱洿鎺ョ被鍨嬭浆鎹㈡椂锛屽疄闄呯被鍨嬩笌鐩爣绫诲瀷涓嶅尮閰? + - 瑙e喅锛氫娇鐢?`typeof` 妫€鏌ョ被鍨嬪悗鍐嶈浆鎹? -3. 最佳实践 - - 使用 `typeof` 检查类型 - - 使用 `!= null` 检查空值 - - 提供默认值防止空指针异常 +3. 鏈€浣冲疄璺? + - 浣跨敤 `typeof` 妫€鏌ョ被鍨? + - 浣跨敤 `!= null` 妫€鏌ョ┖鍊? + - 鎻愪緵榛樿鍊奸槻姝㈢┖鎸囬拡寮傚父 ================================================================================ -四十、2026-02-27 UTSJSONObject 必须正确转换(重要) +鍥涘崄銆?026-02-27 UTSJSONObject 蹇呴』姝g‘杞崲锛堥噸瑕侊級 ================================================================================ -1. `as UTSJSONObject` 不会添加方法 - - 从数据库返回的数据需要正确转换为 UTSJSONObject - - 错误示例: +1. `as UTSJSONObject` 涓嶄細娣诲姞鏂规硶 + - 浠庢暟鎹簱杩斿洖鐨勬暟鎹渶瑕佹纭浆鎹负 UTSJSONObject + - 閿欒绀轰緥锛? ```typescript const item = rawList[i] - const brandObj = item as UTSJSONObject // 错误:brandObj.getString 不存在 + const brandObj = item as UTSJSONObject // 閿欒锛歜randObj.getString 涓嶅瓨鍦? ``` - - 正确示例: + - 姝g‘绀轰緥锛? ```typescript 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())` 进行转换 +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?getString is not a function" + - 鍘熷洜锛氬璞℃病鏈夋纭浆鎹负 UTSJSONObject + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜杞崲 -3. 最佳实践 - - 对于从数据库/API 返回的数据,统一使用 `JSON.parse(JSON.stringify())` 转换 - - 使用 `.getString()`、`.getNumber()` 等方法安全访问属性 +3. 鏈€浣冲疄璺? + - 瀵逛簬浠庢暟鎹簱/API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? + +================================================================================ +鍥涘崄浜屻€?026-02-27 getBoolean 鏂规硶鍙兘瀵艰嚧绫诲瀷杞崲寮傚父锛堥噸瑕侊級 +================================================================================ + +1. `UTSJSONObject.getBoolean()` 鍙兘瀵艰嚧绫诲瀷杞崲寮傚父 + - 褰撴暟鎹簱瀛楁绫诲瀷涓庨鏈熶笉绗︽椂锛宍getBoolean()` 鍙兘鎶涘嚭寮傚父 + - 閿欒绀轰緥锛? + ```typescript + const isFeatured = prodObj.getBoolean('is_featured') ?? false // 鍙兘鎶涘嚭寮傚父 + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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 鏂规硶鏈熸湜鐨勭被鍨嬩笉鍖归厤 + - 瑙e喅锛氫娇鐢?`.get()` 鏂规硶鑾峰彇鍘熷鍊硷紝鐒跺悗鎵嬪姩妫€鏌ョ被鍨? + +3. 鏈€浣冲疄璺? + - 閬垮厤浣跨敤 `.getBoolean()`锛屾敼鐢?`.get()` + `typeof` 妫€鏌? + - 鍦?SQL 鏌ヨ涓槑纭寚瀹氶渶瑕佺殑瀛楁锛岄伩鍏?`SELECT *` + - 瀵逛簬甯冨皵鍊硷紝浣跨敤 `typeof val == 'boolean'` 妫€鏌ョ被鍨? + +================================================================================ +鍥涘崄涓夈€?026-02-27 SELECT * 鍙兘瀵艰嚧绫诲瀷杞崲闂锛堥噸瑕侊級 +================================================================================ + +1. 閬垮厤浣跨敤 `SELECT *` 鏌ヨ鎵€鏈夊瓧娈? + - 鏁版嵁搴撳彲鑳藉寘鍚墠绔笉闇€瑕佺殑瀛楁锛屽鑷寸被鍨嬭浆鎹㈠紓甯? + - 閿欒绀轰緥锛? + ```typescript + .select('*') // 鍙兘杩斿洖鎰忓鐨勫瓧娈电被鍨? + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, description, base_price, market_price, main_image_url') + ``` + +2. 鏈€浣冲疄璺? + - 鍙煡璇㈤渶瑕佺殑瀛楁 + - 鍙傝€冩暟鎹簱鏂囨。纭瀛楁绫诲瀷 + - 瀵逛簬瑙嗗浘锛堝 `ml_products_detail_view`锛夛紝娉ㄦ剰瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚? + +================================================================================ +鍥涘崄鍥涖€?026-02-27 鍒涘缓杈呭姪鍑芥暟澶勭悊鏁版嵁杞崲锛堥噸瑕侊級 +================================================================================ + +1. 鍒涘缓杈呭姪鍑芥暟缁熶竴澶勭悊鏁版嵁绫诲瀷杞崲 + - 閬垮厤鍦ㄦ瘡涓柟娉曚腑閲嶅鍐欑被鍨嬫鏌ヤ唬鐮? + - 绀轰緥锛? + ```typescript + // 杈呭姪鍑芥暟锛氬畨鍏ㄨ幏鍙栧瓧绗︿覆鍊? + 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() + if (typeof val == 'boolean') return (val as boolean) ? 'true' : 'false' + 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') { + const parsed = parseFloat(val as string) + return isNaN(parsed) ? 0 : parsed + } + return 0 + } + + // 杈呭姪鍑芥暟锛氬畨鍏ㄨ幏鍙栧竷灏斿€? + function safeGetBoolean(obj: UTSJSONObject, key: string): boolean { + 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 + } + + // 杈呭姪鍑芥暟锛氫粠鍘熷鏁版嵁瑙f瀽鍟嗗搧 + 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. 浼樼偣 + - 浠g爜澶嶇敤锛屽噺灏戦噸澶? + - 缁熶竴澶勭悊鍚勭绫诲瀷杞崲寮傚父 + - 鏄撲簬缁存姢鍜屼慨鏀? + +3. 鏈€浣冲疄璺? + - 灏嗚緟鍔╁嚱鏁版斁鍦ㄦ枃浠堕《閮? + - 瀵规墍鏈変粠鏁版嵁搴撹幏鍙栫殑鏁版嵁浣跨敤杈呭姪鍑芥暟 + - 澶勭悊鎵€鏈夊彲鑳界殑绫诲瀷杞崲鎯呭喌 + +================================================================================ +鍥涘崄浜斻€?026-02-27 瑙嗗浘瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚岋紙閲嶈锛? +================================================================================ + +1. 鏁版嵁搴撹鍥剧殑瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚? + - `ml_products_detail_view` 瑙嗗浘涓病鏈?`image_url` 瀛楁 + - 鍙湁 `main_image_url` 鍜?`image_urls` 瀛楁 + - 閿欒绀轰緥锛? + ```typescript + .select('id, name, image_url') // 閿欒锛氳鍥炬病鏈?image_url 瀛楁 + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, main_image_url, image_urls') // 姝g‘ + ``` + +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?column ml_products_detail_view.image_url does not exist" + - 鎻愮ず锛?Perhaps you meant to reference the column 'ml_products_detail_view.image_urls'" + - 鍘熷洜锛氭煡璇簡瑙嗗浘涓笉瀛樺湪鐨勫瓧娈? + - 瑙e喅锛氬弬鑰冩暟鎹簱鏂囨。纭瑙嗗浘瀛楁鍚? + +3. 鏈€浣冲疄璺? + - 鏌ヨ瑙嗗浘鍓嶅厛纭瀛楁鍚? + - 鍙傝€?`CONSUMER_DB_DOC.md` 鏂囨。 + - 浣跨敤鏄庣‘瀛楁鍒楄〃鑰岄潪 `SELECT *` + +================================================================================ +鍥涘崄鍏€?026-02-27 杈呭姪鍑芥暟搴斾娇鐢?try-catch 鍖呰锛堥噸瑕侊級 +================================================================================ + +1. 杈呭姪鍑芥暟搴斾娇鐢?try-catch 鍖呰闃叉宕╂簝 + - 鍦?Android 绔紝绫诲瀷杞崲鍙兘鎶涘嚭寮傚父 + - 浣跨敤 try-catch 鍖呰鍙互闃叉鏁翠釜搴旂敤宕╂簝 + - 绀轰緥锛? + ```typescript + 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. 浼樼偣 + - 闃叉鍗曚釜瀛楁瑙f瀽澶辫触瀵艰嚧鏁翠釜搴旂敤宕╂簝 + - 鎻愪緵璇︾粏鐨勯敊璇棩蹇椾究浜庤皟璇? + - 杩斿洖榛樿鍊间繚璇佸簲鐢ㄧ户缁繍琛? + +3. 鏈€浣冲疄璺? + - 鎵€鏈夎緟鍔╁嚱鏁伴兘搴斾娇鐢?try-catch 鍖呰 + - 鍦?catch 涓褰曢敊璇棩蹇? + - 杩斿洖鍚堢悊鐨勯粯璁ゅ€? + +================================================================================ +鍥涘崄涓冦€?026-02-27 鍟嗗搧瑙嗗浘娌℃湁 shop_id 瀛楁锛堥噸瑕侊級 +================================================================================ + +1. `ml_products_detail_view` 瑙嗗浘娌℃湁 `shop_id` 瀛楁 + - 鍟嗗搧閫氳繃 `merchant_id` 鍏宠仈鍟嗗/搴楅摵 + - 閿欒绀轰緥锛? + ```typescript + .select('id, name, shop_id') // 閿欒锛氳鍥炬病鏈?shop_id 瀛楁 + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, merchant_id') // 姝g‘锛氫娇鐢?merchant_id + ``` + +2. 鏁版嵁搴撳瓧娈靛搴斿叧绯? + - `ml_products` 琛細`merchant_id` 鍏宠仈鍟嗗 + - `ml_shops` 琛細`user_id` 绛変簬鍟嗗鐨?`merchant_id` + - 瑙嗗浘涓€氳繃 `merchant_id` JOIN `ml_shops` 鑾峰彇搴楅摵淇℃伅 + +3. 鏈€浣冲疄璺? + - 鏌ヨ鍟嗗搧鏃朵娇鐢?`merchant_id` 鑰岄潪 `shop_id` + - 鍙傝€?`CONSUMER_DB_DOC.md` 纭瀛楁鍚? + +================================================================================ +鍥涘崄鍏€?026-02-27 any 绫诲瀷涓嶆敮鎸佺储寮曡闂紙閲嶈锛? +================================================================================ + +1. UTS Android 涓?`any` 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 涓嶈兘浣跨敤 `obj[key]` 璇硶璁块棶 `any` 绫诲瀷瀵硅薄鐨勫睘鎬? + - 閿欒绀轰緥锛? + ```typescript + function safeGetString(obj: any, key: string): string { + const val = obj[key] // 閿欒锛歎nresolved reference + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + function safeGetString(obj: UTSJSONObject, key: string): string { + const val = obj.get(key) // 姝g‘锛氫娇鐢?UTSJSONObject 鐨?get 鏂规硶 + } + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch" + - 鍘熷洜锛歚any` 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 瑙e喅锛氬厛灏嗗璞¤浆鎹负 `UTSJSONObject`锛屽啀浣跨敤 `.get()` 鏂规硶 + +3. 鏈€浣冲疄璺? + - 浣跨敤 `toUTSJSONObject()` 鍑芥暟灏?`any` 杞崲涓?`UTSJSONObject` + - 浣跨敤 `.get()`銆乣.getString()`銆乣.getNumber()` 绛夋柟娉曡闂睘鎬? + - 杈呭姪鍑芥暟鍙傛暟绫诲瀷搴斾负 `UTSJSONObject` 鑰岄潪 `any` + +================================================================================ +鍥涘崄涔濄€?026-02-27 浣跨敤 getString/getNumber/getBoolean 鏂规硶锛堥噸瑕侊級 +================================================================================ + +1. UTSJSONObject 鎻愪緵浜嗙被鍨嬪畨鍏ㄧ殑璁块棶鏂规硶 + - `getString(key)` - 鐩存帴杩斿洖瀛楃涓叉垨 null + - `getNumber(key)` - 鐩存帴杩斿洖鏁板€兼垨 null + - `getBoolean(key)` - 鐩存帴杩斿洖甯冨皵鍊兼垨 null + - `getArray(key)` - 鐩存帴杩斿洖鏁扮粍鎴?null + - 杩欎簺鏂规硶姣?`.get()` 鏇村畨鍏紝浼氳嚜鍔ㄨ繘琛岀被鍨嬭浆鎹? + +2. 鎺ㄨ崘鐢ㄦ硶 + ```typescript + // 鎺ㄨ崘浣跨敤 + 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 鍧椾腑澶勭悊寮傚父 + +================================================================================ +鍥涘崄涓€銆?026-02-27 item as UTSJSONObject 涓嶄細娣诲姞鏂规硶锛堥噸瑕侊級 +================================================================================ + +1. `item as UTSJSONObject` 涓嶄細璁╁璞¤幏寰?`getString` 绛夋柟娉? + - 鐩存帴浣跨敤 `as UTSJSONObject` 鍙槸绫诲瀷鏂█锛屼笉浼氭敼鍙樿繍琛屾椂瀵硅薄 + - 閿欒绀轰緥锛? + ```typescript + const item = rawList[i] + const prodObj = item as UTSJSONObject // 閿欒锛歡etString 涓嶅瓨鍦? + const id = prodObj.getString('id') // 杩愯鏃堕敊璇? + ``` + - 姝g‘绀轰緥锛? + ```typescript + const item = rawList[i] + const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject + const id = prodObj.getString('id') ?? '' // 姝g‘ + ``` + +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?getString is not a function" + - 鍘熷洜锛氬璞℃病鏈夋纭浆鎹负 UTSJSONObject + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜杞崲 + +3. 鏈€浣冲疄璺? + - 瀵逛簬浠庢暟鎹簱/API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? ================================================================================ ================================================================================ + + +================================================================================ +二十八、2026-03-02 自定义导航栏 Android 端适配(重要) +================================================================================ + +1. .smart-navbar 样式规范 + - 问题:在 Android 端沉浸式导航栏中,若 .smart-navbar 设置了 paddingTop: statusBarHeight,且 flex-direction 未显式设置为 column,内部元素(标题、按钮)可能会错位。 + - 修复:.smart-navbar 必须显式设置 flex-direction: column,内部内容容器 .nav-container 设置为 flex-direction: row。 + - 正确示例: + ```css + .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; + } + ``` + + +================================================================================ +二十八、2026-03-02 自定义导航栏 Android 端适配(重要) +================================================================================ + +1. .smart-navbar 样式规范 + - 问题:在 Android 端沉浸式导航栏中,若 .smart-navbar 设置了 paddingTop: statusBarHeight,且 flex-direction 未显式设置为 column,内部元素(标题、按钮)可能会错位。 + - 修复:.smart-navbar 必须显式设置 flex-direction: column,内部内容容器 .nav-container 设置为 flex-direction: row。 + - 正确示例: + ```css + .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; + } + ``` diff --git a/pages/mall/consumer/doc/uts_final.txt b/pages/mall/consumer/doc/uts_final.txt new file mode 100644 index 00000000..1609696a --- /dev/null +++ b/pages/mall/consumer/doc/uts_final.txt @@ -0,0 +1,2334 @@ +================================================================================ + UTS-Android 鍏煎鎬у紑鍙戣鑼? +================================================================================ +> 浠ヤ笅涓?uni-app-x (UTS) Android 绔紑鍙戝父瑙佹敞鎰忎簨椤逛笌韪╁潙鐐癸紝寤鸿鎵€鏈夊紑鍙戞垚鍛橀伒寰細 + +================================================================================ +涓€銆佸熀纭€璇硶瑙勮寖 +================================================================================ + +1. 鍙橀噺澹版槑 + - 鍙兘浣跨敤 let 鍜?const锛屼笉鑳戒娇鐢?var + - 鍙橀噺澹版槑蹇呴』鏈夋樉寮忕被鍨嬫垨鍒濆鍖栧€? + - 涓嶆敮鎸?undefined 绫诲瀷锛屽彉閲忔湭璧嬪€煎氨鏄?null + - 涓嶆敮鎸?undefined 鍏抽敭瀛楋紝鍒ゆ柇鏄惁瀛樺湪瑕佺敤 != null + +2. 绫诲瀷瀹氫箟 + - 鍙€傚悎杞?type锛屼笉閫傚悎浣跨敤 interface锛坕nterface 鍦?kotlin/swift 涓彟鏈変笉鍚岋級 + - 涓嶆敮鎸?Intersection Type锛堜氦鍙夌被鍨嬶級 + - 涓嶆敮鎸?Index Signature锛堢储寮曠鍚嶏級 + - 绫诲瀷鎺ㄦ柇涓ユ牸锛屽繀瑕佹椂鐢?as Type 鏄庣‘绫诲瀷 + - 涓嶆敮鎸佸唴鑱斿璞$被鍨嬶紙Object Literal Type锛夛紝闇€瑕佸崟鐙畾涔?type + +3. 鍑芥暟瀹氫箟 + - 鍑芥暟蹇呴』鍦ㄤ娇鐢ㄥ墠瀹氫箟锛堜笉鏀寔鍑芥暟鎻愬崌锛? + - 鍦?setup 妯″紡涓嬶紝璋冪敤鐨勫嚱鏁板繀椤诲湪璋冪敤涔嬪墠瀹氫箟 + - 渚濊禆鍏崇郴闇€瑕佹槑纭細琚皟鐢ㄧ殑鍑芥暟蹇呴』鍏堝畾涔? + - 杩欎笌 JavaScript 鐨勫嚱鏁版彁鍗囪涓轰笉鍚岋紝UTS 鏇存帴杩?C/Java 鐨勭紪璇戞柟寮? + +4. 寰幆 + - for 寰幆鐨?i 蹇呴』鍐欐槑绫诲瀷锛歭et i: Int = 0 + - 涓嶈鐢?forEach銆乵ap锛屾暟缁勯亶鍘嗙敤 for 寰幆 + - 宓屽鐨勬暟缁勬柟娉曡皟鐢ㄥ彲鑳藉鑷寸被鍨嬫帹鏂け璐ワ紝搴旀敼鐢?for 寰幆 + +================================================================================ +浜屻€佺被鍨嬩笌瀵硅薄璁块棶 +================================================================================ + +1. any 绫诲瀷璁块棶 + - 涓嶈兘鐩存帴璁块棶 any 绫诲瀷瀵硅薄鐨勫睘鎬? + - 闇€瑕佸皢瀵硅薄杞崲涓?UTSJSONObject 绫诲瀷鍚庝娇鐢?getString()銆乬etNumber() 绛夋柟娉曡闂睘鎬? + - any 绫诲瀷灞炴€ц闂渶杞崲涓?Record 鍚庣敤绱㈠紩璁块棶 + - 浣跨敤绱㈠紩璁块棶灞炴€ф椂锛屾帹鑽愪娇鐢ㄦ柟鎷彿璇硶 obj['property'] 鑰岄潪鐐硅娉?obj.property + - any 绫诲瀷涓嶆敮鎸佺储寮曡闂?obj['key']锛屽繀椤诲厛杞崲涓?UTSJSONObject + +2. UTSJSONObject 浣跨敤 + - 鐢?utils/utis 涓嬬殑 UTSJSONObject 鍋氱被鍨嬭浆鎹? + - 涓嶈鐢?safeget锛屽彧瑕?UTSJSONObject 灏卞ソ浜? + - 闇€瑕佸垱寤哄姩鎬佸璞℃椂锛屽簲浣跨敤 new UTSJSONObject() 鐒跺悗璋冪敤 .set() 鏂规硶 + - 瀵逛簬 type 瀹氫箟鐨勫璞$被鍨嬶紝鍚屾牱闇€瑕佷娇鐢?UTSJSONObject + - 浣跨敤 getString()銆乬etNumber() 鏂规硶鑾峰彇灞炴€у€? + +3. 鏁扮粍绫诲瀷 + - 鏁扮粍绫诲瀷寤鸿鍐欐垚 Array锛屼笉瑕佺敤 Type[] 绠€鍐? + - 绌烘暟缁勯渶瑕佹槑纭寚瀹氱被鍨嬶紝濡?[] as string[] + - 鏁扮粍鍏冪礌闇€瑕佹槑纭殑绫诲瀷瀹氫箟鎵嶈兘鍦ㄦā鏉夸腑姝g‘璁块棶灞炴€? + - 瀵逛簬 any[] 鎴?reactive 鏁扮粍锛岃闂厓绱犲睘鎬ф椂闇€瑕佸厛杞崲涓?Record 鎴?any[] + +4. 瀵硅薄鎿嶄綔 + - 涓嶆敮鎸?Object.keys()銆丱bject.values()銆丱bject.entries() + - 涓嶆敮鎸?Record 瀵硅薄瀛楅潰閲忚娉? + - 瀵硅薄瀛楅潰閲?{...} 鍙兘鐢ㄤ簬鏋勯€犵被鍨嬶紙class锛夛紝涓嶈兘鐢ㄤ簬鎺ュ彛锛坕nterface锛? + - reactive 瀵硅薄鍦?UTS 涓笉鏀寔绱㈠紩鍣ㄨ祴鍊兼搷浣? + +================================================================================ +涓夈€佹潯浠跺垽鏂笌閫昏緫杩愮畻 +================================================================================ + +1. if 鏉′欢 + - if 鍒ゆ柇鍙帴鍙?boolean 绫诲瀷锛屼笉鑳芥槸鍏朵粬绫诲瀷鐨勫€? + - 鍒ゆ柇绌鸿鐢?!== null锛屼笉鑳界敤 !鍙橀噺锛坲ts android 涓嶆敮鎸?!鍦ㄥ彉閲忓墠闈㈢殑鍒ゆ柇绌烘柟寮忥級 + - 妯℃澘涓殑 || 杩愮畻绗﹀乏杈瑰繀椤绘槸 boolean 绫诲瀷 + - 鍙┖绫诲瀷浣跨敤鍙€夐摼 ?. 鍜岀┖鍊煎悎骞??? + - 瀛楃涓插垽鏂┖瑕佺敤锛歷ariable != null && variable !== '' + +2. 閫昏緫杩愮畻绗? + - || 琛ㄧず閫昏緫鎴? + - && 琛ㄧず閫昏緫涓? + - ! 琛ㄧず閫昏緫闈烇紙浣?!鍙橀噺 涓嶆敮鎸佺敤浜庡垽鏂┖锛? + - ?? 琛ㄧず绌哄€煎悎骞惰繍绠楃锛堝綋宸︿晶涓?null 鏃惰繑鍥炲彸渚у€硷級 + - ts 鐨勪负绌哄垯浣跨敤榛樿鍊肩殑璇硶鍦?uts 涓笉鑳界敤 ||锛岃鐢??? 鏉ヤ唬鏇? + +================================================================================ +鍥涖€佺粍浠朵笌妯℃澘 +================================================================================ + +1. 琛ㄥ崟涓庤緭鍏? + - 琛ㄥ崟浼樺厛鐢?form 缁勪欢 + - 涓嶆敮鎸?uni-easyinput锛岀敤 input 浠f浛 + - 鏃堕棿閫夋嫨鐢?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 浠f浛 + - 涓嶆敮鎸?uni-datetime-picker锛岀敤 components/picker-date 鎴?components/picker-time 浠f浛 + - 涓嶆敮鎸?uni-icons + +4. 妯℃澘娉ㄦ剰浜嬮」 + - 璺?template 浜や簰鐨勫彉閲忓敖閲忕敤涓€缁村彉閲忥紙涓嶈宓屽瀵硅薄锛? + - 妯℃澘涓彲绌虹被鍨嬪繀椤讳娇鐢??. 瀹夊叏璁块棶 + - 妯℃澘涓闂彲绌虹被鍨嬪睘鎬у墠蹇呴』鍏堝垽绌?v-if="order != null" + +================================================================================ +浜斻€丆SS 鏍峰紡闄愬埗 +================================================================================ + +1. 甯冨眬鏂瑰紡 + - 鍙敮鎸?display: flex + - 涓嶆敮鎸?display: grid + - 涓嶆敮鎸?gap + - 涓嶆敮鎸?table銆乬rid銆乬rid-template-columns + +2. 鍗曚綅涓庤绠? + - 涓嶆敮鎸?calc() + - 涓嶆敮鎸佺殑鍗曚綅: vh + - property value `100%` is not supported for min-height (supported values are: number|pixel) + - property value `calc(33.33% - 10px)` is not supported for min-width + +3. 閫夋嫨鍣? + - [APP-ANDROID] 涓嶆敮鎸佷吉绫婚€夋嫨鍣? + - [APP-IOS] 涓嶆敮鎸佷吉绫婚€夋嫨鍣? + - ERROR: Selector `.login-button[disabled]` is not supported. uvue only support classname selector + +4. 鍏朵粬鏍峰紡 + - WARNING: `backdrop-filter` is not a standard property name + - style property `white-space` is only supported on `| + + + + ``` + +3. Map 绫诲瀷璁块棶闄愬埗 + - UTS Android 涓嶆敮鎸?Map 绫诲瀷鐨?get() 鏂规硶璁块棶灞炴€? + - 搴旂粺涓€浣跨敤 UTSJSONObject + - 绀轰緥锛? + ```typescript + // 閿欒 + const m = item as Map + const idVal = m.get('id') + + // 姝g‘ + const itemObj = item as UTSJSONObject + const id = itemObj.getString('id') ?? '' + ``` + +4. 鍑芥暟鍐呭彇鍙嶆搷浣滅 + - if (!isValid.value) 涓嶆敮鎸佸彇鍙? + - 浣跨敤 if (isValid.value === false) + - 绀轰緥锛? + ```typescript + // 閿欒 + if (!isValid.value) return + + // 姝g‘ + if (isValid.value === false) return + ``` + +================================================================================ +浜屽崄涓€銆?026-02-25 user 鐩綍椤甸潰淇璁板綍 +================================================================================ + +1. change-password.uvue 淇 + - 闂锛?oldPassword.value 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?oldPassword.value == '' 鏄惧紡鍒ゆ柇 + - 闂锛歝onst { error } = await ... 瑙f瀯璧嬪€间笉鏀寔 + - 淇锛氭敼涓?const result = await ... 鐒跺悗 result.error 璁块棶 + +2. login.uvue 淇 + - 闂锛歛s unknown as number 鍙岄噸绫诲瀷杞崲涓嶆敮鎸? + - 淇锛氭敼涓?as number 鍗曚竴绫诲瀷杞崲 + - 闂锛歵ypeof err === 'object' 涓嶆敮鎸? + - 淇锛氫娇鐢?try-catch 鍖呰9绫诲瀷杞崲 + +3. forgot-password.uvue 淇 + - 闂锛?emailRegex.test(this.email) 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?emailRegex.test(this.email) == false + - 闂锛歵ypeof err === 'object' 涓嶆敮鎸? + - 淇锛氫娇鐢?try-catch 鍖呰9绫诲瀷杞崲 + +4. register.uvue 淇 + - 闂锛?protocol.value 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?protocol.value == false + - 闂锛?validateEmail() 绛夊彇鍙嶆搷浣滀笉鏀寔 + - 淇锛氭敼涓?validateEmail() == false + +================================================================================ +浜屽崄浜屻€佸父瑙佷慨澶嶆ā寮忛€熸煡 +================================================================================ + +1. 鍙栧弽鎿嶄綔淇妯″紡 + ```typescript + // 閿欒 + if (!variable) { ... } + if (!isValid.value) { ... } + if (!validate()) { ... } + + // 姝g‘ - 鏍规嵁绫诲瀷閫夋嫨 + if (variable == null || variable == '') { ... } // 瀛楃涓插垽绌? + if (isValid.value == false) { ... } // 甯冨皵鍊煎彇鍙? + if (validate() == false) { ... } // 鍑芥暟杩斿洖甯冨皵鍊煎彇鍙? + ``` + +2. 瑙f瀯璧嬪€间慨澶嶆ā寮? + ```typescript + // 閿欒 + const { data, error } = await someAsyncCall() + + // 姝g‘ + const result = await someAsyncCall() + const data = result.data + const error = result.error + ``` + +3. typeof 妫€鏌ヤ慨澶嶆ā寮? + ```typescript + // 閿欒 + if (typeof err === 'object') { ... } + if (typeof xxx === 'function') { ... } + + // 姝g‘ + try { + const e = err as Error + // 浣跨敤 e + } catch (e2) { + // 澶勭悊杞崲澶辫触 + } + ``` + +4. as unknown as 淇妯″紡 + ```typescript + // 閿欒 + const timer = setInterval(...) as unknown as number + + // 姝g‘ + const timer = setInterval(...) as number + ``` + +================================================================================ +浜屽崄涓夈€侀敊璇鐞嗘渶浣冲疄璺? +================================================================================ + +1. 缁熶竴閿欒澶勭悊妯″紡 + ```typescript + 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. 鍙┖绫诲瀷瀹夊叏璁块棶 + ```typescript + // 瀹夊叏璁块棶瀵硅薄灞炴€? + const value = obj != null ? obj.property : null + + // 瀹夊叏璋冪敤鏂规硶 + const result = obj != null ? obj.method() : null + ``` + +3. 鏁扮粍瀹夊叏璁块棶 + ```typescript + // 瀹夊叏璁块棶鏁扮粍鍏冪礌 + if (arr.length > index) { + const item = arr[index] + // 浣跨敤 item + } + ``` + +================================================================================ +鏂囨。缁撴潫 +================================================================================ + +================================================================================ +浜屽崄鍥涖€?026-02-27 鍑芥暟鍙€夊弬鏁伴檺鍒讹紙閲嶈锛? +================================================================================ + +1. 鍙€夊弬鏁颁笉鑳借烦杩囦紶閫? + - UTS Android 涓嶆敮鎸佽烦杩囧彲閫夊弬鏁颁紶閫? + - 濡傛灉鍑芥暟鏈夊涓彲閫夊弬鏁帮紝蹇呴』鎸夐『搴忎紶閫掓墍鏈夊弬鏁? + - 閿欒绀轰緥锛? + ```typescript + // 鍑芥暟瀹氫箟 + async addToCart(productId: string, quantity: number = 1, skuId?: string, merchantId?: string): Promise + + // 閿欒璋冪敤 - 璺宠繃浜?merchantId 鍙傛暟 + await supabaseService.addToCart(productId, 1, '') + // 缂栬瘧閿欒锛歂o value passed for parameter 'merchantId' + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 鏂规1锛氱粰鍙€夊弬鏁版坊鍔犻粯璁ゅ€? + async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise + + // 鏂规2锛氳皟鐢ㄦ椂浼犻€掓墍鏈夊弬鏁? + await supabaseService.addToCart(productId, 1, '', '') + ``` + +2. 鍙€夊弬鏁板畾涔夎鑼? + - 鎺ㄨ崘浣跨敤 `param: Type = defaultValue` 鑰岄潪 `param?: Type` + - `param?: Type` 鍦?Android 绔皟鐢ㄦ椂浠嶉渶浼犻€掑弬鏁? + - `param: Type = defaultValue` 鍙互鍦ㄤ笉浼犲弬鏃朵娇鐢ㄩ粯璁ゅ€? + - 绀轰緥锛? + ```typescript + // 涓嶆帹鑽?- 璋冪敤鏃朵粛闇€浼犻€掑弬鏁? + 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 绔笉鑳借烦杩? + - 瑙e喅锛? + 1. 淇敼鍑芥暟绛惧悕锛屼娇鐢ㄩ粯璁ゅ€?`param: Type = defaultValue` + 2. 璋冪敤鏃朵紶閫掓墍鏈夊弬鏁? + +4. 鏈€浣冲疄璺? + - 瀵逛簬鏈夊涓彲閫夊弬鏁扮殑鍑芥暟锛岀粺涓€浣跨敤榛樿鍊艰娉? + - 璋冪敤鏃舵樉寮忎紶閫掓墍鏈夊弬鏁帮紝閬垮厤渚濊禆鍙€夊弬鏁拌烦杩? + - 鍦ㄦ湇鍔″眰鍑芥暟瀹氫箟涓紝浼樺厛浣跨敤 `= ''` 鎴?`= 0` 绛夐粯璁ゅ€? + +================================================================================ +浜屽崄浜斻€?026-02-27 妯℃澘涓殑闈炵┖鏂█闄愬埗锛堥噸瑕侊級 +================================================================================ + +1. 妯℃澘涓笉鏀寔闈炵┖鏂█鎿嶄綔绗?`!` + - UTS Android 妯℃澘涓笉鑳戒娇鐢?`variable!` 闈炵┖鏂█ + - 閿欒绀轰緥锛? + ```html + + ``` + - 姝g‘绀轰緥锛? + ```html + + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鍙傛暟绫诲瀷涓嶅尮閰嶏細瀹為檯绫诲瀷涓?'Number?'锛岄鏈熺被鍨嬩负 'Number'" + - 鍘熷洜锛氭ā鏉夸腑浣跨敤闈炵┖鏂█ `!` 涓嶈鏀寔 + - 瑙e喅锛氱Щ闄ら潪绌烘柇瑷€ `!`锛岀洿鎺ヤ娇鐢ㄥ彉閲忚繘琛屾瘮杈? + +3. 鏈€浣冲疄璺? + - 鍦ㄦā鏉夸腑锛屽厛鐢?`!= null` 鍒ゆ柇鍙┖绫诲瀷锛岀劧鍚庣洿鎺ヤ娇鐢ㄥ彉閲? + - UTS 缂栬瘧鍣ㄤ細鍦?`!= null` 鍒ゆ柇鍚庤嚜鍔ㄨ瘑鍒彉閲忎负闈炵┖绫诲瀷 + +================================================================================ +浜屽崄鍏€?026-02-27 鏈鍏ョ被鍨嬬殑澶勭悊锛堥噸瑕侊級 +================================================================================ + +1. 鏈鍏ョ殑绫诲瀷涓嶈兘鐩存帴浣跨敤 + - 鍦ㄩ〉闈腑浣跨敤鐨勭被鍨嬪繀椤诲厛瀵煎叆鎴栦娇鐢?UTSJSONObject 鏇夸唬 + - 閿欒绀轰緥锛? + ```typescript + // Shop 绫诲瀷鏈鍏? + const s = shopRespData[i] as Shop + const id = s.id // 鎵句笉鍒板悕绉?"id" + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 浣跨敤 UTSJSONObject + const s = shopRespData[i] as UTSJSONObject + const id = s.getString('id') ?? '' + const name = s.getString('shop_name') ?? '' + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?'XXX'" + - 鍘熷洜锛氱被鍨嬫湭瀵煎叆鎴栫被鍨嬪畾涔変笉瀛樺湪 + - 瑙e喅锛? + 1. 瀵煎叆闇€瑕佺殑绫诲瀷锛歚import { Shop } from '@/utils/supabaseService.uts'` + 2. 浣跨敤 UTSJSONObject 鏇夸唬锛歚as UTSJSONObject` 鐒跺悗鐢?`getString()`銆乣getNumber()` 璁块棶灞炴€? + +3. 鏈€浣冲疄璺? + - 瀵逛簬绠€鍗曠殑鏁版嵁杞崲锛屾帹鑽愪娇鐢?UTSJSONObject + - 閬垮厤鍦ㄥ涓枃浠朵腑閲嶅瀹氫箟鐩稿悓鐨勭被鍨? + - 濡傛灉闇€瑕佺被鍨嬪畨鍏紝浠庢湇鍔″眰瀵煎叆绫诲瀷瀹氫箟 + +================================================================================ +浜屽崄涓冦€?026-02-27 鏈嶅姟灞傛暟鎹瓧娈靛畬鏁存€э紙閲嶈锛? +================================================================================ + +1. 鏈嶅姟灞傝繑鍥炴暟鎹繀椤诲寘鍚墍鏈夊繀瑕佸瓧娈? + - 浠庢暟鎹簱鑾峰彇鏁版嵁鏃讹紝蹇呴』姝g‘鏄犲皠鎵€鏈夐渶瑕佺殑瀛楁 + - 閿欒绀轰緥锛? + ```typescript + const product: Product = { + id: prodObj.getString('id') ?? '', + name: prodObj.getString('name') ?? '', + // 閿欒锛歮erchant_id 纭紪鐮佷负绌哄瓧绗︿覆 + merchant_id: '' + } as Product + ``` + - 姝g‘绀轰緥锛? + ```typescript + const product: Product = { + id: prodObj.getString('id') ?? '', + name: prodObj.getString('name') ?? '', + // 姝g‘锛氫粠鏁版嵁搴撹幏鍙?merchant_id + merchant_id: prodObj.getString('merchant_id') ?? '' + } as Product + ``` + +2. 璋冪敤鏈嶅姟灞傛柟娉曟椂蹇呴』浼犻€掑畬鏁村弬鏁? + - 椤甸潰璋冪敤鏈嶅姟灞傛柟娉曟椂锛岄渶瑕佷紶閫掓墍鏈夊繀瑕佸弬鏁? + - 閿欒绀轰緥锛? + ```typescript + // 閿欒锛歮erchant_id 浼犵┖瀛楃涓? + await supabaseService.addToCart(productId, 1, '', '') + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 姝g‘锛氫粠鍟嗗搧瀵硅薄鑾峰彇 merchant_id + const merchantId = product.merchant_id ?? '' + await supabaseService.addToCart(productId, 1, '', merchantId) + ``` + +3. 缂栬瘧閿欒鎻愮ず + - 闂琛ㄧ幇锛氭暟鎹坊鍔犲埌鏁版嵁搴撳け璐ワ紝鎴栨坊鍔犵殑鏁版嵁涓嶅畬鏁? + - 鍘熷洜锛氭湇鍔″眰鎴栭〉闈㈠眰缂哄皯蹇呰瀛楁鐨勪紶閫? + - 瑙e喅锛? + 1. 妫€鏌ユ湇鍔″眰鏁版嵁鏄犲皠鏄惁瀹屾暣 + 2. 妫€鏌ラ〉闈㈣皟鐢ㄦ椂鏄惁浼犻€掍簡鎵€鏈夊繀瑕佸弬鏁? + +4. 鏈€浣冲疄璺? + - 鏈嶅姟灞傛柟娉曡繑鍥炵殑瀵硅薄搴斿寘鍚暟鎹簱瑙嗗浘鐨勬墍鏈夊瓧娈? + - 椤甸潰璋冪敤鏈嶅姟灞傛柟娉曟椂锛屽簲浠庢暟鎹璞′腑鑾峰彇骞朵紶閫掓墍鏈夊弬鏁? + - 瀵逛簬鍏宠仈鏁版嵁锛堝 merchant_id锛夛紝纭繚鍦ㄦ暟鎹姞杞芥椂涓€骞惰幏鍙? + +================================================================================ +浜屽崄鍏€?026-02-27 妯℃澘涓殑闈炶繍绠楃闄愬埗锛堥噸瑕侊級 +================================================================================ + +1. 妯℃澘涓笉鏀寔 `!` 闈炶繍绠楃 + - UTS Android 妯℃澘涓笉鑳戒娇鐢?`!variable` 闈炶繍绠楃 + - 閿欒绀轰緥锛? + ```html + + ``` + - 姝g‘绀轰緥锛? + ```html + + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?not'" + - 鍘熷洜锛氭ā鏉夸腑涓嶆敮鎸侀潪杩愮畻绗?`!` + - 瑙e喅锛氫娇鐢ㄦ樉寮忕殑姣旇緝琛ㄨ揪寮忔浛浠? + +3. 鏈€浣冲疄璺? + - 浣跨敤 `== null` 鎴?`== ''` 妫€鏌ョ┖鍊? + - 浣跨敤 `!= null && != ''` 妫€鏌ラ潪绌哄€? + +================================================================================ +浜屽崄涔濄€?026-02-27 绱㈠紩璁块棶闄愬埗锛堥噸瑕侊級 +================================================================================ + +1. 涓嶆敮鎸?`(obj as any)['key']` 绱㈠紩璁块棶鏂瑰紡 + - UTS Android 涓嶆敮鎸佸 any 绫诲瀷浣跨敤绱㈠紩璁块棶 + - 閿欒绀轰緥锛? + ```typescript + const detail = (e as any)['detail'] + val = detail['value'] ?? '' + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 鏂规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" + - 鍘熷洜锛歛ny 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鍚庝娇鐢?`.get()` 鏂规硶 + +3. 鏈€浣冲疄璺? + - 缁熶竴浣跨敤 UTSJSONObject 澶勭悊鍔ㄦ€佸璞? + - 浣跨敤 `.get()`銆乣.getString()`銆乣.getNumber()` 鏂规硶璁块棶灞炴€? + - 瀵逛簬澶嶆潅瀵硅薄锛屽厛鐢?`JSON.parse(JSON.stringify(obj))` 杞崲 + +================================================================================ +涓夊崄銆?026-02-27 瀛楃涓蹭笉鑳界洿鎺ヤ綔涓哄竷灏旀潯浠讹紙閲嶈锛? +================================================================================ + +1. 瀛楃涓蹭笉鑳界洿鎺ヤ綔涓?if 鏉′欢 + - UTS Android 涓嶆敮鎸佸皢瀛楃涓茬洿鎺ヤ綔涓哄竷灏旀潯浠跺垽鏂? + - 閿欒绀轰緥锛? + ```typescript + const paramId = '123' + if (paramId) { // 閿欒锛氬瓧绗︿覆涓嶈兘鐩存帴浣滀负甯冨皵鏉′欢 + // ... + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + const paramId = '123' + if (paramId != null && paramId != '') { // 姝g‘锛氭樉寮忓垽鏂? + // ... + } + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Condition type mismatch: inferred type is 'String' but 'Boolean' was expected" + - 鍘熷洜锛氬瓧绗︿覆绫诲瀷涓嶈兘鐩存帴浣滀负甯冨皵鏉′欢 + - 瑙e喅锛氫娇鐢ㄦ樉寮忕殑姣旇緝琛ㄨ揪寮? + +3. 鏈€浣冲疄璺? + - 浣跨敤 `!= null && != ''` 妫€鏌ュ瓧绗︿覆闈炵┖ + - 浣跨敤 `== null || == ''` 妫€鏌ュ瓧绗︿覆涓虹┖ + +================================================================================ +涓夊崄涓€銆?026-02-27 鍑芥暟瀹氫箟椤哄簭锛堥噸瑕侊級 +================================================================================ + +1. 鍑芥暟蹇呴』鍦ㄨ皟鐢ㄥ墠瀹氫箟 + - UTS Android 瑕佹眰鍑芥暟鍦ㄨ皟鐢ㄤ箣鍓嶅畬鎴愬畾涔? + - 杩欎笌 JavaScript 鐨勫嚱鏁版彁鍗囦笉鍚? + - 閿欒绀轰緥锛? + ```typescript + onMounted(() => { + loadData() // 閿欒锛歭oadData 杩樻湭瀹氫箟 + }) + + const loadData = async () => { + // ... + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + const loadData = async () => { + // ... + } + + onMounted(() => { + loadData() // 姝g‘锛歭oadData 宸插畾涔? + }) + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?xxx'" + - 鍘熷洜锛氬嚱鏁板湪璋冪敤鐐逛箣鍚庡畾涔? + - 瑙e喅锛氬皢鍑芥暟瀹氫箟绉诲埌璋冪敤涔嬪墠 + +3. 鏈€浣冲疄璺? + - 灏嗘墍鏈夊嚱鏁板畾涔夋斁鍦ㄧ敓鍛藉懆鏈熼挬瀛愶紙onMounted銆乷nShow 绛夛級涔嬪墠 + - 鎸変緷璧栧叧绯绘帓搴忓嚱鏁板畾涔夐『搴? + +================================================================================ +涓夊崄浜屻€?026-02-27 鑱斿悎绫诲瀷灞炴€ц闂紙閲嶈锛? +================================================================================ + +1. 鑱斿悎绫诲瀷涓嶈兘鐩存帴璁块棶灞炴€? + - 褰撳弬鏁扮被鍨嬩负鑱斿悎绫诲瀷锛堝 `A | B`锛夋椂锛屼笉鑳界洿鎺ヨ闂睘鎬? + - 閿欒绀轰緥锛? + ```typescript + type A = { id: string, name: string } + type B = { id: string, title: string } + + const foo = (item: A | B) => { + const id = item.id // 閿欒锛氳仈鍚堢被鍨嬩笉鑳界洿鎺ヨ闂睘鎬? + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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'" + - 鍘熷洜锛氳仈鍚堢被鍨嬬殑灞炴€ц闂彈闄? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鎴栦娇鐢ㄧ被鍨嬪畧鍗? + +3. 鏈€浣冲疄璺? + - 瀵逛簬鑱斿悎绫诲瀷鍙傛暟锛岀粺涓€杞崲涓?UTSJSONObject 澶勭悊 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? + +================================================================================ +涓夊崄涓夈€?026-02-27 any 绫诲瀷鍙橀噺涓嶈兘璧嬪€间负 null锛堥噸瑕侊級 +================================================================================ + +1. any 绫诲瀷鍙橀噺涓嶈兘璧嬪€间负 null + - UTS Android 涓?`any` 绫诲瀷涓嶈兘璧嬪€间负 `null` + - 閿欒绀轰緥锛? + ```typescript + let res: any = null // 閿欒锛歂ull cannot be a value of a non-null type 'Any' + ``` + - 姝g‘绀轰緥锛? + ```typescript + let res: any = {} // 姝g‘锛氫娇鐢ㄧ┖瀵硅薄 + // 鎴栬€? + let res: any | null = null // 浣跨敤鑱斿悎绫诲瀷 + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Null cannot be a value of a non-null type 'Any'" + - 鍘熷洜锛歛ny 绫诲瀷涓嶅厑璁?null 鍊? + - 瑙e喅锛氫娇鐢ㄧ┖瀵硅薄 `{}` 鎴栬仈鍚堢被鍨?`any | null` + +================================================================================ +涓夊崄鍥涖€?026-02-27 瀵硅薄瀛楅潰閲忕被鍨嬫帹鏂棶棰橈紙閲嶈锛? +================================================================================ + +1. 瀵硅薄瀛楅潰閲忕洿鎺ヨ祴鍊肩粰 ref 鍙兘绫诲瀷涓嶅尮閰? + - 褰撳璞″瓧闈㈤噺鐩存帴璧嬪€肩粰鐗瑰畾绫诲瀷鐨?ref 鏃讹紝鍙兘鎶ョ被鍨嬩笉鍖归厤閿欒 + - 閿欒绀轰緥锛? + ```typescript + merchant.value = { + id: shop.id, + user_id: shop.merchant_id, + // ... + } // 閿欒锛欰ssignment type mismatch + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 鏂规1锛氭樉寮忓0鏄庣被鍨? + 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" + - 鍘熷洜锛氬璞″瓧闈㈤噺琚帹鏂负鍖垮悕绫诲瀷 + - 瑙e喅锛氭樉寮忓0鏄庣被鍨嬫垨浣跨敤绫诲瀷鏂█ + +================================================================================ +涓夊崄浜斻€?026-02-27 any 绫诲瀷涓嶈兘鐩存帴璁块棶灞炴€э紙閲嶈锛? +================================================================================ + +1. any 绫诲瀷鍙傛暟涓嶈兘鐩存帴璁块棶灞炴€? + - 鍦?map銆乫orEach 绛夊洖璋冧腑锛宎ny 绫诲瀷鐨勫弬鏁颁笉鑳界洿鎺ヨ闂睘鎬? + - 閿欒绀轰緥锛? + ```typescript + const list = rawList.map((item): ProductType => { + const id = item.id // 閿欒锛氭壘涓嶅埌鍚嶇О"id" + const name = item.name // 閿欒锛氭壘涓嶅埌鍚嶇О"name" + }) + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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'" + - 鍘熷洜锛歛ny 绫诲瀷鐨勫睘鎬ц闂彈闄? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鍚庝娇鐢?`.getString()` 绛夋柟娉? + +================================================================================ +涓夊崄鍏€?026-02-27 绫诲瀷鏂█涓嶄細娣诲姞鏂规硶锛堥噸瑕侊級 +================================================================================ + +1. `as UTSJSONObject` 涓嶄細缁欏璞℃坊鍔犳柟娉? + - 浣跨敤 `as UTSJSONObject` 鍙槸绫诲瀷鏂█锛屼笉浼氳鏅€氬璞¤幏寰?`getString` 绛夋柟娉? + - 閿欒绀轰緥锛? + ```typescript + const profileObj = profile as UTSJSONObject + const id = profileObj.getString('user_id') // 杩愯鏃堕敊璇細getString is not a function + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 蹇呴』浣跨敤 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" + - 鍘熷洜锛氱被鍨嬫柇瑷€鍙槸缂栬瘧鏃惰涓猴紝涓嶄細鏀瑰彉杩愯鏃跺璞$殑鏂规硶 + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜鐪熸鐨勫璞¤浆鎹? + +3. 鏈€浣冲疄璺? + - 瀵逛簬浠?API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `instanceof UTSJSONObject` 妫€鏌ュ璞$被鍨? + - 涓嶈渚濊禆 `as` 绫诲瀷鏂█鏉ユ坊鍔犳柟娉? + +================================================================================ +涓夊崄涓冦€?026-02-27 绫诲瀷蹇呴』鍖呭惈鎵€鏈夊繀濉瓧娈碉紙閲嶈锛? +================================================================================ + +1. 鍒涘缓绫诲瀷瀹炰緥鏃跺繀椤诲寘鍚墍鏈夊繀濉瓧娈? + - UTS 绫诲瀷瀹氫箟涓殑闈炲彲閫夊瓧娈碉紙涓嶅甫 `?`锛夐兘鏄繀濉殑 + - 閿欒绀轰緥锛? + ```typescript + export type ProductType = { + id: string + merchant_id: string // 蹇呭~ + category_id: string // 蹇呭~ + name: string + // ... + } + + // 閿欒锛氱己灏?merchant_id銆乧ategory_id 绛夊繀濉瓧娈? + return { + id: item.id, + name: item.name, + price: item.price + } as ProductType // 杩愯鏃堕敊璇細missing required property + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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" + - 鍘熷洜锛氱被鍨嬪畾涔変腑鏈夊繀濉瓧娈垫湭鎻愪緵 + - 瑙e喅锛? + 1. 妫€鏌ョ被鍨嬪畾涔夛紝纭鎵€鏈夊繀濉瓧娈? + 2. 涓烘墍鏈夊繀濉瓧娈垫彁渚涘€硷紝鍗充娇鏄┖瀛楃涓叉垨榛樿鍊? + +3. 鏈€浣冲疄璺? + - 鏌ョ湅绫诲瀷瀹氫箟锛岀‘璁ゅ摢浜涘瓧娈垫槸蹇呭~鐨勶紙涓嶅甫 `?`锛? + - 浣跨敤 `??` 杩愮畻绗︽彁渚涢粯璁ゅ€? + - 瀵逛簬鍙€夊瓧娈碉紝鍙互涓嶆彁渚涙垨浣跨敤 `null` + +================================================================================ +涓夊崄鍏€?026-02-27 鍥炶皟鍑芥暟涓嶈兘鏄?async锛堥噸瑕侊級 +================================================================================ + +1. API 鍥炶皟鍑芥暟涓嶈兘浣跨敤 async 淇グ + - uni API 鐨勫洖璋冨嚱鏁帮紙濡?showModal 鐨?success锛変笉鏀寔 async 鍑芥暟 + - 閿欒绀轰緥锛? + ```typescript + uni.showModal({ + title: '纭', + content: '纭畾瑕佸垹闄ゅ悧锛?, + success: async (res) => { // 閿欒锛氬洖璋冨嚱鏁颁笉鑳芥槸 async + if (res.confirm) { + const result = await someAsyncFunction() + } + } + }) + ``` + - 姝g‘绀轰緥锛? + ```typescript + uni.showModal({ + title: '纭', + content: '纭畾瑕佸垹闄ゅ悧锛?, + success: (res) => { + if (res.confirm) { + // 浣跨敤 Promise.then() 浠f浛 await + someAsyncFunction().then((result) => { + // 澶勭悊缁撴灉 + }) + } + } + }) + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鍙傛暟绫诲瀷涓嶅尮閰嶏細瀹為檯绫诲瀷涓?'Function1<..., UTSPromise>'锛岄鏈熺被鍨嬩负 'Function1<..., Unit>?'" + - 鍘熷洜锛氬洖璋冨嚱鏁拌繑鍥?Promise 鑰岄潪 void + - 瑙e喅锛氫娇鐢?`.then()` 浠f浛 `await` + +3. 鏈€浣冲疄璺? + - 鍦ㄥ洖璋冨嚱鏁颁腑浣跨敤 `.then()` 澶勭悊寮傛鎿嶄綔 + - 灏嗗紓姝ラ€昏緫灏佽涓哄崟鐙殑鍑芥暟锛屽湪鍥炶皟涓皟鐢? + +================================================================================ +涓夊崄涔濄€?026-02-27 绫诲瀷杞崲鍓嶅繀椤绘鏌ョ被鍨嬶紙閲嶈锛? +================================================================================ + +1. 浣跨敤 `as` 绫诲瀷杞崲鍓嶅繀椤绘鏌ュ疄闄呯被鍨? + - 鐩存帴浣跨敤 `as string` 杞崲鍙兘瀵艰嚧杩愯鏃剁被鍨嬭浆鎹㈠紓甯? + - 閿欒绀轰緥锛? + ```typescript + const idVal = item['id'] + const id = idVal as string // 閿欒锛氬鏋?idVal 鏄叾浠栫被鍨嬩細宕╂簝 + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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" + - 鍘熷洜锛氱洿鎺ョ被鍨嬭浆鎹㈡椂锛屽疄闄呯被鍨嬩笌鐩爣绫诲瀷涓嶅尮閰? + - 瑙e喅锛氫娇鐢?`typeof` 妫€鏌ョ被鍨嬪悗鍐嶈浆鎹? + +3. 鏈€浣冲疄璺? + - 浣跨敤 `typeof` 妫€鏌ョ被鍨? + - 浣跨敤 `!= null` 妫€鏌ョ┖鍊? + - 鎻愪緵榛樿鍊奸槻姝㈢┖鎸囬拡寮傚父 + +================================================================================ +鍥涘崄銆?026-02-27 UTSJSONObject 蹇呴』姝g‘杞崲锛堥噸瑕侊級 +================================================================================ + +1. `as UTSJSONObject` 涓嶄細娣诲姞鏂规硶 + - 浠庢暟鎹簱杩斿洖鐨勬暟鎹渶瑕佹纭浆鎹负 UTSJSONObject + - 閿欒绀轰緥锛? + ```typescript + const item = rawList[i] + const brandObj = item as UTSJSONObject // 閿欒锛歜randObj.getString 涓嶅瓨鍦? + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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 + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜杞崲 + +3. 鏈€浣冲疄璺? + - 瀵逛簬浠庢暟鎹簱/API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? + +================================================================================ +鍥涘崄浜屻€?026-02-27 getBoolean 鏂规硶鍙兘瀵艰嚧绫诲瀷杞崲寮傚父锛堥噸瑕侊級 +================================================================================ + +1. `UTSJSONObject.getBoolean()` 鍙兘瀵艰嚧绫诲瀷杞崲寮傚父 + - 褰撴暟鎹簱瀛楁绫诲瀷涓庨鏈熶笉绗︽椂锛宍getBoolean()` 鍙兘鎶涘嚭寮傚父 + - 閿欒绀轰緥锛? + ```typescript + const isFeatured = prodObj.getBoolean('is_featured') ?? false // 鍙兘鎶涘嚭寮傚父 + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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 鏂规硶鏈熸湜鐨勭被鍨嬩笉鍖归厤 + - 瑙e喅锛氫娇鐢?`.get()` 鏂规硶鑾峰彇鍘熷鍊硷紝鐒跺悗鎵嬪姩妫€鏌ョ被鍨? + +3. 鏈€浣冲疄璺? + - 閬垮厤浣跨敤 `.getBoolean()`锛屾敼鐢?`.get()` + `typeof` 妫€鏌? + - 鍦?SQL 鏌ヨ涓槑纭寚瀹氶渶瑕佺殑瀛楁锛岄伩鍏?`SELECT *` + - 瀵逛簬甯冨皵鍊硷紝浣跨敤 `typeof val == 'boolean'` 妫€鏌ョ被鍨? + +================================================================================ +鍥涘崄涓夈€?026-02-27 SELECT * 鍙兘瀵艰嚧绫诲瀷杞崲闂锛堥噸瑕侊級 +================================================================================ + +1. 閬垮厤浣跨敤 `SELECT *` 鏌ヨ鎵€鏈夊瓧娈? + - 鏁版嵁搴撳彲鑳藉寘鍚墠绔笉闇€瑕佺殑瀛楁锛屽鑷寸被鍨嬭浆鎹㈠紓甯? + - 閿欒绀轰緥锛? + ```typescript + .select('*') // 鍙兘杩斿洖鎰忓鐨勫瓧娈电被鍨? + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, description, base_price, market_price, main_image_url') + ``` + +2. 鏈€浣冲疄璺? + - 鍙煡璇㈤渶瑕佺殑瀛楁 + - 鍙傝€冩暟鎹簱鏂囨。纭瀛楁绫诲瀷 + - 瀵逛簬瑙嗗浘锛堝 `ml_products_detail_view`锛夛紝娉ㄦ剰瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚? + +================================================================================ +鍥涘崄鍥涖€?026-02-27 鍒涘缓杈呭姪鍑芥暟澶勭悊鏁版嵁杞崲锛堥噸瑕侊級 +================================================================================ + +1. 鍒涘缓杈呭姪鍑芥暟缁熶竴澶勭悊鏁版嵁绫诲瀷杞崲 + - 閬垮厤鍦ㄦ瘡涓柟娉曚腑閲嶅鍐欑被鍨嬫鏌ヤ唬鐮? + - 绀轰緥锛? + ```typescript + // 杈呭姪鍑芥暟锛氬畨鍏ㄨ幏鍙栧瓧绗︿覆鍊? + 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() + if (typeof val == 'boolean') return (val as boolean) ? 'true' : 'false' + 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') { + const parsed = parseFloat(val as string) + return isNaN(parsed) ? 0 : parsed + } + return 0 + } + + // 杈呭姪鍑芥暟锛氬畨鍏ㄨ幏鍙栧竷灏斿€? + function safeGetBoolean(obj: UTSJSONObject, key: string): boolean { + 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 + } + + // 杈呭姪鍑芥暟锛氫粠鍘熷鏁版嵁瑙f瀽鍟嗗搧 + 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. 浼樼偣 + - 浠g爜澶嶇敤锛屽噺灏戦噸澶? + - 缁熶竴澶勭悊鍚勭绫诲瀷杞崲寮傚父 + - 鏄撲簬缁存姢鍜屼慨鏀? + +3. 鏈€浣冲疄璺? + - 灏嗚緟鍔╁嚱鏁版斁鍦ㄦ枃浠堕《閮? + - 瀵规墍鏈変粠鏁版嵁搴撹幏鍙栫殑鏁版嵁浣跨敤杈呭姪鍑芥暟 + - 澶勭悊鎵€鏈夊彲鑳界殑绫诲瀷杞崲鎯呭喌 + +================================================================================ +鍥涘崄浜斻€?026-02-27 瑙嗗浘瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚岋紙閲嶈锛? +================================================================================ + +1. 鏁版嵁搴撹鍥剧殑瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚? + - `ml_products_detail_view` 瑙嗗浘涓病鏈?`image_url` 瀛楁 + - 鍙湁 `main_image_url` 鍜?`image_urls` 瀛楁 + - 閿欒绀轰緥锛? + ```typescript + .select('id, name, image_url') // 閿欒锛氳鍥炬病鏈?image_url 瀛楁 + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, main_image_url, image_urls') // 姝g‘ + ``` + +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?column ml_products_detail_view.image_url does not exist" + - 鎻愮ず锛?Perhaps you meant to reference the column 'ml_products_detail_view.image_urls'" + - 鍘熷洜锛氭煡璇簡瑙嗗浘涓笉瀛樺湪鐨勫瓧娈? + - 瑙e喅锛氬弬鑰冩暟鎹簱鏂囨。纭瑙嗗浘瀛楁鍚? + +3. 鏈€浣冲疄璺? + - 鏌ヨ瑙嗗浘鍓嶅厛纭瀛楁鍚? + - 鍙傝€?`CONSUMER_DB_DOC.md` 鏂囨。 + - 浣跨敤鏄庣‘瀛楁鍒楄〃鑰岄潪 `SELECT *` + +================================================================================ +鍥涘崄鍏€?026-02-27 杈呭姪鍑芥暟搴斾娇鐢?try-catch 鍖呰锛堥噸瑕侊級 +================================================================================ + +1. 杈呭姪鍑芥暟搴斾娇鐢?try-catch 鍖呰闃叉宕╂簝 + - 鍦?Android 绔紝绫诲瀷杞崲鍙兘鎶涘嚭寮傚父 + - 浣跨敤 try-catch 鍖呰鍙互闃叉鏁翠釜搴旂敤宕╂簝 + - 绀轰緥锛? + ```typescript + 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. 浼樼偣 + - 闃叉鍗曚釜瀛楁瑙f瀽澶辫触瀵艰嚧鏁翠釜搴旂敤宕╂簝 + - 鎻愪緵璇︾粏鐨勯敊璇棩蹇椾究浜庤皟璇? + - 杩斿洖榛樿鍊间繚璇佸簲鐢ㄧ户缁繍琛? + +3. 鏈€浣冲疄璺? + - 鎵€鏈夎緟鍔╁嚱鏁伴兘搴斾娇鐢?try-catch 鍖呰 + - 鍦?catch 涓褰曢敊璇棩蹇? + - 杩斿洖鍚堢悊鐨勯粯璁ゅ€? + +================================================================================ +鍥涘崄涓冦€?026-02-27 鍟嗗搧瑙嗗浘娌℃湁 shop_id 瀛楁锛堥噸瑕侊級 +================================================================================ + +1. `ml_products_detail_view` 瑙嗗浘娌℃湁 `shop_id` 瀛楁 + - 鍟嗗搧閫氳繃 `merchant_id` 鍏宠仈鍟嗗/搴楅摵 + - 閿欒绀轰緥锛? + ```typescript + .select('id, name, shop_id') // 閿欒锛氳鍥炬病鏈?shop_id 瀛楁 + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, merchant_id') // 姝g‘锛氫娇鐢?merchant_id + ``` + +2. 鏁版嵁搴撳瓧娈靛搴斿叧绯? + - `ml_products` 琛細`merchant_id` 鍏宠仈鍟嗗 + - `ml_shops` 琛細`user_id` 绛変簬鍟嗗鐨?`merchant_id` + - 瑙嗗浘涓€氳繃 `merchant_id` JOIN `ml_shops` 鑾峰彇搴楅摵淇℃伅 + +3. 鏈€浣冲疄璺? + - 鏌ヨ鍟嗗搧鏃朵娇鐢?`merchant_id` 鑰岄潪 `shop_id` + - 鍙傝€?`CONSUMER_DB_DOC.md` 纭瀛楁鍚? + +================================================================================ +鍥涘崄鍏€?026-02-27 any 绫诲瀷涓嶆敮鎸佺储寮曡闂紙閲嶈锛? +================================================================================ + +1. UTS Android 涓?`any` 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 涓嶈兘浣跨敤 `obj[key]` 璇硶璁块棶 `any` 绫诲瀷瀵硅薄鐨勫睘鎬? + - 閿欒绀轰緥锛? + ```typescript + function safeGetString(obj: any, key: string): string { + const val = obj[key] // 閿欒锛歎nresolved reference + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + function safeGetString(obj: UTSJSONObject, key: string): string { + const val = obj.get(key) // 姝g‘锛氫娇鐢?UTSJSONObject 鐨?get 鏂规硶 + } + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch" + - 鍘熷洜锛歚any` 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 瑙e喅锛氬厛灏嗗璞¤浆鎹负 `UTSJSONObject`锛屽啀浣跨敤 `.get()` 鏂规硶 + +3. 鏈€浣冲疄璺? + - 浣跨敤 `toUTSJSONObject()` 鍑芥暟灏?`any` 杞崲涓?`UTSJSONObject` + - 浣跨敤 `.get()`銆乣.getString()`銆乣.getNumber()` 绛夋柟娉曡闂睘鎬? + - 杈呭姪鍑芥暟鍙傛暟绫诲瀷搴斾负 `UTSJSONObject` 鑰岄潪 `any` + +================================================================================ +鍥涘崄涔濄€?026-02-27 浣跨敤 getString/getNumber/getBoolean 鏂规硶锛堥噸瑕侊級 +================================================================================ + +1. UTSJSONObject 鎻愪緵浜嗙被鍨嬪畨鍏ㄧ殑璁块棶鏂规硶 + - `getString(key)` - 鐩存帴杩斿洖瀛楃涓叉垨 null + - `getNumber(key)` - 鐩存帴杩斿洖鏁板€兼垨 null + - `getBoolean(key)` - 鐩存帴杩斿洖甯冨皵鍊兼垨 null + - `getArray(key)` - 鐩存帴杩斿洖鏁扮粍鎴?null + - 杩欎簺鏂规硶姣?`.get()` 鏇村畨鍏紝浼氳嚜鍔ㄨ繘琛岀被鍨嬭浆鎹? + +2. 鎺ㄨ崘鐢ㄦ硶 + ```typescript + // 鎺ㄨ崘浣跨敤 + 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 鍧椾腑澶勭悊寮傚父 + +================================================================================ +鍥涘崄涓€銆?026-02-27 item as UTSJSONObject 涓嶄細娣诲姞鏂规硶锛堥噸瑕侊級 +================================================================================ + +1. `item as UTSJSONObject` 涓嶄細璁╁璞¤幏寰?`getString` 绛夋柟娉? + - 鐩存帴浣跨敤 `as UTSJSONObject` 鍙槸绫诲瀷鏂█锛屼笉浼氭敼鍙樿繍琛屾椂瀵硅薄 + - 閿欒绀轰緥锛? + ```typescript + const item = rawList[i] + const prodObj = item as UTSJSONObject // 閿欒锛歡etString 涓嶅瓨鍦? + const id = prodObj.getString('id') // 杩愯鏃堕敊璇? + ``` + - 姝g‘绀轰緥锛? + ```typescript + const item = rawList[i] + const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject + const id = prodObj.getString('id') ?? '' // 姝g‘ + ``` + +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?getString is not a function" + - 鍘熷洜锛氬璞℃病鏈夋纭浆鎹负 UTSJSONObject + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜杞崲 + +3. 鏈€浣冲疄璺? + - 瀵逛簬浠庢暟鎹簱/API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? + +================================================================================ + +================================================================================ + + +================================================================================ +二十八、2026-03-02 自定义导航栏 Android 端适配(重要) +================================================================================ + +1. .smart-navbar 样式规范 + - 问题:在 Android 端沉浸式导航栏中,若 .smart-navbar 设置了 paddingTop: statusBarHeight,且 flex-direction 未显式设置为 column,内部元素(标题、按钮)可能会错位。 + - 修复:.smart-navbar 必须显式设置 flex-direction: column,内部内容容器 .nav-container 设置为 flex-direction: row。 + - 正确示例: + ```css + .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; + } + ``` + + +================================================================================ +二十八、2026-03-02 自定义导航栏 Android 端适配(重要) +================================================================================ + +1. .smart-navbar 样式规范 + - 问题:在 Android 端沉浸式导航栏中,若 .smart-navbar 设置了 paddingTop: statusBarHeight,且 flex-direction 未显式设置为 column,内部元素(标题、按钮)可能会错位。 + - 修复:.smart-navbar 必须显式设置 flex-direction: column,内部内容容器 .nav-container 设置为 flex-direction: row。 + - 正确示例: + ```css + .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; + } + ``` diff --git a/pages/mall/consumer/doc/uts_utf8.txt b/pages/mall/consumer/doc/uts_utf8.txt new file mode 100644 index 00000000..4ab34ec2 --- /dev/null +++ b/pages/mall/consumer/doc/uts_utf8.txt @@ -0,0 +1,2286 @@ +================================================================================ + UTS-Android 鍏煎鎬у紑鍙戣鑼? +================================================================================ +> 浠ヤ笅涓?uni-app-x (UTS) Android 绔紑鍙戝父瑙佹敞鎰忎簨椤逛笌韪╁潙鐐癸紝寤鸿鎵€鏈夊紑鍙戞垚鍛橀伒寰細 + +================================================================================ +涓€銆佸熀纭€璇硶瑙勮寖 +================================================================================ + +1. 鍙橀噺澹版槑 + - 鍙兘浣跨敤 let 鍜?const锛屼笉鑳戒娇鐢?var + - 鍙橀噺澹版槑蹇呴』鏈夋樉寮忕被鍨嬫垨鍒濆鍖栧€? + - 涓嶆敮鎸?undefined 绫诲瀷锛屽彉閲忔湭璧嬪€煎氨鏄?null + - 涓嶆敮鎸?undefined 鍏抽敭瀛楋紝鍒ゆ柇鏄惁瀛樺湪瑕佺敤 != null + +2. 绫诲瀷瀹氫箟 + - 鍙€傚悎杞?type锛屼笉閫傚悎浣跨敤 interface锛坕nterface 鍦?kotlin/swift 涓彟鏈変笉鍚岋級 + - 涓嶆敮鎸?Intersection Type锛堜氦鍙夌被鍨嬶級 + - 涓嶆敮鎸?Index Signature锛堢储寮曠鍚嶏級 + - 绫诲瀷鎺ㄦ柇涓ユ牸锛屽繀瑕佹椂鐢?as Type 鏄庣‘绫诲瀷 + - 涓嶆敮鎸佸唴鑱斿璞$被鍨嬶紙Object Literal Type锛夛紝闇€瑕佸崟鐙畾涔?type + +3. 鍑芥暟瀹氫箟 + - 鍑芥暟蹇呴』鍦ㄤ娇鐢ㄥ墠瀹氫箟锛堜笉鏀寔鍑芥暟鎻愬崌锛? + - 鍦?setup 妯″紡涓嬶紝璋冪敤鐨勫嚱鏁板繀椤诲湪璋冪敤涔嬪墠瀹氫箟 + - 渚濊禆鍏崇郴闇€瑕佹槑纭細琚皟鐢ㄧ殑鍑芥暟蹇呴』鍏堝畾涔? + - 杩欎笌 JavaScript 鐨勫嚱鏁版彁鍗囪涓轰笉鍚岋紝UTS 鏇存帴杩?C/Java 鐨勭紪璇戞柟寮? + +4. 寰幆 + - for 寰幆鐨?i 蹇呴』鍐欐槑绫诲瀷锛歭et i: Int = 0 + - 涓嶈鐢?forEach銆乵ap锛屾暟缁勯亶鍘嗙敤 for 寰幆 + - 宓屽鐨勬暟缁勬柟娉曡皟鐢ㄥ彲鑳藉鑷寸被鍨嬫帹鏂け璐ワ紝搴旀敼鐢?for 寰幆 + +================================================================================ +浜屻€佺被鍨嬩笌瀵硅薄璁块棶 +================================================================================ + +1. any 绫诲瀷璁块棶 + - 涓嶈兘鐩存帴璁块棶 any 绫诲瀷瀵硅薄鐨勫睘鎬? + - 闇€瑕佸皢瀵硅薄杞崲涓?UTSJSONObject 绫诲瀷鍚庝娇鐢?getString()銆乬etNumber() 绛夋柟娉曡闂睘鎬? + - any 绫诲瀷灞炴€ц闂渶杞崲涓?Record 鍚庣敤绱㈠紩璁块棶 + - 浣跨敤绱㈠紩璁块棶灞炴€ф椂锛屾帹鑽愪娇鐢ㄦ柟鎷彿璇硶 obj['property'] 鑰岄潪鐐硅娉?obj.property + - any 绫诲瀷涓嶆敮鎸佺储寮曡闂?obj['key']锛屽繀椤诲厛杞崲涓?UTSJSONObject + +2. UTSJSONObject 浣跨敤 + - 鐢?utils/utis 涓嬬殑 UTSJSONObject 鍋氱被鍨嬭浆鎹? + - 涓嶈鐢?safeget锛屽彧瑕?UTSJSONObject 灏卞ソ浜? + - 闇€瑕佸垱寤哄姩鎬佸璞℃椂锛屽簲浣跨敤 new UTSJSONObject() 鐒跺悗璋冪敤 .set() 鏂规硶 + - 瀵逛簬 type 瀹氫箟鐨勫璞$被鍨嬶紝鍚屾牱闇€瑕佷娇鐢?UTSJSONObject + - 浣跨敤 getString()銆乬etNumber() 鏂规硶鑾峰彇灞炴€у€? + +3. 鏁扮粍绫诲瀷 + - 鏁扮粍绫诲瀷寤鸿鍐欐垚 Array锛屼笉瑕佺敤 Type[] 绠€鍐? + - 绌烘暟缁勯渶瑕佹槑纭寚瀹氱被鍨嬶紝濡?[] as string[] + - 鏁扮粍鍏冪礌闇€瑕佹槑纭殑绫诲瀷瀹氫箟鎵嶈兘鍦ㄦā鏉夸腑姝g‘璁块棶灞炴€? + - 瀵逛簬 any[] 鎴?reactive 鏁扮粍锛岃闂厓绱犲睘鎬ф椂闇€瑕佸厛杞崲涓?Record 鎴?any[] + +4. 瀵硅薄鎿嶄綔 + - 涓嶆敮鎸?Object.keys()銆丱bject.values()銆丱bject.entries() + - 涓嶆敮鎸?Record 瀵硅薄瀛楅潰閲忚娉? + - 瀵硅薄瀛楅潰閲?{...} 鍙兘鐢ㄤ簬鏋勯€犵被鍨嬶紙class锛夛紝涓嶈兘鐢ㄤ簬鎺ュ彛锛坕nterface锛? + - reactive 瀵硅薄鍦?UTS 涓笉鏀寔绱㈠紩鍣ㄨ祴鍊兼搷浣? + +================================================================================ +涓夈€佹潯浠跺垽鏂笌閫昏緫杩愮畻 +================================================================================ + +1. if 鏉′欢 + - if 鍒ゆ柇鍙帴鍙?boolean 绫诲瀷锛屼笉鑳芥槸鍏朵粬绫诲瀷鐨勫€? + - 鍒ゆ柇绌鸿鐢?!== null锛屼笉鑳界敤 !鍙橀噺锛坲ts android 涓嶆敮鎸?!鍦ㄥ彉閲忓墠闈㈢殑鍒ゆ柇绌烘柟寮忥級 + - 妯℃澘涓殑 || 杩愮畻绗﹀乏杈瑰繀椤绘槸 boolean 绫诲瀷 + - 鍙┖绫诲瀷浣跨敤鍙€夐摼 ?. 鍜岀┖鍊煎悎骞??? + - 瀛楃涓插垽鏂┖瑕佺敤锛歷ariable != null && variable !== '' + +2. 閫昏緫杩愮畻绗? + - || 琛ㄧず閫昏緫鎴? + - && 琛ㄧず閫昏緫涓? + - ! 琛ㄧず閫昏緫闈烇紙浣?!鍙橀噺 涓嶆敮鎸佺敤浜庡垽鏂┖锛? + - ?? 琛ㄧず绌哄€煎悎骞惰繍绠楃锛堝綋宸︿晶涓?null 鏃惰繑鍥炲彸渚у€硷級 + - ts 鐨勪负绌哄垯浣跨敤榛樿鍊肩殑璇硶鍦?uts 涓笉鑳界敤 ||锛岃鐢??? 鏉ヤ唬鏇? + +================================================================================ +鍥涖€佺粍浠朵笌妯℃澘 +================================================================================ + +1. 琛ㄥ崟涓庤緭鍏? + - 琛ㄥ崟浼樺厛鐢?form 缁勪欢 + - 涓嶆敮鎸?uni-easyinput锛岀敤 input 浠f浛 + - 鏃堕棿閫夋嫨鐢?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 浠f浛 + - 涓嶆敮鎸?uni-datetime-picker锛岀敤 components/picker-date 鎴?components/picker-time 浠f浛 + - 涓嶆敮鎸?uni-icons + +4. 妯℃澘娉ㄦ剰浜嬮」 + - 璺?template 浜や簰鐨勫彉閲忓敖閲忕敤涓€缁村彉閲忥紙涓嶈宓屽瀵硅薄锛? + - 妯℃澘涓彲绌虹被鍨嬪繀椤讳娇鐢??. 瀹夊叏璁块棶 + - 妯℃澘涓闂彲绌虹被鍨嬪睘鎬у墠蹇呴』鍏堝垽绌?v-if="order != null" + +================================================================================ +浜斻€丆SS 鏍峰紡闄愬埗 +================================================================================ + +1. 甯冨眬鏂瑰紡 + - 鍙敮鎸?display: flex + - 涓嶆敮鎸?display: grid + - 涓嶆敮鎸?gap + - 涓嶆敮鎸?table銆乬rid銆乬rid-template-columns + +2. 鍗曚綅涓庤绠? + - 涓嶆敮鎸?calc() + - 涓嶆敮鎸佺殑鍗曚綅: vh + - property value `100%` is not supported for min-height (supported values are: number|pixel) + - property value `calc(33.33% - 10px)` is not supported for min-width + +3. 閫夋嫨鍣? + - [APP-ANDROID] 涓嶆敮鎸佷吉绫婚€夋嫨鍣? + - [APP-IOS] 涓嶆敮鎸佷吉绫婚€夋嫨鍣? + - ERROR: Selector `.login-button[disabled]` is not supported. uvue only support classname selector + +4. 鍏朵粬鏍峰紡 + - WARNING: `backdrop-filter` is not a standard property name + - style property `white-space` is only supported on `| + + + + ``` + +3. Map 绫诲瀷璁块棶闄愬埗 + - UTS Android 涓嶆敮鎸?Map 绫诲瀷鐨?get() 鏂规硶璁块棶灞炴€? + - 搴旂粺涓€浣跨敤 UTSJSONObject + - 绀轰緥锛? + ```typescript + // 閿欒 + const m = item as Map + const idVal = m.get('id') + + // 姝g‘ + const itemObj = item as UTSJSONObject + const id = itemObj.getString('id') ?? '' + ``` + +4. 鍑芥暟鍐呭彇鍙嶆搷浣滅 + - if (!isValid.value) 涓嶆敮鎸佸彇鍙? + - 浣跨敤 if (isValid.value === false) + - 绀轰緥锛? + ```typescript + // 閿欒 + if (!isValid.value) return + + // 姝g‘ + if (isValid.value === false) return + ``` + +================================================================================ +浜屽崄涓€銆?026-02-25 user 鐩綍椤甸潰淇璁板綍 +================================================================================ + +1. change-password.uvue 淇 + - 闂锛?oldPassword.value 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?oldPassword.value == '' 鏄惧紡鍒ゆ柇 + - 闂锛歝onst { error } = await ... 瑙f瀯璧嬪€间笉鏀寔 + - 淇锛氭敼涓?const result = await ... 鐒跺悗 result.error 璁块棶 + +2. login.uvue 淇 + - 闂锛歛s unknown as number 鍙岄噸绫诲瀷杞崲涓嶆敮鎸? + - 淇锛氭敼涓?as number 鍗曚竴绫诲瀷杞崲 + - 闂锛歵ypeof err === 'object' 涓嶆敮鎸? + - 淇锛氫娇鐢?try-catch 鍖呰9绫诲瀷杞崲 + +3. forgot-password.uvue 淇 + - 闂锛?emailRegex.test(this.email) 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?emailRegex.test(this.email) == false + - 闂锛歵ypeof err === 'object' 涓嶆敮鎸? + - 淇锛氫娇鐢?try-catch 鍖呰9绫诲瀷杞崲 + +4. register.uvue 淇 + - 闂锛?protocol.value 鍙栧弽鎿嶄綔涓嶆敮鎸? + - 淇锛氭敼涓?protocol.value == false + - 闂锛?validateEmail() 绛夊彇鍙嶆搷浣滀笉鏀寔 + - 淇锛氭敼涓?validateEmail() == false + +================================================================================ +浜屽崄浜屻€佸父瑙佷慨澶嶆ā寮忛€熸煡 +================================================================================ + +1. 鍙栧弽鎿嶄綔淇妯″紡 + ```typescript + // 閿欒 + if (!variable) { ... } + if (!isValid.value) { ... } + if (!validate()) { ... } + + // 姝g‘ - 鏍规嵁绫诲瀷閫夋嫨 + if (variable == null || variable == '') { ... } // 瀛楃涓插垽绌? + if (isValid.value == false) { ... } // 甯冨皵鍊煎彇鍙? + if (validate() == false) { ... } // 鍑芥暟杩斿洖甯冨皵鍊煎彇鍙? + ``` + +2. 瑙f瀯璧嬪€间慨澶嶆ā寮? + ```typescript + // 閿欒 + const { data, error } = await someAsyncCall() + + // 姝g‘ + const result = await someAsyncCall() + const data = result.data + const error = result.error + ``` + +3. typeof 妫€鏌ヤ慨澶嶆ā寮? + ```typescript + // 閿欒 + if (typeof err === 'object') { ... } + if (typeof xxx === 'function') { ... } + + // 姝g‘ + try { + const e = err as Error + // 浣跨敤 e + } catch (e2) { + // 澶勭悊杞崲澶辫触 + } + ``` + +4. as unknown as 淇妯″紡 + ```typescript + // 閿欒 + const timer = setInterval(...) as unknown as number + + // 姝g‘ + const timer = setInterval(...) as number + ``` + +================================================================================ +浜屽崄涓夈€侀敊璇鐞嗘渶浣冲疄璺? +================================================================================ + +1. 缁熶竴閿欒澶勭悊妯″紡 + ```typescript + 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. 鍙┖绫诲瀷瀹夊叏璁块棶 + ```typescript + // 瀹夊叏璁块棶瀵硅薄灞炴€? + const value = obj != null ? obj.property : null + + // 瀹夊叏璋冪敤鏂规硶 + const result = obj != null ? obj.method() : null + ``` + +3. 鏁扮粍瀹夊叏璁块棶 + ```typescript + // 瀹夊叏璁块棶鏁扮粍鍏冪礌 + if (arr.length > index) { + const item = arr[index] + // 浣跨敤 item + } + ``` + +================================================================================ +鏂囨。缁撴潫 +================================================================================ + +================================================================================ +浜屽崄鍥涖€?026-02-27 鍑芥暟鍙€夊弬鏁伴檺鍒讹紙閲嶈锛? +================================================================================ + +1. 鍙€夊弬鏁颁笉鑳借烦杩囦紶閫? + - UTS Android 涓嶆敮鎸佽烦杩囧彲閫夊弬鏁颁紶閫? + - 濡傛灉鍑芥暟鏈夊涓彲閫夊弬鏁帮紝蹇呴』鎸夐『搴忎紶閫掓墍鏈夊弬鏁? + - 閿欒绀轰緥锛? + ```typescript + // 鍑芥暟瀹氫箟 + async addToCart(productId: string, quantity: number = 1, skuId?: string, merchantId?: string): Promise + + // 閿欒璋冪敤 - 璺宠繃浜?merchantId 鍙傛暟 + await supabaseService.addToCart(productId, 1, '') + // 缂栬瘧閿欒锛歂o value passed for parameter 'merchantId' + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 鏂规1锛氱粰鍙€夊弬鏁版坊鍔犻粯璁ゅ€? + async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise + + // 鏂规2锛氳皟鐢ㄦ椂浼犻€掓墍鏈夊弬鏁? + await supabaseService.addToCart(productId, 1, '', '') + ``` + +2. 鍙€夊弬鏁板畾涔夎鑼? + - 鎺ㄨ崘浣跨敤 `param: Type = defaultValue` 鑰岄潪 `param?: Type` + - `param?: Type` 鍦?Android 绔皟鐢ㄦ椂浠嶉渶浼犻€掑弬鏁? + - `param: Type = defaultValue` 鍙互鍦ㄤ笉浼犲弬鏃朵娇鐢ㄩ粯璁ゅ€? + - 绀轰緥锛? + ```typescript + // 涓嶆帹鑽?- 璋冪敤鏃朵粛闇€浼犻€掑弬鏁? + 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 绔笉鑳借烦杩? + - 瑙e喅锛? + 1. 淇敼鍑芥暟绛惧悕锛屼娇鐢ㄩ粯璁ゅ€?`param: Type = defaultValue` + 2. 璋冪敤鏃朵紶閫掓墍鏈夊弬鏁? + +4. 鏈€浣冲疄璺? + - 瀵逛簬鏈夊涓彲閫夊弬鏁扮殑鍑芥暟锛岀粺涓€浣跨敤榛樿鍊艰娉? + - 璋冪敤鏃舵樉寮忎紶閫掓墍鏈夊弬鏁帮紝閬垮厤渚濊禆鍙€夊弬鏁拌烦杩? + - 鍦ㄦ湇鍔″眰鍑芥暟瀹氫箟涓紝浼樺厛浣跨敤 `= ''` 鎴?`= 0` 绛夐粯璁ゅ€? + +================================================================================ +浜屽崄浜斻€?026-02-27 妯℃澘涓殑闈炵┖鏂█闄愬埗锛堥噸瑕侊級 +================================================================================ + +1. 妯℃澘涓笉鏀寔闈炵┖鏂█鎿嶄綔绗?`!` + - UTS Android 妯℃澘涓笉鑳戒娇鐢?`variable!` 闈炵┖鏂█ + - 閿欒绀轰緥锛? + ```html + + ``` + - 姝g‘绀轰緥锛? + ```html + + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鍙傛暟绫诲瀷涓嶅尮閰嶏細瀹為檯绫诲瀷涓?'Number?'锛岄鏈熺被鍨嬩负 'Number'" + - 鍘熷洜锛氭ā鏉夸腑浣跨敤闈炵┖鏂█ `!` 涓嶈鏀寔 + - 瑙e喅锛氱Щ闄ら潪绌烘柇瑷€ `!`锛岀洿鎺ヤ娇鐢ㄥ彉閲忚繘琛屾瘮杈? + +3. 鏈€浣冲疄璺? + - 鍦ㄦā鏉夸腑锛屽厛鐢?`!= null` 鍒ゆ柇鍙┖绫诲瀷锛岀劧鍚庣洿鎺ヤ娇鐢ㄥ彉閲? + - UTS 缂栬瘧鍣ㄤ細鍦?`!= null` 鍒ゆ柇鍚庤嚜鍔ㄨ瘑鍒彉閲忎负闈炵┖绫诲瀷 + +================================================================================ +浜屽崄鍏€?026-02-27 鏈鍏ョ被鍨嬬殑澶勭悊锛堥噸瑕侊級 +================================================================================ + +1. 鏈鍏ョ殑绫诲瀷涓嶈兘鐩存帴浣跨敤 + - 鍦ㄩ〉闈腑浣跨敤鐨勭被鍨嬪繀椤诲厛瀵煎叆鎴栦娇鐢?UTSJSONObject 鏇夸唬 + - 閿欒绀轰緥锛? + ```typescript + // Shop 绫诲瀷鏈鍏? + const s = shopRespData[i] as Shop + const id = s.id // 鎵句笉鍒板悕绉?"id" + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 浣跨敤 UTSJSONObject + const s = shopRespData[i] as UTSJSONObject + const id = s.getString('id') ?? '' + const name = s.getString('shop_name') ?? '' + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?'XXX'" + - 鍘熷洜锛氱被鍨嬫湭瀵煎叆鎴栫被鍨嬪畾涔変笉瀛樺湪 + - 瑙e喅锛? + 1. 瀵煎叆闇€瑕佺殑绫诲瀷锛歚import { Shop } from '@/utils/supabaseService.uts'` + 2. 浣跨敤 UTSJSONObject 鏇夸唬锛歚as UTSJSONObject` 鐒跺悗鐢?`getString()`銆乣getNumber()` 璁块棶灞炴€? + +3. 鏈€浣冲疄璺? + - 瀵逛簬绠€鍗曠殑鏁版嵁杞崲锛屾帹鑽愪娇鐢?UTSJSONObject + - 閬垮厤鍦ㄥ涓枃浠朵腑閲嶅瀹氫箟鐩稿悓鐨勭被鍨? + - 濡傛灉闇€瑕佺被鍨嬪畨鍏紝浠庢湇鍔″眰瀵煎叆绫诲瀷瀹氫箟 + +================================================================================ +浜屽崄涓冦€?026-02-27 鏈嶅姟灞傛暟鎹瓧娈靛畬鏁存€э紙閲嶈锛? +================================================================================ + +1. 鏈嶅姟灞傝繑鍥炴暟鎹繀椤诲寘鍚墍鏈夊繀瑕佸瓧娈? + - 浠庢暟鎹簱鑾峰彇鏁版嵁鏃讹紝蹇呴』姝g‘鏄犲皠鎵€鏈夐渶瑕佺殑瀛楁 + - 閿欒绀轰緥锛? + ```typescript + const product: Product = { + id: prodObj.getString('id') ?? '', + name: prodObj.getString('name') ?? '', + // 閿欒锛歮erchant_id 纭紪鐮佷负绌哄瓧绗︿覆 + merchant_id: '' + } as Product + ``` + - 姝g‘绀轰緥锛? + ```typescript + const product: Product = { + id: prodObj.getString('id') ?? '', + name: prodObj.getString('name') ?? '', + // 姝g‘锛氫粠鏁版嵁搴撹幏鍙?merchant_id + merchant_id: prodObj.getString('merchant_id') ?? '' + } as Product + ``` + +2. 璋冪敤鏈嶅姟灞傛柟娉曟椂蹇呴』浼犻€掑畬鏁村弬鏁? + - 椤甸潰璋冪敤鏈嶅姟灞傛柟娉曟椂锛岄渶瑕佷紶閫掓墍鏈夊繀瑕佸弬鏁? + - 閿欒绀轰緥锛? + ```typescript + // 閿欒锛歮erchant_id 浼犵┖瀛楃涓? + await supabaseService.addToCart(productId, 1, '', '') + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 姝g‘锛氫粠鍟嗗搧瀵硅薄鑾峰彇 merchant_id + const merchantId = product.merchant_id ?? '' + await supabaseService.addToCart(productId, 1, '', merchantId) + ``` + +3. 缂栬瘧閿欒鎻愮ず + - 闂琛ㄧ幇锛氭暟鎹坊鍔犲埌鏁版嵁搴撳け璐ワ紝鎴栨坊鍔犵殑鏁版嵁涓嶅畬鏁? + - 鍘熷洜锛氭湇鍔″眰鎴栭〉闈㈠眰缂哄皯蹇呰瀛楁鐨勪紶閫? + - 瑙e喅锛? + 1. 妫€鏌ユ湇鍔″眰鏁版嵁鏄犲皠鏄惁瀹屾暣 + 2. 妫€鏌ラ〉闈㈣皟鐢ㄦ椂鏄惁浼犻€掍簡鎵€鏈夊繀瑕佸弬鏁? + +4. 鏈€浣冲疄璺? + - 鏈嶅姟灞傛柟娉曡繑鍥炵殑瀵硅薄搴斿寘鍚暟鎹簱瑙嗗浘鐨勬墍鏈夊瓧娈? + - 椤甸潰璋冪敤鏈嶅姟灞傛柟娉曟椂锛屽簲浠庢暟鎹璞′腑鑾峰彇骞朵紶閫掓墍鏈夊弬鏁? + - 瀵逛簬鍏宠仈鏁版嵁锛堝 merchant_id锛夛紝纭繚鍦ㄦ暟鎹姞杞芥椂涓€骞惰幏鍙? + +================================================================================ +浜屽崄鍏€?026-02-27 妯℃澘涓殑闈炶繍绠楃闄愬埗锛堥噸瑕侊級 +================================================================================ + +1. 妯℃澘涓笉鏀寔 `!` 闈炶繍绠楃 + - UTS Android 妯℃澘涓笉鑳戒娇鐢?`!variable` 闈炶繍绠楃 + - 閿欒绀轰緥锛? + ```html + + ``` + - 姝g‘绀轰緥锛? + ```html + + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?not'" + - 鍘熷洜锛氭ā鏉夸腑涓嶆敮鎸侀潪杩愮畻绗?`!` + - 瑙e喅锛氫娇鐢ㄦ樉寮忕殑姣旇緝琛ㄨ揪寮忔浛浠? + +3. 鏈€浣冲疄璺? + - 浣跨敤 `== null` 鎴?`== ''` 妫€鏌ョ┖鍊? + - 浣跨敤 `!= null && != ''` 妫€鏌ラ潪绌哄€? + +================================================================================ +浜屽崄涔濄€?026-02-27 绱㈠紩璁块棶闄愬埗锛堥噸瑕侊級 +================================================================================ + +1. 涓嶆敮鎸?`(obj as any)['key']` 绱㈠紩璁块棶鏂瑰紡 + - UTS Android 涓嶆敮鎸佸 any 绫诲瀷浣跨敤绱㈠紩璁块棶 + - 閿欒绀轰緥锛? + ```typescript + const detail = (e as any)['detail'] + val = detail['value'] ?? '' + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 鏂规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" + - 鍘熷洜锛歛ny 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鍚庝娇鐢?`.get()` 鏂规硶 + +3. 鏈€浣冲疄璺? + - 缁熶竴浣跨敤 UTSJSONObject 澶勭悊鍔ㄦ€佸璞? + - 浣跨敤 `.get()`銆乣.getString()`銆乣.getNumber()` 鏂规硶璁块棶灞炴€? + - 瀵逛簬澶嶆潅瀵硅薄锛屽厛鐢?`JSON.parse(JSON.stringify(obj))` 杞崲 + +================================================================================ +涓夊崄銆?026-02-27 瀛楃涓蹭笉鑳界洿鎺ヤ綔涓哄竷灏旀潯浠讹紙閲嶈锛? +================================================================================ + +1. 瀛楃涓蹭笉鑳界洿鎺ヤ綔涓?if 鏉′欢 + - UTS Android 涓嶆敮鎸佸皢瀛楃涓茬洿鎺ヤ綔涓哄竷灏旀潯浠跺垽鏂? + - 閿欒绀轰緥锛? + ```typescript + const paramId = '123' + if (paramId) { // 閿欒锛氬瓧绗︿覆涓嶈兘鐩存帴浣滀负甯冨皵鏉′欢 + // ... + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + const paramId = '123' + if (paramId != null && paramId != '') { // 姝g‘锛氭樉寮忓垽鏂? + // ... + } + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Condition type mismatch: inferred type is 'String' but 'Boolean' was expected" + - 鍘熷洜锛氬瓧绗︿覆绫诲瀷涓嶈兘鐩存帴浣滀负甯冨皵鏉′欢 + - 瑙e喅锛氫娇鐢ㄦ樉寮忕殑姣旇緝琛ㄨ揪寮? + +3. 鏈€浣冲疄璺? + - 浣跨敤 `!= null && != ''` 妫€鏌ュ瓧绗︿覆闈炵┖ + - 浣跨敤 `== null || == ''` 妫€鏌ュ瓧绗︿覆涓虹┖ + +================================================================================ +涓夊崄涓€銆?026-02-27 鍑芥暟瀹氫箟椤哄簭锛堥噸瑕侊級 +================================================================================ + +1. 鍑芥暟蹇呴』鍦ㄨ皟鐢ㄥ墠瀹氫箟 + - UTS Android 瑕佹眰鍑芥暟鍦ㄨ皟鐢ㄤ箣鍓嶅畬鎴愬畾涔? + - 杩欎笌 JavaScript 鐨勫嚱鏁版彁鍗囦笉鍚? + - 閿欒绀轰緥锛? + ```typescript + onMounted(() => { + loadData() // 閿欒锛歭oadData 杩樻湭瀹氫箟 + }) + + const loadData = async () => { + // ... + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + const loadData = async () => { + // ... + } + + onMounted(() => { + loadData() // 姝g‘锛歭oadData 宸插畾涔? + }) + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鎵句笉鍒板悕绉?xxx'" + - 鍘熷洜锛氬嚱鏁板湪璋冪敤鐐逛箣鍚庡畾涔? + - 瑙e喅锛氬皢鍑芥暟瀹氫箟绉诲埌璋冪敤涔嬪墠 + +3. 鏈€浣冲疄璺? + - 灏嗘墍鏈夊嚱鏁板畾涔夋斁鍦ㄧ敓鍛藉懆鏈熼挬瀛愶紙onMounted銆乷nShow 绛夛級涔嬪墠 + - 鎸変緷璧栧叧绯绘帓搴忓嚱鏁板畾涔夐『搴? + +================================================================================ +涓夊崄浜屻€?026-02-27 鑱斿悎绫诲瀷灞炴€ц闂紙閲嶈锛? +================================================================================ + +1. 鑱斿悎绫诲瀷涓嶈兘鐩存帴璁块棶灞炴€? + - 褰撳弬鏁扮被鍨嬩负鑱斿悎绫诲瀷锛堝 `A | B`锛夋椂锛屼笉鑳界洿鎺ヨ闂睘鎬? + - 閿欒绀轰緥锛? + ```typescript + type A = { id: string, name: string } + type B = { id: string, title: string } + + const foo = (item: A | B) => { + const id = item.id // 閿欒锛氳仈鍚堢被鍨嬩笉鑳界洿鎺ヨ闂睘鎬? + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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'" + - 鍘熷洜锛氳仈鍚堢被鍨嬬殑灞炴€ц闂彈闄? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鎴栦娇鐢ㄧ被鍨嬪畧鍗? + +3. 鏈€浣冲疄璺? + - 瀵逛簬鑱斿悎绫诲瀷鍙傛暟锛岀粺涓€杞崲涓?UTSJSONObject 澶勭悊 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? + +================================================================================ +涓夊崄涓夈€?026-02-27 any 绫诲瀷鍙橀噺涓嶈兘璧嬪€间负 null锛堥噸瑕侊級 +================================================================================ + +1. any 绫诲瀷鍙橀噺涓嶈兘璧嬪€间负 null + - UTS Android 涓?`any` 绫诲瀷涓嶈兘璧嬪€间负 `null` + - 閿欒绀轰緥锛? + ```typescript + let res: any = null // 閿欒锛歂ull cannot be a value of a non-null type 'Any' + ``` + - 姝g‘绀轰緥锛? + ```typescript + let res: any = {} // 姝g‘锛氫娇鐢ㄧ┖瀵硅薄 + // 鎴栬€? + let res: any | null = null // 浣跨敤鑱斿悎绫诲瀷 + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Null cannot be a value of a non-null type 'Any'" + - 鍘熷洜锛歛ny 绫诲瀷涓嶅厑璁?null 鍊? + - 瑙e喅锛氫娇鐢ㄧ┖瀵硅薄 `{}` 鎴栬仈鍚堢被鍨?`any | null` + +================================================================================ +涓夊崄鍥涖€?026-02-27 瀵硅薄瀛楅潰閲忕被鍨嬫帹鏂棶棰橈紙閲嶈锛? +================================================================================ + +1. 瀵硅薄瀛楅潰閲忕洿鎺ヨ祴鍊肩粰 ref 鍙兘绫诲瀷涓嶅尮閰? + - 褰撳璞″瓧闈㈤噺鐩存帴璧嬪€肩粰鐗瑰畾绫诲瀷鐨?ref 鏃讹紝鍙兘鎶ョ被鍨嬩笉鍖归厤閿欒 + - 閿欒绀轰緥锛? + ```typescript + merchant.value = { + id: shop.id, + user_id: shop.merchant_id, + // ... + } // 閿欒锛欰ssignment type mismatch + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 鏂规1锛氭樉寮忓0鏄庣被鍨? + 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" + - 鍘熷洜锛氬璞″瓧闈㈤噺琚帹鏂负鍖垮悕绫诲瀷 + - 瑙e喅锛氭樉寮忓0鏄庣被鍨嬫垨浣跨敤绫诲瀷鏂█ + +================================================================================ +涓夊崄浜斻€?026-02-27 any 绫诲瀷涓嶈兘鐩存帴璁块棶灞炴€э紙閲嶈锛? +================================================================================ + +1. any 绫诲瀷鍙傛暟涓嶈兘鐩存帴璁块棶灞炴€? + - 鍦?map銆乫orEach 绛夊洖璋冧腑锛宎ny 绫诲瀷鐨勫弬鏁颁笉鑳界洿鎺ヨ闂睘鎬? + - 閿欒绀轰緥锛? + ```typescript + const list = rawList.map((item): ProductType => { + const id = item.id // 閿欒锛氭壘涓嶅埌鍚嶇О"id" + const name = item.name // 閿欒锛氭壘涓嶅埌鍚嶇О"name" + }) + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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'" + - 鍘熷洜锛歛ny 绫诲瀷鐨勫睘鎬ц闂彈闄? + - 瑙e喅锛氳浆鎹负 UTSJSONObject 鍚庝娇鐢?`.getString()` 绛夋柟娉? + +================================================================================ +涓夊崄鍏€?026-02-27 绫诲瀷鏂█涓嶄細娣诲姞鏂规硶锛堥噸瑕侊級 +================================================================================ + +1. `as UTSJSONObject` 涓嶄細缁欏璞℃坊鍔犳柟娉? + - 浣跨敤 `as UTSJSONObject` 鍙槸绫诲瀷鏂█锛屼笉浼氳鏅€氬璞¤幏寰?`getString` 绛夋柟娉? + - 閿欒绀轰緥锛? + ```typescript + const profileObj = profile as UTSJSONObject + const id = profileObj.getString('user_id') // 杩愯鏃堕敊璇細getString is not a function + ``` + - 姝g‘绀轰緥锛? + ```typescript + // 蹇呴』浣跨敤 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" + - 鍘熷洜锛氱被鍨嬫柇瑷€鍙槸缂栬瘧鏃惰涓猴紝涓嶄細鏀瑰彉杩愯鏃跺璞$殑鏂规硶 + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜鐪熸鐨勫璞¤浆鎹? + +3. 鏈€浣冲疄璺? + - 瀵逛簬浠?API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `instanceof UTSJSONObject` 妫€鏌ュ璞$被鍨? + - 涓嶈渚濊禆 `as` 绫诲瀷鏂█鏉ユ坊鍔犳柟娉? + +================================================================================ +涓夊崄涓冦€?026-02-27 绫诲瀷蹇呴』鍖呭惈鎵€鏈夊繀濉瓧娈碉紙閲嶈锛? +================================================================================ + +1. 鍒涘缓绫诲瀷瀹炰緥鏃跺繀椤诲寘鍚墍鏈夊繀濉瓧娈? + - UTS 绫诲瀷瀹氫箟涓殑闈炲彲閫夊瓧娈碉紙涓嶅甫 `?`锛夐兘鏄繀濉殑 + - 閿欒绀轰緥锛? + ```typescript + export type ProductType = { + id: string + merchant_id: string // 蹇呭~ + category_id: string // 蹇呭~ + name: string + // ... + } + + // 閿欒锛氱己灏?merchant_id銆乧ategory_id 绛夊繀濉瓧娈? + return { + id: item.id, + name: item.name, + price: item.price + } as ProductType // 杩愯鏃堕敊璇細missing required property + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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" + - 鍘熷洜锛氱被鍨嬪畾涔変腑鏈夊繀濉瓧娈垫湭鎻愪緵 + - 瑙e喅锛? + 1. 妫€鏌ョ被鍨嬪畾涔夛紝纭鎵€鏈夊繀濉瓧娈? + 2. 涓烘墍鏈夊繀濉瓧娈垫彁渚涘€硷紝鍗充娇鏄┖瀛楃涓叉垨榛樿鍊? + +3. 鏈€浣冲疄璺? + - 鏌ョ湅绫诲瀷瀹氫箟锛岀‘璁ゅ摢浜涘瓧娈垫槸蹇呭~鐨勶紙涓嶅甫 `?`锛? + - 浣跨敤 `??` 杩愮畻绗︽彁渚涢粯璁ゅ€? + - 瀵逛簬鍙€夊瓧娈碉紝鍙互涓嶆彁渚涙垨浣跨敤 `null` + +================================================================================ +涓夊崄鍏€?026-02-27 鍥炶皟鍑芥暟涓嶈兘鏄?async锛堥噸瑕侊級 +================================================================================ + +1. API 鍥炶皟鍑芥暟涓嶈兘浣跨敤 async 淇グ + - uni API 鐨勫洖璋冨嚱鏁帮紙濡?showModal 鐨?success锛変笉鏀寔 async 鍑芥暟 + - 閿欒绀轰緥锛? + ```typescript + uni.showModal({ + title: '纭', + content: '纭畾瑕佸垹闄ゅ悧锛?, + success: async (res) => { // 閿欒锛氬洖璋冨嚱鏁颁笉鑳芥槸 async + if (res.confirm) { + const result = await someAsyncFunction() + } + } + }) + ``` + - 姝g‘绀轰緥锛? + ```typescript + uni.showModal({ + title: '纭', + content: '纭畾瑕佸垹闄ゅ悧锛?, + success: (res) => { + if (res.confirm) { + // 浣跨敤 Promise.then() 浠f浛 await + someAsyncFunction().then((result) => { + // 澶勭悊缁撴灉 + }) + } + } + }) + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?鍙傛暟绫诲瀷涓嶅尮閰嶏細瀹為檯绫诲瀷涓?'Function1<..., UTSPromise>'锛岄鏈熺被鍨嬩负 'Function1<..., Unit>?'" + - 鍘熷洜锛氬洖璋冨嚱鏁拌繑鍥?Promise 鑰岄潪 void + - 瑙e喅锛氫娇鐢?`.then()` 浠f浛 `await` + +3. 鏈€浣冲疄璺? + - 鍦ㄥ洖璋冨嚱鏁颁腑浣跨敤 `.then()` 澶勭悊寮傛鎿嶄綔 + - 灏嗗紓姝ラ€昏緫灏佽涓哄崟鐙殑鍑芥暟锛屽湪鍥炶皟涓皟鐢? + +================================================================================ +涓夊崄涔濄€?026-02-27 绫诲瀷杞崲鍓嶅繀椤绘鏌ョ被鍨嬶紙閲嶈锛? +================================================================================ + +1. 浣跨敤 `as` 绫诲瀷杞崲鍓嶅繀椤绘鏌ュ疄闄呯被鍨? + - 鐩存帴浣跨敤 `as string` 杞崲鍙兘瀵艰嚧杩愯鏃剁被鍨嬭浆鎹㈠紓甯? + - 閿欒绀轰緥锛? + ```typescript + const idVal = item['id'] + const id = idVal as string // 閿欒锛氬鏋?idVal 鏄叾浠栫被鍨嬩細宕╂簝 + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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" + - 鍘熷洜锛氱洿鎺ョ被鍨嬭浆鎹㈡椂锛屽疄闄呯被鍨嬩笌鐩爣绫诲瀷涓嶅尮閰? + - 瑙e喅锛氫娇鐢?`typeof` 妫€鏌ョ被鍨嬪悗鍐嶈浆鎹? + +3. 鏈€浣冲疄璺? + - 浣跨敤 `typeof` 妫€鏌ョ被鍨? + - 浣跨敤 `!= null` 妫€鏌ョ┖鍊? + - 鎻愪緵榛樿鍊奸槻姝㈢┖鎸囬拡寮傚父 + +================================================================================ +鍥涘崄銆?026-02-27 UTSJSONObject 蹇呴』姝g‘杞崲锛堥噸瑕侊級 +================================================================================ + +1. `as UTSJSONObject` 涓嶄細娣诲姞鏂规硶 + - 浠庢暟鎹簱杩斿洖鐨勬暟鎹渶瑕佹纭浆鎹负 UTSJSONObject + - 閿欒绀轰緥锛? + ```typescript + const item = rawList[i] + const brandObj = item as UTSJSONObject // 閿欒锛歜randObj.getString 涓嶅瓨鍦? + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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 + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜杞崲 + +3. 鏈€浣冲疄璺? + - 瀵逛簬浠庢暟鎹簱/API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? + +================================================================================ +鍥涘崄浜屻€?026-02-27 getBoolean 鏂规硶鍙兘瀵艰嚧绫诲瀷杞崲寮傚父锛堥噸瑕侊級 +================================================================================ + +1. `UTSJSONObject.getBoolean()` 鍙兘瀵艰嚧绫诲瀷杞崲寮傚父 + - 褰撴暟鎹簱瀛楁绫诲瀷涓庨鏈熶笉绗︽椂锛宍getBoolean()` 鍙兘鎶涘嚭寮傚父 + - 閿欒绀轰緥锛? + ```typescript + const isFeatured = prodObj.getBoolean('is_featured') ?? false // 鍙兘鎶涘嚭寮傚父 + ``` + - 姝g‘绀轰緥锛? + ```typescript + 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 鏂规硶鏈熸湜鐨勭被鍨嬩笉鍖归厤 + - 瑙e喅锛氫娇鐢?`.get()` 鏂规硶鑾峰彇鍘熷鍊硷紝鐒跺悗鎵嬪姩妫€鏌ョ被鍨? + +3. 鏈€浣冲疄璺? + - 閬垮厤浣跨敤 `.getBoolean()`锛屾敼鐢?`.get()` + `typeof` 妫€鏌? + - 鍦?SQL 鏌ヨ涓槑纭寚瀹氶渶瑕佺殑瀛楁锛岄伩鍏?`SELECT *` + - 瀵逛簬甯冨皵鍊硷紝浣跨敤 `typeof val == 'boolean'` 妫€鏌ョ被鍨? + +================================================================================ +鍥涘崄涓夈€?026-02-27 SELECT * 鍙兘瀵艰嚧绫诲瀷杞崲闂锛堥噸瑕侊級 +================================================================================ + +1. 閬垮厤浣跨敤 `SELECT *` 鏌ヨ鎵€鏈夊瓧娈? + - 鏁版嵁搴撳彲鑳藉寘鍚墠绔笉闇€瑕佺殑瀛楁锛屽鑷寸被鍨嬭浆鎹㈠紓甯? + - 閿欒绀轰緥锛? + ```typescript + .select('*') // 鍙兘杩斿洖鎰忓鐨勫瓧娈电被鍨? + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, description, base_price, market_price, main_image_url') + ``` + +2. 鏈€浣冲疄璺? + - 鍙煡璇㈤渶瑕佺殑瀛楁 + - 鍙傝€冩暟鎹簱鏂囨。纭瀛楁绫诲瀷 + - 瀵逛簬瑙嗗浘锛堝 `ml_products_detail_view`锛夛紝娉ㄦ剰瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚? + +================================================================================ +鍥涘崄鍥涖€?026-02-27 鍒涘缓杈呭姪鍑芥暟澶勭悊鏁版嵁杞崲锛堥噸瑕侊級 +================================================================================ + +1. 鍒涘缓杈呭姪鍑芥暟缁熶竴澶勭悊鏁版嵁绫诲瀷杞崲 + - 閬垮厤鍦ㄦ瘡涓柟娉曚腑閲嶅鍐欑被鍨嬫鏌ヤ唬鐮? + - 绀轰緥锛? + ```typescript + // 杈呭姪鍑芥暟锛氬畨鍏ㄨ幏鍙栧瓧绗︿覆鍊? + 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() + if (typeof val == 'boolean') return (val as boolean) ? 'true' : 'false' + 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') { + const parsed = parseFloat(val as string) + return isNaN(parsed) ? 0 : parsed + } + return 0 + } + + // 杈呭姪鍑芥暟锛氬畨鍏ㄨ幏鍙栧竷灏斿€? + function safeGetBoolean(obj: UTSJSONObject, key: string): boolean { + 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 + } + + // 杈呭姪鍑芥暟锛氫粠鍘熷鏁版嵁瑙f瀽鍟嗗搧 + 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. 浼樼偣 + - 浠g爜澶嶇敤锛屽噺灏戦噸澶? + - 缁熶竴澶勭悊鍚勭绫诲瀷杞崲寮傚父 + - 鏄撲簬缁存姢鍜屼慨鏀? + +3. 鏈€浣冲疄璺? + - 灏嗚緟鍔╁嚱鏁版斁鍦ㄦ枃浠堕《閮? + - 瀵规墍鏈変粠鏁版嵁搴撹幏鍙栫殑鏁版嵁浣跨敤杈呭姪鍑芥暟 + - 澶勭悊鎵€鏈夊彲鑳界殑绫诲瀷杞崲鎯呭喌 + +================================================================================ +鍥涘崄浜斻€?026-02-27 瑙嗗浘瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚岋紙閲嶈锛? +================================================================================ + +1. 鏁版嵁搴撹鍥剧殑瀛楁鍚嶅彲鑳戒笌鍩虹琛ㄤ笉鍚? + - `ml_products_detail_view` 瑙嗗浘涓病鏈?`image_url` 瀛楁 + - 鍙湁 `main_image_url` 鍜?`image_urls` 瀛楁 + - 閿欒绀轰緥锛? + ```typescript + .select('id, name, image_url') // 閿欒锛氳鍥炬病鏈?image_url 瀛楁 + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, main_image_url, image_urls') // 姝g‘ + ``` + +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?column ml_products_detail_view.image_url does not exist" + - 鎻愮ず锛?Perhaps you meant to reference the column 'ml_products_detail_view.image_urls'" + - 鍘熷洜锛氭煡璇簡瑙嗗浘涓笉瀛樺湪鐨勫瓧娈? + - 瑙e喅锛氬弬鑰冩暟鎹簱鏂囨。纭瑙嗗浘瀛楁鍚? + +3. 鏈€浣冲疄璺? + - 鏌ヨ瑙嗗浘鍓嶅厛纭瀛楁鍚? + - 鍙傝€?`CONSUMER_DB_DOC.md` 鏂囨。 + - 浣跨敤鏄庣‘瀛楁鍒楄〃鑰岄潪 `SELECT *` + +================================================================================ +鍥涘崄鍏€?026-02-27 杈呭姪鍑芥暟搴斾娇鐢?try-catch 鍖呰锛堥噸瑕侊級 +================================================================================ + +1. 杈呭姪鍑芥暟搴斾娇鐢?try-catch 鍖呰闃叉宕╂簝 + - 鍦?Android 绔紝绫诲瀷杞崲鍙兘鎶涘嚭寮傚父 + - 浣跨敤 try-catch 鍖呰鍙互闃叉鏁翠釜搴旂敤宕╂簝 + - 绀轰緥锛? + ```typescript + 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. 浼樼偣 + - 闃叉鍗曚釜瀛楁瑙f瀽澶辫触瀵艰嚧鏁翠釜搴旂敤宕╂簝 + - 鎻愪緵璇︾粏鐨勯敊璇棩蹇椾究浜庤皟璇? + - 杩斿洖榛樿鍊间繚璇佸簲鐢ㄧ户缁繍琛? + +3. 鏈€浣冲疄璺? + - 鎵€鏈夎緟鍔╁嚱鏁伴兘搴斾娇鐢?try-catch 鍖呰 + - 鍦?catch 涓褰曢敊璇棩蹇? + - 杩斿洖鍚堢悊鐨勯粯璁ゅ€? + +================================================================================ +鍥涘崄涓冦€?026-02-27 鍟嗗搧瑙嗗浘娌℃湁 shop_id 瀛楁锛堥噸瑕侊級 +================================================================================ + +1. `ml_products_detail_view` 瑙嗗浘娌℃湁 `shop_id` 瀛楁 + - 鍟嗗搧閫氳繃 `merchant_id` 鍏宠仈鍟嗗/搴楅摵 + - 閿欒绀轰緥锛? + ```typescript + .select('id, name, shop_id') // 閿欒锛氳鍥炬病鏈?shop_id 瀛楁 + ``` + - 姝g‘绀轰緥锛? + ```typescript + .select('id, name, merchant_id') // 姝g‘锛氫娇鐢?merchant_id + ``` + +2. 鏁版嵁搴撳瓧娈靛搴斿叧绯? + - `ml_products` 琛細`merchant_id` 鍏宠仈鍟嗗 + - `ml_shops` 琛細`user_id` 绛変簬鍟嗗鐨?`merchant_id` + - 瑙嗗浘涓€氳繃 `merchant_id` JOIN `ml_shops` 鑾峰彇搴楅摵淇℃伅 + +3. 鏈€浣冲疄璺? + - 鏌ヨ鍟嗗搧鏃朵娇鐢?`merchant_id` 鑰岄潪 `shop_id` + - 鍙傝€?`CONSUMER_DB_DOC.md` 纭瀛楁鍚? + +================================================================================ +鍥涘崄鍏€?026-02-27 any 绫诲瀷涓嶆敮鎸佺储寮曡闂紙閲嶈锛? +================================================================================ + +1. UTS Android 涓?`any` 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 涓嶈兘浣跨敤 `obj[key]` 璇硶璁块棶 `any` 绫诲瀷瀵硅薄鐨勫睘鎬? + - 閿欒绀轰緥锛? + ```typescript + function safeGetString(obj: any, key: string): string { + const val = obj[key] // 閿欒锛歎nresolved reference + } + ``` + - 姝g‘绀轰緥锛? + ```typescript + function safeGetString(obj: UTSJSONObject, key: string): string { + const val = obj.get(key) // 姝g‘锛氫娇鐢?UTSJSONObject 鐨?get 鏂规硶 + } + ``` + +2. 缂栬瘧閿欒鎻愮ず + - 閿欒淇℃伅锛?Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch" + - 鍘熷洜锛歚any` 绫诲瀷涓嶆敮鎸佺储寮曡闂? + - 瑙e喅锛氬厛灏嗗璞¤浆鎹负 `UTSJSONObject`锛屽啀浣跨敤 `.get()` 鏂规硶 + +3. 鏈€浣冲疄璺? + - 浣跨敤 `toUTSJSONObject()` 鍑芥暟灏?`any` 杞崲涓?`UTSJSONObject` + - 浣跨敤 `.get()`銆乣.getString()`銆乣.getNumber()` 绛夋柟娉曡闂睘鎬? + - 杈呭姪鍑芥暟鍙傛暟绫诲瀷搴斾负 `UTSJSONObject` 鑰岄潪 `any` + +================================================================================ +鍥涘崄涔濄€?026-02-27 浣跨敤 getString/getNumber/getBoolean 鏂规硶锛堥噸瑕侊級 +================================================================================ + +1. UTSJSONObject 鎻愪緵浜嗙被鍨嬪畨鍏ㄧ殑璁块棶鏂规硶 + - `getString(key)` - 鐩存帴杩斿洖瀛楃涓叉垨 null + - `getNumber(key)` - 鐩存帴杩斿洖鏁板€兼垨 null + - `getBoolean(key)` - 鐩存帴杩斿洖甯冨皵鍊兼垨 null + - `getArray(key)` - 鐩存帴杩斿洖鏁扮粍鎴?null + - 杩欎簺鏂规硶姣?`.get()` 鏇村畨鍏紝浼氳嚜鍔ㄨ繘琛岀被鍨嬭浆鎹? + +2. 鎺ㄨ崘鐢ㄦ硶 + ```typescript + // 鎺ㄨ崘浣跨敤 + 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 鍧椾腑澶勭悊寮傚父 + +================================================================================ +鍥涘崄涓€銆?026-02-27 item as UTSJSONObject 涓嶄細娣诲姞鏂规硶锛堥噸瑕侊級 +================================================================================ + +1. `item as UTSJSONObject` 涓嶄細璁╁璞¤幏寰?`getString` 绛夋柟娉? + - 鐩存帴浣跨敤 `as UTSJSONObject` 鍙槸绫诲瀷鏂█锛屼笉浼氭敼鍙樿繍琛屾椂瀵硅薄 + - 閿欒绀轰緥锛? + ```typescript + const item = rawList[i] + const prodObj = item as UTSJSONObject // 閿欒锛歡etString 涓嶅瓨鍦? + const id = prodObj.getString('id') // 杩愯鏃堕敊璇? + ``` + - 姝g‘绀轰緥锛? + ```typescript + const item = rawList[i] + const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject + const id = prodObj.getString('id') ?? '' // 姝g‘ + ``` + +2. 杩愯鏃堕敊璇彁绀? + - 閿欒淇℃伅锛?getString is not a function" + - 鍘熷洜锛氬璞℃病鏈夋纭浆鎹负 UTSJSONObject + - 瑙e喅锛氫娇鐢?`JSON.parse(JSON.stringify())` 杩涜杞崲 + +3. 鏈€浣冲疄璺? + - 瀵逛簬浠庢暟鎹簱/API 杩斿洖鐨勬暟鎹紝缁熶竴浣跨敤 `JSON.parse(JSON.stringify())` 杞崲 + - 浣跨敤 `.getString()`銆乣.getNumber()` 绛夋柟娉曞畨鍏ㄨ闂睘鎬? + +================================================================================ + +================================================================================ diff --git a/pages/mall/consumer/favorites.uvue b/pages/mall/consumer/favorites.uvue index db423f80..c4c7a681 100644 --- a/pages/mall/consumer/favorites.uvue +++ b/pages/mall/consumer/favorites.uvue @@ -66,7 +66,9 @@ const addToCart = async (product: Product) => { const loadFavorites = async () => { const res = await supabaseService.getFavorites() - favorites.value = res.map((item: any): Product => { + const productList: Product[] = [] + for (let i = 0; i < res.length; i++) { + const item = res[i] let prod: any | null = null let itemObj: UTSJSONObject | null = null @@ -117,7 +119,7 @@ const loadFavorites = async () => { } } - return { + const product: Product = { id: id, name: name, price: price, @@ -126,7 +128,9 @@ const loadFavorites = async () => { main_image_url: image, sale_count: sales } as Product - }) + productList.push(product) + } + favorites.value = productList } const goShopping = () => { diff --git a/pages/mall/consumer/footprint.uvue b/pages/mall/consumer/footprint.uvue index ccabe6c1..b5e72472 100644 --- a/pages/mall/consumer/footprint.uvue +++ b/pages/mall/consumer/footprint.uvue @@ -8,7 +8,7 @@ - + 👣 暂无浏览记录 @@ -501,6 +501,7 @@ onMounted(() => { .footprint-content { flex: 1; + height: 0px; } .empty-footprints { diff --git a/pages/mall/consumer/index copy.uvue b/pages/mall/consumer/index copy.uvue new file mode 100644 index 00000000..ccc90416 --- /dev/null +++ b/pages/mall/consumer/index copy.uvue @@ -0,0 +1,2666 @@ + + + + + + diff --git a/pages/mall/consumer/index.uvue b/pages/mall/consumer/index.uvue index 74ee5f31..8d34140a 100644 --- a/pages/mall/consumer/index.uvue +++ b/pages/mall/consumer/index.uvue @@ -174,7 +174,7 @@ --> - + @@ -285,10 +285,12 @@ const hasMore = ref(true) const activeSort = ref('recommend') // 默认展示智能推荐 const activeFilter = ref('recommend') const currentPage = ref(1) +const priceAscending = ref(true) // 价格排序方向:true=升序,false=降序 // 数据源 const hotProducts = ref([]) const recommendedProducts = ref([]) +const hotKeywords = ref([]) // 屏幕尺寸检测 const isMobile = ref(false) @@ -367,7 +369,10 @@ const loadCategories = async (): Promise => { // 获取二级分类数据 const loadSubCategories = async (parentId: string): Promise => { try { + console.log('[loadSubCategories] 开始加载二级分类, parentId:', parentId) const subData = await supabaseService.getSubCategories(parentId) + console.log('[loadSubCategories] 获取到二级分类数量:', subData.length) + console.log('[loadSubCategories] 二级分类数据:', JSON.stringify(subData)) subCategories.value = subData } catch (error) { console.error('加载子分类数据失败:', error) @@ -377,18 +382,31 @@ const loadSubCategories = async (parentId: string): Promise => { // 点击一级分类 const onParentCategoryClick = async (category: Category): Promise => { - // 如果已经选中,则切换显示/隐藏二级分类 - if (selectedParentCategory.value != null && selectedParentCategory.value.id === category.id) { + console.log('[onParentCategoryClick] 点击一级分类:', category.name, 'id:', category.id) + + // 如果已经选中,则切换显示/隐藏二级分类 + if (selectedParentCategory.value != null && selectedParentCategory.value.id === category.id) { + console.log('[onParentCategoryClick] 切换显示状态') showSubCategories.value = !showSubCategories.value return - } + } - // 选中新的分类 - selectedParentCategory.value = category - showSubCategories.value = true + // 选中新的分类 + selectedParentCategory.value = category + showSubCategories.value = true + console.log('[onParentCategoryClick] showSubCategories 设置为 true') - // 加载二级分类 - await loadSubCategories(category.id) + // 加载二级分类 + await loadSubCategories(category.id) + + // 如果没有二级分类,直接跳转到分类页 + if (subCategories.value.length == 0) { + console.log('[onParentCategoryClick] 没有二级分类,直接跳转到分类页') + uni.setStorageSync('selectedCategory', category.id) + uni.switchTab({ + url: '/pages/mall/consumer/category' + }) + } } // 点击二级分类 @@ -453,32 +471,28 @@ const doLoadHotProducts = async (targetLimit: number, resolve: (value: void) => switch (activeSort.value) { case 'sales': - console.log('调用 getHotProducts') - products = await supabaseService.getHotProducts(limit) + console.log('调用 getProductsBySales') + products = await supabaseService.getProductsBySales(limit) break case 'price': - console.log('调用 getProductsByPrice') - // 按价格升序(从低到高) - products = await supabaseService.getProductsByPrice(limit, true) + console.log('调用 getProductsByPrice, 升序:', priceAscending.value) + products = await supabaseService.getProductsByPrice(limit, priceAscending.value) break case 'new': console.log('调用 getProductsByNewest') - // 按创建时间,最新的在前 products = await supabaseService.getProductsByNewest(limit) break case 'recommend': - console.log('调用 getRecommendedProducts') - // 推荐商品(带badge的商品) - products = await supabaseService.getRecommendedProducts(limit) + console.log('调用 getSmartRecommendations') + products = await supabaseService.getSmartRecommendations(limit) break case 'discount': console.log('调用 getDiscountProducts') - // 特价商品(badge为'特价') products = await supabaseService.getDiscountProducts(limit) break default: - console.log('调用默认 getHotProducts') - products = await supabaseService.getHotProducts(limit) + console.log('调用默认 getProductsBySales') + products = await supabaseService.getProductsBySales(limit) } console.log('加载到的商品数量:', products.length) @@ -516,6 +530,25 @@ function loadRecommendedProducts(limit: number): Promise { }) } +// 加载热搜词 +const loadHotKeywords = async (): Promise => { + try { + const keywords = await supabaseService.getHotKeywords(10) + hotKeywords.value = keywords + console.log('加载热搜词:', keywords.length, '个') + } catch (error) { + console.error('加载热搜词失败:', error) + hotKeywords.value = [] + } +} + +// 点击热搜词进行搜索 +const searchByKeyword = (keyword: string): void => { + uni.navigateTo({ + url: `/pages/mall/consumer/search?keyword=${encodeURIComponent(keyword)}` + }) +} + // 初始化数据 const initData = async () => { // 首先确保用户资料已加载 @@ -527,6 +560,7 @@ const initData = async () => { } await loadCategories() await loadBrands() + await loadHotKeywords() await loadHotProducts(defaultLoadLimit) await loadRecommendedProducts(defaultLoadLimit) } @@ -719,7 +753,17 @@ const switchBrand = (brand: Brand) => { // 切换排序 const switchSort = (sortId: string) => { - activeSort.value = sortId + // 如果点击的是价格排序,切换升序/降序 + if (sortId === 'price' && activeSort.value === 'price') { + priceAscending.value = !priceAscending.value + console.log('切换价格排序方向,升序:', priceAscending.value) + } else { + // 切换到其他排序时,重置价格排序为升序 + if (sortId !== 'price') { + priceAscending.value = true + } + activeSort.value = sortId + } hasMore.value = true // 重置加载更多状态 // 重新加载热销商品,排序由 Supabase 服务处理 loadHotProducts(defaultLoadLimit) @@ -776,26 +820,45 @@ const loadMore = async () => { try { // 获取当前热销商品的数量 const currentCount = hotProducts.value.length - const nextLimit = currentCount + 6 + const nextPage = Math.floor(currentCount / 6) + 1 + const additionalLimit = 6 - console.log('开始加载更多,当前数量:', currentCount, '目标数量:', nextLimit) + console.log('开始加载更多,当前数量:', currentCount, '页码:', nextPage) - // 加载更多热销商品 - await loadHotProducts(nextLimit) + // 加载更多商品 + 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 + case 'new': + newProducts = await supabaseService.getProductsByNewest(currentCount + additionalLimit) + break + case 'recommend': + newProducts = await supabaseService.getSmartRecommendations(currentCount + additionalLimit) + break + case 'discount': + newProducts = await supabaseService.getDiscountProducts(currentCount + additionalLimit) + break + default: + newProducts = await supabaseService.getProductsBySales(currentCount + additionalLimit) + } + + console.log('加载到的新商品数量:', newProducts.length) // 检查是否还有更多数据 - if (hotProducts.value.length === currentCount) { + if (newProducts.length <= currentCount) { hasMore.value = false uni.showToast({ title: '没有更多了', icon: 'none' }) } else { - // 还有数据,或者是刚加载了一批 - /* uni.showToast({ - title: '加载完成', - icon: 'success' - }) */ + // 更新商品列表 + hotProducts.value = newProducts } } catch (error) { console.error('加载更多失败:', error) @@ -963,14 +1026,10 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders' top: 0; left: 0; right: 0; - background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%); + background-color: #4CAF50; z-index: 1000; - box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15); /* 调整为与分类页一致 */ + box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - /* will-change: transform, opacity; removed for uniapp-x support */ - /* pointer-events: auto; */ - /* backface-visibility: hidden; */ - /* -webkit-backface-visibility: hidden; */ } /* 导航栏搜索框容器内边距调整 */ @@ -1320,14 +1379,12 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders' .card-name { font-size: 12px; - font-weight: 500; + font-weight: normal; color: #333; margin-bottom: 4px; text-align: center; - lines: 1; - text-overflow: ellipsis; - overflow: hidden; width: 100%; + overflow-wrap: break-word; } .card-desc { @@ -1524,6 +1581,59 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders' color: #666; } +/* 热搜词区域 */ +.hot-keywords-section { + background: white; + border-radius: 16px; + padding: 20px; + margin-bottom: 20px; +} + +.keywords-list { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 15px; +} + +.keyword-item { + display: flex; + align-items: center; + padding: 8px 16px; + background: #f5f5f5; + border-radius: 20px; + cursor: pointer; + transition: all 0.2s ease; +} + +.keyword-item:hover { + background: #fff0f0; +} + +.keyword-rank { + width: 20px; + height: 20px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: bold; + color: #999; + background: #eee; + margin-right: 8px; +} + +.keyword-rank.top-three { + background: #ff4757; + color: white; +} + +.keyword-text { + font-size: 14px; + color: #333; +} + /* 热销药品 */ .hot-products { background: white; @@ -2076,45 +2186,43 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders' } .category-grid { - /* grid-template-columns: repeat(5, 1fr); removed for uniapp-x support */ - /* gap: 8px; removed */ margin: 0 -1%; padding: 0 4px; } .category-grid .category-card { - width: 18%; /* 5 cols : 20 - 2 */ - margin: 0 1% 8px 1%; - padding: 8px 0; - background: transparent; /* 移除卡片背景 */ - box-shadow: none; /* 移除阴影 */ - border: none; /* 移除边框 */ + width: 18%; + margin: 0 1% 6px 1%; + padding: 4px 0; + background: transparent; + box-shadow: none; + border: none; } .category-card:hover { - transform: none; /* 移动端移除悬停效果 */ + transform: none; box-shadow: none; } .card-icon { - width: 44px; /* 减小图标尺寸 */ - height: 44px; - border-radius: 22px; - margin-bottom: 6px; + width: 36px; + height: 36px; + border-radius: 18px; + margin-bottom: 4px; } .card-icon-text { - font-size: 20px; + font-size: 18px; } .card-name { - font-size: 11px; /* 减小文字大小 */ + font-size: 10px; font-weight: normal; color: #333; } .card-desc { - display: none; /* 手机端隐藏描述文字,保持界面整洁 */ + display: none; } .services-grid .service-card { diff --git a/pages/mall/consumer/messages.uvue b/pages/mall/consumer/messages.uvue index 826a5f37..f3c59492 100644 --- a/pages/mall/consumer/messages.uvue +++ b/pages/mall/consumer/messages.uvue @@ -13,8 +13,8 @@ - - + + @@ -350,9 +350,15 @@ const loadMessages = async () => { promoMessages.length = 0 // 1. 获取通知 (系统、订单、优惠) + console.log('[loadMessages] 开始获取通知...') const notes = await supabaseService.getUserNotifications() + console.log('[loadMessages] 获取到通知数量:', notes.length) - notes.forEach((note: Notification) => { + // 使用 for 循环替代 forEach + for (let i = 0; i < notes.length; i++) { + const note = notes[i] + console.log('[loadMessages] 通知类型:', note.type, '标题:', note.title) + const item: MessageItem = { id: note.id, title: note.title, @@ -361,7 +367,7 @@ const loadMessages = async () => { read: note.is_read, type: note.type, avatar: note.icon_url, - important: note.type === 'system', + important: note.type == 'system', coupon: '点击查看', expiry: '', claimed: false, @@ -378,15 +384,17 @@ const loadMessages = async () => { active: false } - if (note.type === 'system') { + if (note.type == 'system') { systemMessages.push(item) - } else if (note.type === 'order') { + } else if (note.type == 'order') { orderMessages.push(item) - } else if (note.type === 'promotion') { + } else if (note.type == 'promotion') { item.type = 'promo' promoMessages.push(item) } - }) + } + + console.log('[loadMessages] 系统消息:', systemMessages.length, '订单消息:', orderMessages.length, '优惠消息:', promoMessages.length) // 2. 获取客服消息 (Chat) const rooms = await supabaseService.getChatRooms() @@ -641,15 +649,13 @@ const onRefresh = () => { top: 0; left: 0; right: 0; - background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%); + background-color: #4CAF50; z-index: 1000; box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15); - /* height: 50px; 移除固定高度,由内容决定 */ display: flex; - flex-direction: row; /* 显式设置行方向 */ - align-items: center; - justify-content: center; - flex-shrink: 0; /* 防止被压缩 */ + flex-direction: column; + justify-content: flex-start; + flex-shrink: 0; } .nav-container { diff --git a/pages/mall/consumer/order-detail.uvue b/pages/mall/consumer/order-detail.uvue index a1f57206..0fda352b 100644 --- a/pages/mall/consumer/order-detail.uvue +++ b/pages/mall/consumer/order-detail.uvue @@ -279,21 +279,63 @@ const loadOrderDetail = async () => { try { const data = await supabaseService.getOrderDetail(orderId.value) if (data != null) { - const dataObj = data as Record + // 使用 JSON.parse(JSON.stringify()) 转换数据 + const dataObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject order.value = data as OrderType - const items = dataObj['ml_order_items'] - orderItems.value = items != null ? (items as OrderItemType[]) : [] - deliveryAddress.value = dataObj['shipping_address'] as AddressType + + // 解析订单商品 + 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 item = items[i] + const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject + + const orderItem: OrderItemType = { + id: (itemObj.get('id') ?? '') as string, + product_id: (itemObj.get('product_id') ?? '') as string, + product_name: (itemObj.get('product_name') ?? '') as string, + price: (itemObj.get('price') ?? 0) as number, + quantity: (itemObj.get('quantity') ?? 1) as number, + image_url: (itemObj.get('image_url') ?? '') as string, + specifications: (itemObj.get('specifications') ?? '') as string + } + orderItems.value.push(orderItem) + } + } + + // 解析收货地址 + const addressRaw = dataObj.get('shipping_address') + if (addressRaw != null) { + const addressObj = JSON.parse(JSON.stringify(addressRaw)) as UTSJSONObject + const province = (addressObj.get('province') ?? '') as string + const city = (addressObj.get('city') ?? '') as string + const district = (addressObj.get('district') ?? '') as string + const detail = (addressObj.get('detail') ?? '') as string + deliveryAddress.value = { + name: (addressObj.get('name') ?? '') as string, + phone: (addressObj.get('phone') ?? '') as string, + province: province, + city: city, + district: district, + detail: detail, + address: province + city + district + detail + } as AddressType + } // 获取店铺信息 - const merchantId = dataObj['merchant_id'] as string + const merchantId = dataObj.get('merchant_id') as string if (merchantId != null && merchantId != '') { loadShopInfo(merchantId) } + + console.log('订单详情加载成功,商品数量:', orderItems.value.length) } else { uni.showToast({ title: '订单不存在', icon: 'none' }) } } catch (e) { + console.error('加载订单详情失败:', e) uni.showToast({ title: '加载失败', icon: 'none' }) } finally { uni.hideLoading() diff --git a/pages/mall/consumer/product-detail.uvue b/pages/mall/consumer/product-detail.uvue index 81f71398..0b336c19 100644 --- a/pages/mall/consumer/product-detail.uvue +++ b/pages/mall/consumer/product-detail.uvue @@ -379,90 +379,53 @@ export default { async loadProductDetail(productId: string, options: any = {}) { uni.showLoading({ title: '加载中...' }) try { - const dbProductResponse = await supabaseService.getProductById(productId) - let dbProduct: UTSJSONObject | null = null - if (Array.isArray(dbProductResponse)) { - const arr = dbProductResponse as any[] - if (arr.length > 0) { - dbProduct = arr[0] as UTSJSONObject - } - } else if (dbProductResponse != null) { - dbProduct = dbProductResponse as UTSJSONObject - } + const dbProduct = await supabaseService.getProductById(productId) if (dbProduct != null) { - // Map DB product to local product - const dbObj = dbProduct as UTSJSONObject + // 使用 getProductById 返回的 Product 对象 this.product = { - id: dbProduct['id'] as string, - merchant_id: (dbProduct['merchant_id'] ?? dbProduct['shop_id'] ?? '') as string, - category_id: (dbProduct['category_id'] ?? '') as string, - name: dbProduct['name'] as string, - description: (dbProduct['description'] ?? '') as string, - images: [] as string[], - price: (dbProduct['base_price'] ?? dbProduct['price'] ?? 0) as number, - original_price: (dbProduct['market_price'] ?? dbProduct['original_price'] ?? 0) as number, - stock: (dbProduct['available_stock'] ?? dbProduct['total_stock'] ?? dbProduct['stock'] ?? 0) as number, - sales: (dbProduct['sale_count'] ?? dbProduct['sales'] ?? 0) as number, - status: dbProduct['status'] != null ? dbProduct['status'] as number : 1, - created_at: (dbProduct['created_at'] ?? new Date().toISOString()) as string, - // Attributes - specification: dbProduct['specification'] as string | null, - usage: dbProduct['usage'] as string | null, - side_effects: dbProduct['side_effects'] as string | null, - precautions: dbProduct['precautions'] as string | null, - expiry_date: dbProduct['expiry_date'] as string | null, - storage_conditions: dbProduct['storage_conditions'] as string | null, - approval_number: dbProduct['approval_number'] as string | null, + id: dbProduct.id, + merchant_id: dbProduct.merchant_id ?? '', + category_id: dbProduct.category_id ?? '', + name: dbProduct.name, + description: dbProduct.description ?? '', + images: dbProduct.images ?? [] as string[], + price: dbProduct.price ?? dbProduct.base_price ?? 0, + original_price: dbProduct.original_price ?? dbProduct.market_price ?? 0, + stock: dbProduct.stock ?? dbProduct.total_stock ?? 0, + sales: dbProduct.sale_count ?? 0, + status: dbProduct.status ?? 1, + created_at: dbProduct.created_at ?? new Date().toISOString(), + specification: dbProduct.specification ?? null, + usage: dbProduct.usage ?? null, + side_effects: dbProduct.side_effects ?? null, + precautions: dbProduct.precautions ?? null, + expiry_date: dbProduct.expiry_date ?? null, + storage_conditions: dbProduct.storage_conditions ?? null, + approval_number: dbProduct.approval_number ?? null, tags: [] as string[] } as ProductType - - // Handle Images - if (dbProduct['image_urls'] != null) { + + // 解析 tags + if (dbProduct.tags != null && dbProduct.tags != '') { try { - const imageUrls = dbProduct['image_urls'] - const parsed = typeof imageUrls === 'string' ? JSON.parse(imageUrls) : imageUrls - if (Array.isArray(parsed)) { - this.product.images = (parsed as any[]).map((i: any): string => i as string) - } - } catch (e) { console.error('Error parsing image_urls', e) } - } - // Fallback to main_image_url if no images found - if (this.product.images.length === 0 && dbProduct['main_image_url'] != null) { - this.product.images.push(dbProduct['main_image_url'] as string) - } - // Fallback to 'image' field (legacy) - if (this.product.images.length === 0 && dbProduct['image'] != null) { - this.product.images.push(dbProduct['image'] as string) - } - // Final fallback - if (this.product.images.length === 0) { - this.product.images.push('/static/default-product.png') - } - - // Handle Tags - if (dbProduct['tags'] != null) { - try { - const tagsData = dbProduct['tags'] - const parsedTags = typeof tagsData === 'string' ? JSON.parse(tagsData) : tagsData + 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) {} + } catch(e) {} + } + + // Handle Images - 使用 main_image_url 作为后备 + if (this.product.images.length == 0 && dbProduct.main_image_url != null && dbProduct.main_image_url != '') { + this.product.images.push(dbProduct.main_image_url) + } + // Final fallback + if (this.product.images.length == 0) { + this.product.images.push('/static/default-product.png') } - // Handle JSON attributes if present - const attributes = dbProduct['attributes'] - if (attributes != null && typeof attributes === 'string') { - try { - const attrs = JSON.parse(attributes) as UTSJSONObject | null - if (attrs != null) { - // Merge attributes into product if they match keys - if (attrs['specification'] != null) this.product.specification = attrs['specification'] as string - if (attrs['usage'] != null) this.product.usage = attrs['usage'] as string - } - } catch(e) {} - } + console.log('商品详情加载成功:', this.product.name, '库存:', this.product.stock, '销量:', this.product.sales) } else { throw new Error('No product found') } @@ -471,12 +434,21 @@ export default { // Fallback to options if available this.product.id = productId const opts = options as UTSJSONObject - const nameOpt = opts['name'] as string | null - this.product.name = (nameOpt != null && nameOpt !== '') ? decodeURIComponent(nameOpt) ?? '未知商品' : '未知商品' - const priceOpt = opts['price'] as string | null - this.product.price = (priceOpt != null && priceOpt !== '') ? parseFloat(priceOpt) : 0 - const imageOpt = opts['image'] as string | null - const decodedImage = (imageOpt != null && imageOpt !== '') ? decodeURIComponent(imageOpt) : null + const nameOpt = opts['name'] + this.product.name = (nameOpt != null && nameOpt != '') ? decodeURIComponent(nameOpt as string) ?? '未知商品' : '未知商品' + + // price 可能是 string 或 number 类型 + 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 + } + + const imageOpt = opts['image'] + const decodedImage = (imageOpt != null && imageOpt != '') ? decodeURIComponent(imageOpt as string) : null this.product.images = decodedImage != null ? [decodedImage] : ['/static/default-product.png'] } @@ -499,22 +471,23 @@ export default { try { const shopResponse = await supabaseService.getShopByMerchantId(merchantId) if (shopResponse != null) { - const shop = shopResponse as UTSJSONObject + // 直接使用 Shop 对象的属性 this.merchant = { - id: shop['id'] as string, - user_id: shop['merchant_id'] as string, - shop_name: shop['shop_name'] as string, - shop_logo: (shop['shop_logo'] ?? '/static/default-shop.png') as string, - shop_banner: (shop['shop_banner'] ?? '/static/default-banner.png') as string, - shop_description: (shop['description'] ?? '') as string, - contact_name: (shop['contact_name'] ?? '店主') as string, - contact_phone: (shop['contact_phone'] ?? '') as string, + id: shopResponse.id, + user_id: shopResponse.merchant_id, + shop_name: shopResponse.shop_name, + shop_logo: shopResponse.shop_logo ?? '/static/default-shop.png', + shop_banner: shopResponse.shop_banner ?? '/static/default-banner.png', + shop_description: shopResponse.description ?? '', + contact_name: shopResponse.contact_name ?? '店主', + contact_phone: shopResponse.contact_phone ?? '', shop_status: 1, - rating: (shop['rating_avg'] ?? 5.0) as number, - total_sales: (shop['total_sales'] ?? 0) as number, - created_at: (shop['created_at'] ?? new Date().toISOString()) as string + rating: shopResponse.rating_avg ?? 5.0, + total_sales: shopResponse.total_sales ?? 0, + created_at: shopResponse.created_at ?? new Date().toISOString() } as MerchantType realMerchantLoaded = true + console.log('店铺信息加载成功:', this.merchant.shop_name) } } catch (e) { console.error('Load shop failed', e) @@ -555,33 +528,30 @@ export default { const skus = await supabaseService.getProductSkus(productId) if (skus.length > 0) { console.log('加载到商品SKU:', skus.length) - this.productSkus = skus.map((skuData): ProductSkuType => { - const sku = skuData as UTSJSONObject + this.productSkus = [] + for (let i = 0; i < skus.length; i++) { + const skuData = skus[i] + // 解析 specifications JSON 字符串 let specs: UTSJSONObject = {} - const specsData = sku['specifications'] - if (specsData != null) { + if (skuData.specifications != null && skuData.specifications != '') { try { - if (typeof specsData === 'string') { - specs = JSON.parse(specsData) as UTSJSONObject - } else { - // 假设已经是对象 - specs = specsData as UTSJSONObject - } + specs = JSON.parse(skuData.specifications) as UTSJSONObject } catch(e) { - console.error('解析SKU规格失败', e) + console.error('解析SKU规格失败', e) } - } - return { - id: sku['id'] as string, - product_id: sku['product_id'] as string, - sku_code: sku['sku_code'] as string, - specifications: specs, - price: sku['price'] as number, - stock: sku['stock'] != null ? sku['stock'] as number : 0, - image_url: sku['image_url'] != null ? sku['image_url'] as string : '', - status: sku['status'] != null ? sku['status'] as number : 1 - } as ProductSkuType - }) + } + const sku: ProductSkuType = { + id: skuData.id, + product_id: skuData.product_id, + sku_code: skuData.sku_code, + specifications: specs, + price: skuData.price, + stock: skuData.stock ?? 0, + image_url: skuData.image_url ?? '', + status: skuData.status ?? 1 + } + this.productSkus.push(sku) + } return } } catch (e) { @@ -594,17 +564,54 @@ export default { if (this.product.merchant_id == '') return // Safety check for cached service definition try { - // @ts-ignore const couponData = await supabaseService.fetchShopCoupons(this.product.merchant_id) - this.coupons = couponData as Array - } catch (e) { - try { - // @ts-ignore - const couponData2 = await supabaseService.getAvailableCoupons(this.product.merchant_id) - this.coupons = couponData2 as Array - } catch (e2) { - console.warn('SupabaseService coupon methods not available:', e2) + // 解析优惠券数据 + this.coupons = [] + if (couponData != null && couponData.length > 0) { + for (let i = 0; i < couponData.length; i++) { + const item = couponData[i] + const couponObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject + + const getSafeString = (key: string): string => { + const val = couponObj.get(key) + if (val == null) return '' + if (typeof val == 'string') return val + return '' + } + + const getSafeNumber = (key: string): number => { + const val = couponObj.get(key) + if (val == null) return 0 + if (typeof val == 'number') return val + return 0 + } + + const coupon: CouponTemplateType = { + id: getSafeString('id'), + name: getSafeString('name'), + description: getSafeString('description'), + coupon_type: getSafeNumber('coupon_type'), + discount_type: getSafeNumber('discount_type'), + discount_value: getSafeNumber('discount_value'), + min_order_amount: getSafeNumber('min_order_amount'), + max_discount_amount: getSafeNumber('max_discount_amount'), + total_quantity: getSafeNumber('total_quantity'), + per_user_limit: getSafeNumber('per_user_limit'), + usage_limit: getSafeNumber('usage_limit'), + merchant_id: getSafeString('merchant_id'), + category_ids: [] as string[], + product_ids: [] as string[], + user_type_limit: getSafeNumber('user_type_limit'), + start_time: getSafeString('start_time'), + end_time: getSafeString('end_time'), + status: getSafeNumber('status'), + created_at: getSafeString('created_at') + } + this.coupons.push(coupon) + } } + } catch (e) { + console.warn('SupabaseService coupon methods not available:', e) } }, @@ -801,9 +808,16 @@ export default { }, goToShop() { - if (this.merchant.user_id != null && this.merchant.user_id !== '') { + const merchantId = this.merchant.id ?? this.product.merchant_id ?? '' + if (merchantId != '') { + console.log('进店点击,merchantId:', merchantId) uni.navigateTo({ - url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.id}` + url: `/pages/mall/consumer/shop-detail?merchantId=${merchantId}` + }) + } else { + uni.showToast({ + title: '店铺信息加载中', + icon: 'none' }) } }, diff --git a/pages/mall/consumer/profile.uvue b/pages/mall/consumer/profile.uvue index deda02a3..02a9c0d8 100644 --- a/pages/mall/consumer/profile.uvue +++ b/pages/mall/consumer/profile.uvue @@ -40,9 +40,9 @@ - - - + + + @@ -249,7 +249,7 @@