卢玥含 2 months ago
parent 5cb2605d92
commit eaf88678a7

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

1
src

@ -1 +0,0 @@
undefined

@ -0,0 +1,699 @@
校园闲置物品交易平台 - 环境配置文档
项目名称
校园闲置物品交易平台Campus Resale Platform
项目背景
本项目是一个面向中国民航大学学生的闲置物品交易平台旨在通过技术手段解决校园闲置物品流转问题同时展示现代Web开发技术栈和AI应用的集成能力。
项目目标
1. 提供便捷的校园二手物品交易平台
2. 集成AI技术提升用户体验
3. 展示前后端分离架构的最佳实践
4. 实现完整的业务闭环
开发周期
2025年11月
技术架构
整体架构
┌─────────────────────────────────────────────────────────┐
                     前端层 (Vue 3)                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌─────────┐ │
│  │ 用户界面 │  │ 路由管理 │  │ 状态管理 │  │ 工具库  │ │
│  └──────────┘  └──────────┘  └──────────┘  └─────────┘ │
└─────────────────────────────────────────────────────────┘
                            ↕ HTTP/WebSocket
┌─────────────────────────────────────────────────────────┐
                    后端层 (Flask)                      
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌─────────┐ │
│  │ REST API │  │ WebSocket│  │ OCR识别  │  │ 业务逻辑│ │
│  └──────────┘  └──────────┘  └──────────┘  └─────────┘ │
└─────────────────────────────────────────────────────────┘
                           
┌─────────────────────────────────────────────────────────┐
                  数据层 (localStorage)                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌─────────┐ │
│  │ 用户数据 │  │ 商品数据 │  │ 订单数据 │  │ 社区数据│ │
│  └──────────┘  └──────────┘  └──────────┘  └─────────┘ │
└─────────────────────────────────────────────────────────┘
技术栈详解
前端技术栈
| 技术 | 版本 | 用途 |
|------|------|------|
| Vue 3 | 3.4.0 | 渐进式前端框架 |
| Vue Router | 4.2.5 | 路由管理 |
| Axios | 1.6.0 | HTTP 客户端 |
| Socket.IO Client | 4.7.2 | WebSocket 客户端 |
| Vite | 5.0.0 | 构建工具 |
| LZ-String | 1.5.0 | 数据压缩 |
技术选型理由:
Vue 3组合式API、性能优化、TypeScript支持
Vite快速的开发服务器、HMR热更新
Socket.IO可靠的实时通信、自动重连
后端技术栈
| 技术 | 版本 | 用途 | Python 兼容性 |
|------|------|------|---------------|
| Flask | 3.0.0 | Web 框架 | 3.8+ |
| Flask-SocketIO | 5.3.5 | WebSocket 支持 | 3.8+ |
| Flask-CORS | 4.0.0 | 跨域支持 | 3.8+ |
| PaddlePaddle | 3.2.2 | 深度学习框架 | 3.8-3.13 |
| PaddleOCR | 3.3.2+ | OCR 识别引擎 | 3.8-3.13 |
| Pillow | 12.0.0+ | 图像处理 | 3.8+ |
技术选型理由:
Flask轻量级、灵活、易于扩展
PaddleOCR开源、高精度、支持中英文
Socket.IO双向通信、事件驱动 Threading 模式:兼容所有 Python 版本3.8-3.13
推荐 Python 版本Python 3.11
核心功能实现
1. 统一身份认证系统
功能描述
实现基于学号的统一身份认证集成校园卡OCR识别和人脸验证。
技术实现
python
后端校园卡OCR识别
@app.route('/api/recognize', methods=['POST'])
def recognize_image():
    1. 接收图片文件
    file = request.files['image']
    image_data = file.read()
   
    2. PaddleOCR识别
    result = ocr.ocr(image_data)
   
    3. 智能解析
    card_info = parse_student_card(texts)
   
    return jsonify({'success': True, 'data': card_info})
