解决登录显示、首页显示bug
This commit is contained in:
312
utils/deliveryAuth.uts
Normal file
312
utils/deliveryAuth.uts
Normal file
@@ -0,0 +1,312 @@
|
||||
import { AkReq } from '@/uni_modules/ak-req/index.uts'
|
||||
import type { UserProfile } from '@/types/mall-types.uts'
|
||||
import type { DeliveryInfoType } from '@/types/delivery.uts'
|
||||
import { getDeliveryProfileByUserId } from '@/api/delivery.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { getCurrentUser, setIsLoggedIn, setUserProfile } from '@/utils/store.uts'
|
||||
|
||||
export type DeliveryAuthOptions = {
|
||||
redirectOnFail?: boolean
|
||||
toastOnFail?: boolean
|
||||
redirect?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type DeliveryAuthResult = {
|
||||
ok: boolean
|
||||
message: string
|
||||
userInfo: UserProfile | null
|
||||
deliveryInfo: DeliveryInfoType | null
|
||||
}
|
||||
|
||||
const DELIVERY_LOGIN_PATH = '/pages/user/login'
|
||||
const DELIVERY_LOGIN_TARGET = '/pages/user/login?mode=delivery'
|
||||
const DELIVERY_HOME_PATH = '/pages/mall/delivery/home/index'
|
||||
const DELIVERY_USER_KEY = 'delivery_user_info'
|
||||
const DELIVERY_INFO_KEY = 'delivery_info'
|
||||
const DELIVERY_TOKEN_KEY = 'delivery_token'
|
||||
const DELIVERY_ROLE = 'delivery'
|
||||
const DELIVERY_ROLE_ALIAS = 'Deliverer'
|
||||
|
||||
let redirectingToDeliveryLogin = false
|
||||
|
||||
function hasText(value: string | null): boolean {
|
||||
return value != null && value !== ''
|
||||
}
|
||||
|
||||
function shouldShowToast(enabled: boolean | null): boolean {
|
||||
return enabled == null || enabled === true
|
||||
}
|
||||
|
||||
function shouldRedirect(enabled: boolean | null): boolean {
|
||||
return enabled == null || enabled === true
|
||||
}
|
||||
|
||||
function getCurrentRoute(): string {
|
||||
try {
|
||||
const pages = getCurrentPages() as Array<UTSJSONObject>
|
||||
if (pages.length === 0) {
|
||||
return ''
|
||||
}
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const route = currentPage['route']
|
||||
if (route != null) {
|
||||
return '/' + route
|
||||
}
|
||||
} catch (e) {}
|
||||
return ''
|
||||
}
|
||||
|
||||
function buildFailureResult(message: string, userInfo: UserProfile | null = null): DeliveryAuthResult {
|
||||
return {
|
||||
ok: false,
|
||||
message,
|
||||
userInfo,
|
||||
deliveryInfo: null
|
||||
}
|
||||
}
|
||||
|
||||
function parseUserInfo(raw: string): UserProfile | null {
|
||||
try {
|
||||
return JSON.parse(raw) as UserProfile
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function parseDeliveryInfo(raw: string): DeliveryInfoType | null {
|
||||
try {
|
||||
return JSON.parse(raw) as DeliveryInfoType
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function persistUserInfo(userInfo: UserProfile): void {
|
||||
uni.setStorageSync(DELIVERY_USER_KEY, JSON.stringify(userInfo))
|
||||
}
|
||||
|
||||
function persistDeliveryInfo(info: DeliveryInfoType): void {
|
||||
uni.setStorageSync(DELIVERY_INFO_KEY, JSON.stringify(info))
|
||||
}
|
||||
|
||||
export function setDeliveryInfo(info: DeliveryInfoType): void {
|
||||
persistDeliveryInfo(info)
|
||||
}
|
||||
|
||||
export function clearDeliveryInfo(): void {
|
||||
uni.removeStorageSync(DELIVERY_INFO_KEY)
|
||||
}
|
||||
|
||||
export function saveDeliverySession(token: string, userInfo: UserProfile, deliveryInfo: DeliveryInfoType | null): void {
|
||||
AkReq.setToken(token, '', Date.now() + 24 * 60 * 60 * 1000)
|
||||
uni.setStorageSync(DELIVERY_TOKEN_KEY, token)
|
||||
persistUserInfo(userInfo)
|
||||
setIsLoggedIn(true)
|
||||
setUserProfile(userInfo)
|
||||
if (deliveryInfo != null) {
|
||||
persistDeliveryInfo(deliveryInfo)
|
||||
} else {
|
||||
uni.removeStorageSync(DELIVERY_INFO_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
export function getToken(): string {
|
||||
const stored = uni.getStorageSync(DELIVERY_TOKEN_KEY) as string | null
|
||||
if (stored != null && stored != '') {
|
||||
return stored
|
||||
}
|
||||
const token = AkReq.getToken()
|
||||
return token != null ? token : ''
|
||||
}
|
||||
|
||||
export function getUserInfo(): UserProfile | null {
|
||||
const raw = uni.getStorageSync(DELIVERY_USER_KEY) as string | null
|
||||
if (!hasText(raw)) {
|
||||
return null
|
||||
}
|
||||
return parseUserInfo(raw as string)
|
||||
}
|
||||
|
||||
async function resolveUserInfo(): Promise<UserProfile | null> {
|
||||
const cached = getUserInfo()
|
||||
if (cached != null) {
|
||||
return cached
|
||||
}
|
||||
try {
|
||||
const profile = await getCurrentUser()
|
||||
if (profile != null) {
|
||||
persistUserInfo(profile)
|
||||
}
|
||||
return profile
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function getDeliveryInfo(): DeliveryInfoType | null {
|
||||
const raw = uni.getStorageSync(DELIVERY_INFO_KEY) as string | null
|
||||
if (!hasText(raw)) {
|
||||
return null
|
||||
}
|
||||
return parseDeliveryInfo(raw as string)
|
||||
}
|
||||
|
||||
export function isLoggedIn(): boolean {
|
||||
if (hasText(getToken()) && getUserInfo() != null) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
return session != null && session.user != null
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isDelivererRole(userInfo: UserProfile | null): boolean {
|
||||
if (userInfo == null) {
|
||||
return false
|
||||
}
|
||||
const role = userInfo.role != null ? userInfo.role : ''
|
||||
return role == DELIVERY_ROLE || role == DELIVERY_ROLE_ALIAS
|
||||
}
|
||||
|
||||
export async function checkDeliveryAccount(userInfo: UserProfile): Promise<DeliveryInfoType | null> {
|
||||
const userId = userInfo.id != null ? userInfo.id : ''
|
||||
if (!hasText(userId)) {
|
||||
return null
|
||||
}
|
||||
return await getDeliveryProfileByUserId(userId)
|
||||
}
|
||||
|
||||
export function clearDeliveryAuth(): void {
|
||||
try {
|
||||
AkReq.clearToken()
|
||||
} catch (e) {}
|
||||
uni.removeStorageSync(DELIVERY_TOKEN_KEY)
|
||||
uni.removeStorageSync(DELIVERY_USER_KEY)
|
||||
uni.removeStorageSync('user_id')
|
||||
clearDeliveryInfo()
|
||||
setIsLoggedIn(false)
|
||||
setUserProfile({ username: '', email: '' } as UserProfile)
|
||||
}
|
||||
|
||||
export async function logoutDelivery(): Promise<void> {
|
||||
clearDeliveryAuth()
|
||||
try {
|
||||
await supa.signOut()
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function getRedirectTarget(explicitRedirect: string | null): string {
|
||||
if (hasText(explicitRedirect)) {
|
||||
return explicitRedirect as string
|
||||
}
|
||||
const currentRoute = getCurrentRoute()
|
||||
if (hasText(currentRoute) && currentRoute !== DELIVERY_LOGIN_PATH) {
|
||||
return currentRoute
|
||||
}
|
||||
return DELIVERY_HOME_PATH
|
||||
}
|
||||
|
||||
export function redirectToDeliveryLogin(redirect?: string): void {
|
||||
const currentRoute = getCurrentRoute()
|
||||
if (currentRoute == DELIVERY_LOGIN_PATH) {
|
||||
return
|
||||
}
|
||||
if (redirectingToDeliveryLogin) {
|
||||
return
|
||||
}
|
||||
redirectingToDeliveryLogin = true
|
||||
let targetUrl = DELIVERY_LOGIN_TARGET
|
||||
const redirectTarget = getRedirectTarget(redirect != null ? redirect : null)
|
||||
if (hasText(redirectTarget)) {
|
||||
targetUrl = DELIVERY_LOGIN_TARGET + '&redirect=' + encodeURIComponent(redirectTarget)
|
||||
}
|
||||
uni.reLaunch({
|
||||
url: targetUrl,
|
||||
complete: () => {
|
||||
setTimeout(() => {
|
||||
redirectingToDeliveryLogin = false
|
||||
}, 300)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function goDeliveryHome(): void {
|
||||
uni.reLaunch({ url: DELIVERY_HOME_PATH })
|
||||
}
|
||||
|
||||
export function isCurrentDeliveryPage(): boolean {
|
||||
return getCurrentRoute().startsWith('/pages/mall/delivery/')
|
||||
}
|
||||
|
||||
export function handleDeliveryUnauthorized(message?: string, redirect?: string): void {
|
||||
clearDeliveryAuth()
|
||||
redirectToDeliveryLogin(redirect)
|
||||
if (message != null && message != '') {
|
||||
uni.showToast({ title: message, icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function handleFailure(message: string, options?: DeliveryAuthOptions, userInfo: UserProfile | null = null): DeliveryAuthResult {
|
||||
clearDeliveryAuth()
|
||||
if (shouldShowToast(options?.toastOnFail ?? null) && hasText(message)) {
|
||||
uni.showToast({ title: message, icon: 'none' })
|
||||
}
|
||||
if (shouldRedirect(options?.redirectOnFail ?? null)) {
|
||||
redirectToDeliveryLogin(getRedirectTarget(options?.redirect ?? null))
|
||||
}
|
||||
return buildFailureResult(message, userInfo)
|
||||
}
|
||||
|
||||
export async function requireDeliveryAuth(options?: DeliveryAuthOptions): Promise<DeliveryAuthResult> {
|
||||
const currentRoute = getCurrentRoute()
|
||||
const onLoginPage = currentRoute == DELIVERY_LOGIN_PATH
|
||||
const effectiveOptions: DeliveryAuthOptions = {
|
||||
redirectOnFail: onLoginPage ? false : options?.redirectOnFail,
|
||||
toastOnFail: options?.toastOnFail,
|
||||
redirect: options?.redirect,
|
||||
message: options?.message
|
||||
}
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
return handleFailure(options?.message != null ? options.message : '请先登录服务人员账号', effectiveOptions)
|
||||
}
|
||||
|
||||
const userInfo = await resolveUserInfo()
|
||||
if (userInfo == null) {
|
||||
return handleFailure('登录状态已失效,请重新登录', effectiveOptions)
|
||||
}
|
||||
|
||||
if (!isDelivererRole(userInfo)) {
|
||||
return handleFailure('当前账号不是上门服务人员账号', effectiveOptions, userInfo)
|
||||
}
|
||||
|
||||
const deliveryInfo = await checkDeliveryAccount(userInfo)
|
||||
if (deliveryInfo == null) {
|
||||
return handleFailure('当前账号未绑定服务人员档案', effectiveOptions, userInfo)
|
||||
}
|
||||
|
||||
if (deliveryInfo.status == 'disabled' || deliveryInfo.status == 'locked' || deliveryInfo.status == 'suspended') {
|
||||
return handleFailure('账号不可接单', effectiveOptions, userInfo)
|
||||
}
|
||||
|
||||
if (deliveryInfo.usesMock && (!hasText(deliveryInfo.organizationId) || !hasText(deliveryInfo.organizationName))) {
|
||||
return handleFailure('当前账号未绑定服务机构', effectiveOptions, userInfo)
|
||||
}
|
||||
|
||||
if (deliveryInfo.usesMock && deliveryInfo.certificateStatus == 'expired') {
|
||||
return handleFailure('资质已过期,请联系管理员', effectiveOptions, userInfo)
|
||||
}
|
||||
|
||||
persistUserInfo(userInfo)
|
||||
persistDeliveryInfo(deliveryInfo)
|
||||
return {
|
||||
ok: true,
|
||||
message: '',
|
||||
userInfo,
|
||||
deliveryInfo
|
||||
}
|
||||
}
|
||||
21
utils/deliveryRoute.uts
Normal file
21
utils/deliveryRoute.uts
Normal file
@@ -0,0 +1,21 @@
|
||||
export function getDeliveryRouteParam(options: UTSJSONObject | null, key: string): string {
|
||||
if (options == null || key == '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
try {
|
||||
const value = options.getString(key)
|
||||
if (value != null && value != '') {
|
||||
return value
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
const rawValue = options[key]
|
||||
if (rawValue != null) {
|
||||
return '' + rawValue
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return ''
|
||||
}
|
||||
249
utils/homeServiceUiMock.uts
Normal file
249
utils/homeServiceUiMock.uts
Normal file
@@ -0,0 +1,249 @@
|
||||
import { HomeServiceCatalogType } from '@/types/home-service.uts'
|
||||
|
||||
// TODO: 接入真实服务分类、机构与可预约时段接口后替换这些 UI mock。
|
||||
|
||||
export type HomeServiceCategoryType = {
|
||||
id: string
|
||||
name: string
|
||||
iconText: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export type HomeServicePromoCardType = {
|
||||
id: string
|
||||
title: string
|
||||
desc: string
|
||||
tone: string
|
||||
}
|
||||
|
||||
export type HomeServiceSceneCardType = {
|
||||
id: string
|
||||
title: string
|
||||
subtitle: string
|
||||
iconText: string
|
||||
tone: string
|
||||
categoryId: string
|
||||
}
|
||||
|
||||
export type HomeServiceItemType = {
|
||||
id: string
|
||||
title: string
|
||||
subtitle: string
|
||||
category: string
|
||||
price: number
|
||||
unit: string
|
||||
tags: Array<string>
|
||||
imageText: string
|
||||
suitableFor: string
|
||||
}
|
||||
|
||||
export type BookingTimeSlotType = {
|
||||
id: string
|
||||
label: string
|
||||
available: boolean
|
||||
}
|
||||
|
||||
export type BookingDayOptionType = {
|
||||
id: string
|
||||
label: string
|
||||
dateText: string
|
||||
weekday: string
|
||||
}
|
||||
|
||||
export type HomeServiceAgencyType = {
|
||||
id: string
|
||||
name: string
|
||||
distance: string
|
||||
rating: string
|
||||
summary: string
|
||||
}
|
||||
|
||||
export type HomeServiceGuaranteeItemType = {
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const HOME_SERVICE_CATEGORIES: Array<HomeServiceCategoryType> = [
|
||||
{ id: 'basic-care', name: '基础照护', iconText: '护', color: '#EAFBF7' },
|
||||
{ id: 'rehab-guide', name: '康复指导', iconText: '康', color: '#EFF6FF' },
|
||||
{ id: 'medical-assist', name: '陪诊服务', iconText: '诊', color: '#FFF7ED' },
|
||||
{ id: 'home-nursing', name: '上门护理', iconText: '护', color: '#EAF2FF' },
|
||||
{ id: 'chronic-follow', name: '慢病随访', iconText: '访', color: '#ECFDF5' },
|
||||
{ id: 'aging-friendly', name: '适老改造', iconText: '改', color: '#FFF1F2' },
|
||||
{ id: 'home-cleaning', name: '居家保洁', iconText: '洁', color: '#F1F5F9' },
|
||||
{ id: 'storage-plan', name: '收纳整理', iconText: '纳', color: '#FEF3C7' },
|
||||
{ id: 'health-check', name: '健康评估', iconText: '评', color: '#E0F2FE' },
|
||||
{ id: 'all-services', name: '全部服务', iconText: '全', color: '#E2E8F0' }
|
||||
]
|
||||
|
||||
const HOME_SERVICE_PROMOS: Array<HomeServicePromoCardType> = [
|
||||
{ id: 'consult', title: '人工客服', desc: '不确定服务怎么选?先问客服', tone: 'green' },
|
||||
{ id: 'first-order', title: '新人首单', desc: '居家服务首单立减', tone: 'orange' },
|
||||
{ id: 'senior-plan', title: '长者专享', desc: '适老照护套餐更安心', tone: 'blue' },
|
||||
{ id: 'family-pack', title: '家庭护理包', desc: '康复 慢病 陪诊组合', tone: 'teal' }
|
||||
]
|
||||
|
||||
const HOME_SERVICE_SCENES: Array<HomeServiceSceneCardType> = [
|
||||
{ id: 'scene-1', title: '日常照护', subtitle: '全屋照护 协助起居', iconText: '日护', tone: 'green', categoryId: 'basic-care' },
|
||||
{ id: 'scene-2', title: '术后康复', subtitle: '训练指导 上门陪练', iconText: '康复', tone: 'blue', categoryId: 'rehab-guide' },
|
||||
{ id: 'scene-3', title: '慢病随访', subtitle: '血压血糖 健康记录', iconText: '随访', tone: 'teal', categoryId: 'chronic-follow' },
|
||||
{ id: 'scene-4', title: '陪诊陪护', subtitle: '就医陪同 取药协助', iconText: '陪诊', tone: 'orange', categoryId: 'medical-assist' }
|
||||
]
|
||||
|
||||
const FALLBACK_HOME_SERVICE_ITEMS: Array<HomeServiceItemType> = [
|
||||
{ id: 'svc-ui-001', title: '基础照护到家', subtitle: '协助起居 用药提醒 生命体征监测', category: 'basic-care', price: 99, unit: '次', tags: ['平台认证', '服务保障', '可预约'], imageText: '照护', suitableFor: '老人 术后 家庭' },
|
||||
{ id: 'svc-ui-002', title: '居家康复指导', subtitle: '术后恢复 动作训练 居家陪练', category: 'rehab-guide', price: 168, unit: '次', tags: ['康复指导', '方案清晰', '可连续预约'], imageText: '康复', suitableFor: '术后 康复 家庭' },
|
||||
{ id: 'svc-ui-003', title: '陪诊协助服务', subtitle: '挂号陪同 检查陪同 取药协助', category: 'medical-assist', price: 128, unit: '次', tags: ['流程熟悉', '省心省时', '服务可追溯'], imageText: '陪诊', suitableFor: '老人 慢病 家庭' },
|
||||
{ id: 'svc-ui-004', title: '慢病随访关怀', subtitle: '血压血糖记录 用药核对 健康宣教', category: 'chronic-follow', price: 118, unit: '次', tags: ['可预约', '记录留档', '明码标价'], imageText: '随访', suitableFor: '慢病 长者 家庭' }
|
||||
]
|
||||
|
||||
const BOOKING_DAY_OPTIONS: Array<BookingDayOptionType> = [
|
||||
{ id: 'day-1', label: '今天', dateText: '05/18', weekday: '周一' },
|
||||
{ id: 'day-2', label: '明天', dateText: '05/19', weekday: '周二' },
|
||||
{ id: 'day-3', label: '后天', dateText: '05/20', weekday: '周三' },
|
||||
{ id: 'day-4', label: '周四', dateText: '05/21', weekday: '周四' }
|
||||
]
|
||||
|
||||
const BOOKING_TIME_SLOTS: Array<BookingTimeSlotType> = [
|
||||
{ id: 'slot-1', label: '09:00-10:00', available: true },
|
||||
{ id: 'slot-2', label: '10:00-11:00', available: true },
|
||||
{ id: 'slot-3', label: '14:00-15:00', available: true },
|
||||
{ id: 'slot-4', label: '15:00-16:00', available: true },
|
||||
{ id: 'slot-5', label: '18:00-19:00', available: false }
|
||||
]
|
||||
|
||||
const SERVICE_GUARANTEES: Array<HomeServiceGuaranteeItemType> = [
|
||||
{ id: 'guarantee-1', label: '平台认证' },
|
||||
{ id: 'guarantee-2', label: '明码标价' },
|
||||
{ id: 'guarantee-3', label: '服务可追溯' },
|
||||
{ id: 'guarantee-4', label: '异常可申诉' },
|
||||
{ id: 'guarantee-5', label: '隐私保护' }
|
||||
]
|
||||
|
||||
export function getHomeServiceCategories(): Array<HomeServiceCategoryType> {
|
||||
return HOME_SERVICE_CATEGORIES.map((item) => ({ ...item }))
|
||||
}
|
||||
|
||||
export function getHomeServicePromoCards(): Array<HomeServicePromoCardType> {
|
||||
return HOME_SERVICE_PROMOS.map((item) => ({ ...item }))
|
||||
}
|
||||
|
||||
export function getHomeServiceSceneCards(): Array<HomeServiceSceneCardType> {
|
||||
return HOME_SERVICE_SCENES.map((item) => ({ ...item }))
|
||||
}
|
||||
|
||||
function mapCatalogCategory(category: string): string {
|
||||
if (category == '日常照护') {
|
||||
return 'basic-care'
|
||||
}
|
||||
if (category == '康复支持') {
|
||||
return 'rehab-guide'
|
||||
}
|
||||
if (category == '健康管理') {
|
||||
return 'chronic-follow'
|
||||
}
|
||||
return 'all-services'
|
||||
}
|
||||
|
||||
function getImageTextByCategory(categoryId: string): string {
|
||||
if (categoryId == 'basic-care') {
|
||||
return '照护'
|
||||
}
|
||||
if (categoryId == 'rehab-guide') {
|
||||
return '康复'
|
||||
}
|
||||
if (categoryId == 'medical-assist') {
|
||||
return '陪诊'
|
||||
}
|
||||
if (categoryId == 'home-nursing') {
|
||||
return '护理'
|
||||
}
|
||||
if (categoryId == 'chronic-follow') {
|
||||
return '随访'
|
||||
}
|
||||
return '服务'
|
||||
}
|
||||
|
||||
export function getHomeServiceItems(catalog: Array<HomeServiceCatalogType>): Array<HomeServiceItemType> {
|
||||
if (catalog.length == 0) {
|
||||
return FALLBACK_HOME_SERVICE_ITEMS.map((item) => ({ ...item, tags: item.tags.slice(0) }))
|
||||
}
|
||||
|
||||
const result: Array<HomeServiceItemType> = []
|
||||
for (let i = 0; i < catalog.length; i++) {
|
||||
const item = catalog[i]
|
||||
const categoryId = mapCatalogCategory(item.category)
|
||||
result.push({
|
||||
id: item.id,
|
||||
title: item.name,
|
||||
subtitle: item.summary,
|
||||
category: categoryId,
|
||||
price: item.price,
|
||||
unit: '次',
|
||||
tags: item.tags.slice(0),
|
||||
imageText: getImageTextByCategory(categoryId),
|
||||
suitableFor: item.suitableFor
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function getBookingDayOptions(): Array<BookingDayOptionType> {
|
||||
return BOOKING_DAY_OPTIONS.map((item) => ({ ...item }))
|
||||
}
|
||||
|
||||
export function getBookingTimeSlots(): Array<BookingTimeSlotType> {
|
||||
return BOOKING_TIME_SLOTS.map((item) => ({ ...item }))
|
||||
}
|
||||
|
||||
export function getServiceGuarantees(): Array<HomeServiceGuaranteeItemType> {
|
||||
return SERVICE_GUARANTEES.map((item) => ({ ...item }))
|
||||
}
|
||||
|
||||
export function getRecommendedAgency(serviceId: string): HomeServiceAgencyType {
|
||||
if (serviceId == 'svc-002') {
|
||||
return {
|
||||
id: 'agency-002',
|
||||
name: '康宁居家康复中心',
|
||||
distance: '距您 2.4km',
|
||||
rating: '4.9',
|
||||
summary: '擅长术后恢复、动作训练和连续上门指导。'
|
||||
}
|
||||
}
|
||||
if (serviceId == 'svc-003') {
|
||||
return {
|
||||
id: 'agency-003',
|
||||
name: '安护慢病管理服务站',
|
||||
distance: '距您 1.8km',
|
||||
rating: '4.8',
|
||||
summary: '提供慢病随访、用药核对与健康宣教。'
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: 'agency-001',
|
||||
name: '梅江居家护理服务中心',
|
||||
distance: '距您 1.2km',
|
||||
rating: '4.9',
|
||||
summary: '提供基础照护、上门护理和长者陪伴服务。'
|
||||
}
|
||||
}
|
||||
|
||||
export function getServiceIncludes(serviceId: string): Array<string> {
|
||||
if (serviceId == 'svc-002') {
|
||||
return ['康复动作指导', '居家练习建议', '阶段反馈记录']
|
||||
}
|
||||
if (serviceId == 'svc-003') {
|
||||
return ['血压血糖监测', '用药核对', '健康宣教提醒']
|
||||
}
|
||||
return ['协助起居', '用药提醒', '基础生命体征监测']
|
||||
}
|
||||
|
||||
export function getServiceExcludes(serviceId: string): Array<string> {
|
||||
if (serviceId == 'svc-002') {
|
||||
return ['高风险医疗操作', '院内康复器械治疗']
|
||||
}
|
||||
if (serviceId == 'svc-003') {
|
||||
return ['静脉注射', '住院陪护']
|
||||
}
|
||||
return ['高风险处置', '住院陪护', '急诊陪诊']
|
||||
}
|
||||
325
utils/merchantAuth.uts
Normal file
325
utils/merchantAuth.uts
Normal file
@@ -0,0 +1,325 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { HOME_REDIRECT } from '@/ak/config.uts'
|
||||
import { AkReq } from '@/uni_modules/ak-req/index.uts'
|
||||
import { getCurrentUser, logout, state } from '@/utils/store.uts'
|
||||
import type { UserProfile } from '@/types/mall-types.uts'
|
||||
|
||||
export type MerchantInfo = {
|
||||
id: string
|
||||
merchant_id: string
|
||||
shop_name: string
|
||||
shop_logo: string
|
||||
shop_banner: string
|
||||
description: string
|
||||
contact_name: string
|
||||
contact_phone: string
|
||||
status: number | null
|
||||
}
|
||||
|
||||
export type MerchantAuthOptions = {
|
||||
redirectOnFail?: boolean
|
||||
toastOnFail?: boolean
|
||||
redirect?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type MerchantAuthResult = {
|
||||
ok: boolean
|
||||
message: string
|
||||
userInfo: UserProfile | null
|
||||
merchantInfo: MerchantInfo | null
|
||||
}
|
||||
|
||||
const MERCHANT_LOGIN_PATH = '/pages/user/login?mode=merchant'
|
||||
const MERCHANT_HOME_PATH = HOME_REDIRECT !== '' ? HOME_REDIRECT : '/pages/mall/merchant/index'
|
||||
|
||||
let redirectingToMerchantLogin = false
|
||||
|
||||
function hasText(value: string | null): boolean {
|
||||
return value != null && value !== ''
|
||||
}
|
||||
|
||||
function getCurrentRoute(): string {
|
||||
try {
|
||||
const pages = getCurrentPages() as Array<UTSJSONObject>
|
||||
if (pages.length === 0) {
|
||||
return ''
|
||||
}
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const route = currentPage['route']
|
||||
if (route != null) {
|
||||
return '/' + route
|
||||
}
|
||||
} catch (e) {}
|
||||
return ''
|
||||
}
|
||||
|
||||
function isMerchantRoute(route: string): boolean {
|
||||
return route.startsWith('/pages/mall/merchant/')
|
||||
}
|
||||
|
||||
function shouldShowToast(enabled: boolean | null): boolean {
|
||||
return enabled == null || enabled === true
|
||||
}
|
||||
|
||||
function shouldRedirect(enabled: boolean | null): boolean {
|
||||
return enabled == null || enabled === true
|
||||
}
|
||||
|
||||
function buildMerchantInfo(raw: UTSJSONObject): MerchantInfo {
|
||||
return {
|
||||
id: raw.getString('id') ?? '',
|
||||
merchant_id: raw.getString('merchant_id') ?? '',
|
||||
shop_name: raw.getString('shop_name') ?? '',
|
||||
shop_logo: raw.getString('shop_logo') ?? '',
|
||||
shop_banner: raw.getString('shop_banner') ?? '',
|
||||
description: raw.getString('description') ?? '',
|
||||
contact_name: raw.getString('contact_name') ?? '',
|
||||
contact_phone: raw.getString('contact_phone') ?? '',
|
||||
status: raw.getNumber('status')
|
||||
}
|
||||
}
|
||||
|
||||
function persistMerchantInfo(info: MerchantInfo): void {
|
||||
const payload = new UTSJSONObject()
|
||||
payload.set('id', info.id)
|
||||
payload.set('merchant_id', info.merchant_id)
|
||||
payload.set('shop_name', info.shop_name)
|
||||
payload.set('shop_logo', info.shop_logo)
|
||||
payload.set('shop_banner', info.shop_banner)
|
||||
payload.set('description', info.description)
|
||||
payload.set('contact_name', info.contact_name)
|
||||
payload.set('contact_phone', info.contact_phone)
|
||||
if (info.status != null) {
|
||||
payload.set('status', info.status)
|
||||
}
|
||||
uni.setStorageSync('merchant_info', JSON.stringify(payload))
|
||||
if (hasText(info.merchant_id)) {
|
||||
uni.setStorageSync('merchant_id', info.merchant_id)
|
||||
} else {
|
||||
uni.removeStorageSync('merchant_id')
|
||||
}
|
||||
}
|
||||
|
||||
function buildFailureResult(message: string, userInfo: UserProfile | null = null): MerchantAuthResult {
|
||||
return {
|
||||
ok: false,
|
||||
message,
|
||||
userInfo,
|
||||
merchantInfo: null
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMerchantInfoByUserId(userId: string): Promise<MerchantInfo | null> {
|
||||
let response = await supa
|
||||
.from('ml_shops')
|
||||
.select('id, merchant_id, shop_name, shop_logo, shop_banner, description, contact_name, contact_phone, status')
|
||||
.eq('merchant_id', userId)
|
||||
.limit(1)
|
||||
.execute()
|
||||
|
||||
if (response.error == null && response.data != null) {
|
||||
const rows = response.data as Array<UTSJSONObject>
|
||||
if (rows.length > 0) {
|
||||
return buildMerchantInfo(rows[0])
|
||||
}
|
||||
}
|
||||
|
||||
response = await supa
|
||||
.from('ml_shops')
|
||||
.select('id, merchant_id, shop_name, shop_logo, shop_banner, description, contact_name, contact_phone, status')
|
||||
.eq('id', userId)
|
||||
.limit(1)
|
||||
.execute()
|
||||
|
||||
if (response.error == null && response.data != null) {
|
||||
const rows = response.data as Array<UTSJSONObject>
|
||||
if (rows.length > 0) {
|
||||
return buildMerchantInfo(rows[0])
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function getRedirectTarget(explicitRedirect: string | null): string {
|
||||
if (hasText(explicitRedirect)) {
|
||||
return explicitRedirect as string
|
||||
}
|
||||
const currentRoute = getCurrentRoute()
|
||||
if (hasText(currentRoute) && currentRoute !== '/pages/user/login') {
|
||||
return currentRoute
|
||||
}
|
||||
return MERCHANT_HOME_PATH
|
||||
}
|
||||
|
||||
function handleFailure(message: string, options?: MerchantAuthOptions, userInfo: UserProfile | null = null): MerchantAuthResult {
|
||||
clearAuth()
|
||||
if (shouldShowToast(options?.toastOnFail ?? null) && hasText(message)) {
|
||||
uni.showToast({ title: message, icon: 'none' })
|
||||
}
|
||||
if (shouldRedirect(options?.redirectOnFail ?? null)) {
|
||||
redirectToMerchantLogin(getRedirectTarget(options?.redirect ?? null))
|
||||
}
|
||||
return buildFailureResult(message, userInfo)
|
||||
}
|
||||
|
||||
export function getToken(): string {
|
||||
const token = AkReq.getToken()
|
||||
return token != null ? token : ''
|
||||
}
|
||||
|
||||
export async function getUserInfo(): Promise<UserProfile | null> {
|
||||
try {
|
||||
const profile = state.userProfile
|
||||
if (profile != null && hasText(profile.id ?? null)) {
|
||||
return profile as UserProfile
|
||||
}
|
||||
} catch (e) {}
|
||||
return await getCurrentUser()
|
||||
}
|
||||
|
||||
export function getMerchantInfo(): MerchantInfo | null {
|
||||
try {
|
||||
const raw = uni.getStorageSync('merchant_info') as string | null
|
||||
if (hasText(raw)) {
|
||||
const parsed = JSON.parse(raw as string) as UTSJSONObject
|
||||
return buildMerchantInfo(parsed)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[merchantAuth] 读取 merchant_info 失败:', e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function setMerchantInfo(info: MerchantInfo): void {
|
||||
persistMerchantInfo(info)
|
||||
}
|
||||
|
||||
export function isLoggedIn(): boolean {
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
const hasSession = session != null && session.user != null
|
||||
const storedId = uni.getStorageSync('user_id') as string | null
|
||||
const hasStoredId = hasText(storedId)
|
||||
return hasSession || hasStoredId
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isMerchantRole(userInfo: UserProfile | null): boolean {
|
||||
if (userInfo == null) {
|
||||
return false
|
||||
}
|
||||
const role = userInfo.role != null ? userInfo.role : ''
|
||||
return role === 'merchant'
|
||||
}
|
||||
|
||||
export async function checkMerchantAccount(userInfo: UserProfile): Promise<MerchantInfo | null> {
|
||||
const userId = userInfo.id != null ? userInfo.id : ''
|
||||
if (!hasText(userId)) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
return await fetchMerchantInfoByUserId(userId)
|
||||
} catch (e) {
|
||||
console.error('[merchantAuth] 查询商家信息失败:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function clearAuth(): void {
|
||||
try {
|
||||
AkReq.clearToken()
|
||||
} catch (e) {}
|
||||
uni.removeStorageSync('user_id')
|
||||
uni.removeStorageSync('merchant_id')
|
||||
uni.removeStorageSync('merchant_info')
|
||||
uni.removeStorageSync('merchant_idx_cache')
|
||||
try {
|
||||
logout()
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export function redirectToMerchantLogin(redirect?: string): void {
|
||||
const currentRoute = getCurrentRoute()
|
||||
if (currentRoute === '/pages/user/login') {
|
||||
return
|
||||
}
|
||||
if (redirectingToMerchantLogin) {
|
||||
return
|
||||
}
|
||||
redirectingToMerchantLogin = true
|
||||
let targetUrl = MERCHANT_LOGIN_PATH
|
||||
const redirectTarget = getRedirectTarget(redirect != null ? redirect : null)
|
||||
if (hasText(redirectTarget)) {
|
||||
targetUrl = `${MERCHANT_LOGIN_PATH}&redirect=${encodeURIComponent(redirectTarget)}`
|
||||
}
|
||||
uni.reLaunch({
|
||||
url: targetUrl,
|
||||
complete: () => {
|
||||
setTimeout(() => {
|
||||
redirectingToMerchantLogin = false
|
||||
}, 300)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function goMerchantHome(): void {
|
||||
uni.reLaunch({ url: MERCHANT_HOME_PATH })
|
||||
}
|
||||
|
||||
export async function requireMerchantAuth(options?: MerchantAuthOptions): Promise<MerchantAuthResult> {
|
||||
const currentRoute = getCurrentRoute()
|
||||
if (currentRoute === '/pages/user/login') {
|
||||
return {
|
||||
ok: true,
|
||||
message: '',
|
||||
userInfo: null,
|
||||
merchantInfo: null
|
||||
}
|
||||
}
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
return handleFailure(options?.message ?? '请先登录商家账号', options)
|
||||
}
|
||||
|
||||
const userInfo = await getUserInfo()
|
||||
if (userInfo == null || !hasText(userInfo.id ?? null)) {
|
||||
return handleFailure('登录状态已失效,请重新登录', options)
|
||||
}
|
||||
|
||||
if (!isMerchantRole(userInfo)) {
|
||||
const message = options?.message != null && options.message !== ''
|
||||
? options.message
|
||||
: '当前账号不是商家账号'
|
||||
return handleFailure(message, options, userInfo)
|
||||
}
|
||||
|
||||
const merchantInfo = await checkMerchantAccount(userInfo)
|
||||
if (merchantInfo == null) {
|
||||
return handleFailure('当前账号未绑定商家信息', options, userInfo)
|
||||
}
|
||||
|
||||
if (merchantInfo.status != null) {
|
||||
if (merchantInfo.status === 0) {
|
||||
return handleFailure('当前商家信息待审核', options, userInfo)
|
||||
}
|
||||
if (merchantInfo.status !== 1) {
|
||||
return handleFailure('当前商家状态异常,暂不可登录', options, userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
persistMerchantInfo(merchantInfo)
|
||||
return {
|
||||
ok: true,
|
||||
message: '',
|
||||
userInfo,
|
||||
merchantInfo
|
||||
}
|
||||
}
|
||||
|
||||
export function isCurrentMerchantPage(): boolean {
|
||||
return isMerchantRoute(getCurrentRoute())
|
||||
}
|
||||
1
utils/pagesMode.uts
Normal file
1
utils/pagesMode.uts
Normal file
@@ -0,0 +1 @@
|
||||
export const CURRENT_PAGES_MODE = 'consumer'
|
||||
@@ -49,6 +49,27 @@ export const setUserProfile = (profile : UserProfile) => {
|
||||
state.userProfile = profile
|
||||
}
|
||||
|
||||
function clearUserAuthStorage() : void {
|
||||
const keys : Array<string> = [
|
||||
'userInfo',
|
||||
'user_id',
|
||||
'access_token',
|
||||
'refresh_token',
|
||||
'token',
|
||||
'currentUser',
|
||||
'current_user',
|
||||
'user',
|
||||
'auth_user',
|
||||
'supabase.auth.token'
|
||||
]
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
try {
|
||||
uni.removeStorageSync(keys[i])
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前用户信息(含补全 profile)
|
||||
export async function getCurrentUser() : Promise<UserProfile | null> {
|
||||
try {
|
||||
@@ -191,6 +212,8 @@ export async function getCurrentUser() : Promise<UserProfile | null> {
|
||||
// 登出并清空用户信息
|
||||
export function logout() {
|
||||
supa.signOut()
|
||||
clearUserAuthStorage()
|
||||
state.authUser = null
|
||||
state.userProfile = { username: '', email: '' } as UserProfile
|
||||
state.isLoggedIn = false // 登出
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'
|
||||
import { APP_ROLE, CURRENT_CLIENT, SUPA_KEY, SUPA_URL } from '@/ak/config.uts'
|
||||
|
||||
// 导出 supa 实例,供 services 层统一使用
|
||||
export { supa }
|
||||
@@ -35,6 +36,65 @@ function fixImageUrls(urls: any): string[] {
|
||||
return []
|
||||
}
|
||||
|
||||
function canUseConsumerData(): boolean {
|
||||
return CURRENT_CLIENT === 'consumer' || CURRENT_CLIENT === 'full'
|
||||
}
|
||||
|
||||
function maskConsumerKey(key: string): string {
|
||||
const keyLen = key.length
|
||||
if (keyLen <= 20) {
|
||||
return '(too short)'
|
||||
}
|
||||
return key.substring(0, 10) + '...' + key.substring(keyLen - 8)
|
||||
}
|
||||
|
||||
function logConsumerQueryStart(action: string, tableName: string, fieldList: string = '*'): boolean {
|
||||
if (!canUseConsumerData()) {
|
||||
console.error('[consumer-db] 已阻止非 consumer 数据请求')
|
||||
console.error('[consumer-db] 当前端类型:', CURRENT_CLIENT)
|
||||
console.error('[consumer-db] 当前应用角色:', APP_ROLE)
|
||||
console.error('[consumer-db] 查询表:', tableName)
|
||||
console.error('[consumer-db] 查询动作:', action)
|
||||
return false
|
||||
}
|
||||
console.log('[consumer-db] 当前端类型:', CURRENT_CLIENT)
|
||||
console.log('[consumer-db] 当前应用角色:', APP_ROLE)
|
||||
console.log('[consumer-db] supabaseUrl 已加载:', SUPA_URL)
|
||||
console.log('[consumer-db] supabaseKey 已加载:', maskConsumerKey(SUPA_KEY))
|
||||
console.log('[consumer-db] 首页开始加载数据')
|
||||
console.log('[consumer-db] 查询表:', tableName)
|
||||
console.log('[consumer-db] 查询字段:', fieldList)
|
||||
console.log('[consumer-db] 查询动作:', action)
|
||||
return true
|
||||
}
|
||||
|
||||
function logConsumerQuerySuccess(action: string, tableName: string, count: number): void {
|
||||
console.log('[consumer-db] 查询成功,动作:', action)
|
||||
console.log('[consumer-db] 查询成功,表:', tableName)
|
||||
console.log('[consumer-db] 查询成功,数量:', count)
|
||||
}
|
||||
|
||||
function logConsumerQueryFailure(action: string, tableName: string, error: any): void {
|
||||
console.error('[consumer-db] 查询失败')
|
||||
console.error('[consumer-db] 查询动作:', action)
|
||||
console.error('[consumer-db] 查询表:', tableName)
|
||||
if (error != null) {
|
||||
try {
|
||||
console.error('[consumer-db] error.message:', error.message)
|
||||
} catch (e) {}
|
||||
try {
|
||||
console.error('[consumer-db] error.code:', error.code)
|
||||
} catch (e) {}
|
||||
try {
|
||||
console.error('[consumer-db] error.details:', error.details)
|
||||
} catch (e) {}
|
||||
try {
|
||||
console.error('[consumer-db] error.hint:', error.hint)
|
||||
} catch (e) {}
|
||||
}
|
||||
console.error('[consumer-db] error.raw:', error)
|
||||
}
|
||||
|
||||
// 使用单例 Supabase 客户端
|
||||
// const supa = createClient(SUPA_URL, SUPA_KEY)
|
||||
|
||||
@@ -602,14 +662,18 @@ class SupabaseService {
|
||||
// 获取一级分类
|
||||
async getParentCategories(): Promise<Category[]> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getParentCategories', 'ml_categories', '*')) {
|
||||
return []
|
||||
}
|
||||
const response = await supa
|
||||
.from('ml_categories')
|
||||
.select('*')
|
||||
.is('parent_id', null)
|
||||
.eq('level', 1)
|
||||
.order('sort_order', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
logConsumerQueryFailure('getParentCategories', 'ml_categories', response.error)
|
||||
console.error('获取一级分类失败:', response.error)
|
||||
return []
|
||||
}
|
||||
@@ -643,8 +707,10 @@ class SupabaseService {
|
||||
}
|
||||
categories.push(cat)
|
||||
}
|
||||
logConsumerQuerySuccess('getParentCategories', 'ml_categories', categories.length)
|
||||
return categories
|
||||
} catch (error) {
|
||||
logConsumerQueryFailure('getParentCategories', 'ml_categories', error)
|
||||
console.error('获取一级分类异常:', error)
|
||||
return []
|
||||
}
|
||||
@@ -653,16 +719,22 @@ class SupabaseService {
|
||||
// 获取子分类
|
||||
async getSubCategories(parentId: string): Promise<Category[]> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getSubCategories', 'ml_categories', '*')) {
|
||||
return []
|
||||
}
|
||||
console.log('[getSubCategories] 开始获取子分类, parentId:', parentId)
|
||||
const response = await supa
|
||||
.from('ml_categories')
|
||||
.select('*')
|
||||
.eq('level', 2)
|
||||
.eq('parent_id', parentId)
|
||||
.order('sort_order', { ascending: true })
|
||||
.execute()
|
||||
|
||||
console.log('[getSubCategories] 查询完成')
|
||||
|
||||
if (response.error != null) {
|
||||
logConsumerQueryFailure('getSubCategories', 'ml_categories', response.error)
|
||||
console.error('获取子分类失败:', response.error)
|
||||
return []
|
||||
}
|
||||
@@ -674,20 +746,13 @@ class SupabaseService {
|
||||
}
|
||||
|
||||
const categories: Category[] = []
|
||||
const rawList = rawData as any[]
|
||||
const rawList = rawData as UTSJSONObject[]
|
||||
console.log('[getSubCategories] 原始数据条数:', rawList.length)
|
||||
|
||||
for (let i = 0; i < rawList.length; i++) {
|
||||
const item = rawList[i]
|
||||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||||
|
||||
// 手动过滤 parent_id
|
||||
const itemParentId = safeGetString(itemObj, 'parent_id')
|
||||
const isMatch = (itemParentId.length > 0 && itemParentId == parentId)
|
||||
if (!isMatch) {
|
||||
continue
|
||||
}
|
||||
|
||||
const icon = this.getCategoryIcon(itemObj)
|
||||
const cat: Category = {
|
||||
id: safeGetString(itemObj, 'id'),
|
||||
@@ -702,8 +767,10 @@ class SupabaseService {
|
||||
categories.push(cat)
|
||||
}
|
||||
console.log('[getSubCategories] 返回分类数量:', categories.length)
|
||||
logConsumerQuerySuccess('getSubCategories', 'ml_categories', categories.length)
|
||||
return categories
|
||||
} catch (error) {
|
||||
logConsumerQueryFailure('getSubCategories', 'ml_categories', error)
|
||||
console.error('获取子分类异常:', error)
|
||||
return []
|
||||
}
|
||||
@@ -736,6 +803,9 @@ class SupabaseService {
|
||||
// 获取所有品牌
|
||||
async getBrands(): Promise<Brand[]> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getBrands', 'ml_brands', 'id, name, logo_url, description, is_active')) {
|
||||
return []
|
||||
}
|
||||
console.log('[getBrands] 开始获取品牌数据...')
|
||||
const response = await supa
|
||||
.from('ml_brands')
|
||||
@@ -744,6 +814,7 @@ class SupabaseService {
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
logConsumerQueryFailure('getBrands', 'ml_brands', response.error)
|
||||
console.error('获取品牌失败:', response.error)
|
||||
return []
|
||||
}
|
||||
@@ -788,8 +859,10 @@ class SupabaseService {
|
||||
brands.push(brand)
|
||||
}
|
||||
console.log('[getBrands] 返回品牌数量:', brands.length)
|
||||
logConsumerQuerySuccess('getBrands', 'ml_brands', brands.length)
|
||||
return brands
|
||||
} catch (error) {
|
||||
logConsumerQueryFailure('getBrands', 'ml_brands', error)
|
||||
console.error('获取品牌异常:', error)
|
||||
return []
|
||||
}
|
||||
@@ -802,6 +875,9 @@ class SupabaseService {
|
||||
limit: number = 20
|
||||
): Promise<PaginatedResponse<Product>> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getProductsByCategory', 'ml_products_detail_view', '*')) {
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
console.log('[getProductsByCategory] 开始查询,分类ID:', categoryId, '页码:', page)
|
||||
|
||||
// 在数据库层面进行分类过滤
|
||||
@@ -818,6 +894,7 @@ class SupabaseService {
|
||||
console.log('[getProductsByCategory] 查询完成,total:', response.total)
|
||||
|
||||
if (response.error != null) {
|
||||
logConsumerQueryFailure('getProductsByCategory', 'ml_products_detail_view', response.error)
|
||||
console.error('获取商品失败:', response.error)
|
||||
return {
|
||||
data: [] as Product[],
|
||||
@@ -847,6 +924,7 @@ class SupabaseService {
|
||||
const item = rawList[i]
|
||||
products.push(parseProductFromRaw(item))
|
||||
}
|
||||
logConsumerQuerySuccess('getProductsByCategory', 'ml_products_detail_view', products.length)
|
||||
|
||||
return {
|
||||
data: products,
|
||||
@@ -856,6 +934,7 @@ class SupabaseService {
|
||||
hasmore: response.hasmore ?? false
|
||||
}
|
||||
} catch (error) {
|
||||
logConsumerQueryFailure('getProductsByCategory', 'ml_products_detail_view', error)
|
||||
console.error('获取商品异常:', error)
|
||||
return {
|
||||
data: [] as Product[],
|
||||
@@ -1689,7 +1768,7 @@ class SupabaseService {
|
||||
// 在数据库层面过滤 status,获取更多数据以便手动过滤 is_hot
|
||||
const response = await supa
|
||||
.from('ml_products_detail_view')
|
||||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
|
||||
.select('*')
|
||||
.eq('status', '1') // 使用字符串 '1'
|
||||
.order('sale_count', { ascending: false })
|
||||
.limit(limit * 5) // 获取更多数据以便过滤
|
||||
@@ -1739,10 +1818,13 @@ class SupabaseService {
|
||||
// 获取按销量排序的商品(所有商品,不限制 is_hot)
|
||||
async getProductsBySales(page: number = 1, limit: number = 10): Promise<PaginatedResponse<Product>> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getProductsBySales', 'ml_products_detail_view', '*')) {
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
console.log('[getProductsBySales] 开始获取销量排序商品...')
|
||||
const response = await supa
|
||||
.from('ml_products_detail_view')
|
||||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot', { count: 'exact' })
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('status', '1')
|
||||
.order('sale_count', { ascending: false })
|
||||
.page(page)
|
||||
@@ -1750,6 +1832,7 @@ class SupabaseService {
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
logConsumerQueryFailure('getProductsBySales', 'ml_products_detail_view', response.error)
|
||||
console.error('获取销量排序商品失败:', response.error)
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
@@ -1766,6 +1849,7 @@ class SupabaseService {
|
||||
products.push(parseProductFromRaw(item))
|
||||
}
|
||||
console.log('[getProductsBySales] 返回商品数:', products.length)
|
||||
logConsumerQuerySuccess('getProductsBySales', 'ml_products_detail_view', products.length)
|
||||
return {
|
||||
data: products,
|
||||
total: response.total ?? products.length,
|
||||
@@ -1774,6 +1858,7 @@ class SupabaseService {
|
||||
hasmore: response.hasmore ?? false
|
||||
}
|
||||
} catch (error) {
|
||||
logConsumerQueryFailure('getProductsBySales', 'ml_products_detail_view', error)
|
||||
console.error('获取销量排序商品异常:', error)
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
@@ -1782,9 +1867,12 @@ class SupabaseService {
|
||||
// 获取按价格排序的商品(升序:从低到高)
|
||||
async getProductsByPrice(page: number = 1, limit: number = 10, ascending: boolean = true): Promise<PaginatedResponse<Product>> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getProductsByPrice', 'ml_products_detail_view', '*')) {
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
const response = await supa
|
||||
.from('ml_products_detail_view')
|
||||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot', { count: 'exact' })
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('status', '1') // 在数据库层面过滤
|
||||
.order('base_price', { ascending })
|
||||
.page(page)
|
||||
@@ -1792,6 +1880,7 @@ class SupabaseService {
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
logConsumerQueryFailure('getProductsByPrice', 'ml_products_detail_view', response.error)
|
||||
console.error('获取价格排序商品失败:', response.error)
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
@@ -1807,6 +1896,7 @@ class SupabaseService {
|
||||
const item = rawList[i]
|
||||
products.push(parseProductFromRaw(item))
|
||||
}
|
||||
logConsumerQuerySuccess('getProductsByPrice', 'ml_products_detail_view', products.length)
|
||||
return {
|
||||
data: products,
|
||||
total: response.total ?? products.length,
|
||||
@@ -1815,6 +1905,7 @@ class SupabaseService {
|
||||
hasmore: response.hasmore ?? false
|
||||
}
|
||||
} catch (error) {
|
||||
logConsumerQueryFailure('getProductsByPrice', 'ml_products_detail_view', error)
|
||||
console.error('获取价格排序商品异常:', error)
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
@@ -1823,17 +1914,22 @@ class SupabaseService {
|
||||
// 获取新品(按创建时间排序,最新的在前)
|
||||
async getProductsByNewest(page: number = 1, limit: number = 10): Promise<PaginatedResponse<Product>> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getProductsByNewest', 'ml_products_detail_view', '*')) {
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
console.log('[getProductsByNewest] 开始获取新品...')
|
||||
const response = await supa
|
||||
.from('ml_products_detail_view')
|
||||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot', { count: 'exact' })
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('status', '1')
|
||||
.order('is_new', { ascending: false })
|
||||
.order('created_at', { ascending: false })
|
||||
.page(page)
|
||||
.limit(limit * 5)
|
||||
.limit(limit)
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
logConsumerQueryFailure('getProductsByNewest', 'ml_products_detail_view', response.error)
|
||||
console.error('获取新品失败:', response.error)
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
@@ -1847,54 +1943,20 @@ class SupabaseService {
|
||||
const rawList = rawData as any[]
|
||||
for (let i: number = 0; i < rawList.length; i++) {
|
||||
const item = rawList[i]
|
||||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||||
|
||||
// 手动过滤 is_new
|
||||
const rawIsNew = prodObj.get('is_new')
|
||||
let isNewBool: boolean = false
|
||||
if (typeof rawIsNew == 'boolean') {
|
||||
isNewBool = rawIsNew as boolean
|
||||
} else if (typeof rawIsNew == 'number') {
|
||||
isNewBool = (rawIsNew as number) == 1
|
||||
}
|
||||
if (!isNewBool) continue
|
||||
|
||||
products.push(parseProductFromRaw(item))
|
||||
if (products.length >= limit) break
|
||||
}
|
||||
|
||||
// 如果 is_new 商品不足,补充普通商品
|
||||
if (products.length < limit) {
|
||||
console.log('[getProductsByNewest] is_new商品不足,补充普通商品')
|
||||
const addedIds = new Set<string>()
|
||||
for (let i = 0; i < products.length; i++) {
|
||||
addedIds.add(products[i].id)
|
||||
}
|
||||
|
||||
for (let i: number = 0; i < rawList.length; i++) {
|
||||
const item = rawList[i]
|
||||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||||
const rawId = prodObj.get('id')
|
||||
const itemId = (typeof rawId == 'string') ? (rawId as string) : ''
|
||||
|
||||
if (!addedIds.has(itemId)) {
|
||||
products.push(parseProductFromRaw(item))
|
||||
addedIds.add(itemId)
|
||||
if (products.length >= limit) break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[getProductsByNewest] 返回商品数:', products.length)
|
||||
const pageData = products.slice(0, limit)
|
||||
logConsumerQuerySuccess('getProductsByNewest', 'ml_products_detail_view', products.length)
|
||||
return {
|
||||
data: pageData,
|
||||
data: products,
|
||||
total: response.total ?? products.length,
|
||||
page,
|
||||
limit,
|
||||
hasmore: products.length > limit || (response.hasmore ?? false)
|
||||
hasmore: response.hasmore ?? false
|
||||
}
|
||||
} catch (error) {
|
||||
logConsumerQueryFailure('getProductsByNewest', 'ml_products_detail_view', error)
|
||||
console.error('获取新品异常:', error)
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
@@ -1903,6 +1965,9 @@ class SupabaseService {
|
||||
// 获取推荐商品(is_featured=true)
|
||||
async getRecommendedProducts(limit: number = 10): Promise<Product[]> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getRecommendedProducts', 'ml_products_detail_view', 'id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')) {
|
||||
return []
|
||||
}
|
||||
console.log('[getRecommendedProducts] 开始获取推荐商品...')
|
||||
const response = await supa
|
||||
.from('ml_products_detail_view')
|
||||
@@ -1915,6 +1980,7 @@ class SupabaseService {
|
||||
console.log('[getRecommendedProducts] 查询完成')
|
||||
|
||||
if (response.error != null) {
|
||||
logConsumerQueryFailure('getRecommendedProducts', 'ml_products_detail_view', response.error)
|
||||
console.error('获取推荐商品失败:', response.error)
|
||||
return []
|
||||
}
|
||||
@@ -1947,8 +2013,10 @@ class SupabaseService {
|
||||
products.push(parseProductFromRaw(item))
|
||||
if (products.length >= limit) break
|
||||
}
|
||||
logConsumerQuerySuccess('getRecommendedProducts', 'ml_products_detail_view', products.length)
|
||||
return products
|
||||
} catch (error) {
|
||||
logConsumerQueryFailure('getRecommendedProducts', 'ml_products_detail_view', error)
|
||||
console.error('获取推荐商品异常:', error)
|
||||
return []
|
||||
}
|
||||
@@ -1957,6 +2025,10 @@ class SupabaseService {
|
||||
// 获取特价商品(这里假设没有specific flag, just use logic or tag if exists, defaulting to hot for now or just skip)
|
||||
// Modify to use compatible logic if badge column doesn't exist
|
||||
async getDiscountProducts(limit: number = 10): Promise<Product[]> {
|
||||
if (!logConsumerQueryStart('getDiscountProducts', 'ml_products_detail_view', '(none)')) {
|
||||
return [] as Product[]
|
||||
}
|
||||
logConsumerQuerySuccess('getDiscountProducts', 'ml_products_detail_view', 0)
|
||||
return [] as Product[] // 暂无特价字段
|
||||
}
|
||||
|
||||
@@ -5660,6 +5732,9 @@ class SupabaseService {
|
||||
// 获取热搜词(全站搜索频率最高的关键词)
|
||||
async getHotKeywords(limit: number = 10): Promise<string[]> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getHotKeywords', 'ml_search_history', 'keyword')) {
|
||||
return [] as string[]
|
||||
}
|
||||
const response = await supa
|
||||
.from('ml_search_history')
|
||||
.select('keyword')
|
||||
@@ -5668,6 +5743,7 @@ class SupabaseService {
|
||||
.execute()
|
||||
|
||||
if (response.error != null || response.data == null) {
|
||||
logConsumerQueryFailure('getHotKeywords', 'ml_search_history', response.error)
|
||||
return [] as string[]
|
||||
}
|
||||
|
||||
@@ -5712,8 +5788,10 @@ class SupabaseService {
|
||||
sortedKeywords.push(entryArray[i].keyword)
|
||||
}
|
||||
|
||||
logConsumerQuerySuccess('getHotKeywords', 'ml_search_history', sortedKeywords.length)
|
||||
return sortedKeywords
|
||||
} catch (e) {
|
||||
logConsumerQueryFailure('getHotKeywords', 'ml_search_history', e)
|
||||
console.error('获取热搜词失败:', e)
|
||||
return [] as string[]
|
||||
}
|
||||
@@ -5809,8 +5887,8 @@ class SupabaseService {
|
||||
|
||||
// 查询这些商品的分类
|
||||
const prodResponse = await supa
|
||||
.from('ml_products')
|
||||
.select('category_id')
|
||||
.from('ml_products_detail_view')
|
||||
.select('id, category_id')
|
||||
.limit(50)
|
||||
.execute()
|
||||
|
||||
@@ -5852,6 +5930,9 @@ class SupabaseService {
|
||||
// 智能推荐:综合用户搜索历史、浏览历史、热销商品
|
||||
async getSmartRecommendations(page: number = 1, limit: number = 10): Promise<PaginatedResponse<Product>> {
|
||||
try {
|
||||
if (!logConsumerQueryStart('getSmartRecommendations', 'ml_products_detail_view', 'composed: search_history + browse_history + products_detail_view')) {
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
console.log('[getSmartRecommendations] 开始获取智能推荐...')
|
||||
|
||||
const products: Product[] = []
|
||||
@@ -5922,6 +6003,7 @@ class SupabaseService {
|
||||
const endIndex = startIndex + limit
|
||||
const pageData = products.slice(startIndex, endIndex)
|
||||
console.log('[getSmartRecommendations] 返回商品数量:', pageData.length)
|
||||
logConsumerQuerySuccess('getSmartRecommendations', 'ml_products_detail_view', pageData.length)
|
||||
return {
|
||||
data: pageData,
|
||||
total: products.length,
|
||||
@@ -5930,6 +6012,7 @@ class SupabaseService {
|
||||
hasmore: products.length > endIndex
|
||||
}
|
||||
} catch (e) {
|
||||
logConsumerQueryFailure('getSmartRecommendations', 'ml_products_detail_view', e)
|
||||
console.error('获取智能推荐失败:', e)
|
||||
return emptyProductPage(page, limit)
|
||||
}
|
||||
|
||||
@@ -152,12 +152,22 @@ export function responsiveState() {
|
||||
export function goToLogin(redirectUrl?: string | null) {
|
||||
try {
|
||||
const target = redirectUrl != null && redirectUrl.length > 0 ? redirectUrl : ''
|
||||
if (target.length > 0) {
|
||||
const redirect = encodeURIComponent(target)
|
||||
uni.navigateTo({ url: `/pages/user/login?redirect=${redirect}` })
|
||||
} else {
|
||||
uni.navigateTo({ url: '/pages/user/login' })
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '当前操作需要登录,是否前往登录页继续?',
|
||||
confirmText: '去登录',
|
||||
cancelText: '先逛逛',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
if (target.length > 0) {
|
||||
const redirect = encodeURIComponent(target)
|
||||
uni.navigateTo({ url: `/pages/user/login?redirect=${redirect}` })
|
||||
} else {
|
||||
uni.navigateTo({ url: '/pages/user/login' })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
uni.navigateTo({ url: '/pages/user/login' })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user