Claw 项目完整结构提交

This commit is contained in:
zqm
2026-03-16 15:47:55 +08:00
parent ca4970bcbf
commit fb0aeb6ca2
118 changed files with 28648 additions and 281 deletions

View File

@@ -0,0 +1,392 @@
// API接口封装
const API_BASE = 'https://pactgo.cn/api/v1'
class API {
// 用户登录
static async login(code) {
return this.request('/wechat/login', 'POST', { code })
}
// 获取用户信息
static async getUserInfo() {
return this.request('/wechat/userinfo', 'GET')
}
// 提交任务
static async submitTask(taskData) {
return this.request('/task/submit', 'POST', taskData)
}
// 获取任务列表
static async getTaskList(page = 1, limit = 20) {
return this.request(`/task/list?page=${page}&limit=${limit}`, 'GET')
}
// 获取任务详情
static async getTaskDetail(taskId) {
return this.request(`/task/${taskId}`, 'GET')
}
// 获取任务状态
static async getTaskStatus(taskId) {
return this.request(`/task/${taskId}/status`, 'GET')
}
// 发送聊天消息
static async sendMessage(content, type = 'text') {
return this.request('/chat/message', 'POST', { content, type })
}
// 获取聊天记录
static async getChatHistory(page = 1, limit = 50) {
return this.request(`/chat/history?page=${page}&limit=${limit}`, 'GET')
}
// 获取设备信息
static async getDeviceInfo() {
return this.request('/device/info', 'GET')
}
// 更新设备状态
static async updateDeviceStatus(status) {
return this.request('/device/status', 'POST', { status })
}
// 获取系统状态
static async getSystemStatus() {
return this.request('/system/status', 'GET')
}
// 通用请求方法
static async request(url, method = 'GET', data = null) {
return new Promise((resolve, reject) => {
const token = wx.getStorageSync('token') || ''
wx.request({
url: API_BASE + url,
method,
data,
header: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
if (res.statusCode === 200) {
if (res.data.success !== false) {
resolve(res.data)
} else {
reject(new Error(res.data.message || '请求失败'))
}
} else if (res.statusCode === 401) {
// Token过期重新登录
wx.removeStorageSync('token')
wx.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
})
reject(new Error('登录已过期'))
} else {
reject(new Error(`请求失败: ${res.statusCode}`))
}
},
fail: (err) => {
console.error('API请求失败:', err)
wx.showToast({
title: '网络连接失败',
icon: 'none'
})
reject(err)
}
})
})
}
// 上传文件
static async uploadFile(filePath, formData = {}) {
return new Promise((resolve, reject) => {
const token = wx.getStorageSync('token') || ''
wx.uploadFile({
url: API_BASE + '/upload',
filePath: filePath,
name: 'file',
formData: formData,
header: {
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
if (res.statusCode === 200) {
try {
const data = JSON.parse(res.data)
resolve(data)
} catch (e) {
reject(new Error('解析响应失败'))
}
} else {
reject(new Error(`上传失败: ${res.statusCode}`))
}
},
fail: (err) => {
console.error('文件上传失败:', err)
reject(err)
}
})
})
}
}
// WebSocket管理器
class WebSocketManager {
constructor() {
this.socket = null
this.isConnected = false
this.reconnectTimer = null
this.messageHandlers = new Map()
}
connect(url) {
if (this.socket) {
this.disconnect()
}
this.socket = wx.connectSocket({
url: url,
header: {
'content-type': 'application/json'
}
})
this.socket.onOpen(() => {
console.log('WebSocket连接已打开')
this.isConnected = true
this.clearReconnectTimer()
// 发送认证信息
this.send({
type: 'auth',
userId: wx.getStorageSync('userId') || 'anonymous',
deviceId: wx.getSystemInfoSync().model,
timestamp: Date.now()
})
})
this.socket.onMessage((res) => {
console.log('收到WebSocket消息:', res.data)
try {
const data = JSON.parse(res.data)
this.handleMessage(data)
} catch (e) {
console.error('解析WebSocket消息失败:', e)
}
})
this.socket.onClose(() => {
console.log('WebSocket连接已关闭')
this.isConnected = false
this.socket = null
this.scheduleReconnect(url)
})
this.socket.onError((err) => {
console.error('WebSocket连接错误:', err)
this.isConnected = false
this.scheduleReconnect(url)
})
}
disconnect() {
this.clearReconnectTimer()
if (this.socket) {
this.socket.close()
this.socket = null
}
this.isConnected = false
}
send(data) {
if (this.isConnected && this.socket) {
this.socket.send({
data: JSON.stringify(data)
})
} else {
console.warn('WebSocket未连接无法发送消息')
}
}
onMessage(type, handler) {
if (!this.messageHandlers.has(type)) {
this.messageHandlers.set(type, [])
}
this.messageHandlers.get(type).push(handler)
}
offMessage(type, handler) {
if (this.messageHandlers.has(type)) {
const handlers = this.messageHandlers.get(type)
const index = handlers.indexOf(handler)
if (index > -1) {
handlers.splice(index, 1)
}
}
}
handleMessage(data) {
if (data.type && this.messageHandlers.has(data.type)) {
const handlers = this.messageHandlers.get(data.type)
handlers.forEach(handler => {
try {
handler(data)
} catch (e) {
console.error('消息处理错误:', e)
}
})
}
}
scheduleReconnect(url) {
this.clearReconnectTimer()
this.reconnectTimer = setTimeout(() => {
console.log('尝试重新连接WebSocket...')
this.connect(url)
}, 3000)
}
clearReconnectTimer() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
}
}
}
// 工具函数
const util = {
// 格式化时间
formatTime(date) {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return [year, month, day].map(this.formatNumber).join('/') + ' ' +
[hour, minute, second].map(this.formatNumber).join(':')
},
formatNumber(n) {
n = n.toString()
return n[1] ? n : '0' + n
},
// 显示加载提示
showLoading(title = '加载中...') {
wx.showLoading({
title: title,
mask: true
})
},
// 隐藏加载提示
hideLoading() {
wx.hideLoading()
},
// 显示成功提示
showSuccess(title = '操作成功') {
wx.showToast({
title: title,
icon: 'success',
duration: 2000
})
},
// 显示错误提示
showError(title = '操作失败') {
wx.showToast({
title: title,
icon: 'error',
duration: 2000
})
},
// 显示模态框
showModal(title, content) {
return new Promise((resolve) => {
wx.showModal({
title: title,
content: content,
success: (res) => {
resolve(res.confirm)
}
})
})
},
// 防抖函数
debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
},
// 节流函数
throttle(func, limit) {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
}
// 常量定义
const constants = {
// API基础地址
API_BASE: 'https://pactgo.cn/api/v1',
// WebSocket地址
WEBSOCKET_URL: 'wss://pactgo.cn/ws/task',
// 任务状态
TASK_STATUS: {
PENDING: 'pending',
PROCESSING: 'processing',
COMPLETED: 'completed',
FAILED: 'failed'
},
// 消息类型
MESSAGE_TYPE: {
TEXT: 'text',
IMAGE: 'image',
FILE: 'file',
SYSTEM: 'system'
},
// 本地存储键名
STORAGE_KEYS: {
TOKEN: 'token',
USER_INFO: 'userInfo',
DEVICE_ID: 'deviceId',
TASK_HISTORY: 'taskHistory'
}
}
module.exports = {
API,
WebSocketManager,
util,
constants
}