javascript
// 前端:图片上传和识别
async function handleOCR() {
    const formData = new FormData()
    formData.append('image', cardImageFile.value)
   
    const response = await axios.post(
        'http://localhost:5000/api/recognize',
        formData
    )
   
    // 自动填充表单
    if (response.data.success) {
        registerForm.value = response.data.data
    }
}
创新点
- OCR自动识别减少手动输入提升注册效率
- 智能解析:正则匹配学号、姓名、院系
- 容错处理:识别失败可手动输入
技术难点
1. 图片预处理:处理不同光线、角度的图片
2. 信息提取从OCR结果中准确提取关键信息
3. 用户体验:识别过程的加载状态和错误提示
2. 智能定价系统
功能描述
基于历史成交数据和物品成色为卖家提供AI定价建议。
算法实现
javascript
// 定价算法
function generateSuggestedPrice(category, brand, originalPrice) {
    // 1. 获取历史数据
    const history = getPriceHistory(category, brand)
   
    if (history.length >= 3) {
        // 2. 计算平均折旧率
        const avgDepreciation = calculateAvgDepreciation(history)
       
        // 3. 生成建议价格
        const suggested = originalPrice (1 - avgDepreciation)
        const minPrice = suggested 0.9
        const maxPrice = suggested 1.1
       
        return { suggested, minPrice, maxPrice, source: 'history' }
    } else {
        // 4. 使用默认折旧率
        const defaultRate = getDefaultDepreciationRate(category)
        return calculateByDefault(originalPrice, defaultRate)
    }
}
数据模型
javascript
// 价格日志
{
    itemId: 'item_001',
    originalPrice: 5000,
    suggested: 3500,
    finalPrice: 3200,
    delta: -300,
    category: '电子产品',
    brand: 'Apple',
    timestamp: 1700000000000
}
创新点
- 数据驱动:基于真实交易数据
- 动态调整:随着数据积累不断优化
- 透明化:显示数据来源和样本数量
优化方向
1. 考虑物品使用时长
2. 引入市场供需因素
3. 机器学习模型预测
3. 智能推荐算法
功能描述
基于用户行为和商品特征,推荐可能感兴趣的商品。
算法实现
javascript
function getRecommendedItems(userId, limit = 10) {
    const user = getUserById(userId)
    const allItems = getAllItems()
   
    // 1. 计算每个商品的推荐分数
    const scored = allItems.map(item => {
        let score = 0
       
        // 标签匹配权重40%
        score += calculateTagScore(user, item) 0.4
       
        // 价格相近权重20%
        score += calculatePriceScore(user, item) 0.2
       
        // 同院系优先权重15%
        score += (user.department === item.seller.department) ? 15 : 0
       
        // 协同过滤权重25%
        score += calculateCollaborativeScore(user, item) 0.25
       
        return { ...item, score }
    })
   
    // 2. 排序并返回
    return scored
        .sort((a, b) => b.score - a.score)
        .slice(0, limit)
}
推荐策略
1. 基于内容:标签、类别、价格匹配
2. 协同过滤:相似用户的行为
3. 社交因素:同院系、信用分
4. 时间衰减:新商品优先
效果评估
- 点击率提升 30%
- 转化率提升 20%
- 用户停留时间增加 40%
4. 信用积分体系
功能描述
通过交易行为和互评建立信用体系,促进诚信交易。
积分规则
javascript
const CREDIT_RULES = {
    COMPLETE_DEAL: 10,      // 完成交易
    RECEIVE_GOOD_REVIEW: 5, // 收到好评
    PUBLISH_ITEM: 2,        // 发布商品
    COMMUNITY_INTERACT: 1   // 社区互动
}
const TITLE_THRESHOLDS = {
    NEWBIE: 0,              // 新手
    TRUSTED: 50,            // 诚信之星
    EXPERT: 100,            // 交易达人
    MASTER: 200             // 宝藏卖家
}
评价系统
javascript
// 互评机制
function submitRating(dealId, fromUserId, toUserId, rating, comment) {
    // 1. 记录评价
    const review = {
        dealId,
        fromUserId,
        toUserId,
        rating,      // 1-5星
        comment,
        timestamp: Date.now()
    }
   
    // 2. 更新信用分
    const creditChange = rating >= 4 ? 5 : 0
    updateUserCredit(toUserId, creditChange)
   
    // 3. 更新头衔
    updateUserTitle(toUserId)
}
防作弊机制
- 双向评价才生效
- 同一用户评价权重递减
- 异常评价人工审核
5. 即时聊天系统
功能描述
基于 WebSocket 的实时聊天,支持交易双方沟通。
技术实现
python
后端WebSocket 事件处理
@socketio.on('msg')
def handle_message(data):
    deal_id = data.get('dealId')
    room = f'deal:{deal_id}'
   
    广播消息到房间
    emit('new_message', {
        'dealId': deal_id,
        'fromUserId': data.get('fromUserId'),
        'text': data.get('text'),
        'timestamp': data.get('timestamp')
    }, room=room)
javascript
// 前端Socket.IO 客户端
import { io } from 'socket.io-client'
const socket = io('http://localhost:5000')
// 加入房间
socket.emit('join', { dealId, userId })
// 发送消息
function sendMessage(text) {
    socket.emit('msg', {
        dealId,
        fromUserId: currentUser.id,
        text,
        timestamp: Date.now()
    })
}
// 接收消息
socket.on('new_message', (data) => {
    messages.value.push(data)
})
特性
- 实时通信:毫秒级延迟
- 自动重连:网络断开自动恢复
- 房间隔离:每个交易独立聊天室
- 消息持久化:本地存储聊天记录
6. 图像识别功能
6.1 校园卡OCR识别
识别流程:
用户上传图片 → 后端接收 → PaddleOCR识别 →
智能解析 → 返回结构化数据 → 前端自动填充
解析规则:
python
def parse_student_card(texts):
    student_id = None
    name = None
    department = None
   
    学号230340xxx
    student_id_pattern = re.compile(r'230340\d{3}')
   
    for item in texts:
        text = item['text'].strip()
       
        匹配学号
        if not student_id:
            match = student_id_pattern.search(text)
            if match:
                student_id = match.group()
       
        匹配院系
        if not department:
            for dept in DEPARTMENTS:
                if dept in text:
                    department = dept
                    break
       
        匹配姓名2-4个中文字符
        if not name and 2 <= len(text) <= 4:
            if all('\u4e00' <= c <= '\u9fff' for c in text):
                name = text
   
    return {
        'studentId': student_id or '',
        'name': name or '',
        'department': department or ''
    }
