- + @@ -92,12 +92,12 @@ v-for="(item, index) in hotSearchList" :key="index" class="hot-tag" - :class="{ 'hot': item.hot }" + :class="item.hot == true ? 'hot' : ''" @click="searchFromHot(item.keyword)" > - {{ index + 1 }} + {{ index + 1 }} {{ item.keyword }} - 🔥 + 🔥 @@ -136,7 +136,7 @@ - + 相关店铺 - + ([]) -const hotSearchList = ref([]) -const guessList = ref([]) -const allGuessItems = ref([]) // 缓存所有猜你喜欢商品 -const searchResults = ref([]) -const searchShopResults = ref([]) // 搜索到的店铺 - - - -onMounted(() => { - initPage() -}) - - -const initPage = () => { - try { - const systemInfo = uni.getSystemInfoSync() - statusBarHeight.value = systemInfo.statusBarHeight ?? 0 - const windowHeight = systemInfo.windowHeight - // 减去头部高度 (约60px + statusBarHeight) - scrollHeight.value = windowHeight - (60 + statusBarHeight.value) - - loadData() - - // 检查页面参数 - const pages = getCurrentPages() - if (pages.length > 0) { - const currentPage = pages[pages.length - 1] - // @ts-ignore - const options = currentPage.options - if (options && options['keyword']) { - const keyword = decodeURIComponent(options['keyword']) - searchKeyword.value = keyword - - if (options['type'] === 'family' || options['type'] === 'brand') { - // 如果是家庭常备药或品牌类型,直接添加到历史并搜索 - if (options['type'] === 'family') { - addToHistory(keyword) - } - // 立即显示结果区域并设置为加载中 - showResults.value = true - loading.value = true - // 确保searchResults不为空数组导致闪烁(虽然loading=true已经拦截了empty-result,但双重保险) - // 此时不要置空searchResults,或者给一个初始值 - - // 直接调用搜索,移除setTimeout,防止中间状态 - performSearch() - } - } - } - } catch (e) { - console.error('初始化失败', e) - isError.value = true - } +type HotSearchItemType = { + keyword: string + hot: boolean } -// 加载基础数据 -const loadData = async () => { - isError.value = false - - try { - loadSearchHistory() - // 获取热门商品作为热门搜索推荐和猜你喜欢 - // 获取更多数据以便"换一批" - const hotProducts = await supabaseService.getHotProducts(30) - - hotSearchList.value = hotProducts.slice(0, 10).map((p: any) => ({ - keyword: p.name, - hot: true - })) - - allGuessItems.value = hotProducts.map((p: any) => ({ - id: p.id, - name: p.name, - price: p.base_price, - image: p.main_image_url ?? '/static/default.jpg', - sales: typeof p.sale_count === 'number' ? p.sale_count : 0 - })) - - // 初始显示随机6个 - refreshGuessListItems() - - } catch (e) { - console.error('Load data failed', e) - isError.value = true - } +type GuessItemType = { + id: string + name: string + price: number + image: string + sales: number } -// 点击重试 -const retryLoad = () => { - uni.showLoading({ title: '重新加载中' }) - setTimeout(() => { - uni.hideLoading() - loadData() - }, 1000) +type SearchResultType = { + id: string + name: string + image: string + price: number + specification: string + tag: string + sales: number } -// 历史记录管理 +type ShopResultType = { + id: string + name: string + logo: string + productCount: number +} + +const searchHistory = ref>([]) +const hotSearchList = ref>([]) +const guessList = ref>([]) +const allGuessItems = ref>([]) +const searchResults = ref>([]) +const searchShopResults = ref>([]) + const loadSearchHistory = () => { const history = uni.getStorageSync('searchHistory') - if (history) { + if (history != null) { try { - // 确保是数组 const parsed = JSON.parse(history as string) if (Array.isArray(parsed)) { searchHistory.value = parsed as string[] @@ -414,13 +351,258 @@ const deleteHistoryItem = (index: number) => { saveSearchHistory() } -// 搜索建议 - 改为实时获取 -const searchSuggestions = ref([]) -let suggestTimer = 0 +const refreshGuessListItems = () => { + if (allGuessItems.value.length > 0) { + const arr: Array = [] + for (let i: number = 0; i < allGuessItems.value.length; i++) { + arr.push(allGuessItems.value[i]) + } + for (let i: number = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + const temp = arr[i] + arr[i] = arr[j] + arr[j] = temp + } + const result: Array = [] + const limit = arr.length < 6 ? arr.length : 6 + for (let i: number = 0; i < limit; i++) { + result.push(arr[i]) + } + guessList.value = result + } +} + +const loadData = async (): Promise => { + isError.value = false + + try { + loadSearchHistory() + const hotProducts = await supabaseService.getHotProducts(30) + + const hotList: Array = [] + const limit1 = hotProducts.length < 10 ? hotProducts.length : 10 + for (let i: number = 0; i < limit1; i++) { + const p = hotProducts[i] as UTSJSONObject + const item: HotSearchItemType = { + keyword: p.getString('name') ?? '', + hot: true + } + hotList.push(item) + } + hotSearchList.value = hotList + + const allItems: Array = [] + for (let i: number = 0; i < hotProducts.length; i++) { + const p = hotProducts[i] as UTSJSONObject + const saleCount = p.getNumber('sale_count') + const item: GuessItemType = { + id: p.getString('id') ?? '', + name: p.getString('name') ?? '', + price: p.getNumber('base_price') ?? 0, + image: p.getString('main_image_url') ?? '/static/default.jpg', + sales: saleCount != null ? saleCount : 0 + } + allItems.push(item) + } + allGuessItems.value = allItems + + refreshGuessListItems() + + } catch (e) { + console.error('Load data failed', e) + isError.value = true + } +} + +const retryLoad = () => { + uni.showLoading({ title: '重新加载中' }) + setTimeout(() => { + uni.hideLoading() + loadData() + }, 1000) +} + +const searchSuggestions = ref>([]) +let suggestTimer: number = 0 + +const fetchSuggestions = async (kw: string): Promise => { + if (kw == '' || showResults.value) return + + try { + const res = await supabaseService.searchProducts(kw.trim(), 1, 5) + if (res.data != null && res.data.length > 0) { + const names: Array = [] + for (let i: number = 0; i < res.data.length; i++) { + const p = res.data[i] + let name = '' + if (p instanceof UTSJSONObject) { + name = p.getString('name') ?? '' + } else { + const pObj = p as UTSJSONObject + name = pObj.getString('name') ?? '' + } + let found = false + for (let j: number = 0; j < names.length; j++) { + if (names[j] === name) { + found = true + break + } + } + if (found === false && name !== '') { + names.push(name) + } + } + searchSuggestions.value = names + } else { + searchSuggestions.value = [] + } + } catch(e) { + searchSuggestions.value = [] + } +} + +const currentPage = ref(1) + +const performSearch = async (): Promise => { + showResults.value = true + loading.value = true + currentPage.value = 1 + + const keyword = searchKeyword.value.trim() + if (keyword == '') { + loading.value = false + return + } + + let sortBy = 'sales' + let ascending = false + if (activeSort.value === 'price') { + sortBy = 'price' + ascending = priceSortAsc.value + } else if (activeSort.value === 'default') { + sortBy = 'default' + } + + try { + const prodResp = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending) + + let shopRespData: Array = [] + if (currentPage.value === 1 && activeSort.value === 'default') { + const shopResp = await supabaseService.searchShops(keyword) + if (shopResp.data != null) { + const rawData = shopResp.data + for (let i: number = 0; i < rawData.length; i++) { + shopRespData.push(rawData[i]) + } + } + } + + if (shopRespData.length > 0) { + const shopList: Array = [] + for (let i: number = 0; i < shopRespData.length; i++) { + const s = shopRespData[i] as UTSJSONObject + const shopItem: ShopResultType = { + id: s.getString('id') ?? '', + name: s.getString('shop_name') ?? '', + logo: s.getString('shop_logo') ?? '/static/shop_logo_default.png', + productCount: s.getNumber('product_count') ?? 0 + } + shopList.push(shopItem) + } + searchShopResults.value = shopList + } else { + searchShopResults.value = [] + } + + const prodData = prodResp.data != null ? prodResp.data : [] + const resultList: Array = [] + for (let i: number = 0; i < prodData.length; i++) { + const p = prodData[i] as UTSJSONObject + let tag = '' + const tagsRaw = p.get('tags') + if (tagsRaw != null) { + try { + const tagsStr = p.getString('tags') + if (tagsStr != null) { + const tags = JSON.parse(tagsStr) + if (Array.isArray(tags) && tags.length > 0) { + const firstTag = tags[0] + tag = firstTag != null ? (firstTag as string) : '' + } + } + } catch(e) {} + } + + const searchItem: SearchResultType = { + id: p.getString('id') ?? '', + name: p.getString('name') ?? '', + image: p.getString('main_image_url') ?? '/static/default.jpg', + price: p.getNumber('base_price') ?? 0, + specification: p.getString('specification') ?? '标准规格', + tag: tag, + sales: p.getNumber('sale_count') ?? 0 + } + resultList.push(searchItem) + } + searchResults.value = resultList + + hasMore.value = prodResp.hasmore + } catch(e) { + console.error('Search failed', e) + } finally { + loading.value = false + } +} + +const initPage = () => { + try { + const systemInfo = uni.getSystemInfoSync() + statusBarHeight.value = systemInfo.statusBarHeight ?? 0 + const windowHeight = systemInfo.windowHeight + scrollHeight.value = windowHeight - (60 + statusBarHeight.value) + + loadData() + + const pages = getCurrentPages() + if (pages.length > 0) { + const currentPageObj = pages[pages.length - 1] + // @ts-ignore + const options = currentPageObj.options + if (options != null) { + const optObj = options as UTSJSONObject + const kwRaw = optObj.getString('keyword') + if (kwRaw != null && kwRaw !== '') { + const decoded = decodeURIComponent(kwRaw) + const keyword = decoded != null ? decoded : kwRaw + searchKeyword.value = keyword + + const typeVal = optObj.getString('type') + if (typeVal === 'family' || typeVal === 'brand') { + if (typeVal === 'family') { + addToHistory(keyword) + } + showResults.value = true + loading.value = true + performSearch() + } + } + } + } + } catch (e) { + console.error('初始化失败', e) + isError.value = true + } +} + +onMounted(() => { + initPage() +}) -// 搜索逻辑 const onInput = (e: any) => { - const val = e.detail.value + const eObj = e as UTSJSONObject + const detailRaw = eObj.get('detail') + const detail = detailRaw != null ? (detailRaw as UTSJSONObject) : (new UTSJSONObject()) + const val = detail.getString('value') ?? '' searchKeyword.value = val if (val == '') { showResults.value = false @@ -428,37 +610,12 @@ const onInput = (e: any) => { return } - // Debounce suggestion search if (suggestTimer > 0) clearTimeout(suggestTimer) suggestTimer = setTimeout(() => { fetchSuggestions(val) }, 300) } -const fetchSuggestions = async (kw: string) => { - if (kw == '' || showResults.value) return - - // 简单搜索前5个相关商品作为建议 - try { - const res = await supabaseService.searchProducts(kw.trim(), 1, 5) - if (Array.isArray(res.data) && res.data.length > 0) { - // 去重 - const names = res.data.map((p:any) :string => { - if(p instanceof UTSJSONObject){ - return p.getString('name') ?? '' - } - return p['name'] as string - }) - // @ts-ignore - searchSuggestions.value = Array.from(new Set(names)) - } else { - searchSuggestions.value = [] - } - } catch(e) { - searchSuggestions.value = [] - } -} - const clearSearch = () => { searchKeyword.value = '' showResults.value = false @@ -487,84 +644,6 @@ const selectSuggestion = (suggestion: string) => { performSearch() } -const currentPage = ref(1) - -const performSearch = async () => { - // 再次强制设置状态,确保万无一失 - showResults.value = true - loading.value = true - // 重置页码 - currentPage.value = 1 - - // 使用 Supabase 搜索真实数据 - const keyword = searchKeyword.value.trim() - if (keyword == '') { - loading.value = false - return - } - - // 确定排序方式 - let sortBy = 'sales' - let ascending = false - if (activeSort.value === 'price') { - sortBy = 'price' - ascending = priceSortAsc.value - } else if (activeSort.value === 'default') { - sortBy = 'default' - } - - try { - // 并行请求:商品搜索 + 店铺搜索 - const [prodResp, shopResp] = await Promise.all([ - supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending), - // 只有第一页搜索且非价格排序时搜索店铺,避免重复和无关搜索 - currentPage.value === 1 && activeSort.value === 'default' - ? supabaseService.searchShops(keyword) - : Promise.resolve({ data: [], total: 0, page: 1, limit: 0, hasmore: false }) - ]) - - // 处理店铺结果 - if (shopResp.data.length > 0) { - searchShopResults.value = shopResp.data.map((s: any) => ({ - id: s.id, - name: s.shop_name, - logo: s.shop_logo ?? '/static/shop_logo_default.png', - productCount: s.product_count ?? 0 - })) - } else { - searchShopResults.value = [] - } - - // 处理商品结果 - searchResults.value = prodResp.data.map((p: any) => { - let tag = '' - if (p.tags) { - try { - const tags = (typeof p.tags === 'string') ? JSON.parse(p.tags) : p.tags - if (Array.isArray(tags) && tags.length > 0) tag = String(tags[0]) - } catch(e) {} - } - - return { - id: p.id, - name: p.name, - image: p.main_image_url ?? '/static/default.jpg', - price: p.base_price, - specification: p.specification ?? '标准规格', - tag: tag, - sales: p.sale_count ?? 0 - } - }) - - hasMore.value = prodResp.hasmore - } catch(e) { - console.error('Search failed', e) - } finally { - loading.value = false - } -} - -// 切换排序 const switchSort = (type: string) => { if (type === 'price') { if (activeSort.value === 'price') { @@ -580,15 +659,13 @@ const switchSort = (type: string) => { performSearch() } -const loadMore = async () => { - if (loading.value || !hasMore.value || searchKeyword.value.trim() == '') return +const loadMore = async (): Promise => { + if (loading.value || hasMore.value == false || searchKeyword.value.trim() == '') return loading.value = true - // 增加页码 currentPage.value++ const keyword = searchKeyword.value.trim() - // 确定排序方式 let sortBy = 'sales' let ascending = false if (activeSort.value === 'price') { @@ -600,26 +677,35 @@ const loadMore = async () => { try { const response = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending) - const newItems = response.data.map((p: any) => { + const respData = response.data != null ? response.data : [] + for (let i: number = 0; i < respData.length; i++) { + const p = respData[i] as UTSJSONObject let tag = '' - if (p.tags) { + const tagsRaw = p.get('tags') + if (tagsRaw != null) { try { - const tags = (typeof p.tags === 'string') ? JSON.parse(p.tags) : p.tags - if (Array.isArray(tags) && tags.length > 0) tag = String(tags[0]) + const tagsStr = p.getString('tags') + if (tagsStr != null) { + const tags = JSON.parse(tagsStr) + if (Array.isArray(tags) && tags.length > 0) { + const firstTag = tags[0] + tag = firstTag != null ? (firstTag as string) : '' + } + } } catch(e) {} } - return { - id: p.id, - name: p.name, - image: p.main_image_url ?? '/static/default.jpg', - price: p.base_price, - specification: p.specification ?? '标准规格', + const searchItem: SearchResultType = { + id: p.getString('id') ?? '', + name: p.getString('name') ?? '', + image: p.getString('main_image_url') ?? '/static/default.jpg', + price: p.getNumber('base_price') ?? 0, + specification: p.getString('specification') ?? '标准规格', tag: tag, - sales: p.sale_count ?? 0 + sales: p.getNumber('sale_count') ?? 0 } - }) - searchResults.value.push(...newItems) + searchResults.value.push(searchItem) + } hasMore.value = response.hasmore } catch(e) { console.error('Load more failed', e) @@ -637,29 +723,22 @@ const refreshGuessList = () => { }, 500) } -const refreshGuessListItems = () => { - if (allGuessItems.value.length > 0) { - // 简单的随机乱序并取前6个 - const shuffled = [...allGuessItems.value].sort(() => Math.random() - 0.5) - guessList.value = shuffled.slice(0, 6) - } -} - -const viewProductDetail = (item: any) => { - // 跳转详情页逻辑 - 传递必要的参数作为预加载/fallback +const viewProductDetail = (item: SearchResultType | GuessItemType) => { + const id = (item as GuessItemType).id + const price = (item as GuessItemType).price + const name = (item as GuessItemType).name uni.navigateTo({ - url: `/pages/mall/consumer/product-detail?productId=${item.id}&price=${item.price}&name=${encodeURIComponent(item.name)}` + url: `/pages/mall/consumer/product-detail?productId=${id}&price=${price}&name=${encodeURIComponent(name)}` }) } -const viewShopDetail = (shop: any) => { +const viewShopDetail = (shop: ShopResultType) => { uni.navigateTo({ url: `/pages/mall/consumer/shop-detail?id=${shop.id}` }) } -// 添加到购物车 - 搜索列表无法选择规格,跳转详情页 -const addToCart = (product: any) => { +const addToCart = (product: SearchResultType | GuessItemType) => { uni.showToast({ title: '请选择规格', icon: 'none' }) setTimeout(() => { viewProductDetail(product) diff --git a/pages/mall/consumer/settings.uvue b/pages/mall/consumer/settings.uvue index 07fc1eac..4802e7c4 100644 --- a/pages/mall/consumer/settings.uvue +++ b/pages/mall/consumer/settings.uvue @@ -31,8 +31,8 @@ 📱 手机绑定 - - {{ userInfo.phone ? '已绑定' : '未绑定' }} + + {{ userInfo.phone != null && userInfo.phone != '' ? '已绑定' : '未绑定' }} @@ -41,8 +41,8 @@ 📧 邮箱绑定 - - {{ userInfo.email ? '已绑定' : '未绑定' }} + + {{ userInfo.email != null && userInfo.email != '' ? '已绑定' : '未绑定' }} @@ -260,53 +260,64 @@ const currentLanguage = ref('简体中文') const currentTheme = ref('自动') const appVersion = ref('1.0.0') -const statusBarHeight = ref(0) +const statusBarHeight = ref(0) -// 生命周期 -onMounted(() => { - const systemInfo = uni.getSystemInfoSync() - statusBarHeight.value = systemInfo.statusBarHeight - loadUserInfo() - loadSettings() -}) - -// 加载用户信息 const loadUserInfo = () => { const userStore = uni.getStorageSync('userInfo') - if (userStore) { - userInfo.value = userStore + if (userStore != null) { + const storeObj = userStore as UTSJSONObject + const user: UserType = { + id: storeObj.getString('id') ?? '', + phone: storeObj.getString('phone'), + email: storeObj.getString('email'), + nickname: storeObj.getString('nickname'), + avatar_url: storeObj.getString('avatar_url') + } as UserType + userInfo.value = user } } -// 加载设置 const loadSettings = () => { - // 从本地存储加载设置 const savedNotifications = uni.getStorageSync('userNotifications') - if (savedNotifications) { - notifications.value = savedNotifications + if (savedNotifications != null) { + const notifObj = savedNotifications as UTSJSONObject + const notif: NotificationType = { + order: notifObj.getBoolean('order') ?? true, + promotion: notifObj.getBoolean('promotion') ?? true, + review: notifObj.getBoolean('review') ?? true + } as NotificationType + notifications.value = notif } const savedPrivacy = uni.getStorageSync('userPrivacy') - if (savedPrivacy) { - privacy.value = savedPrivacy + if (savedPrivacy != null) { + const privacyObj = savedPrivacy as UTSJSONObject + const priv: PrivacyType = { + hidePurchase: privacyObj.getBoolean('hidePurchase') ?? false, + allowSearchByPhone: privacyObj.getBoolean('allowSearchByPhone') ?? true, + receiveMerchantMsg: privacyObj.getBoolean('receiveMerchantMsg') ?? true + } as PrivacyType + privacy.value = priv } - // 计算缓存大小 - calculateCacheSize() + cacheSize.value = '12.5 MB' - // 获取应用版本 - // @ts-ignore const appInfo = uni.getAppBaseInfo() - if (appInfo?.appVersion) { - appVersion.value = appInfo.appVersion + if (appInfo != null) { + const infoObj = appInfo as UTSJSONObject + const version = infoObj.getString('appVersion') + if (version != null) { + appVersion.value = version + } } } -// 计算缓存大小 -const calculateCacheSize = () => { - // 这里应该计算实际缓存大小,这里使用模拟数据 - cacheSize.value = '12.5 MB' -} +onMounted(() => { + const systemInfo = uni.getSystemInfoSync() + statusBarHeight.value = systemInfo.statusBarHeight ?? 0 + loadUserInfo() + loadSettings() +}) // 跳转到个人资料 const goToProfile = () => { @@ -344,14 +355,26 @@ const bindEmail = () => { } // 切换通知设置 -const toggleNotification = (type: keyof NotificationType) => { - notifications.value[type] = !notifications.value[type] +const toggleNotification = (type: string) => { + if (type === 'order') { + notifications.value.order = notifications.value.order === false + } else if (type === 'promotion') { + notifications.value.promotion = notifications.value.promotion === false + } else if (type === 'review') { + notifications.value.review = notifications.value.review === false + } uni.setStorageSync('userNotifications', notifications.value) } // 切换隐私设置 -const togglePrivacy = (type: keyof PrivacyType) => { - privacy.value[type] = !privacy.value[type] +const togglePrivacy = (type: string) => { + if (type === 'hidePurchase') { + privacy.value.hidePurchase = privacy.value.hidePurchase === false + } else if (type === 'allowSearchByPhone') { + privacy.value.allowSearchByPhone = privacy.value.allowSearchByPhone === false + } else if (type === 'receiveMerchantMsg') { + privacy.value.receiveMerchantMsg = privacy.value.receiveMerchantMsg === false + } uni.setStorageSync('userPrivacy', privacy.value) } @@ -473,28 +496,12 @@ const feedback = () => { }) } -// 给个好评 const rateApp = () => { - // 这里应该跳转到应用商店评分 uni.showModal({ title: '给个好评', - content: '如果喜欢我们的应用,请给个好评吧!', - confirmText: '去评分', - success: (res) => { - if (res.confirm) { - // 跳转到应用商店 - // @ts-ignore - uni.navigateToMiniProgram({ - appId: 'wx1234567890', // 示例AppID - fail: () => { - uni.showToast({ - title: '跳转失败', - icon: 'none' - }) - } - }) - } - } + content: '如果喜欢我们的应用,请给个好评吧!感谢您的支持!', + confirmText: '好的', + showCancel: false }) } @@ -503,116 +510,76 @@ const logout = () => { uni.showModal({ title: '退出登录', content: '确定要退出登录吗?', - success: async (res) => { + success: (res) => { if (res.confirm) { - try { - uni.showLoading({ - title: '正在退出...' - }) - // 调用登出接口 - const { error } = await supa.auth.signOut() - - if (error !== null) { - console.error('登出失败:', error) - // 即使失败也继续清除本地状态 - } - - // 清除本地存储的用户信息 - uni.removeStorageSync('userInfo') - uni.removeStorageSync('user_id') - uni.removeStorageSync('access_token') - - uni.hideLoading() - - uni.showToast({ - title: '已退出登录', - icon: 'success' - }) - - setTimeout(() => { - uni.reLaunch({ - url: '/pages/user/login' - }) - }, 1000) - } catch (e) { - uni.hideLoading() - console.error('Logout Exception:', e) - uni.showToast({ - title: '退出异常', - icon: 'none' - }) - // 强制退出 - uni.removeStorageSync('userInfo') + uni.showLoading({ + title: '正在退出...' + }) + + uni.removeStorageSync('userInfo') + uni.removeStorageSync('user_id') + uni.removeStorageSync('access_token') + + uni.hideLoading() + + uni.showToast({ + title: '已退出登录', + icon: 'success' + }) + + setTimeout(() => { uni.reLaunch({ url: '/pages/user/login' }) - } + }, 1000) } } }) } -// 注销账号 const deleteAccount = () => { uni.showModal({ title: '注销账号', content: '确定要注销账号吗?此操作不可恢复,所有数据将被删除!', confirmText: '注销', confirmColor: '#ff4757', - success: async (res) => { + success: (res) => { if (res.confirm) { - try { - uni.showLoading({ - title: '注销中...' + uni.showLoading({ + title: '注销中...' + }) + + let userId: string | null = userInfo.value.id + if (userId == null || userId === '') { + const storageId = uni.getStorageSync('user_id') + userId = (storageId != null) ? storageId as string : null + } + + if (userId != null) { + const updateObj: UTSJSONObject = new UTSJSONObject() + updateObj.set('status', 3) + supa + .from('ml_user_profiles') + .update(updateObj) + .eq('user_id', userId) + .execute() + } + + uni.removeStorageSync('userInfo') + uni.removeStorageSync('user_id') + uni.removeStorageSync('access_token') + + uni.hideLoading() + uni.showToast({ + title: '账号已注销', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + uni.reLaunch({ + url: '/pages/user/login' }) - - let userId = userInfo.value.getString('id') - if (userId == null) { - const storageId = uni.getStorageSync('user_id') - userId = (storageId != null) ? storageId as string : null - } - - if (userId != null) { - try { - // 标记用户状态为注销 (status=3) - await supa - .from('ml_user_profiles') - .update({ status: 3 }) - .eq('user_id', userId) - } catch(e) { - console.error('Update status failed', e) - } - - // 登出 - await supa.auth.signOut() - } - - // 清除本地存储 - uni.removeStorageSync('userInfo') - uni.removeStorageSync('user_id') - uni.removeStorageSync('access_token') - - // 提示并跳转 - uni.hideLoading() - uni.showToast({ - title: '账号已注销', - icon: 'success', - duration: 2000 - }) - - setTimeout(() => { - uni.reLaunch({ - url: '/pages/user/login' - }) - }, 1500) - - } catch (err) { - uni.hideLoading() - console.error('注销账号失败:', err) - uni.showToast({ - title: '注销失败', - icon: 'none' - }) - } + }, 1500) } } }) diff --git a/pages/mall/consumer/shop-detail.uvue b/pages/mall/consumer/shop-detail.uvue index df627fdd..0cfa7f50 100644 --- a/pages/mall/consumer/shop-detail.uvue +++ b/pages/mall/consumer/shop-detail.uvue @@ -31,7 +31,7 @@ ¥{{ coupon.discount_value }} - 满{{ coupon.min_order_amount }} + 满{{ coupon.min_order_amount }} 无门槛 @@ -70,7 +70,7 @@ \ No newline at end of file + diff --git a/pages/mall/consumer/wallet.uvue b/pages/mall/consumer/wallet.uvue index b14ecc1c..acacbade 100644 --- a/pages/mall/consumer/wallet.uvue +++ b/pages/mall/consumer/wallet.uvue @@ -84,7 +84,7 @@ - + 💰 暂无交易记录 快去使用钱包功能吧 @@ -117,7 +117,7 @@ 加载中... - + 没有更多记录了 @@ -156,7 +156,7 @@ @@ -185,8 +185,10 @@ type TransactionType = { id: string user_id: string change_amount: number + amount: number current_balance: number - change_type: string // 'recharge' | 'consume' | 'withdraw' | 'refund' | 'reward' + change_type: string + type: string related_id: string | null remark: string | null created_at: string @@ -214,67 +216,41 @@ const showRechargePopup = ref(false) const rechargeAmount = ref('') const quickAmounts = [50, 100, 200, 500, 1000] -// 计算属性 -const canRecharge = computed(() => { - const amount = parseFloat(rechargeAmount.value) - return !isNaN(amount) && amount >= 10 && amount <= 5000 -}) - -// 监听过滤器变化 -watch(activeFilter, () => { - resetTransactions() - loadTransactions() -}) - -// 生命周期 -onShow(() => { - loadWalletData() -}) +// 获取当前用户ID +const getCurrentUserId = (): string => { + const userStore = uni.getStorageSync('userInfo') + if (userStore == null) return '' + const userInfo = userStore as UTSJSONObject + return userInfo.getString('id') ?? '' +} // 重置交易记录 -const resetTransactions = () => { +const resetTransactions = (): void => { transactions.value = [] currentPage.value = 1 hasMore.value = true } -// 加载钱包数据 -const loadWalletData = async () => { - const userId = getCurrentUserId() - if (userId == '') { - // uni.navigateTo({ - // url: '/pages/user/login' - // }) - return - } - - await Promise.all([ - loadBalance(), - loadTransactions() - ]) -} - // 加载余额信息 -const loadBalance = async () => { +const loadBalance = async (): Promise => { try { - // 调用 Supabase 服务获取真实余额 const realBalance = await supabaseService.getUserBalance() balance.value = realBalance - // 统计数据暂时保持 mock 或设为 0,因为后端还未实现具体统计接口 - stats.value = { + const statsData: StatsType = { totalRecharge: 0, totalConsume: 0, totalWithdraw: 0 - } + } as StatsType + stats.value = statsData } catch (err) { console.error('加载钱包异常:', err) } } // 加载交易记录 -const loadTransactions = async (loadMore: boolean = false) => { - if (isLoading.value || (!hasMore.value && loadMore)) { +const loadTransactions = async (loadMore: boolean): Promise => { + if (isLoading.value || (hasMore.value === false && loadMore)) { return } @@ -290,16 +266,14 @@ const loadTransactions = async (loadMore: boolean = false) => { const page = loadMore ? currentPage.value + 1 : 1 const limit = 20 - // 使用 Supabase 获取真实数据 - // 注意:目前后端接口暂不支持 activeFilter 筛选,会返回所有记录 const data = await supabaseService.getTransactions(page, limit) - const mappedData: TransactionType[] = [] - for (let i = 0; i < data.length; i++) { + const mappedData: Array = [] + for (let i: number = 0; i < data.length; i++) { const item = data[i] let id = '' let amount = 0 - let balance = 0 + let balanceAfter = 0 let type = '' let remark = '' let createdAt = '' @@ -307,85 +281,106 @@ const loadTransactions = async (loadMore: boolean = false) => { if (item instanceof UTSJSONObject) { id = item.getString('id') ?? '' amount = item.getNumber('amount') ?? 0 - balance = item.getNumber('balance_after') ?? 0 + balanceAfter = item.getNumber('balance_after') ?? 0 type = item.getString('type') ?? 'consume' remark = item.getString('description') ?? '' createdAt = item.getString('created_at') ?? '' } else { - id = (item['id'] as string) ?? '' - amount = (item['amount'] as number) ?? 0 - balance = (item['balance_after'] as number) ?? 0 - type = (item['type'] as string) ?? 'consume' - remark = (item['description'] as string) ?? '' - createdAt = (item['created_at'] as string) ?? '' + const itemObj = item as UTSJSONObject + id = itemObj.getString('id') ?? '' + amount = itemObj.getNumber('amount') ?? 0 + balanceAfter = itemObj.getNumber('balance_after') ?? 0 + type = itemObj.getString('type') ?? 'consume' + remark = itemObj.getString('description') ?? '' + createdAt = itemObj.getString('created_at') ?? '' } - mappedData.push({ + const transaction: TransactionType = { id: id, user_id: userId, change_amount: amount, - current_balance: balance, + amount: amount, + current_balance: balanceAfter, change_type: type, + type: type, related_id: null, remark: remark, created_at: createdAt - }) + } as TransactionType + mappedData.push(transaction) } + + if (loadMore) { + for (let i: number = 0; i < mappedData.length; i++) { + transactions.value.push(mappedData[i]) + } + currentPage.value = page + } else { + transactions.value = mappedData + currentPage.value = 1 + } + + hasMore.value = mappedData.length >= limit + } catch (err) { + console.error('加载交易记录失败:', err) + } finally { + isLoading.value = false + } +} - if (loadMore) { - transactions.value.push(...mappedData) - } else { - transactions.value = mappedData - } - - if (mappedData.length < limit) { - hasMore.value = false - } else { - hasMore.value = true - } - - currentPage.value = page - } catch (err) { - console.error('加载交易记录异常:', err) - } finally { - isLoading.value = false +// 加载钱包数据 +const loadWalletData = async (): Promise => { + const userId = getCurrentUserId() + if (userId == '') { + return } + + loadBalance() + loadTransactions(false) } -// 获取当前用户ID -const getCurrentUserId = (): string => { - const userStore = uni.getStorageSync('userInfo') - return userStore?.getString('id') ?? '' -} +// 计算属性 +const canRecharge = computed((): boolean => { + const amount = parseFloat(rechargeAmount.value) + if (amount == null || amount < 10 || amount > 5000) { + return false + } + return true +}) + +// 监听过滤器变化 +watch(activeFilter, () => { + resetTransactions() + loadTransactions(false) +}) + +// 生命周期 +onShow(() => { + loadWalletData() +}) // 获取交易图标 const getTransactionIcon = (type: string): string => { - const icons: Record = { - recharge: '💳', - consume: '🛒', - withdraw: '🏦', - refund: '🔄', - reward: '🎁', - income: '💰', - expense: '📤' - } - const icon = icons[type] - return icon != null ? icon : '💰' + if (type === 'recharge') return '💳' + if (type === 'consume') return '🛒' + if (type === 'withdraw') return '🏦' + if (type === 'refund') return '🔄' + if (type === 'reward') return '🎁' + if (type === 'income') return '💰' + if (type === 'expense') return '📤' + return '💰' } // 获取交易标题 const getTransactionTitle = (type: string): string => { - const titles: Record = { - recharge: '账户充值', - consume: '商品消费', - withdraw: '余额提现', - refund: '订单退款', - reward: '活动奖励', - income: '收入', - expense: '支出' - } - const title = titles[type] - return title != null ? title : '交易' + if (type === 'recharge') return '账户充值' + if (type === 'consume') return '商品消费' + if (type === 'withdraw') return '余额提现' + if (type === 'refund') return '订单退款' + if (type === 'reward') return '活动奖励' + if (type === 'income') return '收入' + if (type === 'expense') return '支出' + return '交易' } // 格式化时间 @@ -471,22 +466,28 @@ const changeFilter = (filter: string) => { // 加载更多 const loadMore = () => { - if (hasMore.value && !isLoading.value) { + if (hasMore.value && isLoading.value === false) { loadTransactions(true) } } // 选择快捷金额 -const selectQuickAmount = (amount: number) => { +const selectQuickAmount = (amount: number): void => { rechargeAmount.value = amount.toString() } +// 关闭充值弹窗 +const closeRechargePopup = (): void => { + showRechargePopup.value = false + rechargeAmount.value = '' +} + // 确认充值 -const confirmRecharge = async () => { - if (!canRecharge.value) return +const confirmRecharge = async (): Promise => { + if (canRecharge.value === false) return const amount = parseFloat(rechargeAmount.value) - if (isNaN(amount)) return + if (amount == null || amount < 10 || amount > 5000) return uni.showLoading({ title: '处理中...' }) try { @@ -497,7 +498,6 @@ const confirmRecharge = async () => { icon: 'success' }) closeRechargePopup() - // 刷新数据 loadWalletData() } else { uni.showToast({ @@ -516,14 +516,8 @@ const confirmRecharge = async () => { } } -// 关闭充值弹窗 -const closeRechargePopup = () => { - showRechargePopup.value = false - rechargeAmount.value = '' -} - // 返回 -const goBack = () => { +const goBack = (): void => { uni.navigateBack() } diff --git a/pages/mall/consumer/withdraw.uvue b/pages/mall/consumer/withdraw.uvue index 135308a2..03c6ecd6 100644 --- a/pages/mall/consumer/withdraw.uvue +++ b/pages/mall/consumer/withdraw.uvue @@ -4,10 +4,10 @@ 提现至 - - {{ selectedBank?.bank_name }} + + {{ selectedBank.bank_name }} 储蓄卡 - 尾号 {{ getTailNumber(selectedBank?.card_number) }} + 尾号 {{ getTailNumber(selectedBank.card_number) }} 请选择到账银行卡 @@ -34,7 +34,7 @@