View File

@@ -0,0 +1,210 @@
// 常量定义
// API基础地址
const API_BASE = 'https://pactgo.cn/api/v1'
// WebSocket地址
const WEBSOCKET_URL = 'wss://pactgo.cn/ws/task'
// 任务状态
const TASK_STATUS = {
PENDING: 'pending',
PROCESSING: 'processing',
COMPLETED: 'completed',
FAILED: 'failed',
CANCELLED: 'cancelled'
}
// 任务类型
const TASK_TYPE = {
TEXT: 'text',
IMAGE: 'image',
FILE: 'file',
AUDIO: 'audio',
VIDEO: 'video'
}
// 消息类型
const MESSAGE_TYPE = {
TEXT: 'text',
IMAGE: 'image',
FILE: 'file',
SYSTEM: 'system',
TASK: 'task'
}
// 本地存储键名
const STORAGE_KEYS = {
TOKEN: 'token',
USER_INFO: 'userInfo',
DEVICE_ID: 'deviceId',
TASK_HISTORY: 'taskHistory',
CHAT_HISTORY: 'chatHistory',
SYSTEM_CONFIG: 'systemConfig'
}
// 网络超时时间(毫秒)
const NETWORK_TIMEOUT = {
REQUEST: 10000,
UPLOAD: 30000,
DOWNLOAD: 30000,
WEBSOCKET: 10000
}
// 重试配置
const RETRY_CONFIG = {
MAX_RETRIES: 3,
RETRY_DELAY: 1000,
BACKOFF_MULTIPLIER: 2
}
// 分页配置
const PAGINATION = {
DEFAULT_PAGE_SIZE: 20,
MAX_PAGE_SIZE: 100,
PRELOAD_PAGES: 2
}
// 文件上传限制
const UPLOAD_LIMITS = {
MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB
MAX_IMAGE_SIZE: 5 * 1024 * 1024, // 5MB
ALLOWED_TYPES: ['image/jpeg', 'image/png', 'image/gif', 'text/plain', 'application/pdf']
}
// WebSocket消息类型
const WS_MESSAGE_TYPE = {
AUTH: 'auth',
PING: 'ping',
PONG: 'pong',
MESSAGE: 'message',
TASK_STATUS: 'task_status',
SYSTEM: 'system',
ERROR: 'error'
}
// 系统配置
const SYSTEM_CONFIG = {
APP_NAME: '智控未来',
APP_VERSION: '1.0.0',
API_VERSION: 'v1',
SUPPORTED_FEATURES: ['chat', 'task', 'file_upload', 'websocket']
}
// 错误代码
const ERROR_CODES = {
NETWORK_ERROR: 1000,
AUTH_ERROR: 1001,
API_ERROR: 1002,
VALIDATION_ERROR: 1003,
FILE_ERROR: 1004,
WEBSOCKET_ERROR: 1005,
TASK_ERROR: 1006
}
// 错误消息
const ERROR_MESSAGES = {
[ERROR_CODES.NETWORK_ERROR]: '网络连接失败',
[ERROR_CODES.AUTH_ERROR]: '认证失败',
[ERROR_CODES.API_ERROR]: '接口调用失败',
[ERROR_CODES.VALIDATION_ERROR]: '参数验证失败',
[ERROR_CODES.FILE_ERROR]: '文件处理失败',
[ERROR_CODES.WEBSOCKET_ERROR]: 'WebSocket连接失败',
[ERROR_CODES.TASK_ERROR]: '任务处理失败'
}
// 默认头像
const DEFAULT_AVATAR = '/assets/images/default-avatar.png'
// 默认图标
const DEFAULT_ICONS = {
HOME: '/assets/icons/home.png',
CHAT: '/assets/icons/chat.png',
TASK: '/assets/icons/task.png',
USER: '/assets/icons/user.png',
SEND: '/assets/icons/send.png',
UPLOAD: '/assets/icons/upload.png',
SETTINGS: '/assets/icons/settings.png'
}
// 颜色配置
const COLORS = {
PRIMARY: '#07c160',
SECONDARY: '#10aeff',
SUCCESS: '#07c160',
WARNING: '#f0ad4e',
ERROR: '#dd524d',
INFO: '#10aeff',
TEXT_PRIMARY: '#333333',
TEXT_SECONDARY: '#666666',
TEXT_HINT: '#999999',
BACKGROUND: '#f5f5f5',
WHITE: '#ffffff',
BORDER: '#e0e0e0'
}
// 字体大小
const FONT_SIZES = {
SMALL: '24rpx',
NORMAL: '28rpx',
LARGE: '32rpx',
XLARGE: '36rpx',
TITLE: '40rpx'
}
// 间距配置
const SPACING = {
XS: '10rpx',
SM: '20rpx',
MD: '30rpx',
LG: '40rpx',
XL: '50rpx'
}
// 圆角配置
const BORDER_RADIUS = {
SMALL: '5rpx',
NORMAL: '10rpx',
LARGE: '20rpx',
ROUND: '50%'
}
// 阴影配置
const SHADOWS = {
SMALL: '0 2rpx 10rpx rgba(0, 0, 0, 0.1)',
NORMAL: '0 4rpx 20rpx rgba(0, 0, 0, 0.15)',
LARGE: '0 8rpx 30rpx rgba(0, 0, 0, 0.2)'
}
// 动画配置
const ANIMATIONS = {
DURATION_SHORT: 200,
DURATION_NORMAL: 300,
DURATION_LONG: 500,
EASING: 'ease-in-out'
}
module.exports = {
API_BASE,
WEBSOCKET_URL,
TASK_STATUS,
TASK_TYPE,
MESSAGE_TYPE,
STORAGE_KEYS,
NETWORK_TIMEOUT,
RETRY_CONFIG,
PAGINATION,
UPLOAD_LIMITS,
WS_MESSAGE_TYPE,
SYSTEM_CONFIG,
ERROR_CODES,
ERROR_MESSAGES,
DEFAULT_AVATAR,
DEFAULT_ICONS,
COLORS,
FONT_SIZES,
SPACING,
BORDER_RADIUS,
SHADOWS,
ANIMATIONS
}