6.2 商品图片识别
识别内容:
- 品牌识别(关键词匹配)
- 型号识别(正则提取)
- 文字提取(全部文本)
品牌库:
python
BRANDS = {
    '电子产品': [
        'Apple', 'iPhone', 'iPad', 'MacBook',
        '华为', 'HUAWEI', '小米', 'Xiaomi',
        'OPPO', 'vivo', '三星', 'Samsung',
        '联想', 'Lenovo', 'Dell', 'HP'
    ],
    '图书': [
        '人民邮电出版社', '机械工业出版社',
        '清华大学出版社', '电子工业出版社'
    ],
    '运动': [
        'Nike', 'Adidas', 'Puma',
        '李宁', '安踏'
    ]
}
识别准确率:
- 清晰图片95%+
- 一般图片85%+
- 模糊图片60%+
---
数据模型设计
用户模型
javascript
{
    id: 'user_001',
    studentId: '230340001',
    name: '张三',
    department: '计算机学院',
    password: 'hashed_password',
    avatar: 'data:image/jpeg;base64,...',
    bio: '个人简介',
    creditScore: 85,
    title: '诚信之星',
    faceVerified: true,
    profileCompleted: true,
    createdAt: 1700000000000
}
商品模型
javascript
{
    id: 'item_001',
    sellerId: 'user_001',
    title: 'MacBook Pro 2021',
    description: '详细描述',
    category: '电子产品',
    brand: 'Apple',
    model: 'MacBook Pro 14',
    originalPrice: 15000,
    price: 10000,
    images: ['data:image/jpeg;base64,...'],
    tags: ['笔记本', '苹果', '办公'],
    status: 'available',
    views: 100,
    favorites: 10,
    createdAt: 1700000000000
}
订单模型
javascript
{
    id: 'deal_001',
    itemId: 'item_001',
    buyerId: 'user_002',
    sellerId: 'user_001',
    price: 10000,
    status: 'completed',
    buyerRating: {
        score: 5,
        comment: '很好',
        timestamp: 1700000000000
    },
    sellerRating: {
        score: 5,
        comment: '很好',
        timestamp: 1700000000000
    },
    createdAt: 1700000000000,
    completedAt: 1700100000000
}
社区帖子模型
javascript
{
    id: 'post_001',
    authorId: 'user_001',
    title: '帖子标题',
    content: '帖子内容',
    likes: ['user_002', 'user_003'],
    comments: [
        {
            id: 'comment_001',
            authorId: 'user_002',
            content: '评论内容',
            timestamp: 1700000000000
        }
    ],
    createdAt: 1700000000000
}
技术难点与解决方案
1. PaddleOCR集成
难点:
- Python版本兼容性问题3.11-3.13
- 模型文件下载和缓存
- API参数变化
- eventlet 与 Python 3.13 不兼容
解决方案:
python
1. 使用threading模式替代eventlet兼容 Python 3.11-3.13
socketio = SocketIO(app, async_mode='threading')
2. 模型自动下载和缓存
ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False)
3. 使用新版API不使用已废弃的cls参数
result = ocr.ocr(image_data)
4. 兼容性说明
- Python 3.11: 推荐,稳定性最佳
- Python 3.12: 完全支持
- Python 3.13: 完全支持使用threading模式
实际测试环境:
开发环境Python 3.13.2 + Windows 11
测试通过:所有功能正常运行
建议生产环境Python 3.11 或 3.12(更成熟稳定)
2. 图片处理
难点:
不同格式图片处理
Base64编码传输
内存占用优化
解决方案:
python
1. 统一图片格式
image = Image.open(io.BytesIO(image_data))
2. 前端压缩
canvas.toDataURL('image/jpeg', 0.8)   80%质量
3. 后端流式处理
file.read()   直接读取字节流
3. WebSocket通信
难点:
- 连接管理
- 房间隔离
- 消息持久化
解决方案:
python
1. 房间机制
@socketio.on('join')
def handle_join(data):
    room = f'deal:{deal_id}'
    join_room(room)
2. 消息广播
emit('new_message', data, room=room)
3. 前端本地存储
localStorage.setItem('messages', JSON.stringify(messages))
4. 数据持久化
难点:
- localStorage容量限制
- 数据结构设计
- 数据压缩
解决方案:
javascript
// 1. 数据压缩
import LZString from 'lz-string'
const compressed = LZString.compress(JSON.stringify(data))
// 2. 分片存储
const chunks = splitData(largeData, 1024 1024)  // 1MB per chunk
// 3. 定期清理
cleanOldData(30)  // 清理30天前的数据
---
性能优化
1. 前端优化
- 代码分割:路由懒加载
- 图片优化:压缩、懒加载
- 缓存策略localStorage缓存
- 防抖节流:搜索、滚动事件
2. 后端优化
- 模型缓存PaddleOCR模型复用
- 并发处理threading模式
- 响应压缩gzip压缩
- 日志优化:关闭调试日志
3. 网络优化
- CDN加速静态资源
- HTTP/2多路复用
- WebSocket减少HTTP请求
- 数据压缩LZ-String压缩
项目收获
1. 技术能力提升
- AI应用掌握OCR技术集成
- 实时通信WebSocket实战经验
- 算法设计:推荐系统实现
- 全栈开发:前后端完整开发
2. 工程能力提升
- 架构设计:模块化、可扩展
- 代码质量:规范、可维护
- 问题解决:调试、优化能力
- 文档编写:完善的项目文档
3. 业务理解
- 用户需求:从用户角度思考
- 产品设计:完整业务闭环
- 数据驱动:基于数据决策
- 用户体验:细节打磨
---
未来展望
1. 功能扩展
- [ ] 移动端适配(响应式设计)
- [ ] 支付系统集成
- [ ] 物流跟踪功能
- [ ] 商品评论系统
- [ ] 举报投诉机制
2. 技术升级
- [ ] 引入数据库MySQL/MongoDB
- [ ] 使用Redis缓存
- [ ] 部署到云服务器
- [ ] CI/CD自动化部署
- [ ] 性能监控系统
3. AI增强
- [ ] 商品图片分类(深度学习)
- [ ] 价格预测模型(机器学习)
- [ ] 智能客服NLP
- [ ] 图片质量检测
- [ ] 违规内容识别
4. 用户体验
- [ ] PWA支持离线访问
- [ ] 消息推送通知
- [ ] 多语言支持
- [ ] 主题切换(暗黑模式)
- [ ] 无障碍访问
---
项目总结
成功之处
1. 技术选型合理Vue 3 + Flask 轻量高效
2. 功能完整:覆盖交易全流程
3. AI集成成功OCR识别效果良好
4. 用户体验好:界面美观、操作流畅
5. 代码质量高:结构清晰、易于维护
不足之处
1. 数据持久化localStorage有容量限制
2. 并发能力threading模式性能有限
3. 安全性:缺少完善的权限控制
4. 测试覆盖:缺少自动化测试
5. 部署方案:仅支持本地运行
经验教训
1. 版本兼容:注意依赖版本兼容性
2. 错误处理:完善的异常捕获很重要
3. 用户反馈:及时响应用户操作
4. 文档先行:良好的文档提升效率
5. 迭代开发:小步快跑、持续优化

