Initial commit
This commit is contained in:
81
miniprogram/utils/api.ts
Normal file
81
miniprogram/utils/api.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Post, UserProfile, NearbyUser, Pet } from '../types/index'
|
||||
|
||||
type CloudResult<T> = { result: { code: number; data: T; message?: string } }
|
||||
|
||||
async function call<T>(name: string, data?: Record<string, unknown>): Promise<T> {
|
||||
const res = await wx.cloud.callFunction({ name, data: data || {} }) as CloudResult<T>
|
||||
if (res.result.code !== 0) throw new Error(res.result.message || '请求失败')
|
||||
return res.result.data
|
||||
}
|
||||
|
||||
export const api = {
|
||||
login: () =>
|
||||
call<{ userInfo: UserProfile; token: string }>('login'),
|
||||
|
||||
getUserProfile: (userId: string) =>
|
||||
call<UserProfile>('getUser', { userId }),
|
||||
|
||||
getPosts: (params: { page?: number; type?: 'feed' | 'nearby' | 'hot'; latitude?: number; longitude?: number }) =>
|
||||
call<{ list: Post[]; total: number; hasMore: boolean }>('getPosts', params),
|
||||
|
||||
getUserPosts: (userId: string, page = 0) =>
|
||||
call<{ list: Post[]; total: number }>('getPosts', { userId, page }),
|
||||
|
||||
createPost: (params: {
|
||||
content: string
|
||||
images: string[]
|
||||
video?: string
|
||||
location?: { name: string; latitude: number; longitude: number }
|
||||
hashtags: string[]
|
||||
mood?: string
|
||||
petId?: string
|
||||
}) => call<Post>('createPost', params),
|
||||
|
||||
deletePost: (postId: string) =>
|
||||
call<void>('deletePost', { postId }),
|
||||
|
||||
likePost: (postId: string) =>
|
||||
call<{ liked: boolean; count: number }>('likePost', { postId }),
|
||||
|
||||
getComments: (postId: string, page = 0) =>
|
||||
call<{ list: Comment[]; total: number }>('getComments', { postId, page }),
|
||||
|
||||
addComment: (postId: string, content: string) =>
|
||||
call<Comment>('addComment', { postId, content }),
|
||||
|
||||
getNearbyPets: (latitude: number, longitude: number, radiusKm = 5) =>
|
||||
call<NearbyUser[]>('getNearbyPets', { latitude, longitude, radiusKm }),
|
||||
|
||||
updateLocation: (latitude: number, longitude: number) =>
|
||||
call<void>('updateLocation', { latitude, longitude }),
|
||||
|
||||
sendGreeting: (toUserId: string, petId: string) =>
|
||||
call<void>('sendGreeting', { toUserId, petId }),
|
||||
|
||||
followUser: (targetId: string) =>
|
||||
call<{ following: boolean }>('followUser', { targetId }),
|
||||
|
||||
getFollowList: (type: 'following' | 'followers', userId?: string) =>
|
||||
call<UserProfile[]>('getFollowList', { type, userId }),
|
||||
|
||||
getMessages: (page = 0) =>
|
||||
call<{ list: any[]; unreadCount: number }>('getMessages', { page }),
|
||||
|
||||
sendMessage: (toId: string, content: string, type = 'text') =>
|
||||
call<void>('sendMessage', { toId, content, type }),
|
||||
|
||||
updatePet: (pet: Partial<Pet> & { _id?: string }) =>
|
||||
call<Pet>('updatePet', { pet }),
|
||||
|
||||
uploadImage: async (filePath: string): Promise<string> => {
|
||||
const ext = filePath.split('.').pop() || 'jpg'
|
||||
const cloudPath = `posts/${Date.now()}_${Math.random().toString(36).slice(2)}.${ext}`
|
||||
const res = await wx.cloud.uploadFile({ cloudPath, filePath })
|
||||
return res.fileID
|
||||
},
|
||||
|
||||
getFileURL: async (fileID: string): Promise<string> => {
|
||||
const res = await wx.cloud.getTempFileURL({ fileList: [fileID] })
|
||||
return res.fileList[0]?.tempFileURL || fileID
|
||||
},
|
||||
}
|
||||
74
miniprogram/utils/auth.ts
Normal file
74
miniprogram/utils/auth.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { UserProfile } from '../types/index'
|
||||
import { api } from './api'
|
||||
|
||||
const TOKEN_KEY = 'wangquan_token'
|
||||
const USER_KEY = 'wangquan_user'
|
||||
|
||||
export async function login(): Promise<UserProfile> {
|
||||
const { userInfo } = await api.login()
|
||||
wx.setStorageSync(USER_KEY, JSON.stringify(userInfo))
|
||||
return userInfo
|
||||
}
|
||||
|
||||
export function getCachedUser(): UserProfile | null {
|
||||
try {
|
||||
const raw = wx.getStorageSync(USER_KEY)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function clearAuth(): void {
|
||||
wx.removeStorageSync(TOKEN_KEY)
|
||||
wx.removeStorageSync(USER_KEY)
|
||||
}
|
||||
|
||||
export async function requireLocation(): Promise<{ latitude: number; longitude: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.getLocation({
|
||||
type: 'gcj02',
|
||||
success: res => resolve({ latitude: res.latitude, longitude: res.longitude }),
|
||||
fail: () => {
|
||||
wx.showModal({
|
||||
title: '需要位置权限',
|
||||
content: '汪圈需要获取您的位置来显示附近的宠物,请在设置中开启位置权限',
|
||||
confirmText: '去设置',
|
||||
success: res => {
|
||||
if (res.confirm) wx.openSetting({})
|
||||
reject(new Error('位置权限被拒绝'))
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function chooseAndUploadMedia(count = 9): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.chooseMedia({
|
||||
count,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album', 'camera'],
|
||||
maxDuration: 30,
|
||||
camera: 'back',
|
||||
success: async res => {
|
||||
try {
|
||||
wx.showLoading({ title: '上传中...', mask: true })
|
||||
const uploads = res.tempFiles.map(f => {
|
||||
const ext = f.tempFilePath.split('.').pop() || 'jpg'
|
||||
const cloudPath = `posts/${Date.now()}_${Math.random().toString(36).slice(2)}.${ext}`
|
||||
return wx.cloud.uploadFile({ cloudPath, filePath: f.tempFilePath })
|
||||
})
|
||||
const results = await Promise.all(uploads)
|
||||
wx.hideLoading()
|
||||
resolve(results.map(r => r.fileID))
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
reject(e)
|
||||
}
|
||||
},
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
}
|
||||
58
miniprogram/utils/constants.ts
Normal file
58
miniprogram/utils/constants.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export const CLOUD_ENV = 'YOUR_CLOUD_ENV_ID'
|
||||
|
||||
export const COLORS = {
|
||||
pink: '#ff4f91',
|
||||
orange: '#ff9f1c',
|
||||
yellow: '#ffe15a',
|
||||
green: '#2fd37a',
|
||||
mint: '#67e8c9',
|
||||
blue: '#4d8dff',
|
||||
violet: '#8c5cff',
|
||||
ink: '#272235',
|
||||
bgPrimary: '#fff9fb',
|
||||
textPrimary: '#272235',
|
||||
textSecondary: '#6a6178',
|
||||
textTertiary: '#9b8fa8',
|
||||
}
|
||||
|
||||
export const AVATAR_GRADIENTS = [
|
||||
'linear-gradient(135deg, #ffd6e8, #fff1a6)',
|
||||
'linear-gradient(135deg, #ffe7a0, #b7f4d2)',
|
||||
'linear-gradient(135deg, #c9f7df, #d9d2ff)',
|
||||
'linear-gradient(135deg, #cfe8ff, #ffd6e8)',
|
||||
'linear-gradient(135deg, #e3d8ff, #ffd6e8)',
|
||||
]
|
||||
|
||||
export const MOOD_OPTIONS = [
|
||||
{ label: '开心 😄', value: 'happy' },
|
||||
{ label: '累了 😮💨', value: 'tired' },
|
||||
{ label: '求玩伴 🐾', value: 'playmate' },
|
||||
{ label: '晒娃 📸', value: 'show' },
|
||||
{ label: '散步中 🌿', value: 'walking' },
|
||||
{ label: '在医院 🏥', value: 'hospital' },
|
||||
]
|
||||
|
||||
export const HOT_HASHTAGS = [
|
||||
'#上海遛狗',
|
||||
'#求玩伴',
|
||||
'#静安区',
|
||||
'#比熊',
|
||||
'#柴犬',
|
||||
'#金毛',
|
||||
'#布偶猫',
|
||||
'#英短',
|
||||
'#宠物友好',
|
||||
'#周末遛狗',
|
||||
]
|
||||
|
||||
export const BREED_OPTIONS = {
|
||||
dog: ['比熊', '柴犬', '金毛', '泰迪', '边牧', '法斗', '哈士奇', '萨摩耶', '拉布拉多', '其他'],
|
||||
cat: ['英短', '布偶', '美短', '加菲', '暹罗', '橘猫', '黑猫', '其他'],
|
||||
other: ['兔子', '仓鼠', '鹦鹉', '龟', '其他'],
|
||||
}
|
||||
|
||||
export const NEARBY_RADIUS_KM = 5
|
||||
|
||||
export const PAGE_SIZE = 10
|
||||
|
||||
export const HEARTBEAT_INTERVAL_MS = 60000
|
||||
72
miniprogram/utils/format.ts
Normal file
72
miniprogram/utils/format.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
export function formatDistance(meters: number): string {
|
||||
if (meters < 1000) return `${Math.round(meters)}m`
|
||||
return `${(meters / 1000).toFixed(1)}km`
|
||||
}
|
||||
|
||||
export function formatTime(isoString: string): string {
|
||||
const date = new Date(isoString)
|
||||
const now = new Date()
|
||||
const diffMs = now.getTime() - date.getTime()
|
||||
const diffMin = Math.floor(diffMs / 60000)
|
||||
const diffHour = Math.floor(diffMin / 60)
|
||||
const diffDay = Math.floor(diffHour / 24)
|
||||
|
||||
if (diffMin < 1) return '刚刚'
|
||||
if (diffMin < 60) return `${diffMin}分钟前`
|
||||
if (diffHour < 24) return `${diffHour}小时前`
|
||||
if (diffDay < 7) return `${diffDay}天前`
|
||||
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
return `${month}月${day}日`
|
||||
}
|
||||
|
||||
export function formatLastSeen(isoString: string): string {
|
||||
const diffMin = Math.floor((Date.now() - new Date(isoString).getTime()) / 60000)
|
||||
if (diffMin < 2) return '刚刚在线'
|
||||
if (diffMin < 60) return `${diffMin}分钟前`
|
||||
const diffHour = Math.floor(diffMin / 60)
|
||||
if (diffHour < 24) return `${diffHour}小时前`
|
||||
return '昨天在线'
|
||||
}
|
||||
|
||||
export function formatCount(n: number): string {
|
||||
if (n < 1000) return String(n)
|
||||
if (n < 10000) return `${(n / 1000).toFixed(1)}k`
|
||||
return `${Math.floor(n / 10000)}w`
|
||||
}
|
||||
|
||||
export function getAvatarGradient(seed: string): string {
|
||||
const gradients = [
|
||||
'linear-gradient(135deg, #ffd6e8, #fff1a6)',
|
||||
'linear-gradient(135deg, #ffe7a0, #b7f4d2)',
|
||||
'linear-gradient(135deg, #c9f7df, #d9d2ff)',
|
||||
'linear-gradient(135deg, #cfe8ff, #ffd6e8)',
|
||||
'linear-gradient(135deg, #e3d8ff, #c9f7df)',
|
||||
]
|
||||
let hash = 0
|
||||
for (let i = 0; i < seed.length; i++) hash = (hash * 31 + seed.charCodeAt(i)) & 0xffffffff
|
||||
return gradients[Math.abs(hash) % gradients.length]
|
||||
}
|
||||
|
||||
export function getPetEmoji(species: string, breed?: string): string {
|
||||
if (species === 'cat') return '🐱'
|
||||
if (species === 'other') return '🐾'
|
||||
const dogBreedEmoji: Record<string, string> = {
|
||||
'比熊': '🐩',
|
||||
'柴犬': '🐕',
|
||||
'金毛': '🦮',
|
||||
'哈士奇': '🐺',
|
||||
'萨摩耶': '🐕🦺',
|
||||
}
|
||||
return (breed && dogBreedEmoji[breed]) || '🐶'
|
||||
}
|
||||
|
||||
export function calcDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
|
||||
const R = 6371000
|
||||
const dLat = (lat2 - lat1) * Math.PI / 180
|
||||
const dLng = (lng2 - lng1) * Math.PI / 180
|
||||
const a = Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng / 2) ** 2
|
||||
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||||
}
|
||||
Reference in New Issue
Block a user