/** * Mosquito Vue 3 增强版插件 * 包含全局配置、错误处理、加载状态管理 */ import { inject, type App, type Plugin } from 'vue' import MosquitoShareButton from './components/MosquitoShareButton.vue' import MosquitoPosterCard from './components/MosquitoPosterCard.vue' import MosquitoLeaderboard from './components/MosquitoLeaderboard.vue' import './style.css' // 全局配置接口 export interface MosquitoConfig { baseUrl: string apiKey: string userToken?: string timeout?: number retryCount?: number enableLogging?: boolean defaultTheme?: 'light' | 'dark' locale?: string } // 默认配置 const defaultConfig: MosquitoConfig = { baseUrl: '', apiKey: '', userToken: '', timeout: 10000, retryCount: 3, enableLogging: false, defaultTheme: 'light', locale: 'zh-CN' } // 全局配置实例 let globalConfig = { ...defaultConfig } // API错误类 export class MosquitoError extends Error { constructor( message: string, public code?: string, public statusCode?: number, public details?: any ) { super(message) this.name = 'MosquitoError' } } export interface ShortenResponse { code: string path: string originalUrl: string trackingId?: string } export interface ApiResponse { code: number message: string data: T meta?: { pagination?: { page: number size: number total: number totalPages: number hasNext: boolean hasPrevious: boolean } extra?: Record } error?: { message?: string details?: any code?: string } timestamp?: string traceId?: string } // 加载状态管理 export class LoadingManager { private static loadingStates = new Map() private static callbacks = new Map void)[]>() static setLoading(key: string, loading: boolean) { this.loadingStates.set(key, loading) const callbacks = this.callbacks.get(key) || [] callbacks.forEach(callback => callback(loading)) } static isLoading(key: string): boolean { return this.loadingStates.get(key) || false } static onLoadingChange(key: string, callback: (loading: boolean) => void) { if (!this.callbacks.has(key)) { this.callbacks.set(key, []) } this.callbacks.get(key)!.push(callback) // 返回清理函数 return () => { const callbacks = this.callbacks.get(key) if (callbacks) { const index = callbacks.indexOf(callback) if (index > -1) { callbacks.splice(index, 1) } } } } } // 增强的API客户端 export class EnhancedApiClient { private config: MosquitoConfig constructor(config: MosquitoConfig) { this.config = config } private async request( endpoint: string, options: RequestInit = {} ): Promise> { const url = `${this.config.baseUrl}${endpoint}` const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), this.config.timeout) try { const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', 'X-API-Key': this.config.apiKey, ...(this.config.userToken ? { Authorization: `Bearer ${this.config.userToken}` } : {}), ...options.headers, }, signal: controller.signal, }) clearTimeout(timeoutId) const payload = await response.json().catch(() => ({})) if (!response.ok) { const message = payload.message || payload.error?.message || `HTTP ${response.status}: ${response.statusText}` throw new MosquitoError( message, payload.error?.code || payload.code, response.status, payload.error?.details || payload.error || payload.details ) } if (typeof payload?.code === 'number' && payload.code >= 400) { throw new MosquitoError( payload.message || '请求失败', payload.error?.code || payload.code, response.status, payload.error?.details || payload.error || payload.details ) } return payload as ApiResponse } catch (error) { clearTimeout(timeoutId) if (error instanceof Error && error.name === 'AbortError') { throw new MosquitoError('请求超时', 'TIMEOUT', 408) } throw error } } private async requestData(endpoint: string, options: RequestInit = {}): Promise { const response = await this.request(endpoint, options) return response.data } async getActivity(id: number): Promise { return this.requestData(`/api/v1/activities/${id}`) } async getActivities(): Promise { const response = await this.requestData('/api/v1/activities') // 兼容分页响应 (content 字段) 和数组响应 if (response && typeof response === 'object' && 'content' in response) { return response.content || [] } if (Array.isArray(response)) { return response } return [] } async createActivity(data: any): Promise { return this.requestData('/api/v1/activities', { method: 'POST', body: JSON.stringify(data), }) } async getActivityStats(activityId: number): Promise { return this.requestData(`/api/v1/activities/${activityId}/stats`) } async getShareUrl(activityId: number, userId: number, template?: string): Promise { const params = new URLSearchParams({ activityId: activityId.toString(), userId: userId.toString(), ...(template && { template }), }) return this.requestData(`/api/v1/me/share-url?${params}`) } async getPosterImage(activityId: number, userId: number, template?: string): Promise { const params = new URLSearchParams({ activityId: activityId.toString(), userId: userId.toString(), ...(template && { template }), }) const response = await fetch(`${this.config.baseUrl}/api/v1/me/poster/image?${params}`, { headers: { 'X-API-Key': this.config.apiKey, ...(this.config.userToken ? { Authorization: `Bearer ${this.config.userToken}` } : {}), }, }) if (!response.ok) { throw new MosquitoError('获取海报失败', 'POSTER_ERROR', response.status) } return response.blob() } async getLeaderboard( activityId: number, page: number = 0, size: number = 20 ): Promise { const params = new URLSearchParams({ activityId: activityId.toString(), page: page.toString(), size: size.toString(), }) return this.request(`/api/v1/activities/${activityId}/leaderboard?${params}`) } async getShareMetrics(activityId: number): Promise { const params = new URLSearchParams({ activityId: activityId.toString(), }) return this.requestData(`/api/v1/share/metrics?${params}`) } async getRewards(activityId: number, userId: number, page: number = 0, size: number = 20): Promise { const params = new URLSearchParams({ activityId: activityId.toString(), userId: userId.toString(), page: page.toString(), size: size.toString(), }) return this.requestData(`/api/v1/me/rewards?${params}`) } async exportLeaderboardCsv(activityId: number): Promise { const response = await fetch(`${this.config.baseUrl}/api/v1/activities/${activityId}/leaderboard/export`, { headers: { 'X-API-Key': this.config.apiKey, ...(this.config.userToken ? { Authorization: `Bearer ${this.config.userToken}` } : {}), }, }) if (!response.ok) { throw new MosquitoError('导出排行榜失败', 'EXPORT_ERROR', response.status) } return response.text() } } // Vue 插件 const MosquitoEnhancedPlugin: Plugin = { install(app: App, options: MosquitoConfig) { // 合并配置 globalConfig = { ...globalConfig, ...options } // 注册全局属性 app.config.globalProperties.$mosquito = { config: globalConfig, apiClient: new EnhancedApiClient(globalConfig), loadingManager: LoadingManager, } // 注册全局组件 app.component('MosquitoShareButton', MosquitoShareButton) app.component('MosquitoPosterCard', MosquitoPosterCard) app.component('MosquitoLeaderboard', MosquitoLeaderboard) // 提供组合式API app.provide('mosquito', { config: globalConfig, apiClient: new EnhancedApiClient(globalConfig), loadingManager: LoadingManager, }) }, } // 组合式API export function useMosquito() { const mosquito = inject('mosquito') as any if (!mosquito) { throw new Error('Mosquito plugin is not installed. Please install it with app.use(MosquitoEnhancedPlugin, config)') } const getShareUrl = async (activityId: number, userId: number, template?: string) => { const loadingKey = `share-url-${activityId}-${userId}` LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.getShareUrl(activityId, userId, template) } catch (error) { if (globalConfig.enableLogging) { console.error('获取分享URL失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const getPosterImage = async (activityId: number, userId: number, template?: string) => { const loadingKey = `poster-image-${activityId}-${userId}` LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.getPosterImage(activityId, userId, template) } catch (error) { if (globalConfig.enableLogging) { console.error('获取海报失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const getLeaderboard = async (activityId: number, page: number = 0, size: number = 20) => { const loadingKey = `leaderboard-${activityId}-${page}-${size}` LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.getLeaderboard(activityId, page, size) } catch (error) { if (globalConfig.enableLogging) { console.error('获取排行榜失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const exportLeaderboardCsv = async (activityId: number) => { const loadingKey = `export-csv-${activityId}` LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.exportLeaderboardCsv(activityId) } catch (error) { if (globalConfig.enableLogging) { console.error('导出排行榜失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const getActivity = async (id: number) => { const loadingKey = `activity-${id}` LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.getActivity(id) } catch (error) { if (globalConfig.enableLogging) { console.error('获取活动信息失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const getActivities = async () => { const loadingKey = 'activities' LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.getActivities() } catch (error) { if (globalConfig.enableLogging) { console.error('获取活动列表失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const createActivity = async (data: any) => { const loadingKey = 'create-activity' LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.createActivity(data) } catch (error) { if (globalConfig.enableLogging) { console.error('创建活动失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const getActivityStats = async (activityId: number) => { const loadingKey = `activity-stats-${activityId}` LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.getActivityStats(activityId) } catch (error) { if (globalConfig.enableLogging) { console.error('获取活动统计失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const getShareMetrics = async (activityId: number) => { const loadingKey = `share-metrics-${activityId}` LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.getShareMetrics(activityId) } catch (error) { if (globalConfig.enableLogging) { console.error('获取分享指标失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } const getRewards = async (activityId: number, userId: number, page: number = 0, size: number = 20) => { const loadingKey = `rewards-${activityId}-${userId}-${page}-${size}` LoadingManager.setLoading(loadingKey, true) try { return await mosquito.apiClient.getRewards(activityId, userId, page, size) } catch (error) { if (globalConfig.enableLogging) { console.error('获取奖励失败:', error) } throw error } finally { LoadingManager.setLoading(loadingKey, false) } } return { config: mosquito.config, getShareUrl, getPosterImage, getLeaderboard, exportLeaderboardCsv, getActivity, getActivities, createActivity, getActivityStats, getShareMetrics, getRewards, loadingManager: LoadingManager, } } export default MosquitoEnhancedPlugin export { MosquitoEnhancedPlugin }