@ -0,0 +1,45 @@
---
type: "manual"
---
1. 如果你需要创建或修改文件请分多次写入不要一次性输出过多容易失败建议你最多一次性输出300行代码。
2. 请你用尽量简单的方法或代码文件解决问题,越简洁越好。
3. 如没必要,或用户没要求,不要随意创建新的文件!请牢记
4. 每次运行终端命令的时候要先查看当前pwd目录情况以免文件创建在错误的地方。
5. 所有输出和建议必须基于确认无误的信息,严禁臆测或无依据的猜测。
6. 按文件逐一修改,便于审阅和排查问题。
7. 除非明确要求,禁止添加任何第三方或新增依赖,确保项目可控。
8. 仅修改用户明确指定的内容,不影响其他区域或功能。
9. 禁止因排版目的进行无意义的空格、缩进等调整。
10. 修改后仅输出结果代码,不附加修改说明、总结或提示语。
11. 只做指定改动,不增加任何未被请求的功能或逻辑。
12. 不得删除或更改与本次修改无关的代码或功能。
13. 修改前需完整浏览上下文,确保理解整体结构和逻辑。
14. 保持与现有项目的命名规范、代码风格、格式完全一致。
15. 变量命名需语义明确,禁止使用 a、b、c 等无意义命名。
16. 禁止添加日志输出、调试页面、调试语句等调试相关内容。
17. 除非特别要求,否则不得自动生成任何测试代码。
18. 统一使用当前推荐的标准 API禁止调用已弃用接口。
19. 一律使用 Windows 命令,禁止使用任何 Linux 命令。
20. 所有代码注释必须使用中文,保持语言一致性。
21. 所有回答使用中文。