View File

@@ -0,0 +1,310 @@
// 通用工具函数
/**
* 格式化时间
* @param {Date} date - 日期对象
* @returns {string} 格式化后的时间字符串
*/
function formatTime(date) {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return [year, month, day].map(formatNumber).join('/') + ' ' +
[hour, minute, second].map(formatNumber).join(':')
}
/**
* 格式化数字,补零
* @param {number} n - 数字
* @returns {string} 格式化后的数字字符串
*/
function formatNumber(n) {
n = n.toString()
return n[1] ? n : '0' + n
}
/**
* 格式化相对时间
* @param {number} timestamp - 时间戳
* @returns {string} 相对时间描述
*/
function formatRelativeTime(timestamp) {
const now = Date.now()
const diff = now - timestamp
const minute = 60 * 1000
const hour = 60 * minute
const day = 24 * hour
const week = 7 * day
const month = 30 * day
if (diff < minute) {
return '刚刚'
} else if (diff < hour) {
return Math.floor(diff / minute) + '分钟前'
} else if (diff < day) {
return Math.floor(diff / hour) + '小时前'
} else if (diff < week) {
return Math.floor(diff / day) + '天前'
} else if (diff < month) {
return Math.floor(diff / week) + '周前'
} else {
return formatTime(new Date(timestamp))
}
}
/**
* 防抖函数
* @param {Function} func - 要执行的函数
* @param {number} wait - 等待时间(毫秒)
* @returns {Function} 防抖后的函数
*/
function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
/**
* 节流函数
* @param {Function} func - 要执行的函数
* @param {number} limit - 限制时间(毫秒)
* @returns {Function} 节流后的函数
*/
function throttle(func, limit) {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
/**
* 深拷贝对象
* @param {Object} obj - 要拷贝的对象
* @returns {Object} 拷贝后的对象
*/
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj.getTime())
if (obj instanceof Array) return obj.map(item => deepClone(item))
if (typeof obj === 'object') {
const cloned = {}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key])
}
}
return cloned
}
}
/**
* 判断对象是否为空
* @param {Object} obj - 要判断的对象
* @returns {boolean} 是否为空
*/
function isEmpty(obj) {
if (obj == null) return true
if (obj.length > 0) return false
if (obj.length === 0) return true
if (typeof obj !== 'object') return true
for (const key in obj) {
if (obj.hasOwnProperty(key)) return false
}
return true
}
/**
* 生成唯一ID
* @returns {string} 唯一ID
*/
function generateUniqueId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2)
}
/**
* 验证手机号
* @param {string} phone - 手机号
* @returns {boolean} 是否有效
*/
function validatePhone(phone) {
return /^1[3-9]\d{9}$/.test(phone)
}
/**
* 验证邮箱
* @param {string} email - 邮箱
* @returns {boolean} 是否有效
*/
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
/**
* 验证URL
* @param {string} url - URL
* @returns {boolean} 是否有效
*/
function validateUrl(url) {
try {
new URL(url)
return true
} catch {
return false
}
}
/**
* 获取文件扩展名
* @param {string} filename - 文件名
* @returns {string} 扩展名
*/
function getFileExtension(filename) {
return filename.split('.').pop().toLowerCase()
}
/**
* 格式化文件大小
* @param {number} bytes - 字节数
* @returns {string} 格式化后的文件大小
*/
function formatFileSize(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
/**
* 随机生成字符串
* @param {number} length - 字符串长度
* @returns {string} 随机字符串
*/
function randomString(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length))
}
return result
}
/**
* 首字母大写
* @param {string} str - 字符串
* @returns {string} 首字母大写的字符串
*/
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
/**
* 驼峰命名转下划线
* @param {string} str - 驼峰命名字符串
* @returns {string} 下划线命名字符串
*/
function camelToSnake(str) {
return str.replace(/([A-Z])/g, '_$1').toLowerCase()
}
/**
* 下划线命名转驼峰
* @param {string} str - 下划线命名字符串
* @returns {string} 驼峰命名字符串
*/
function snakeToCamel(str) {
return str.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase())
}
/**
* 数组去重
* @param {Array} arr - 数组
* @returns {Array} 去重后的数组
*/
function uniqueArray(arr) {
return [...new Set(arr)]
}
/**
* 数组分组
* @param {Array} arr - 数组
* @param {Function} keyFn - 分组键函数
* @returns {Object} 分组后的对象
*/
function groupBy(arr, keyFn) {
return arr.reduce((groups, item) => {
const key = keyFn(item)
if (!groups[key]) {
groups[key] = []
}
groups[key].push(item)
return groups
}, {})
}
/**
* 获取URL参数
* @param {string} url - URL字符串
* @returns {Object} URL参数对象
*/
function getUrlParams(url) {
const params = {}
const urlObj = new URL(url)
for (const [key, value] of urlObj.searchParams) {
params[key] = value
}
return params
}
/**
* 构建URL参数
* @param {Object} params - 参数对象
* @returns {string} URL参数字符串
*/
function buildUrlParams(params) {
return Object.keys(params)
.filter(key => params[key] !== null && params[key] !== undefined)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&')
}
module.exports = {
formatTime,
formatNumber,
formatRelativeTime,
debounce,
throttle,
deepClone,
isEmpty,
generateUniqueId,
validatePhone,
validateEmail,
validateUrl,
getFileExtension,
formatFileSize,
randomString,
capitalize,
camelToSnake,
snakeToCamel,
uniqueArray,
groupBy,
getUrlParams,
buildUrlParams
}