consumer模块完成90%,前端完成supabase对接
This commit is contained in:
62
mall/utils/authRedirect.uts
Normal file
62
mall/utils/authRedirect.uts
Normal file
@@ -0,0 +1,62 @@
|
||||
export type AuthRedirectOptions = {
|
||||
loginPath?: string
|
||||
registerPath?: string
|
||||
fallbackUrl?: string
|
||||
}
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
if (!path) return ''
|
||||
return path.startsWith('/') ? path : `/${path}`
|
||||
}
|
||||
|
||||
function safeEncode(url: string): string {
|
||||
try {
|
||||
return encodeURIComponent(url)
|
||||
} catch (_) {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export function getCurrentPageUrlWithQuery(): string {
|
||||
const pages = getCurrentPages() as any[]
|
||||
const currentPage = pages.length > 0 ? pages[pages.length - 1] : null
|
||||
if (!currentPage) return ''
|
||||
|
||||
const route = currentPage.route as string | null
|
||||
const options = (currentPage.options ?? {}) as Record<string, any>
|
||||
if (!route) return ''
|
||||
|
||||
const path = normalizePath(route)
|
||||
const keys = Object.keys(options)
|
||||
if (keys.length === 0) return path
|
||||
|
||||
const query = keys
|
||||
.filter((k) => options[k] != null)
|
||||
.map((k) => `${k}=${encodeURIComponent(String(options[k]))}`)
|
||||
.join('&')
|
||||
return query.length > 0 ? `${path}?${query}` : path
|
||||
}
|
||||
|
||||
export function toLoginWithRedirect(redirectUrl?: string, opts?: AuthRedirectOptions) {
|
||||
const loginPath = opts?.loginPath ?? '/pages/user/login'
|
||||
const fallback = opts?.fallbackUrl ?? '/pages/mall/consumer/index'
|
||||
const target = (redirectUrl != null && redirectUrl.length > 0)
|
||||
? redirectUrl
|
||||
: (getCurrentPageUrlWithQuery() || fallback)
|
||||
|
||||
uni.navigateTo({
|
||||
url: `${loginPath}?redirect=${safeEncode(target)}`
|
||||
})
|
||||
}
|
||||
|
||||
export function toRegisterWithRedirect(redirectUrl?: string, opts?: AuthRedirectOptions) {
|
||||
const registerPath = opts?.registerPath ?? '/pages/user/register'
|
||||
const fallback = opts?.fallbackUrl ?? '/pages/mall/consumer/index'
|
||||
const target = (redirectUrl != null && redirectUrl.length > 0)
|
||||
? redirectUrl
|
||||
: (getCurrentPageUrlWithQuery() || fallback)
|
||||
|
||||
uni.navigateTo({
|
||||
url: `${registerPath}?redirect=${safeEncode(target)}`
|
||||
})
|
||||
}
|
||||
14
mall/utils/i18nfun.uts
Normal file
14
mall/utils/i18nfun.uts
Normal file
@@ -0,0 +1,14 @@
|
||||
import i18n from '@/uni_modules/i18n/index.uts'
|
||||
|
||||
// 包装一个带参数智能判断的 t 函数,支持缺省值
|
||||
export function tt(key: string, values: any | null = null, locale: string | null = null): string {
|
||||
const isLocale = typeof values === 'string'
|
||||
const _values = isLocale ? null : values
|
||||
const _locale = isLocale ? values : locale
|
||||
return i18n.global.t(key, _values, _locale)
|
||||
}
|
||||
|
||||
// 示例用法
|
||||
// tSmart('prev')
|
||||
// tSmart('prev', 'en-US')
|
||||
// tSmart('prev', {name: '张三'})
|
||||
271
mall/utils/mock-category-data.uts
Normal file
271
mall/utils/mock-category-data.uts
Normal file
@@ -0,0 +1,271 @@
|
||||
// 分类页面mock数据模块
|
||||
// 导出医药分类数据
|
||||
export const medicineCategories = [
|
||||
{ id: 'cold', name: '感冒发烧', icon: '🤧', desc: '解热镇痛', color: '#2196F3' },
|
||||
{ id: 'stomach', name: '肠胃用药', icon: '🤢', desc: '消化系统', color: '#4CAF50' },
|
||||
{ id: 'pain', name: '止痛消炎', icon: '💊', desc: '镇痛消炎', color: '#F44336' },
|
||||
{ id: 'skin', name: '皮肤用药', icon: '🤕', desc: '皮肤护理', color: '#9C27B0' },
|
||||
{ id: 'vitamin', name: '维生素', icon: '🍊', desc: '营养补充', color: '#FF9800' },
|
||||
{ id: 'chronic', name: '慢性病', icon: '🫀', desc: '长期管理', color: '#795548' },
|
||||
{ id: 'child', name: '儿童用药', icon: '👶', desc: '儿童专用', color: '#00BCD4' },
|
||||
{ id: 'external', name: '外用药品', icon: '🧴', desc: '外用制剂', color: '#8BC34A' },
|
||||
{ id: 'device', name: '医疗器械', icon: '🩺', desc: '医疗设备', color: '#607D8B' },
|
||||
{ id: 'health', name: '健康食品', icon: '🥗', desc: '保健食品', color: '#FFC107' }
|
||||
]
|
||||
|
||||
// 导出mock商品数据
|
||||
export const mockProducts = {
|
||||
cold: [
|
||||
{
|
||||
id: 'cold1',
|
||||
name: '布洛芬缓释胶囊',
|
||||
specification: '0.3g*24粒',
|
||||
price: 18.5,
|
||||
originalPrice: 25.8,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '修正药业',
|
||||
sales: 2560,
|
||||
badge: '热销'
|
||||
},
|
||||
{
|
||||
id: 'cold2',
|
||||
name: '板蓝根颗粒',
|
||||
specification: '10g*20袋',
|
||||
price: 22.8,
|
||||
originalPrice: 29.9,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '白云山',
|
||||
sales: 1890,
|
||||
badge: '推荐'
|
||||
},
|
||||
{
|
||||
id: 'cold3',
|
||||
name: '连花清瘟胶囊',
|
||||
specification: '0.35g*36粒',
|
||||
price: 42.8,
|
||||
originalPrice: 48.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '以岭药业',
|
||||
sales: 3200,
|
||||
badge: '爆款'
|
||||
},
|
||||
{
|
||||
id: 'cold4',
|
||||
name: '对乙酰氨基酚片',
|
||||
specification: '0.5g*12片',
|
||||
price: 8.9,
|
||||
originalPrice: 12.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '强生制药',
|
||||
sales: 1420,
|
||||
badge: '特价'
|
||||
},
|
||||
{
|
||||
id: 'cold5',
|
||||
name: '感冒清热颗粒',
|
||||
specification: '3g*10袋',
|
||||
price: 16.5,
|
||||
originalPrice: 19.9,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '同仁堂',
|
||||
sales: 980,
|
||||
badge: '新品'
|
||||
},
|
||||
{
|
||||
id: 'cold6',
|
||||
name: '复方氨酚烷胺片',
|
||||
specification: '10片/盒',
|
||||
price: 12.8,
|
||||
originalPrice: 15.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '三九医药',
|
||||
sales: 1650,
|
||||
badge: '家庭装'
|
||||
}
|
||||
],
|
||||
|
||||
stomach: [
|
||||
{
|
||||
id: 'stomach1',
|
||||
name: '胃康灵胶囊',
|
||||
specification: '0.4g*24粒',
|
||||
price: 32.8,
|
||||
originalPrice: 38.5,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '三九医药',
|
||||
sales: 890,
|
||||
badge: '热销'
|
||||
},
|
||||
{
|
||||
id: 'stomach2',
|
||||
name: '奥美拉唑肠溶胶囊',
|
||||
specification: '20mg*14粒',
|
||||
price: 28.5,
|
||||
originalPrice: 35.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '阿斯利康',
|
||||
sales: 1250,
|
||||
badge: '处方药'
|
||||
},
|
||||
{
|
||||
id: 'stomach3',
|
||||
name: '健胃消食片',
|
||||
specification: '0.8g*32片',
|
||||
price: 15.9,
|
||||
originalPrice: 19.9,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '江中制药',
|
||||
sales: 2100,
|
||||
badge: '推荐'
|
||||
},
|
||||
{
|
||||
id: 'stomach4',
|
||||
name: '蒙脱石散',
|
||||
specification: '3g*10袋',
|
||||
price: 18.6,
|
||||
originalPrice: 22.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '益普生',
|
||||
sales: 1780,
|
||||
badge: '止泻'
|
||||
},
|
||||
{
|
||||
id: 'stomach5',
|
||||
name: '多潘立酮片',
|
||||
specification: '10mg*30片',
|
||||
price: 22.8,
|
||||
originalPrice: 26.5,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '西安杨森',
|
||||
sales: 950,
|
||||
badge: '促消化'
|
||||
},
|
||||
{
|
||||
id: 'stomach6',
|
||||
name: '铝碳酸镁咀嚼片',
|
||||
specification: '0.5g*20片',
|
||||
price: 25.9,
|
||||
originalPrice: 29.9,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '拜耳',
|
||||
sales: 1320,
|
||||
badge: '护胃'
|
||||
}
|
||||
],
|
||||
|
||||
pain: [
|
||||
{
|
||||
id: 'pain1',
|
||||
name: '阿莫西林胶囊',
|
||||
specification: '0.25g*24粒',
|
||||
price: 28.5,
|
||||
originalPrice: 35.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '华北制药',
|
||||
sales: 1560,
|
||||
badge: '处方药'
|
||||
},
|
||||
{
|
||||
id: 'pain2',
|
||||
name: '双氯芬酸钠缓释片',
|
||||
specification: '75mg*10片',
|
||||
price: 19.8,
|
||||
originalPrice: 24.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '诺华制药',
|
||||
sales: 1280,
|
||||
badge: '止痛'
|
||||
},
|
||||
{
|
||||
id: 'pain3',
|
||||
name: '云南白药胶囊',
|
||||
specification: '0.25g*32粒',
|
||||
price: 35.9,
|
||||
originalPrice: 42.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '云南白药',
|
||||
sales: 2350,
|
||||
badge: '经典'
|
||||
},
|
||||
{
|
||||
id: 'pain4',
|
||||
name: '塞来昔布胶囊',
|
||||
specification: '0.2g*10粒',
|
||||
price: 48.6,
|
||||
originalPrice: 55.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '辉瑞',
|
||||
sales: 890,
|
||||
badge: '抗炎'
|
||||
},
|
||||
{
|
||||
id: 'pain5',
|
||||
name: '布洛芬片',
|
||||
specification: '0.1g*24片',
|
||||
price: 12.5,
|
||||
originalPrice: 15.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '中美史克',
|
||||
sales: 1680,
|
||||
badge: '经济装'
|
||||
},
|
||||
{
|
||||
id: 'pain6',
|
||||
name: '头孢克肟胶囊',
|
||||
specification: '0.1g*6粒',
|
||||
price: 32.8,
|
||||
originalPrice: 38.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '广州白云山',
|
||||
sales: 1120,
|
||||
badge: '抗生素'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 为其他分类生成默认商品数据
|
||||
export const generateDefaultProducts = (categoryId: string) => {
|
||||
const baseProducts = [
|
||||
{ name: '通用药品1', price: 25.8, manufacturer: '知名药企', sales: 1200 },
|
||||
{ name: '通用药品2', price: 18.5, manufacturer: '知名药企', sales: 950 },
|
||||
{ name: '通用药品3', price: 32.0, manufacturer: '知名药企', sales: 1450 },
|
||||
{ name: '通用药品4', price: 22.8, manufacturer: '知名药企', sales: 880 },
|
||||
{ name: '通用药品5', price: 28.9, manufacturer: '知名药企', sales: 1100 },
|
||||
{ name: '通用药品6', price: 19.9, manufacturer: '知名药企', sales: 920 }
|
||||
]
|
||||
|
||||
return baseProducts.map((product, index) => ({
|
||||
id: `${categoryId}${index + 1}`,
|
||||
...product,
|
||||
specification: '规格待定',
|
||||
originalPrice: product.price * 1.2,
|
||||
image: '/static/images/default-product.png',
|
||||
badge: index === 0 ? '热销' : index === 1 ? '推荐' : ''
|
||||
}))
|
||||
}
|
||||
|
||||
// 获取分类商品数据
|
||||
export const getCategoryProducts = (categoryId: string) => {
|
||||
if (mockProducts[categoryId]) {
|
||||
return mockProducts[categoryId]
|
||||
} else {
|
||||
return generateDefaultProducts(categoryId)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分类信息
|
||||
export const getCategoryInfo = (categoryId: string) => {
|
||||
const category = medicineCategories.find(cat => cat.id === categoryId)
|
||||
if (category) {
|
||||
return {
|
||||
name: category.name,
|
||||
desc: category.desc
|
||||
}
|
||||
} else {
|
||||
// 返回默认分类信息
|
||||
return {
|
||||
name: '感冒发烧',
|
||||
desc: '解热镇痛'
|
||||
}
|
||||
}
|
||||
}
|
||||
104
mall/utils/sapi.uts
Normal file
104
mall/utils/sapi.uts
Normal file
@@ -0,0 +1,104 @@
|
||||
import supabase, { supaReady } from '@/components/supadb/aksupainstance.uts'
|
||||
import type { UserProfile } from '@/types/mall-types.uts'
|
||||
|
||||
/**
|
||||
* 确保用户资料存在,如果不存在则创建基础资料
|
||||
* @param sessionUser 会话用户对象 (UTSJSONObject)
|
||||
* @returns 创建的用户资料,如果创建失败则返回 null
|
||||
*/
|
||||
export async function ensureUserProfile(sessionUser: UTSJSONObject): Promise<UserProfile | null> {
|
||||
try {
|
||||
await supaReady
|
||||
|
||||
// 从 sessionUser 中获取用户ID和邮箱
|
||||
const userId = sessionUser.getString('id')
|
||||
const email = sessionUser.getString('email') ?? ''
|
||||
|
||||
if (userId == null || userId === '') {
|
||||
console.error('无法获取用户ID')
|
||||
return null
|
||||
}
|
||||
|
||||
// 检查用户是否已存在(ak_users 通过 auth_id 关联 auth.users.id)
|
||||
const checkRes = await supabase.from('ak_users')
|
||||
.select('*', {})
|
||||
.eq('auth_id', userId)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
console.log('ensureUserProfile check ak_users:', {
|
||||
status: checkRes.status,
|
||||
hasData: checkRes.data != null,
|
||||
hasError: (checkRes as any).error != null,
|
||||
error: (checkRes as any).error
|
||||
})
|
||||
|
||||
if (checkRes.status >= 200 && checkRes.status < 300 && checkRes.data != null) {
|
||||
// 用户已存在,返回现有资料(H5 下 checkRes.data 可能是 plain object,不一定是 UTSJSONObject)
|
||||
const existingUser = (typeof (checkRes.data as any).getString === 'function')
|
||||
? (checkRes.data as UTSJSONObject)
|
||||
: new UTSJSONObject(checkRes.data as any)
|
||||
return {
|
||||
id: existingUser.getString('id') ?? '',
|
||||
username: existingUser.getString('username') ?? '',
|
||||
email: existingUser.getString('email') ?? email,
|
||||
gender: existingUser.getString('gender'),
|
||||
birthday: existingUser.getString('birthday'),
|
||||
height_cm: existingUser.getNumber('height_cm'),
|
||||
weight_kg: existingUser.getNumber('weight_kg'),
|
||||
bio: existingUser.getString('bio'),
|
||||
avatar_url: existingUser.getString('avatar_url'),
|
||||
preferred_language: existingUser.getString('preferred_language'),
|
||||
role: existingUser.getString('role') ?? 'consumer',
|
||||
created_at: existingUser.getString('created_at'),
|
||||
updated_at: existingUser.getString('updated_at')
|
||||
} as UserProfile
|
||||
}
|
||||
|
||||
// 用户不存在,创建新用户资料
|
||||
const newUserData = new UTSJSONObject()
|
||||
newUserData.set('id', userId)
|
||||
newUserData.set('email', email)
|
||||
newUserData.set('username', email.split('@')[0] ?? 'user') // 默认用户名为邮箱前缀
|
||||
|
||||
const insertRes = await supabase.from('ak_users')
|
||||
.insert(newUserData)
|
||||
.select('*', {})
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
console.log('ensureUserProfile insert ak_users:', {
|
||||
status: insertRes.status,
|
||||
hasData: insertRes.data != null,
|
||||
hasError: (insertRes as any).error != null,
|
||||
error: (insertRes as any).error
|
||||
})
|
||||
|
||||
if (insertRes.status >= 200 && insertRes.status < 300 && insertRes.data != null) {
|
||||
const newUser = (typeof (insertRes.data as any).getString === 'function')
|
||||
? (insertRes.data as UTSJSONObject)
|
||||
: new UTSJSONObject(insertRes.data as any)
|
||||
return {
|
||||
id: newUser.getString('id') ?? '',
|
||||
username: newUser.getString('username') ?? '',
|
||||
email: newUser.getString('email') ?? email,
|
||||
gender: newUser.getString('gender'),
|
||||
birthday: newUser.getString('birthday'),
|
||||
height_cm: newUser.getNumber('height_cm'),
|
||||
weight_kg: newUser.getNumber('weight_kg'),
|
||||
bio: newUser.getString('bio'),
|
||||
avatar_url: newUser.getString('avatar_url'),
|
||||
preferred_language: newUser.getString('preferred_language'),
|
||||
role: newUser.getString('role') ?? 'consumer',
|
||||
created_at: newUser.getString('created_at'),
|
||||
updated_at: newUser.getString('updated_at')
|
||||
} as UserProfile
|
||||
} else {
|
||||
console.error('创建用户资料失败:', insertRes.status)
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('ensureUserProfile 异常:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
457
mall/utils/store.uts
Normal file
457
mall/utils/store.uts
Normal file
@@ -0,0 +1,457 @@
|
||||
import supa, { supaReady } from '@/components/supadb/aksupainstance.uts'
|
||||
import type { UserProfile, UserStats } from '@/pages/user/types.uts'
|
||||
import type { DeviceInfo } from '@/pages/sense/types.uts'
|
||||
import { SenseDataService, type DeviceParams } from '@/pages/sense/senseDataService.uts'
|
||||
import { reactive } from 'vue'
|
||||
import { ensureUserProfile } from './sapi.uts'
|
||||
|
||||
// 设备状态类型
|
||||
export type DeviceState = {
|
||||
devices : Array<DeviceInfo>
|
||||
currentDevice : DeviceInfo | null
|
||||
isLoading : boolean
|
||||
lastUpdated : number | null
|
||||
}
|
||||
|
||||
//定义一个大写的State类型
|
||||
export type State = {
|
||||
globalNum : number
|
||||
userProfile ?: UserProfile
|
||||
isLoggedIn : boolean // 新增字段
|
||||
deviceState : DeviceState // 新增设备状态
|
||||
// 如有需要,可增加更多属性
|
||||
}
|
||||
|
||||
// 实例化为state
|
||||
export const state = reactive({
|
||||
globalNum: 0,
|
||||
userProfile: { username: '', email: '' },
|
||||
isLoggedIn: false,
|
||||
deviceState: {
|
||||
devices: [],
|
||||
currentDevice: null,
|
||||
isLoading: false,
|
||||
lastUpdated: null
|
||||
} as DeviceState
|
||||
} as State)
|
||||
// 定义修改属性值的方法
|
||||
export const setGlobalNum = (num : number) => {
|
||||
state.globalNum = num
|
||||
}
|
||||
// 新增:设置登录状态的方法
|
||||
export const setIsLoggedIn = (val : boolean) => {
|
||||
state.isLoggedIn = val
|
||||
}
|
||||
// 定义全局设置用户信息的方法
|
||||
export const setUserProfile = (profile : UserProfile) => {
|
||||
state.userProfile = profile
|
||||
}
|
||||
|
||||
// 获取当前用户信息(含补全 profile)
|
||||
export async function getCurrentUser() : Promise<UserProfile | null> {
|
||||
try {
|
||||
await supaReady
|
||||
} catch (_) {}
|
||||
|
||||
const sessionInfo = supa.getSession()
|
||||
if (sessionInfo.user == null) {
|
||||
state.userProfile = { username: '', email: '' }
|
||||
state.isLoggedIn = false // 未登录
|
||||
return null
|
||||
}
|
||||
const userId = sessionInfo.user?.getString("id")
|
||||
if (userId == null) {
|
||||
state.userProfile = { username: '', email: '' }
|
||||
state.isLoggedIn = false // 未登录
|
||||
return null
|
||||
} // 查询 ak_users 表补全 profile
|
||||
const res = await supa.from('ak_users').select('*', {}).eq('id', userId).execute()
|
||||
console.log(res)
|
||||
if (res.status >= 200 && res.status < 300 && (res.data != null)) {
|
||||
let user : UTSJSONObject | null = null;
|
||||
const data = res.data as any;
|
||||
if (Array.isArray(data)) {
|
||||
if (data.length > 0) {
|
||||
user = data[0] as UTSJSONObject;
|
||||
}
|
||||
} else if (data != null) {
|
||||
user = data as UTSJSONObject;
|
||||
} console.log(user)
|
||||
if (user == null) {
|
||||
console.log('用户资料为空,尝试创建基础资料...') // 如果用户资料为空,尝试创建基础用户资料
|
||||
const sessionUser = sessionInfo.user
|
||||
if (sessionUser != null) {
|
||||
const createdProfile = await ensureUserProfile(sessionUser)
|
||||
if (createdProfile != null) {
|
||||
state.userProfile = createdProfile
|
||||
state.isLoggedIn = true
|
||||
return createdProfile
|
||||
} else {
|
||||
console.error('创建用户资料失败')
|
||||
state.userProfile = { username: '', email: '' }
|
||||
state.isLoggedIn = false
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
console.error('会话用户信息为空')
|
||||
state.userProfile = { username: '', email: '' }
|
||||
state.isLoggedIn = false
|
||||
return null
|
||||
}
|
||||
}
|
||||
console.log(user)
|
||||
// 直接用 getString/getNumber,无需兜底属性
|
||||
const profile : UserProfile = {
|
||||
id: user.getString('id'),
|
||||
username: user.getString('username') ?? "",
|
||||
email: user.getString('email') ?? "",
|
||||
gender: user.getString('gender'),
|
||||
birthday: user.getString('birthday'),
|
||||
height_cm: user.getNumber('height_cm'),
|
||||
weight_kg: user.getNumber('weight_kg'),
|
||||
bio: user.getString('bio'),
|
||||
avatar_url: user.getString('avatar_url'),
|
||||
preferred_language: user.getString('preferred_language'),
|
||||
role: user.getString('role'),
|
||||
school_id: user.getString('school_id'),
|
||||
grade_id: user.getString('grade_id'),
|
||||
class_id: user.getString('class_id')
|
||||
}
|
||||
state.userProfile = profile
|
||||
state.isLoggedIn = true // 登录成功
|
||||
return profile
|
||||
} else {
|
||||
state.userProfile = { username: '', email: '' }
|
||||
state.isLoggedIn = false // 未登录
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 登出并清空用户信息
|
||||
export function logout() {
|
||||
supa.signOut()
|
||||
state.userProfile = { username: '', email: '' }
|
||||
state.isLoggedIn = false // 登出
|
||||
}
|
||||
|
||||
// 获取当前用户ID(优先级:state.userProfile.id > session > localStorage)
|
||||
export function getCurrentUserId() : string {
|
||||
try {
|
||||
const profile = state.userProfile
|
||||
if (profile != null && profile.id != null) {
|
||||
const profileId = profile.id
|
||||
if (profileId != null) {
|
||||
return profileId
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
if (session != null) {
|
||||
const curuser = session.user
|
||||
const userId = curuser?.getString('id')
|
||||
if (userId != null) return userId
|
||||
}
|
||||
} catch (e) { }
|
||||
return ''
|
||||
}
|
||||
|
||||
// 获取当前用户的class_id
|
||||
export function getCurrentUserClassId() : string | null {
|
||||
try {
|
||||
const profile = state.userProfile
|
||||
if (profile != null && profile.class_id != null) {
|
||||
return profile.class_id
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取用户class_id失败:', e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// User store API for component compatibility
|
||||
export function getUserStore() {
|
||||
return {
|
||||
getUserId() : string | null {
|
||||
const sessionInfo = supa.getSession()
|
||||
return sessionInfo.user?.getString("id") ?? null
|
||||
},
|
||||
|
||||
getUserName() : string | null {
|
||||
return state.userProfile?.username ?? null
|
||||
},
|
||||
|
||||
getUserRole() : string | null {
|
||||
// Default role logic - can be enhanced based on your needs
|
||||
const sessionInfo = supa.getSession()
|
||||
if (sessionInfo.user == null) return null
|
||||
|
||||
// You can add role detection logic here
|
||||
// For now, return a default role
|
||||
return 'teacher' // or determine from user profile/database
|
||||
},
|
||||
|
||||
getProfile() : UserProfile | null {
|
||||
return state.userProfile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 设备状态管理方法 ==========
|
||||
|
||||
/**
|
||||
* 设置设备加载状态
|
||||
*/
|
||||
export const setDeviceLoading = (loading : boolean) => {
|
||||
state.deviceState.isLoading = loading
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置设备列表
|
||||
*/
|
||||
export const setDevices = (devices : Array<DeviceInfo>) => {
|
||||
state.deviceState.devices = devices
|
||||
state.deviceState.lastUpdated = Date.now()
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加设备到列表
|
||||
*/
|
||||
export const addDevice = (device : DeviceInfo) => {
|
||||
const existingIndex = state.deviceState.devices.findIndex(d => d.id === device.id)
|
||||
if (existingIndex >= 0) {
|
||||
// 更新现有设备
|
||||
state.deviceState.devices[existingIndex] = device
|
||||
} else {
|
||||
// 添加新设备
|
||||
state.deviceState.devices.push(device)
|
||||
}
|
||||
state.deviceState.lastUpdated = Date.now()
|
||||
}
|
||||
|
||||
/**
|
||||
* 从列表中移除设备
|
||||
*/
|
||||
export const removeDevice = (deviceId : string) => {
|
||||
const index = state.deviceState.devices.findIndex(d => d.id === deviceId)
|
||||
if (index >= 0) {
|
||||
state.deviceState.devices.splice(index, 1)
|
||||
// 如果移除的是当前设备,清空当前设备
|
||||
if (state.deviceState.currentDevice?.id === deviceId) {
|
||||
state.deviceState.currentDevice = null
|
||||
}
|
||||
state.deviceState.lastUpdated = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备信息
|
||||
*/
|
||||
export const updateDevice = (device : DeviceInfo) => {
|
||||
const index = state.deviceState.devices.findIndex(d => d.id === device.id)
|
||||
if (index >= 0) {
|
||||
state.deviceState.devices[index] = device
|
||||
// 如果更新的是当前设备,也更新当前设备
|
||||
if (state.deviceState.currentDevice?.id === device.id) {
|
||||
state.deviceState.currentDevice = device
|
||||
}
|
||||
state.deviceState.lastUpdated = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前选中的设备
|
||||
*/
|
||||
export const setCurrentDevice = (device : DeviceInfo | null) => {
|
||||
state.deviceState.currentDevice = device
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备ID获取设备信息
|
||||
*/
|
||||
export const getDeviceById = (deviceId : string) : DeviceInfo | null => {
|
||||
return state.deviceState.devices.find(d => d.id === deviceId) ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线设备列表
|
||||
*/
|
||||
export const getOnlineDevices = () : Array<DeviceInfo> => {
|
||||
return state.deviceState.devices.filter(d => d.status === 'online')
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务器加载设备列表
|
||||
*/
|
||||
export const loadDevices = async (forceRefresh : boolean) : Promise<boolean> => {
|
||||
const userId = getCurrentUserId()
|
||||
if (userId == null || userId === '') {
|
||||
console.log('用户未登录,无法加载设备列表')
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果不是强制刷新且数据较新(5分钟内),直接返回
|
||||
const now = Date.now()
|
||||
const lastUpdated = state.deviceState.lastUpdated
|
||||
if (forceRefresh == false && lastUpdated != null && (now - lastUpdated < 5 * 60 * 1000)) {
|
||||
console.log('设备数据较新,跳过刷新')
|
||||
return true
|
||||
}
|
||||
setDeviceLoading(true)
|
||||
try {
|
||||
const result = await SenseDataService.getDevices({ user_id: userId })
|
||||
if (result.error === null && result.data != null) {
|
||||
const devices = result.data as Array<DeviceInfo>
|
||||
setDevices(devices)
|
||||
console.log(`加载设备列表成功,共${devices.length}个设备`)
|
||||
return true
|
||||
} else {
|
||||
console.log('加载设备列表失败:', result.error?.message ?? '未知错误')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('加载设备列表异常:', error)
|
||||
return false
|
||||
} finally {
|
||||
setDeviceLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务器加载设备列表 - 带默认参数的重载版本
|
||||
*/
|
||||
export const loadDevicesWithDefault = async () : Promise<boolean> => {
|
||||
return await loadDevices(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定新设备
|
||||
*/
|
||||
export const bindNewDevice = async (deviceData : UTSJSONObject) : Promise<boolean> => {
|
||||
const userId = getCurrentUserId()
|
||||
if (userId == null) {
|
||||
console.log('用户未登录,无法绑定设备')
|
||||
return false
|
||||
}
|
||||
|
||||
// 确保设备数据中包含用户ID
|
||||
deviceData.set('user_id', userId)
|
||||
try {
|
||||
const result = await SenseDataService.bindDevice(deviceData)
|
||||
if (result.error === null && result.data != null) {
|
||||
// 添加到本地状态
|
||||
addDevice(result.data as DeviceInfo)
|
||||
const deviceName = (result.data as DeviceInfo).device_name ?? '未知设备'
|
||||
console.log('设备绑定成功:', deviceName)
|
||||
return true
|
||||
} else {
|
||||
console.log('设备绑定失败:', result.error?.message ?? '未知错误')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('设备绑定异常:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑设备
|
||||
*/
|
||||
export const unbindDevice = async (deviceId : string) : Promise<boolean> => {
|
||||
try {
|
||||
const result = await SenseDataService.unbindDevice(deviceId)
|
||||
if (result.error === null) {
|
||||
// 从本地状态中移除
|
||||
removeDevice(deviceId)
|
||||
console.log('设备解绑成功')
|
||||
return true
|
||||
} else {
|
||||
console.log('设备解绑失败:', result.error?.message ?? '未知错误')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('设备解绑异常:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备配置
|
||||
*/
|
||||
export const updateDeviceConfig = async (deviceId : string, configData : UTSJSONObject) : Promise<boolean> => {
|
||||
try {
|
||||
const result = await SenseDataService.updateDevice(deviceId, configData)
|
||||
if (result.error === null && result.data != null) {
|
||||
// 更新本地状态
|
||||
updateDevice(result.data as DeviceInfo)
|
||||
console.log('设备配置更新成功')
|
||||
return true
|
||||
} else {
|
||||
console.log('设备配置更新失败:', result.error?.message ?? '未知错误')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('设备配置更新异常:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 设备管理 API ==========
|
||||
|
||||
/**
|
||||
* 获取设备管理相关的API
|
||||
*/
|
||||
export function getDeviceStore() {
|
||||
return {
|
||||
// 获取设备状态
|
||||
getDevices() : Array<DeviceInfo> {
|
||||
return state.deviceState.devices
|
||||
},
|
||||
|
||||
getCurrentDevice() : DeviceInfo | null {
|
||||
return state.deviceState.currentDevice
|
||||
},
|
||||
|
||||
isLoading() : boolean {
|
||||
return state.deviceState.isLoading
|
||||
},
|
||||
getLastUpdated() : number | null {
|
||||
return state.deviceState.lastUpdated
|
||||
},
|
||||
|
||||
// 设备操作方法
|
||||
async loadDevices(forceRefresh : boolean) : Promise<boolean> {
|
||||
return await loadDevices(forceRefresh)
|
||||
},
|
||||
|
||||
async refreshDevices() : Promise<boolean> {
|
||||
return await loadDevicesWithDefault()
|
||||
},
|
||||
|
||||
async bindDevice(deviceData : UTSJSONObject) : Promise<boolean> {
|
||||
return await bindNewDevice(deviceData)
|
||||
},
|
||||
|
||||
async unbindDevice(deviceId : string) : Promise<boolean> {
|
||||
return await unbindDevice(deviceId)
|
||||
},
|
||||
|
||||
async updateDevice(deviceId : string, configData : UTSJSONObject) : Promise<boolean> {
|
||||
return await updateDeviceConfig(deviceId, configData)
|
||||
},
|
||||
|
||||
// 设备查询方法
|
||||
getDeviceById(deviceId : string) : DeviceInfo | null {
|
||||
return getDeviceById(deviceId)
|
||||
},
|
||||
|
||||
getOnlineDevices() : Array<DeviceInfo> {
|
||||
return getOnlineDevices()
|
||||
},
|
||||
|
||||
// 设备选择
|
||||
setCurrentDevice(device : DeviceInfo | null) {
|
||||
setCurrentDevice(device)
|
||||
}
|
||||
}
|
||||
}
|
||||
2741
mall/utils/supabaseService.uts
Normal file
2741
mall/utils/supabaseService.uts
Normal file
File diff suppressed because it is too large
Load Diff
201
mall/utils/utils.uts
Normal file
201
mall/utils/utils.uts
Normal file
@@ -0,0 +1,201 @@
|
||||
// 通用 UTSJSONObject 转任意 type 的函数
|
||||
// UTS 2024
|
||||
|
||||
import i18n from '@/uni_modules/i18n/index.uts';
|
||||
|
||||
/**
|
||||
* 切换应用语言设置
|
||||
* @param locale 语言代码,如 'zh-CN' 或 'en-US'
|
||||
*/
|
||||
export function switchLocale(locale: string) {
|
||||
// 设置存储
|
||||
uni.setStorageSync('uVueI18nLocale', locale);
|
||||
|
||||
// 设置 i18n 语言
|
||||
try {
|
||||
if (i18n != null && i18n.global != null) {
|
||||
i18n.global.locale.value = locale;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to switch locale:', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前语言设置
|
||||
* @returns 当前语言代码
|
||||
*/
|
||||
export function getCurrentLocale(): string {
|
||||
const locale = uni.getStorageSync('uVueI18nLocale') as string;
|
||||
if (locale == null || locale == '') {
|
||||
return 'zh-CN';
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保语言设置正确初始化
|
||||
*/
|
||||
export function ensureLocaleInitialized() {
|
||||
const currentLocale = getCurrentLocale();
|
||||
if (currentLocale == null || currentLocale == '') {
|
||||
switchLocale('zh-CN');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 将任意错误对象转换为标准的 UniError
|
||||
* @param error 任意类型的错误对象
|
||||
* @param defaultMessage 默认错误消息
|
||||
* @returns 标准化的 UniError 对象
|
||||
*/
|
||||
export function toUniError(error: any, defaultMessage: string = '操作失败'): UniError {
|
||||
// 如果已经是 UniError,直接返回
|
||||
if (error instanceof UniError) {
|
||||
return error
|
||||
}
|
||||
let errorMessage = defaultMessage
|
||||
let errorCode = -1
|
||||
|
||||
try {
|
||||
// 如果是普通 Error 对象
|
||||
if (error instanceof Error) {
|
||||
errorMessage = error.message != null && error.message != '' ? error.message : defaultMessage
|
||||
}
|
||||
// 如果是字符串
|
||||
else if (typeof error === 'string') {
|
||||
errorMessage = error
|
||||
} // 如果是对象,尝试提取错误信息
|
||||
else if (error != null && typeof error === 'object') {
|
||||
const errorObj = error as UTSJSONObject
|
||||
let message: string = ''
|
||||
|
||||
// 逐个检查字段,避免使用 || 操作符
|
||||
if (errorObj['message'] != null) {
|
||||
const msgValue = errorObj['message']
|
||||
if (typeof msgValue === 'string') {
|
||||
message = msgValue
|
||||
}
|
||||
} else if (errorObj['errMsg'] != null) {
|
||||
const msgValue = errorObj['errMsg']
|
||||
if (typeof msgValue === 'string') {
|
||||
message = msgValue
|
||||
}
|
||||
} else if (errorObj['error'] != null) {
|
||||
const msgValue = errorObj['error']
|
||||
if (typeof msgValue === 'string') {
|
||||
message = msgValue
|
||||
}
|
||||
} else if (errorObj['details'] != null) {
|
||||
const msgValue = errorObj['details']
|
||||
if (typeof msgValue === 'string') {
|
||||
message = msgValue
|
||||
}
|
||||
} else if (errorObj['msg'] != null) {
|
||||
const msgValue = errorObj['msg']
|
||||
if (typeof msgValue === 'string') {
|
||||
message = msgValue
|
||||
}
|
||||
}
|
||||
|
||||
if (message != '') {
|
||||
errorMessage = message
|
||||
}
|
||||
|
||||
// 尝试提取错误码
|
||||
let code: number = 0
|
||||
if (errorObj['code'] != null) {
|
||||
const codeValue = errorObj['code']
|
||||
if (typeof codeValue === 'number') {
|
||||
code = codeValue
|
||||
}
|
||||
} else if (errorObj['errCode'] != null) {
|
||||
const codeValue = errorObj['errCode']
|
||||
if (typeof codeValue === 'number') {
|
||||
code = codeValue
|
||||
}
|
||||
} else if (errorObj['status'] != null) {
|
||||
const codeValue = errorObj['status']
|
||||
if (typeof codeValue === 'number') {
|
||||
code = codeValue
|
||||
}
|
||||
}
|
||||
|
||||
if (code != 0) {
|
||||
errorCode = code
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error converting to UniError:', e)
|
||||
errorMessage = defaultMessage
|
||||
}
|
||||
// 创建标准 UniError
|
||||
const uniError = new UniError('AppError', errorCode, errorMessage)
|
||||
return uniError
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应式状态管理
|
||||
* @returns 响应式状态对象
|
||||
*/
|
||||
export function responsiveState() {
|
||||
const screenInfo = uni.getSystemInfoSync()
|
||||
const screenWidth = screenInfo.screenWidth
|
||||
|
||||
return {
|
||||
isLargeScreen: screenWidth >= 768,
|
||||
isSmallScreen: screenWidth < 576,
|
||||
screenWidth: screenWidth,
|
||||
cardColumns: screenWidth >= 768 ? 3 : screenWidth >= 576 ? 2 : 1
|
||||
}
|
||||
}
|
||||
|
||||
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' })
|
||||
}
|
||||
} catch (e) {
|
||||
uni.navigateTo({ url: '/pages/user/login' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容 UTS Android 的剪贴板写入
|
||||
* @param text 要写入剪贴板的文本
|
||||
*/
|
||||
export function setClipboard(text: string): void {
|
||||
// #ifdef WEB
|
||||
uni.setClipboardData({ data: text });
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间,显示为相对时间(如:刚刚,几小时前)
|
||||
* @param dateStr ISO 格式的日期字符串
|
||||
* @returns 格式化后的相对时间字符串
|
||||
*/
|
||||
export function formatTime(dateStr: string): string {
|
||||
if (!dateStr) return ''
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60))
|
||||
|
||||
if (hours < 1) {
|
||||
return '刚刚'
|
||||
} else if (hours < 24) {
|
||||
return `${hours}小时前`
|
||||
} else {
|
||||
return `${Math.floor(hours / 24)}天前`
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('formatTime error:', e)
|
||||
return dateStr.replace('T', ' ').split('.')[0]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user