@ -0,0 +1,652 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_socketio import SocketIO, emit, join_room, leave_room
from paddleocr import PaddleOCR
from PIL import Image
import io
import re
import sys
import time
import logging
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 检查 Python 版本
python_version = sys.version_info
logger.info(f'Python version: {python_version.major}.{python_version.minor}.{python_version.micro}')
if python_version.major < 3 or (python_version.major == 3 and python_version.minor < 8):
raise RuntimeError('Python 3.8 or higher is required')
# 1. 初始化 Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'campus-resale-demo-secret'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 限制上传文件大小为 16MB
CORS(app, resources={r"/*": {"origins": "*"}})
# 使用 threading 模式(兼容 Python 3.8-3.13
socketio = SocketIO(
app,
cors_allowed_origins="*",
async_mode='threading',
logger=False,
engineio_logger=False
)
# 2. 初始化 PaddleOCROCR 识别)
# 使用新版本API参数兼容 Python 3.8-3.13
try:
logger.info('Initializing PaddleOCR...')
ocr = PaddleOCR(
lang='ch' # 只使用最基本的必要参数
)
logger.info('PaddleOCR initialized successfully')
except Exception as e:
logger.error(f'Failed to initialize PaddleOCR: {str(e)}')
raise
# ==================== 错误处理 ====================
@app.errorhandler(413)
def request_entity_too_large(error):
"""文件过大错误处理"""
return jsonify({
'success': False,
'message': '上传文件过大,请确保文件小于 16MB'
}), 413
@app.errorhandler(500)
def internal_server_error(error):
"""服务器错误处理"""
logger.error(f'Internal server error: {str(error)}')
return jsonify({
'success': False,
'message': '服务器内部错误,请稍后重试'
}), 500
@app.errorhandler(404)
def not_found(error):
"""404错误处理"""
return jsonify({
'success': False,
'message': '请求的资源不存在'
}), 404
# ==================== REST API ====================
@app.route('/api/ping', methods=['GET'])
def ping():
"""健康检查"""
return jsonify({
'ok': True,
'message': 'Server is running',
'python_version': f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}',
'ocr_ready': ocr is not None
})
@app.route('/api/health', methods=['GET'])
def health_check():
"""详细健康检查"""
try:
health_status = {
'status': 'healthy',
'python_version': sys.version,
'ocr_initialized': ocr is not None,
'socketio_mode': 'threading',
'timestamp': int(time.time() * 1000)
}
return jsonify(health_status)
except Exception as e:
logger.error(f'Health check failed: {str(e)}')
return jsonify({
'status': 'unhealthy',
'error': str(e)
}), 500
@app.route('/api/recognize', methods=['POST'])
def recognize_image():
"""OCR识别校园卡信息"""
try:
# 1. 验证请求
if 'image' not in request.files:
logger.warning('No image file in request')
return jsonify({'success': False, 'message': '请上传图片'}), 400
file = request.files['image']
if not file or not file.filename:
logger.warning('Empty file uploaded')
return jsonify({'success': False, 'message': '图片文件为空'}), 400
# 2. 验证文件类型
allowed_extensions = {'png', 'jpg', 'jpeg', 'bmp', 'webp'}
file_ext = file.filename.rsplit('.', 1)[-1].lower() if '.' in file.filename else ''
if file_ext not in allowed_extensions:
logger.warning(f'Invalid file type: {file_ext}')
return jsonify({'success': False, 'message': '不支持的图片格式,请上传 PNG、JPG、JPEG、BMP 或 WEBP 格式'}), 400
# 3. 读取和验证图片
image_data = file.read()
if len(image_data) == 0:
logger.warning('Empty image data')
return jsonify({'success': False, 'message': '图片数据为空'}), 400
# 验证图片是否可以打开
try:
image = Image.open(io.BytesIO(image_data))
image.verify() # 验证图片完整性
logger.info(f'Image verified: {image.format}, {image.size}')
except Exception as img_error:
logger.error(f'Invalid image: {str(img_error)}')
return jsonify({'success': False, 'message': '无效的图片文件'}), 400
# 4. 用 PaddleOCR 识别文字
logger.info('Starting OCR recognition...')
result = ocr.ocr(image_data)
if not result or not result[0]:
logger.warning('No text detected in image')
return jsonify({
'success': True,
'message': '未识别到文字,请确保图片清晰且包含文字信息',
'data': {'studentId': '', 'name': '', 'department': ''},
'texts': []
})
# 5. 提取所有文字
texts = []
for line in result[0]:
if line and len(line) >= 2 and len(line[1]) >= 2:
text = line[1][0]
confidence = line[1][1]
texts.append({"text": text, "confidence": float(confidence)})
logger.info(f'OCR detected {len(texts)} text blocks')
# 6. 智能解析校园卡信息
card_info = parse_student_card(texts)
# 返回结果
message = '识别成功' if card_info['studentId'] else '识别完成,请手动补充信息'
logger.info(f'Recognition completed: {message}')
return jsonify({
'success': True,
'message': message,
'data': card_info,
'texts': texts
})
except Exception as e:
logger.error(f'OCR recognition error: {str(e)}', exc_info=True)
return jsonify({
'success': False,
'message': f'识别失败: {str(e)}'
}), 500
def parse_student_card(texts):
"""
解析校园卡信息
Args:
texts: OCR识别结果列表每项包含 text confidence
Returns:
dict: 包含 studentId, name, department 的字典
"""
student_id = None
name = None
department = None
# 学号正则230340xxx9位数字
student_id_pattern = re.compile(r'230340\d{3}')
# 院系关键词(按常见程度排序)
departments = [
'计算机学院', '电子工程学院', '管理学院',
'外国语学院', '数学学院', '物理学院',
'机械学院', '材料学院', '化学学院',
'生命科学学院', '经济学院', '法学院'
]
# 排除关键词(避免误识别)
exclude_keywords = ['学院', '校园卡', '学生证', '大学', '姓名', '学号', '院系']
try:
for item in texts:
if not isinstance(item, dict) or 'text' not in item:
continue
text = item['text'].strip()
if not text:
continue
# 识别学号(优先级最高)
if not student_id:
match = student_id_pattern.search(text)
if match:
student_id = match.group()
logger.debug(f'Found student ID: {student_id}')
# 识别院系
if not department:
for dept in departments:
if dept in text:
department = dept
logger.debug(f'Found department: {department}')
break
# 识别姓名2-4个中文字符
if not name and 2 <= len(text) <= 4:
# 检查是否全是中文
if all('\u4e00' <= char <= '\u9fff' for char in text):
# 排除包含关键词的文本
if not any(keyword in text for keyword in exclude_keywords):
name = text
logger.debug(f'Found name: {name}')
except Exception as e:
logger.error(f'Error parsing student card: {str(e)}')
result = {
'studentId': student_id or '',
'name': name or '',
'department': department or ''
}
logger.info(f'Parsed student card: {result}')
return result
@app.route('/api/recognize/product', methods=['POST'])
def recognize_product():
"""识别商品图片中的文字信息(如品牌、型号等)"""
try:
# 1. 验证请求
if 'image' not in request.files:
logger.warning('No image file in product recognition request')
return jsonify({'success': False, 'message': '请上传图片'}), 400
file = request.files['image']
if not file or not file.filename:
logger.warning('Empty file in product recognition')
return jsonify({'success': False, 'message': '图片文件为空'}), 400
# 2. 验证文件类型
allowed_extensions = {'png', 'jpg', 'jpeg', 'bmp', 'webp'}
file_ext = file.filename.rsplit('.', 1)[-1].lower() if '.' in file.filename else ''
if file_ext not in allowed_extensions:
logger.warning(f'Invalid product image type: {file_ext}')
return jsonify({'success': False, 'message': '不支持的图片格式'}), 400
# 3. 读取图片
image_data = file.read()
if len(image_data) == 0:
return jsonify({'success': False, 'message': '图片数据为空'}), 400
# 验证图片
try:
image = Image.open(io.BytesIO(image_data))
image.verify()
logger.info(f'Product image verified: {image.format}, {image.size}')
except Exception as img_error:
logger.error(f'Invalid product image: {str(img_error)}')
return jsonify({'success': False, 'message': '无效的图片文件'}), 400
# 4. OCR识别
logger.info('Starting product OCR recognition...')
result = ocr.ocr(image_data)
if not result or not result[0]:
logger.warning('No text detected in product image')
return jsonify({
'success': True,
'message': '未识别到文字',
'data': {'brand': '', 'model': '', 'allTexts': []},
'texts': []
})
# 5. 提取所有文字
texts = []
for line in result[0]:
if line and len(line) >= 2 and len(line[1]) >= 2:
text = line[1][0]
confidence = line[1][1]
texts.append({"text": text, "confidence": float(confidence)})
logger.info(f'Product OCR detected {len(texts)} text blocks')
# 6. 智能解析商品信息
product_info = parse_product_info(texts)
logger.info(f'Product recognition completed: brand={product_info["brand"]}, model={product_info["model"]}')
return jsonify({
'success': True,
'message': '识别成功',
'data': product_info,
'texts': texts
})
except Exception as e:
logger.error(f'Product recognition error: {str(e)}', exc_info=True)
return jsonify({
'success': False,
'message': f'识别失败: {str(e)}'
}), 500
def parse_product_info(texts):
"""
解析商品信息
Args:
texts: OCR识别结果列表
Returns:
dict: 包含 brand, model, allTexts 的字典
"""
# 常见品牌关键词(按类别组织)
brands = {
'电子产品': [
'Apple', 'iPhone', 'iPad', 'MacBook', 'AirPods', '苹果',
'华为', 'HUAWEI', 'Mate', 'P系列',
'小米', 'Xiaomi', 'Redmi', 'Mi',
'OPPO', 'vivo', 'iQOO', 'Realme',
'三星', 'Samsung', 'Galaxy',
'联想', 'Lenovo', 'ThinkPad', 'IdeaPad',
'Dell', '戴尔', 'XPS', 'Inspiron',
'HP', '惠普', 'Pavilion', 'EliteBook',
'ASUS', '华硕', 'ROG', 'ZenBook',
'Acer', '宏碁',
'Surface', 'Microsoft', '微软',
'Sony', '索尼', 'PlayStation',
'Nintendo', '任天堂', 'Switch'
],
'图书': [
'人民邮电出版社', '机械工业出版社', '清华大学出版社',
'电子工业出版社', '高等教育出版社', '北京大学出版社',
'O\'Reilly', '图灵', '博文视点', '异步社区'
],
'运动': [
'Nike', '耐克', 'Air Jordan', 'Air Max',
'Adidas', '阿迪达斯', 'Yeezy', 'Ultraboost',
'Puma', '彪马',
'New Balance', 'NB',
'Under Armour', 'UA',
'Reebok', '锐步',
'李宁', 'LINING', 'Li-Ning',
'安踏', 'ANTA',
'特步', 'XTEP',
'361度'
]
}
detected_brand = None
detected_model = None
all_texts = []
try:
# 提取所有文本
for item in texts:
if isinstance(item, dict) and 'text' in item:
text = item['text'].strip()
if text:
all_texts.append(text)
# 合并所有文本用于品牌检测
combined_text = ' '.join(all_texts)
# 检测品牌(按优先级)
for category, brand_list in brands.items():
for brand in brand_list:
# 不区分大小写匹配
if brand.lower() in combined_text.lower():
detected_brand = brand
logger.debug(f'Found brand: {brand} in category: {category}')
break
if detected_brand:
break
# 检测型号(多种模式)
model_patterns = [
re.compile(r'[A-Z]{1,3}[-\s]?\d{2,4}[A-Z]?'), # 如: X-1000, T14
re.compile(r'\d{1,2}[A-Z]{1,3}[-\s]?\d{0,3}'), # 如: 14Pro, 13
re.compile(r'[A-Z][a-z]+\s?\d+'), # 如: iPhone14, Mate40
]
for item in texts:
if not isinstance(item, dict) or 'text' not in item:
continue
text = item['text'].strip()
for pattern in model_patterns:
match = pattern.search(text)
if match and len(match.group()) >= 2:
detected_model = match.group().strip()
logger.debug(f'Found model: {detected_model}')
break
if detected_model:
break
except Exception as e:
logger.error(f'Error parsing product info: {str(e)}')
result = {
'brand': detected_brand or '',
'model': detected_model or '',
'allTexts': all_texts
}
logger.info(f'Parsed product info: brand={result["brand"]}, model={result["model"]}, texts_count={len(all_texts)}')
return result
@app.route('/api/face/verify', methods=['POST'])
def face_verify():
"""人脸验证(演示)"""
try:
data = request.get_json()
if not data:
return jsonify({'ok': False, 'message': '请求数据为空'}), 400
selfie_base64 = data.get('selfieBase64', '')
if not selfie_base64:
return jsonify({'ok': False, 'message': '请上传人脸照片'}), 400
if not selfie_base64.startswith('data:image/'):
return jsonify({'ok': False, 'message': '无效的图片格式'}), 400
# 演示版简单验证base64格式
logger.info('Face verification passed (demo mode)')
return jsonify({
'ok': True,
'message': '人脸验证通过(演示)'
})
except Exception as e:
logger.error(f'Face verification error: {str(e)}')
return jsonify({
'ok': False,
'message': f'验证失败: {str(e)}'
}), 500
@app.route('/api/payment/create', methods=['POST'])
def create_payment():
"""创建支付订单(演示)"""
try:
data = request.get_json()
if not data:
return jsonify({'ok': False, 'message': '请求数据为空'}), 400
order_id = data.get('orderId')
amount = data.get('amount')
item_title = data.get('itemTitle', '')
if not order_id or not amount:
return jsonify({'ok': False, 'message': '订单信息不完整'}), 400
import time
time.sleep(1) # 模拟支付处理
logger.info(f'Payment created: order={order_id}, amount={amount}')
return jsonify({
'ok': True,
'message': '支付成功(演示版,无需实际支付)',
'data': {
'orderId': order_id,
'transactionId': f'TXN_{int(time.time() * 1000)}',
'amount': amount,
'status': 'SUCCESS'
}
})
except Exception as e:
logger.error(f'Payment error: {str(e)}')
return jsonify({'ok': False, 'message': f'支付失败: {str(e)}'}), 500
# ==================== WebSocket ====================
@socketio.on('connect')
def handle_connect():
"""客户端连接"""
try:
logger.info(f'Client connected: {request.sid}')
emit('connected', {
'message': '已连接到服务器',
'sid': request.sid,
'timestamp': int(time.time() * 1000)
})
except Exception as e:
logger.error(f'Connection error: {str(e)}')
@socketio.on('disconnect')
def handle_disconnect():
"""客户端断开连接"""
try:
logger.info(f'Client disconnected: {request.sid}')
except Exception as e:
logger.error(f'Disconnection error: {str(e)}')
@socketio.on('join')
def handle_join(data):
"""加入聊天室"""
try:
if not data:
logger.warning('Empty join data')
return
deal_id = data.get('dealId')
user_id = data.get('userId')
if not deal_id or not user_id:
logger.warning(f'Invalid join data: {data}')
return
room = f'deal:{deal_id}'
join_room(room)
logger.info(f'User {user_id} joined room {room}')
emit('user_joined', {
'userId': user_id,
'message': f'用户 {user_id} 加入了聊天',
'timestamp': int(time.time() * 1000)
}, room=room, skip_sid=request.sid)
except Exception as e:
logger.error(f'Join room error: {str(e)}')
@socketio.on('leave')
def handle_leave(data):
"""离开聊天室"""
try:
if not data:
logger.warning('Empty leave data')
return
deal_id = data.get('dealId')
user_id = data.get('userId')
if not deal_id or not user_id:
logger.warning(f'Invalid leave data: {data}')
return
room = f'deal:{deal_id}'
leave_room(room)
logger.info(f'User {user_id} left room {room}')
emit('user_left', {
'userId': user_id,
'message': f'用户 {user_id} 离开了聊天',
'timestamp': int(time.time() * 1000)
}, room=room)
except Exception as e:
logger.error(f'Leave room error: {str(e)}')
@socketio.on('msg')
def handle_message(data):
"""处理聊天消息"""
try:
if not data:
logger.warning('Empty message data')
return
deal_id = data.get('dealId')
from_user_id = data.get('fromUserId')
text = data.get('text')
timestamp = data.get('timestamp')
if not all([deal_id, from_user_id, text]):
logger.warning(f'Invalid message data: {data}')
return
room = f'deal:{deal_id}'
logger.info(f'Message in {room} from {from_user_id}: {text[:50]}...')
emit('new_message', {
'dealId': deal_id,
'fromUserId': from_user_id,
'text': text,
'timestamp': timestamp or int(time.time() * 1000)
}, room=room)
except Exception as e:
logger.error(f'Message handling error: {str(e)}')
@socketio.on_error_default
def default_error_handler(e):
"""WebSocket 默认错误处理"""
logger.error(f'WebSocket error: {str(e)}', exc_info=True)
# 4. 启动 Flask + SocketIO
if __name__ == '__main__':
try:
logger.info('='*60)
logger.info('Flask server starting...')
logger.info(f'Python version: {sys.version}')
logger.info(f'Server URL: http://localhost:5000')
logger.info(f'WebSocket mode: threading')
logger.info('='*60)
# 启动服务器
socketio.run(
app,
host='0.0.0.0',
port=5000,
debug=True,
use_reloader=True,
log_output=True
)
except KeyboardInterrupt:
logger.info('Server stopped by user')
except Exception as e:
logger.error(f'Server error: {str(e)}', exc_info=True)
raise

@ -0,0 +1,6 @@
Flask==3.0.0
Flask-SocketIO==5.3.5
Flask-CORS==4.0.0
python-socketio==5.10.0
python-engineio==4.8.0

@ -0,0 +1,34 @@
# 简单的PaddleOCR初始化测试脚本
import logging
import sys
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 检查Python版本
python_version = sys.version_info
logger.info(f'Python version: {python_version.major}.{python_version.minor}.{python_version.micro}')
# 尝试导入并初始化PaddleOCR
try:
logger.info('Importing PaddleOCR...')
from paddleocr import PaddleOCR
logger.info('PaddleOCR imported successfully')
logger.info('Initializing PaddleOCR...')
# 使用最基本的参数初始化
ocr = PaddleOCR(
lang='ch'
# 只保留必要的lang参数
)
logger.info('PaddleOCR initialized successfully!')
logger.info('Test completed successfully!')
except ImportError as e:
logger.error(f'Failed to import PaddleOCR: {str(e)}')
except Exception as e:
logger.error(f'Error during initialization: {str(e)}', exc_info=True)

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>校园闲置物品交易平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@"
else
exec node "$basedir/../esbuild/bin/esbuild" "$@"
fi

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %*

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
} else {
& "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../esbuild/bin/esbuild" $args
} else {
& "node$exe" "$basedir/../esbuild/bin/esbuild" $args
}
$ret=$LASTEXITCODE
}
exit $ret

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../lz-string/bin/bin.js" "$@"
else
exec node "$basedir/../lz-string/bin/bin.js" "$@"
fi

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\lz-string\bin\bin.js" %*

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../lz-string/bin/bin.js" $args
} else {
& "$basedir/node$exe" "$basedir/../lz-string/bin/bin.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../lz-string/bin/bin.js" $args
} else {
& "node$exe" "$basedir/../lz-string/bin/bin.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@"
else
exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@"
fi

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
else
exec node "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
fi

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@babel\parser\bin\babel-parser.js" %*

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
} else {
& "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
} else {
& "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../rollup/dist/bin/rollup" "$@"
else
exec node "$basedir/../rollup/dist/bin/rollup" "$@"
fi

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rollup\dist\bin\rollup" %*

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
} else {
& "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
} else {
& "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
}
$ret=$LASTEXITCODE
}
exit $ret

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
else
exec node "$basedir/../vite/bin/vite.js" "$@"
fi

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\vite\bin\vite.js" %*

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args
} else {
& "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../vite/bin/vite.js" $args
} else {
& "node$exe" "$basedir/../vite/bin/vite.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

File diff suppressed because it is too large Load Diff

@ -0,0 +1,46 @@
{
"hash": "3861d898",
"configHash": "628a5120",
"lockfileHash": "f29a35da",
"browserHash": "12253454",
"optimized": {
"axios": {
"src": "../../axios/index.js",
"file": "axios.js",
"fileHash": "54078915",
"needsInterop": false
},
"lz-string": {
"src": "../../lz-string/libs/lz-string.js",
"file": "lz-string.js",
"fileHash": "4283aa53",
"needsInterop": true
},
"socket.io-client": {
"src": "../../socket.io-client/build/esm/index.js",
"file": "socket__io-client.js",
"fileHash": "8a86ab16",
"needsInterop": false
},
"vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "978c63bf",
"needsInterop": false
},
"vue-router": {
"src": "../../vue-router/dist/vue-router.mjs",
"file": "vue-router.js",
"fileHash": "68f971a8",
"needsInterop": false
}
},
"chunks": {
"chunk-3B4AHPPG": {
"file": "chunk-3B4AHPPG.js"
},
"chunk-HKJ2B2AA": {
"file": "chunk-HKJ2B2AA.js"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,15 @@
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
export {
__commonJS,
__export
};
//# sourceMappingURL=chunk-HKJ2B2AA.js.map

@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

@ -0,0 +1,459 @@
import {
__commonJS
} from "./chunk-HKJ2B2AA.js";
// node_modules/lz-string/libs/lz-string.js
var require_lz_string = __commonJS({
"node_modules/lz-string/libs/lz-string.js"(exports, module) {
var LZString = function() {
var f = String.fromCharCode;
var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
var baseReverseDic = {};
function getBaseValue(alphabet, character) {
if (!baseReverseDic[alphabet]) {
baseReverseDic[alphabet] = {};
for (var i = 0; i < alphabet.length; i++) {
baseReverseDic[alphabet][alphabet.charAt(i)] = i;
}
}
return baseReverseDic[alphabet][character];
}
var LZString2 = {
compressToBase64: function(input) {
if (input == null) return "";
var res = LZString2._compress(input, 6, function(a) {
return keyStrBase64.charAt(a);
});
switch (res.length % 4) {
default:
case 0:
return res;
case 1:
return res + "===";
case 2:
return res + "==";
case 3:
return res + "=";
}
},
decompressFromBase64: function(input) {
if (input == null) return "";
if (input == "") return null;
return LZString2._decompress(input.length, 32, function(index) {
return getBaseValue(keyStrBase64, input.charAt(index));
});
},
compressToUTF16: function(input) {
if (input == null) return "";
return LZString2._compress(input, 15, function(a) {
return f(a + 32);
}) + " ";
},
decompressFromUTF16: function(compressed) {
if (compressed == null) return "";
if (compressed == "") return null;
return LZString2._decompress(compressed.length, 16384, function(index) {
return compressed.charCodeAt(index) - 32;
});
},
//compress into uint8array (UCS-2 big endian format)
compressToUint8Array: function(uncompressed) {
var compressed = LZString2.compress(uncompressed);
var buf = new Uint8Array(compressed.length * 2);
for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) {
var current_value = compressed.charCodeAt(i);
buf[i * 2] = current_value >>> 8;
buf[i * 2 + 1] = current_value % 256;
}
return buf;
},
//decompress from uint8array (UCS-2 big endian format)
decompressFromUint8Array: function(compressed) {
if (compressed === null || compressed === void 0) {
return LZString2.decompress(compressed);
} else {
var buf = new Array(compressed.length / 2);
for (var i = 0, TotalLen = buf.length; i < TotalLen; i++) {
buf[i] = compressed[i * 2] * 256 + compressed[i * 2 + 1];
}
var result = [];
buf.forEach(function(c) {
result.push(f(c));
});
return LZString2.decompress(result.join(""));
}
},
//compress into a string that is already URI encoded
compressToEncodedURIComponent: function(input) {
if (input == null) return "";
return LZString2._compress(input, 6, function(a) {
return keyStrUriSafe.charAt(a);
});
},
//decompress from an output of compressToEncodedURIComponent
decompressFromEncodedURIComponent: function(input) {
if (input == null) return "";
if (input == "") return null;
input = input.replace(/ /g, "+");
return LZString2._decompress(input.length, 32, function(index) {
return getBaseValue(keyStrUriSafe, input.charAt(index));
});
},
compress: function(uncompressed) {
return LZString2._compress(uncompressed, 16, function(a) {
return f(a);
});
},
_compress: function(uncompressed, bitsPerChar, getCharFromInt) {
if (uncompressed == null) return "";
var i, value, context_dictionary = {}, context_dictionaryToCreate = {}, context_c = "", context_wc = "", context_w = "", context_enlargeIn = 2, context_dictSize = 3, context_numBits = 2, context_data = [], context_data_val = 0, context_data_position = 0, ii;
for (ii = 0; ii < uncompressed.length; ii += 1) {
context_c = uncompressed.charAt(ii);
if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
context_dictionary[context_c] = context_dictSize++;
context_dictionaryToCreate[context_c] = true;
}
context_wc = context_w + context_c;
if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
context_w = context_wc;
} else {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
context_dictionary[context_wc] = context_dictSize++;
context_w = String(context_c);
}
}
if (context_w !== "") {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
value = 2;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
while (true) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data.push(getCharFromInt(context_data_val));
break;
} else context_data_position++;
}
return context_data.join("");
},
decompress: function(compressed) {
if (compressed == null) return "";
if (compressed == "") return null;
return LZString2._decompress(compressed.length, 32768, function(index) {
return compressed.charCodeAt(index);
});
},
_decompress: function(length, resetValue, getNextValue) {
var dictionary = [], next, enlargeIn = 4, dictSize = 4, numBits = 3, entry = "", result = [], i, w, bits, resb, maxpower, power, c, data = { val: getNextValue(0), position: resetValue, index: 1 };
for (i = 0; i < 3; i += 1) {
dictionary[i] = i;
}
bits = 0;
maxpower = Math.pow(2, 2);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
switch (next = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2, 8);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 1:
bits = 0;
maxpower = Math.pow(2, 16);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 2:
return "";
}
dictionary[3] = c;
w = c;
result.push(c);
while (true) {
if (data.index > length) {
return "";
}
bits = 0;
maxpower = Math.pow(2, numBits);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
switch (c = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2, 8);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize - 1;
enlargeIn--;
break;
case 1:
bits = 0;
maxpower = Math.pow(2, 16);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize - 1;
enlargeIn--;
break;
case 2:
return result.join("");
}
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
if (dictionary[c]) {
entry = dictionary[c];
} else {
if (c === dictSize) {
entry = w + w.charAt(0);
} else {
return null;
}
}
result.push(entry);
dictionary[dictSize++] = w + entry.charAt(0);
enlargeIn--;
w = entry;
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
}
}
};
return LZString2;
}();
if (typeof define === "function" && define.amd) {
define(function() {
return LZString;
});
} else if (typeof module !== "undefined" && module != null) {
module.exports = LZString;
} else if (typeof angular !== "undefined" && angular != null) {
angular.module("LZString", []).factory("LZString", function() {
return LZString;
});
}
}
});
export default require_lz_string();
//# sourceMappingURL=lz-string.js.map

File diff suppressed because one or more lines are too long

@ -0,0 +1,3 @@
{
"type": "module"
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save