diff --git a/.env.prod b/.env.prod
new file mode 100644
index 0000000..995fca4
--- /dev/null
+++ b/.env.prod
@@ -0,0 +1 @@
+NODE_ENV=production
\ No newline at end of file
diff --git a/backend/Dockerfile b/backend/Dockerfile
deleted file mode 100644
index 73e3a7f..0000000
--- a/backend/Dockerfile
+++ /dev/null
@@ -1,29 +0,0 @@
-FROM python:3.10-slim
-
-WORKDIR /app
-
-# 安装系统依赖
-RUN apt-get update && apt-get install -y \
- gcc \
- sqlite3 \
- && rm -rf /var/lib/apt/lists/*
-
-# 复制并安装Python依赖
-COPY requirements.txt .
-RUN pip install --no-cache-dir -r requirements.txt
-
-# 复制应用代码
-COPY . .
-
-# 创建数据目录并设置权限
-RUN mkdir -p data && chmod 777 data
-RUN mkdir -p static/images && chmod 777 static/images
-
-# 初始化数据库
-RUN python init_db.py
-
-# 暴露端口
-EXPOSE 48996
-
-# 启动应用
-CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "48996"]
\ No newline at end of file
diff --git a/backend/LAB_DATA_UPDATE_GUIDE.md b/backend/LAB_DATA_UPDATE_GUIDE.md
deleted file mode 100644
index b3f6a3e..0000000
--- a/backend/LAB_DATA_UPDATE_GUIDE.md
+++ /dev/null
@@ -1,292 +0,0 @@
-# 工程研究中心数据更新指南
-
-## 概述
-
-本文档详细说明如何将 `src/assets/实验室.json` 中的工程研究中心数据导入到数据库中。当有新的工程研究中心数据或者数据需要更新时,请按照本指南进行操作。
-
-## 文件说明
-
-### 主要文件
-- **数据源文件**: `src/assets/实验室.json` - 包含所有工程研究中心的多年度数据
-- **导入脚本**: `backend/import_lab_data_full.py` - 完整的数据导入脚本
-- **JSON修复脚本**: `backend/fix_json_format.py` - 修复JSON格式问题
-- **数据类型修复脚本**: `backend/fix_year_data_types.py` - 修复年份字段类型问题
-- **数据检查脚本**: `backend/check_specific_lab.py` - 检查特定工程研究中心数据
-- **数据库模型**: `backend/models.py` - 定义Lab表结构
-- **本文档**: `backend/LAB_DATA_UPDATE_GUIDE.md` - 操作指南
-
-### 数据结构
-JSON文件包含工程研究中心数组,每个工程研究中心包含:
-```json
-{
- "中心名称": "工程研究中心名称",
- "中心编号": "工程研究中心编号",
- "年度数据": [
- {
- "归属年份": "2024",
- "所属学校": "学校名称",
- "主管部门": "部门名称",
- ... // 详细的年度数据
- }
- ]
-}
-```
-
-## 数据库字段映射
-
-### 基本信息字段
-| JSON字段 | 数据库字段 | 类型 | 说明 |
-|---------|-----------|------|------|
-| 中心名称 | name | String | 工程研究中心名称 |
-| 中心编号 | center_number | String | 工程研究中心编号 |
-| 所属学校 | school | String | 所属学校 |
-| 主管部门 | department | String | 主管部门 |
-| 所属领域 | field | String | 所属领域 |
-| 归属年份 | current_year | String | 当前评估年份 |
-
-### 详细信息字段
-| JSON字段 | 数据库字段 | 类型 | 说明 |
-|---------|-----------|------|------|
-| 技术攻关与创新情况 | innovation_situation | Text | 技术创新描述 |
-| 1.总体情况 | overall_situation | Text | 总体情况描述 |
-| 2.工程化案例 | engineering_cases | Text | 工程化案例 |
-| 3.行业服务情况 | industry_service | Text | 行业服务情况 |
-| 1.学科发展支撑情况 | discipline_support | Text | 学科发展支撑 |
-| 2.人才培养情况 | talent_cultivation | Text | 人才培养情况 |
-| 3.研究队伍建设情况 | team_building | Text | 队伍建设情况 |
-
-### 统计数据字段
-| JSON字段 | 数据库字段 | 类型 | 说明 |
-|---------|-----------|------|------|
-| 国家级科技奖励一等奖(项) | national_awards_first | Integer | 国家一等奖数量 |
-| 国家级科技奖励二等奖(项) | national_awards_second | Integer | 国家二等奖数量 |
-| 省、部级科技奖励一等奖(项) | provincial_awards_first | Integer | 省部一等奖数量 |
-| 省、部级科技奖励二等奖(项) | provincial_awards_second | Integer | 省部二等奖数量 |
-| 有效专利(项) | valid_patents | Integer | 有效专利数量 |
-| 在读博士生 | doctoral_students | Integer | 博士生数量 |
-| 在读硕士生 | master_students | Integer | 硕士生数量 |
-| 固定人员(人) | fixed_personnel | Integer | 固定人员数量 |
-| 流动人员(人) | mobile_personnel | Integer | 流动人员数量 |
-| 当年项目到账总经费(万元) | total_funding | Float | 总经费 |
-
-### 特殊字段
-| 字段 | 说明 |
-|-----|------|
-| annual_data | JSON格式存储所有年度数据,包含多年完整信息 |
-| id | 自动生成的UUID,作为主键 |
-| idcode | 使用中心编号作为显示ID |
-
-## 操作步骤
-
-### 1. 准备工作
-
-确保以下条件满足:
-- [x] 后端服务已停止运行
-- [x] 数据库文件 `backend/data/app.db` 存在
-- [x] Python环境已激活 (`conda activate fast-dashboard-env`)
-- [x] JSON数据文件 `src/assets/实验室.json` 已更新
-
-### 2. 环境准备
-
-在命令行中执行:
-```powershell
-# 激活conda环境
-conda activate fast-dashboard-env
-
-# 进入后端目录
-cd backend
-```
-
-### 3. 执行数据导入
-
-运行导入脚本:
-```powershell
-python import_lab_data_full.py
-```
-
-### 4. 导入过程说明
-
-脚本会执行以下操作:
-
-1. **数据验证**:检查JSON文件是否存在
-2. **数据读取**:解析JSON文件内容
-3. **数据处理**:
- - 遍历每个工程研究中心
- - 检查是否已存在(根据中心编号或名称)
- - 如果存在则更新,否则创建新记录
- - 处理多年度数据,提取最新年份作为当前数据
- - 安全转换数据类型(整数、浮点数、字符串)
-4. **数据库操作**:
- - 添加新记录或更新现有记录
- - 提交事务
- - 显示统计信息
-
-### 5. 输出信息解读
-
-脚本运行时会显示:
-- 📖 正在读取数据文件
-- ✅ 成功读取数据,共 X 个工程研究中心
-- 🔄 正在处理工程研究中心: XXX (编号: XXX)
-- ➕ 创建新工程研究中心 / 📝 工程研究中心已存在,更新数据
-- ✅ 年度数据: X 年, 最新年份: XXXX
-- 💾 正在保存到数据库
-- 📊 统计信息
-
-### 6. 验证导入结果
-
-导入完成后,可以通过以下方式验证:
-
-1. **启动后端服务**:
- ```powershell
- uvicorn main:app --reload
- ```
-
-2. **访问API接口**:
- ```
- GET http://localhost:8000/labs/
- ```
-
-3. **查看前端页面**:
- 打开前端应用,查看工程研究中心列表和详情页
-
-## 常见问题及解决方案
-
-### 1. JSON格式错误
-**问题**: JSON文件中包含Python的`None`值,导致解析失败
-**解决方案**:
-```bash
-python fix_json_format.py
-```
-
-### 2. 年份字段类型错误
-**问题**: 前端报错 `TypeError: b.year.localeCompare is not a function`
-**原因**: 年度数据中的`归属年份`字段是数字类型,前端期望字符串类型
-**解决方案**:
-```bash
-python fix_year_data_types.py
-```
-
-### 3. 检查特定工程研究中心数据
-**用途**: 当某个工程研究中心出现问题时,可以单独检查其数据格式
-**使用方法**:
-```bash
-python check_specific_lab.py
-```
-修改脚本中的工程研究中心名称来检查不同工程研究中心。
-
-## 故障排除
-
-### 常见错误及解决方案
-
-#### 1. 文件不存在错误
-```
-❌ 错误:找不到数据文件
-```
-**解决方案**:确认 `src/assets/实验室.json` 文件存在且路径正确
-
-#### 2. 数据库连接错误
-```
-❌ 数据库操作失败
-```
-**解决方案**:
-- 确认数据库文件 `backend/data/app.db` 存在
-- 确认没有其他进程占用数据库
-- 确认有足够的磁盘空间
-
-#### 3. JSON格式错误
-```
-❌ 导入失败: JSON decode error
-```
-**解决方案**:
-- 使用JSON验证工具检查文件格式
-- 确认文件编码为UTF-8
-- 检查是否有多余的逗号或括号
-
-#### 4. 数据类型转换错误
-```
-❌ 处理工程研究中心 XXX 时出错
-```
-**解决方案**:
-- 检查JSON中数值字段是否包含非数字字符
-- 脚本有safe_int和safe_float函数来处理大部分类型错误
-- 如果持续出错,可以手动检查该工程研究中心的数据
-
-### 数据一致性检查
-
-导入后建议进行以下检查:
-
-1. **数量检查**:
- ```sql
- SELECT COUNT(*) FROM labs;
- ```
-
-2. **年度数据检查**:
- ```sql
- SELECT name, current_year, json_length(annual_data) as year_count
- FROM labs
- WHERE annual_data IS NOT NULL;
- ```
-
-3. **统计数据检查**:
- ```sql
- SELECT name, valid_patents, doctoral_students, total_funding
- FROM labs
- ORDER BY total_funding DESC;
- ```
-
-## 数据更新策略
-
-### 完全重新导入
-如果数据变化很大,建议:
-1. 备份现有数据库
-2. 清空labs表
-3. 重新导入所有数据
-
-### 增量更新
-如果只是部分数据更新:
-1. 脚本会自动检测已存在的工程研究中心
-2. 根据中心编号或名称匹配
-3. 更新现有记录的数据
-
-### 数据备份
-在大规模更新前,建议备份:
-```powershell
-copy backend\data\app.db backend\data\app_backup_$(Get-Date -Format "yyyyMMdd_HHmmss").db
-```
-
-## 性能优化
-
-### 大量数据处理
-如果数据量很大(>100个工程研究中心):
-1. 考虑分批处理
-2. 添加进度条显示
-3. 使用批量插入操作
-
-### 内存优化
-- 避免一次性加载所有数据到内存
-- 使用流式处理方式
-- 及时释放不需要的对象
-
-## 维护建议
-
-### 定期任务
-1. **每月检查**:验证数据一致性
-2. **每季度备份**:完整备份数据库
-3. **每年更新**:根据新的数据字段要求更新脚本
-
-### 版本控制
-- 对导入脚本进行版本控制
-- 记录每次数据更新的变更日志
-- 保留历史数据备份
-
-## 联系信息
-
-如有问题或需要技术支持,请联系:
-- 开发团队:AI助手
-- 文档更新:每次数据模型变更时同步更新
-
----
-
-**最后更新时间**:2024年度
-**文档版本**:v1.0
-**适用环境**:Windows + Python + FastAPI + SQLite
\ No newline at end of file
diff --git a/backend/alter_table.py b/backend/alter_table.py
deleted file mode 100644
index 77b8e88..0000000
--- a/backend/alter_table.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import sqlite3
-import os
-import logging
-import json
-
-# 设置日志
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-# 数据库路径
-DB_DIR = "data"
-DB_PATH = os.path.join(DB_DIR, "app.db")
-
-def check_and_alter_table():
- # 检查数据库文件是否存在
- if not os.path.exists(DB_PATH):
- logger.error(f"数据库文件 {DB_PATH} 不存在")
- return
-
- conn = sqlite3.connect(DB_PATH)
- cursor = conn.cursor()
-
- # 检查talents表是否存在idcode字段
- cursor.execute("PRAGMA table_info(talents)")
- columns = [column[1] for column in cursor.fetchall()]
-
- if 'idcode' not in columns:
- logger.info("talents表中添加idcode字段")
- cursor.execute("ALTER TABLE talents ADD COLUMN idcode TEXT")
- conn.commit()
- else:
- logger.info("talents表已包含idcode字段,无需修改")
-
- # 检查talents表是否存在educationBackground字段
- if 'educationBackground' not in columns:
- logger.info("talents表中添加educationBackground字段")
- cursor.execute("ALTER TABLE talents ADD COLUMN educationBackground TEXT")
- conn.commit()
- else:
- logger.info("talents表已包含educationBackground字段,无需修改")
-
- # 检查labs表是否存在idcode字段
- cursor.execute("PRAGMA table_info(labs)")
- labs_columns = [column[1] for column in cursor.fetchall()]
-
- if 'idcode' not in labs_columns:
- logger.info("labs表中添加idcode字段")
- cursor.execute("ALTER TABLE labs ADD COLUMN idcode TEXT")
- conn.commit()
- else:
- logger.info("labs表已包含idcode字段,无需修改")
-
- # 检查dimensions表是否存在sub_dimensions字段
- cursor.execute("PRAGMA table_info(dimensions)")
- columns = [column[1] for column in cursor.fetchall()]
-
- if 'sub_dimensions' not in columns:
- logger.info("dimensions表中添加sub_dimensions字段")
- cursor.execute("ALTER TABLE dimensions ADD COLUMN sub_dimensions JSON")
- conn.commit()
- else:
- logger.info("dimensions表已包含sub_dimensions字段,无需修改")
-
- # 检查dimensions表是否存在parent_id字段
- if 'parent_id' not in columns:
- logger.info("dimensions表中添加parent_id字段")
- cursor.execute("ALTER TABLE dimensions ADD COLUMN parent_id INTEGER REFERENCES dimensions(id)")
- conn.commit()
- else:
- logger.info("dimensions表已包含parent_id字段,无需修改")
-
- # 检查labs表是否存在sub_dimension_evaluations字段
- if 'sub_dimension_evaluations' not in labs_columns:
- logger.info("labs表中添加sub_dimension_evaluations字段")
- cursor.execute("ALTER TABLE labs ADD COLUMN sub_dimension_evaluations TEXT")
- conn.commit()
- else:
- logger.info("labs表已包含sub_dimension_evaluations字段,无需修改")
-
- conn.close()
-
-if __name__ == "__main__":
- check_and_alter_table()
\ No newline at end of file
diff --git a/backend/crud.py b/backend/crud.py
deleted file mode 100644
index 939f979..0000000
--- a/backend/crud.py
+++ /dev/null
@@ -1,181 +0,0 @@
-from sqlalchemy.orm import Session
-import models
-import schemas
-from jose import JWTError, jwt
-from passlib.context import CryptContext
-from typing import List, Optional, Dict, Any
-import datetime
-import json
-
-# 密码处理上下文
-pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
-
-# 验证密码
-def verify_password(plain_password, hashed_password):
- return pwd_context.verify(plain_password, hashed_password)
-
-# 获取密码哈希
-def get_password_hash(password):
- return pwd_context.hash(password)
-
-# 用户相关操作
-def get_user(db: Session, username: str):
- return db.query(models.User).filter(models.User.username == username).first()
-
-def get_users(db: Session, skip: int = 0, limit: int = 100):
- return db.query(models.User).offset(skip).limit(limit).all()
-
-def create_user(db: Session, username: str, password: str, email: Optional[str] = None, full_name: Optional[str] = None):
- hashed_password = get_password_hash(password)
- db_user = models.User(
- username=username,
- email=email,
- full_name=full_name,
- hashed_password=hashed_password
- )
- db.add(db_user)
- db.commit()
- db.refresh(db_user)
- return db_user
-
-def authenticate_user(db: Session, username: str, password: str):
- user = get_user(db, username)
- if not user:
- return False
- if not verify_password(password, user.hashed_password):
- return False
- return user
-
-# 人才相关操作
-def get_talent(db: Session, talent_id: str):
- return db.query(models.Talent).filter(models.Talent.id == talent_id).first()
-
-def get_talents(db: Session, skip: int = 0, limit: int = 100):
- return db.query(models.Talent).offset(skip).limit(limit).all()
-
-def create_talent(db: Session, talent: schemas.TalentCreate):
- db_talent = models.Talent(**talent.dict())
- db.add(db_talent)
- db.commit()
- db.refresh(db_talent)
- return db_talent
-
-def update_talent(db: Session, talent_id: str, talent_data: Dict[str, Any]):
- db_talent = get_talent(db, talent_id)
- if db_talent:
- for key, value in talent_data.items():
- setattr(db_talent, key, value)
- db.commit()
- db.refresh(db_talent)
- return db_talent
-
-def delete_talent(db: Session, talent_id: str):
- db_talent = get_talent(db, talent_id)
- if db_talent:
- db.delete(db_talent)
- db.commit()
- return True
- return False
-
-# 工程研究中心相关操作
-def get_lab(db: Session, lab_id: str):
- return db.query(models.Lab).filter(models.Lab.id == lab_id).first()
-
-def get_labs(db: Session, skip: int = 0, limit: int = 100):
- return db.query(models.Lab).offset(skip).limit(limit).all()
-
-def create_lab(db: Session, lab: schemas.LabCreate):
- db_lab = models.Lab(**lab.dict())
- db.add(db_lab)
- db.commit()
- db.refresh(db_lab)
- return db_lab
-
-def update_lab(db: Session, lab_id: str, lab_data: Dict[str, Any]):
- db_lab = get_lab(db, lab_id)
- if db_lab:
- for key, value in lab_data.items():
- setattr(db_lab, key, value)
- db.commit()
- db.refresh(db_lab)
- return db_lab
-
-# 仪表盘数据相关操作
-def get_dashboard(db: Session):
- dashboard = db.query(models.DashboardData).first()
- return dashboard
-
-def get_news(db: Session, skip: int = 0, limit: int = 100):
- return db.query(models.News).offset(skip).limit(limit).all()
-
-def create_news(db: Session, title: str, date: str, dashboard_id: int):
- db_news = models.News(title=title, date=date, dashboard_id=dashboard_id)
- db.add(db_news)
- db.commit()
- db.refresh(db_news)
- return db_news
-
-def update_dashboard(db: Session, dashboard_data: Dict[str, Any]):
- dashboard = get_dashboard(db)
- if dashboard:
- # 更新简单字段
- for key, value in dashboard_data.items():
- if key != "newsData": # 新闻数据单独处理
- setattr(dashboard, key, value)
-
- # 如果有新闻数据需要更新
- if "newsData" in dashboard_data:
- # 删除所有旧新闻
- db.query(models.News).filter(models.News.dashboard_id == dashboard.id).delete()
-
- # 添加新的新闻
- for news_item in dashboard_data["newsData"]:
- db_news = models.News(
- title=news_item["title"],
- date=news_item["date"],
- dashboard_id=dashboard.id
- )
- db.add(db_news)
-
- db.commit()
- db.refresh(dashboard)
- return dashboard
-
-# 维度相关操作
-def get_dimension(db: Session, dimension_id: int):
- return db.query(models.Dimension).filter(models.Dimension.id == dimension_id).first()
-
-def get_dimensions_by_category(db: Session, category: str):
- return db.query(models.Dimension).filter(models.Dimension.category == category).all()
-
-def get_all_dimensions(db: Session, skip: int = 0, limit: int = 100):
- return db.query(models.Dimension).offset(skip).limit(limit).all()
-
-def create_dimension(db: Session, name: str, weight: float = 1.0, category: str = None, description: str = None):
- db_dimension = models.Dimension(
- name=name,
- weight=weight,
- category=category,
- description=description
- )
- db.add(db_dimension)
- db.commit()
- db.refresh(db_dimension)
- return db_dimension
-
-def update_dimension(db: Session, dimension_id: int, dimension_data: Dict[str, Any]):
- db_dimension = get_dimension(db, dimension_id)
- if db_dimension:
- for key, value in dimension_data.items():
- setattr(db_dimension, key, value)
- db.commit()
- db.refresh(db_dimension)
- return db_dimension
-
-def delete_dimension(db: Session, dimension_id: int):
- db_dimension = get_dimension(db, dimension_id)
- if db_dimension:
- db.delete(db_dimension)
- db.commit()
- return True
- return False
\ No newline at end of file
diff --git a/backend/data/app.db b/backend/data/app.db
deleted file mode 100644
index e24659a..0000000
Binary files a/backend/data/app.db and /dev/null differ
diff --git a/backend/data/app.db.bak b/backend/data/app.db.bak
deleted file mode 100644
index 64984cb..0000000
Binary files a/backend/data/app.db.bak and /dev/null differ
diff --git a/backend/database.py b/backend/database.py
deleted file mode 100644
index d7d7c64..0000000
--- a/backend/database.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from sqlalchemy import create_engine
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import sessionmaker
-import os
-
-# 确保数据目录存在
-DB_DIR = "data"
-os.makedirs(DB_DIR, exist_ok=True)
-
-# 数据库URL
-SQLALCHEMY_DATABASE_URL = f"sqlite:///{DB_DIR}/app.db"
-
-# 创建引擎
-engine = create_engine(
- SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
-)
-
-# 创建会话
-SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
-
-# 声明基类
-Base = declarative_base()
-
-# 获取数据库会话依赖项
-def get_db():
- db = SessionLocal()
- try:
- yield db
- finally:
- db.close()
\ No newline at end of file
diff --git a/backend/fix_json_format.py b/backend/fix_json_format.py
deleted file mode 100644
index 2f8b44a..0000000
--- a/backend/fix_json_format.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-修复JSON文件格式问题
-将Python的None值替换为JSON标准的null值
-
-使用方法:
-python fix_json_format.py
-"""
-
-import json
-import sys
-import os
-from pathlib import Path
-
-def fix_json_file():
- """修复JSON文件中的None值问题"""
- # 源文件和目标文件路径
- source_file = Path(__file__).parent.parent / "src" / "assets" / "工程研究中心.json"
-
- if not source_file.exists():
- print(f"❌ 错误:找不到文件 {source_file}")
- return False
-
- try:
- print(f"📖 正在读取文件: {source_file}")
-
- # 读取文件内容
- with open(source_file, 'r', encoding='utf-8') as f:
- content = f.read()
-
- print(f"✅ 文件读取成功,大小: {len(content)} 字符")
-
- # 替换None为null
- print("🔄 正在修复None值...")
- fixed_content = content.replace(': None,', ': null,')
- fixed_content = fixed_content.replace(': None}', ': null}')
- fixed_content = fixed_content.replace(': None]', ': null]')
-
- # 统计替换数量
- none_count = content.count(': None,') + content.count(': None}') + content.count(': None]')
- print(f"🔧 找到并修复了 {none_count} 个None值")
-
- # 验证JSON格式
- print("📝 正在验证JSON格式...")
- try:
- json.loads(fixed_content)
- print("✅ JSON格式验证通过")
- except json.JSONDecodeError as e:
- print(f"❌ JSON格式仍有问题: {e}")
- return False
-
- # 保存修复后的文件
- print("💾 正在保存修复后的文件...")
- with open(source_file, 'w', encoding='utf-8') as f:
- f.write(fixed_content)
-
- print("🎉 文件修复完成!")
- return True
-
- except Exception as e:
- print(f"❌ 修复失败: {str(e)}")
- return False
-
-def main():
- """主函数"""
- print("🚀 开始修复JSON文件格式...")
- print("=" * 50)
-
- success = fix_json_file()
-
- print("=" * 50)
- if success:
- print("🎉 修复成功!现在可以运行导入脚本了。")
- else:
- print("💥 修复失败!")
-
- input("\n按回车键退出...")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/backend/fix_year_data_types.py b/backend/fix_year_data_types.py
deleted file mode 100644
index 65bdd19..0000000
--- a/backend/fix_year_data_types.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-修复年度数据中年份字段的数据类型问题
-将所有年份从数字类型转换为字符串类型
-"""
-
-import sys
-import os
-import json
-
-# 添加父目录到路径以便导入模块
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-
-from sqlalchemy.orm import sessionmaker
-from database import SessionLocal
-from models import Lab
-
-def fix_year_data_types():
- """修复所有工程研究中心年度数据中的年份类型问题"""
- try:
- print("🔄 正在连接数据库...")
- db = SessionLocal()
-
- print("🔄 正在查询所有工程研究中心...")
- labs = db.query(Lab).all()
-
- print(f"✅ 找到 {len(labs)} 个工程研究中心")
-
- fixed_count = 0
- error_count = 0
-
- for lab in labs:
- try:
- if not lab.annual_data:
- continue
-
- if isinstance(lab.annual_data, str):
- # 解析JSON数据
- data = json.loads(lab.annual_data)
-
- # 检查是否需要修复
- needs_fix = False
- for year_data in data:
- year_field = year_data.get("归属年份")
- if isinstance(year_field, int):
- needs_fix = True
- break
-
- if needs_fix:
- print(f"🔧 修复工程研究中心: {lab.name}")
-
- # 修复所有年份字段
- for year_data in data:
- year_field = year_data.get("归属年份")
- if isinstance(year_field, int):
- year_data["归属年份"] = str(year_field)
- print(f" - 将年份 {year_field} 转换为字符串")
-
- # 保存修复后的数据
- lab.annual_data = json.dumps(data, ensure_ascii=False)
- fixed_count += 1
-
- except Exception as e:
- print(f"❌ 处理工程研究中心 {lab.name} 时出错: {str(e)}")
- error_count += 1
- continue
-
- # 提交更改
- if fixed_count > 0:
- print(f"\n💾 正在保存修复结果...")
- db.commit()
- print(f"✅ 修复完成!")
- else:
- print("ℹ️ 没有需要修复的数据")
-
- print(f"📊 统计信息:")
- print(f" - 修复的工程研究中心: {fixed_count}")
- print(f" - 错误数量: {error_count}")
- print(f" - 总计处理: {len(labs)}")
-
- db.close()
- return True
-
- except Exception as e:
- print(f"❌ 修复失败: {str(e)}")
- import traceback
- traceback.print_exc()
- return False
-
-def verify_fix():
- """验证修复结果"""
- try:
- print("\n🔍 验证修复结果...")
- db = SessionLocal()
-
- # 检查西部优势矿产资源高效利用工程研究中心
- lab = db.query(Lab).filter(Lab.name == "西部优势矿产资源高效利用").first()
-
- if lab and lab.annual_data:
- data = json.loads(lab.annual_data)
- for i, year_data in enumerate(data):
- year_field = year_data.get("归属年份")
- print(f"📅 年度 {i+1}: {year_field} (类型: {type(year_field)})")
-
- db.close()
- return True
-
- except Exception as e:
- print(f"❌ 验证失败: {str(e)}")
- return False
-
-def main():
- """主函数"""
- print("🚀 开始修复年度数据类型...")
- print("=" * 50)
-
- success = fix_year_data_types()
-
- if success:
- verify_fix()
-
- print("=" * 50)
- if success:
- print("🎉 修复完成!")
- else:
- print("💥 修复失败!")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/backend/import_lab_data_full.py b/backend/import_lab_data_full.py
deleted file mode 100644
index bd3b7c1..0000000
--- a/backend/import_lab_data_full.py
+++ /dev/null
@@ -1,270 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-工程研究中心数据完整导入脚本
-用于导入assets/工程研究中心.json中的所有工程研究中心数据到数据库
-
-使用方法:
-1. 确保后端服务未运行
-2. 在backend目录下执行: python import_lab_data_full.py
-
-主要功能:
-- 导入所有工程研究中心的基本信息
-- 导入多年度数据到annual_data JSON字段
-- 自动生成lab_id
-- 支持数据更新(如果lab已存在)
-"""
-
-import json
-import sys
-import os
-from pathlib import Path
-
-# 添加父目录到路径以便导入模块
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-
-from sqlalchemy.orm import sessionmaker
-from database import SessionLocal, engine
-from models import Lab
-import uuid
-
-def safe_int(value):
- """安全转换为整数"""
- if value is None or value == "":
- return 0
- if isinstance(value, (int, float)):
- return int(value)
- if isinstance(value, str):
- try:
- return int(float(value))
- except (ValueError, TypeError):
- return 0
- return 0
-
-def safe_float(value):
- """安全转换为浮点数"""
- if value is None or value == "":
- return 0.0
- if isinstance(value, (int, float)):
- return float(value)
- if isinstance(value, str):
- try:
- return float(value)
- except (ValueError, TypeError):
- return 0.0
- return 0.0
-
-def safe_str(value):
- """安全转换为字符串"""
- if value is None:
- return ""
- return str(value)
-
-def import_lab_data():
- """导入工程研究中心数据"""
- # 检查JSON文件是否存在
- json_file = Path(__file__).parent.parent / "src" / "assets" / "工程研究中心.json"
- if not json_file.exists():
- print(f"❌ 错误:找不到数据文件 {json_file}")
- return False
-
- try:
- # 读取JSON数据
- print(f"📖 正在读取数据文件: {json_file}")
- with open(json_file, 'r', encoding='utf-8') as f:
- labs_data = json.load(f)
-
- print(f"✅ 成功读取数据,共 {len(labs_data)} 个工程研究中心")
-
- # 创建数据库会话
- db = SessionLocal()
-
- imported_count = 0
- updated_count = 0
- error_count = 0
-
- try:
- for lab_info in labs_data:
- try:
- center_name = lab_info.get("中心名称", "")
- center_number = lab_info.get("中心编号", "")
- annual_data = lab_info.get("年度数据", [])
-
- if not center_name:
- print(f"⚠️ 跳过无名称的工程研究中心")
- continue
-
- print(f"\n🔄 正在处理工程研究中心: {center_name} (编号: {center_number})")
-
- # 检查是否已存在
- existing_lab = None
- if center_number:
- existing_lab = db.query(Lab).filter(Lab.center_number == center_number).first()
-
- if not existing_lab and center_name:
- existing_lab = db.query(Lab).filter(Lab.name == center_name).first()
-
- if existing_lab:
- print(f" 📝 工程研究中心已存在,更新数据...")
- lab = existing_lab
- updated_count += 1
- else:
- print(f" ➕ 创建新工程研究中心...")
- lab = Lab()
- lab.id = str(uuid.uuid4())
- imported_count += 1
-
- # 设置基本信息
- lab.name = center_name
- lab.center_number = center_number
- lab.idcode = center_number # 用编号作为显示ID
-
- # 处理年度数据
- if annual_data:
- # 提取基本信息(从最新年度数据)
- latest_data = max(annual_data, key=lambda x: x.get("归属年份", "0"))
-
- lab.school = latest_data.get("所属学校", "")
- lab.department = latest_data.get("主管部门", "")
- lab.field = latest_data.get("所属领域", "")
- lab.current_year = latest_data.get("归属年份", "")
-
- # 存储多年度数据到annual_data JSON字段
- lab.annual_data = json.dumps(annual_data, ensure_ascii=False)
-
- # 从最新数据中提取主要信息字段
- lab.innovation_situation = latest_data.get("技术攻关与创新情况", "")
- lab.overall_situation = latest_data.get("1.总体情况", "")
- lab.engineering_cases = latest_data.get("2.工程化案例", "")
- lab.industry_service = latest_data.get("3.行业服务情况", "")
- lab.discipline_support = latest_data.get("1.学科发展支撑情况", "") or latest_data.get("1.支撑学科发展情况", "")
- lab.talent_cultivation = latest_data.get("2.人才培养情况", "")
- lab.team_building = latest_data.get("3.研究队伍建设情况", "")
- lab.department_support = latest_data.get("1.主管部门、依托单位支持情况", "")
- lab.equipment_sharing = latest_data.get("2.仪器设备开放共享情况", "")
- lab.academic_style = latest_data.get("3.学风建设情况", "")
- lab.technical_committee = latest_data.get("4.技术委员会工作情况", "")
- lab.next_year_plan = latest_data.get("下一年度工作计划", "")
- lab.problems_suggestions = latest_data.get("问题与建议", "")
- lab.director_opinion = latest_data.get("1.工程中心负责人意见", "")
- lab.institution_opinion = latest_data.get("2.依托单位意见", "")
- lab.research_directions = latest_data.get("研究方向/学术带头人", "")
-
- # 统计数据字段
- lab.national_awards_first = safe_int(latest_data.get("国家级科技奖励一等奖(项)", 0))
- lab.national_awards_second = safe_int(latest_data.get("国家级科技奖励二等奖(项)", 0))
- lab.provincial_awards_first = safe_int(latest_data.get("省、部级科技奖励一等奖(项)", 0))
- lab.provincial_awards_second = safe_int(latest_data.get("省、部级科技奖励二等奖(项)", 0))
- lab.valid_patents = safe_int(latest_data.get("有效专利(项)", 0))
- lab.other_ip = safe_int(latest_data.get("其他知识产权(项)", 0))
- lab.international_standards = safe_int(latest_data.get("国际/国家标准(项)", 0))
- lab.industry_standards = safe_int(latest_data.get("行业/地方标准(项)", 0))
-
- # 专利转化数据
- lab.patent_transfer_contracts = safe_int(latest_data.get("合同项数(项)", 0))
- lab.patent_transfer_amount = safe_float(latest_data.get("合同金额(万元)", 0))
- lab.patent_license_contracts = safe_int(latest_data.get("合同项数(项)_1", 0))
- lab.patent_license_amount = safe_float(latest_data.get("合同金额(万元)_1", 0))
- lab.patent_valuation_contracts = safe_int(latest_data.get("合同项数(项)_2", 0))
- lab.patent_valuation_amount = safe_float(latest_data.get("作价金额(万元)", 0))
-
- # 项目合作
- lab.project_contracts = safe_int(latest_data.get("项目合同项数(项)", 0))
- lab.project_amount = safe_float(latest_data.get("项目合同金额(万元)", 0))
-
- # 学科信息
- lab.discipline_1 = latest_data.get("依托学科1", "")
- lab.discipline_2 = latest_data.get("依托学科2", "")
- lab.discipline_3 = latest_data.get("依托学科3", "")
-
- # 人才培养数据
- lab.doctoral_students = safe_int(latest_data.get("在读博士生", 0))
- lab.master_students = safe_int(latest_data.get("在读硕士生", 0))
- lab.graduated_doctoral = safe_int(latest_data.get("当年毕业博士", 0))
- lab.graduated_master = safe_int(latest_data.get("当年毕业硕士", 0))
- lab.undergraduate_courses = safe_int(latest_data.get("承担本科课程", 0))
- lab.graduate_courses = safe_int(latest_data.get("承担研究生课程", 0))
- lab.textbooks = safe_int(latest_data.get("大专院校教材", 0))
-
- # 人员结构
- lab.professors = safe_int(latest_data.get("科技人才-教授(人)", 0))
- lab.associate_professors = safe_int(latest_data.get("科技人才-副教授(人)", 0))
- lab.lecturers = safe_int(latest_data.get("科技人才-讲师(人)", 0))
- lab.domestic_visitors = safe_int(latest_data.get("访问学者-国内(人)", 0))
- lab.foreign_visitors = safe_int(latest_data.get("访问学者-国外(人)", 0))
- lab.postdoc_in = safe_int(latest_data.get("本年度进站博士后(人)", 0))
- lab.postdoc_out = safe_int(latest_data.get("本年度出站博士后(人)", 0))
-
- # 基础设施
- lab.center_area = safe_float(latest_data.get("工程中心面积(m²)", 0))
- lab.new_area = safe_float(latest_data.get("当年新增面积(m²)", 0))
- lab.fixed_personnel = safe_int(latest_data.get("固定人员(人)", 0))
- lab.mobile_personnel = safe_int(latest_data.get("流动人员(人)", 0))
-
- # 经费情况
- lab.total_funding = safe_float(latest_data.get("当年项目到账总经费(万元)", 0))
- lab.vertical_funding = safe_float(latest_data.get("纵向经费(万元)", 0))
- lab.horizontal_funding = safe_float(latest_data.get("横向经费(万元)", 0))
-
- # 服务情况
- lab.technical_consultations = safe_int(latest_data.get("技术咨询(次)", 0))
- lab.training_services = safe_int(latest_data.get("培训服务(人次)", 0))
-
- # 设置兼容性字段
- lab.personnel = f"{lab.fixed_personnel + lab.mobile_personnel}人"
- lab.nationalProjects = str(lab.project_contracts)
- lab.otherProjects = "0"
- lab.achievements = str(lab.valid_patents)
- lab.image = "/image/实验室1.png" # 默认图片
-
- print(f" ✅ 年度数据: {len(annual_data)} 年, 最新年份: {lab.current_year}")
-
- # 添加到数据库(如果是新记录)
- if lab not in db.query(Lab).all():
- db.add(lab)
-
- except Exception as e:
- print(f" ❌ 处理工程研究中心 {center_name} 时出错: {str(e)}")
- error_count += 1
- continue
-
- # 提交更改
- print(f"\n💾 正在保存到数据库...")
- db.commit()
- print(f"✅ 数据导入完成!")
- print(f"📊 统计信息:")
- print(f" - 新增工程研究中心: {imported_count}")
- print(f" - 更新工程研究中心: {updated_count}")
- print(f" - 错误数量: {error_count}")
- print(f" - 总计处理: {imported_count + updated_count}")
-
- return True
-
- except Exception as e:
- print(f"❌ 数据库操作失败: {str(e)}")
- db.rollback()
- return False
- finally:
- db.close()
-
- except Exception as e:
- print(f"❌ 导入失败: {str(e)}")
- return False
-
-def main():
- """主函数"""
- print("🚀 开始导入工程研究中心数据...")
- print("=" * 50)
-
- success = import_lab_data()
-
- print("=" * 50)
- if success:
- print("🎉 导入完成!")
- else:
- print("💥 导入失败!")
-
- input("\n按回车键退出...")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/backend/init_db.py b/backend/init_db.py
deleted file mode 100644
index 9d6854f..0000000
--- a/backend/init_db.py
+++ /dev/null
@@ -1,226 +0,0 @@
-import json
-from sqlalchemy.orm import Session
-from database import engine, SessionLocal, Base
-import models
-import main # 导入原有假数据
-import logging
-
-# 设置日志
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-# 默认仪表盘数据(如果main.py中没有定义)
-default_dashboard_data = {
- "paperCount": 3500,
- "patentCount": 2000,
- "highImpactPapers": 100,
- "keyProjects": 50,
- "fundingAmount": "500万元",
- "researcherStats": {
- "academician": 12,
- "chiefScientist": 28,
- "distinguishedProfessor": 56,
- "youngScientist": 120
- },
- "newsData": [
- {"title": "我校科研团队在量子计算领域取得重大突破", "date": "2023-05-15"},
- {"title": "张教授团队论文被Nature收录", "date": "2023-04-28"},
- {"title": "校长率团访问美国麻省理工学院商讨合作事宜", "date": "2023-04-15"},
- {"title": "我校获批3项国家重点研发计划", "date": "2023-03-20"},
- {"title": "2023年度国家自然科学基金申请工作启动", "date": "2023-02-10"}
- ]
-}
-
-# 创建所有表
-def create_tables():
- Base.metadata.create_all(bind=engine)
- logger.info("数据库表已创建")
-
-# 导入用户数据
-def import_users(db: Session):
- # 检查是否已存在用户
- existing_users = db.query(models.User).count()
- if existing_users == 0:
- for username, user_data in main.fake_users_db.items():
- db_user = models.User(
- username=user_data["username"],
- email=user_data.get("email"),
- full_name=user_data.get("full_name"),
- hashed_password=user_data["hashed_password"],
- disabled=user_data.get("disabled", False)
- )
- db.add(db_user)
-
- db.commit()
- logger.info("用户数据已导入")
- else:
- logger.info("用户数据已存在,跳过导入")
-
-# 导入人才数据
-def import_talents(db: Session):
- # 检查是否已存在人才数据
- existing_talents = db.query(models.Talent).count()
- if existing_talents == 0:
- for talent_data in main.talents:
- # 处理evaluationData字段 - 确保是JSON格式
- evaluation_data = talent_data.get("evaluationData")
-
- db_talent = models.Talent(
- id=talent_data["id"],
- name=talent_data["name"],
- gender=talent_data.get("gender"),
- birthDate=talent_data.get("birthDate"),
- title=talent_data.get("title"),
- position=talent_data.get("position"),
- education=talent_data.get("education"),
- address=talent_data.get("address"),
- academicDirection=talent_data.get("academicDirection"),
- talentPlan=talent_data.get("talentPlan"),
- officeLocation=talent_data.get("officeLocation"),
- email=talent_data.get("email"),
- phone=talent_data.get("phone"),
- tutorType=talent_data.get("tutorType"),
- papers=talent_data.get("papers"),
- projects=talent_data.get("projects"),
- photo=talent_data.get("photo"),
- eduWorkHistory=talent_data.get("eduWorkHistory"),
- researchDirection=talent_data.get("researchDirection"),
- recentProjects=talent_data.get("recentProjects"),
- representativePapers=talent_data.get("representativePapers"),
- patents=talent_data.get("patents"),
- evaluationData=evaluation_data
- )
- db.add(db_talent)
-
- db.commit()
- logger.info("人才数据已导入")
- else:
- logger.info("人才数据已存在,跳过导入")
-
-# 导入工程研究中心数据
-def import_labs(db: Session):
- # 检查是否已存在工程研究中心数据
- existing_labs = db.query(models.Lab).count()
- if existing_labs == 0:
- for lab_data in main.labs:
- # 处理evaluationData字段 - 确保是JSON格式
- evaluation_data = lab_data.get("evaluationData")
-
- db_lab = models.Lab(
- id=lab_data["id"],
- name=lab_data["name"],
- personnel=lab_data.get("personnel"),
- nationalProjects=lab_data.get("nationalProjects"),
- otherProjects=lab_data.get("otherProjects"),
- achievements=lab_data.get("achievements"),
- labAchievements=lab_data.get("labAchievements"),
- image=lab_data.get("image"),
- score=lab_data.get("score"),
- evaluationData=evaluation_data
- )
- db.add(db_lab)
-
- db.commit()
- logger.info("工程研究中心数据已导入")
- else:
- logger.info("工程研究中心数据已存在,跳过导入")
-
-# 导入仪表盘数据
-def import_dashboard(db: Session):
- # 检查是否已存在仪表盘数据
- existing_dashboard = db.query(models.DashboardData).count()
- if existing_dashboard == 0:
- # 优先使用main.py中的数据,如果不存在则使用默认数据
- dashboard_data = getattr(main, "dashboard_data", default_dashboard_data)
-
- # 创建仪表盘记录
- db_dashboard = models.DashboardData(
- id=1, # 主键ID设为1
- paperCount=dashboard_data["paperCount"],
- patentCount=dashboard_data["patentCount"],
- highImpactPapers=dashboard_data["highImpactPapers"],
- keyProjects=dashboard_data["keyProjects"],
- fundingAmount=dashboard_data["fundingAmount"],
- researcherStats=dashboard_data["researcherStats"]
- )
- db.add(db_dashboard)
- db.flush() # 立即写入数据库,获取ID
-
- # 创建新闻数据记录
- for news_item in dashboard_data["newsData"]:
- db_news = models.News(
- title=news_item["title"],
- date=news_item["date"],
- dashboard_id=db_dashboard.id
- )
- db.add(db_news)
-
- db.commit()
- logger.info("仪表盘和新闻数据已导入")
- else:
- logger.info("仪表盘数据已存在,跳过导入")
-
-# 导入默认维度
-def import_dimensions(db: Session):
- # 人才评估维度
- talent_dimensions = [
- {"name": "学术成果", "weight": 1.0, "category": "talent", "description": "包括论文发表、专著、专利等"},
- {"name": "科研项目", "weight": 1.0, "category": "talent", "description": "承担的科研项目数量和级别"},
- {"name": "人才引进", "weight": 1.0, "category": "talent", "description": "引进的人才数量和质量"},
- {"name": "学术影响力", "weight": 1.0, "category": "talent", "description": "学术引用和影响力指标"},
- {"name": "教学质量", "weight": 1.0, "category": "talent", "description": "教学评估和学生反馈"},
- {"name": "社会服务", "weight": 1.0, "category": "talent", "description": "社会服务和贡献"}
- ]
-
- # 工程研究中心评估维度
- lab_dimensions = [
- {"name": "科研产出", "weight": 1.0, "category": "lab", "description": "工程研究中心科研成果产出"},
- {"name": "人才培养", "weight": 1.0, "category": "lab", "description": "培养的研究生和博士后数量"},
- {"name": "项目承担", "weight": 1.0, "category": "lab", "description": "承担的科研项目数量和级别"},
- {"name": "设备利用", "weight": 1.0, "category": "lab", "description": "设备利用率和效益"},
- {"name": "学术交流", "weight": 1.0, "category": "lab", "description": "国内外学术交流和合作"},
- {"name": "社会服务", "weight": 1.0, "category": "lab", "description": "社会服务和贡献"}
- ]
-
- # 检查是否已存在维度
- existing_dimensions = db.query(models.Dimension).count()
- if existing_dimensions == 0:
- # 添加人才评估维度
- for dim in talent_dimensions:
- db_dimension = models.Dimension(**dim)
- db.add(db_dimension)
-
- # 添加工程研究中心评估维度
- for dim in lab_dimensions:
- db_dimension = models.Dimension(**dim)
- db.add(db_dimension)
-
- db.commit()
- logger.info("默认评估维度已导入")
- else:
- logger.info("评估维度已存在,跳过导入")
-
-# 主函数
-def init_db():
- # 创建表结构
- create_tables()
-
- # 获取数据库会话
- db = SessionLocal()
- try:
- # 导入各类数据
- import_users(db)
- import_talents(db)
- import_labs(db)
- import_dashboard(db)
- import_dimensions(db)
-
- logger.info("数据库初始化完成!")
- except Exception as e:
- logger.error(f"数据库初始化失败: {e}")
- finally:
- db.close()
-
-# 直接运行脚本时执行初始化
-if __name__ == "__main__":
- init_db()
\ No newline at end of file
diff --git a/backend/init_dimensions.py b/backend/init_dimensions.py
deleted file mode 100644
index fc4d962..0000000
--- a/backend/init_dimensions.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from sqlalchemy.orm import Session
-from database import SessionLocal, engine, Base
-import models
-import crud
-
-def init_dimensions():
- # 创建会话
- db = SessionLocal()
- try:
- # 检查是否已经存在维度数据
- existing_dimensions = db.query(models.Dimension).count()
-
- if existing_dimensions == 0:
- print("初始化维度数据...")
-
- # 教师科研人才评估维度
- talent_dimensions = [
- {"name": "教育和工作经历", "weight": 10, "category": "talent", "description": "教育背景和工作经历评估"},
- {"name": "研究方向前沿性", "weight": 8, "category": "talent", "description": "研究是否处于学科前沿"},
- {"name": "主持科研项目情况", "weight": 12, "category": "talent", "description": "项目规模、数量及影响力"},
- {"name": "科研成果质量", "weight": 16, "category": "talent", "description": "论文、专利等成果的质量与影响"},
- {"name": "教学能力与效果", "weight": 14, "category": "talent", "description": "教学水平及学生评价"},
- {"name": "学术服务与影响力", "weight": 40, "category": "talent", "description": "学术服务与社会影响力"}
- ]
-
- # 工程研究中心评估维度
- lab_dimensions = [
- {"name": "工程技术研发能力与水平", "weight": 30, "category": "lab", "description": "工程研究中心整体工程技术研发水平"},
- {"name": "创新水平", "weight": 10, "category": "lab", "description": "工程研究中心科研创新程度"},
- {"name": "人才与队伍", "weight": 10, "category": "lab", "description": "工程研究中心人才梯队建设情况"},
- {"name": "装备与场地", "weight": 10, "category": "lab", "description": "工程研究中心设备和场地条件"},
- {"name": "成果转化与行业贡献", "weight": 30, "category": "lab", "description": "成果产业化情况与行业贡献"},
- {"name": "学科发展与人才培养", "weight": 20, "category": "lab", "description": "对学科发展与人才培养的贡献"},
- {"name": "开放与运行管理", "weight": 20, "category": "lab", "description": "工程研究中心开放程度与管理水平"}
- ]
-
- # 添加教师科研人才评估维度
- for dim in talent_dimensions:
- crud.create_dimension(
- db,
- name=dim["name"],
- weight=dim["weight"],
- category=dim["category"],
- description=dim["description"]
- )
-
- # 添加工程研究中心评估维度
- for dim in lab_dimensions:
- crud.create_dimension(
- db,
- name=dim["name"],
- weight=dim["weight"],
- category=dim["category"],
- description=dim["description"]
- )
-
- print("维度数据初始化完成!")
- else:
- print("数据库中已存在维度数据,跳过初始化。")
-
- finally:
- db.close()
-
-if __name__ == "__main__":
- # 确保表已创建
- Base.metadata.create_all(bind=engine)
- # 初始化维度数据
- init_dimensions()
\ No newline at end of file
diff --git a/backend/main.py b/backend/main.py
deleted file mode 100644
index b4cdccc..0000000
--- a/backend/main.py
+++ /dev/null
@@ -1,1556 +0,0 @@
-from fastapi import FastAPI, Depends, HTTPException, status, Body, UploadFile, File
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from fastapi.staticfiles import StaticFiles
-from jose import JWTError, jwt
-from passlib.context import CryptContext
-from datetime import datetime, timedelta
-from pydantic import BaseModel
-from typing import List, Optional, Dict, Any
-import json
-import requests
-from bs4 import BeautifulSoup
-from fastapi.responses import JSONResponse
-import os
-import pickle
-import random
-import urllib.parse
-import uuid
-import shutil
-from sqlalchemy.orm import Session
-import base64
-import docx # 用于解析Word文档
-import re # 用于正则表达式匹配
-
-# 导入数据库相关模块
-from database import get_db, engine
-import models
-import schemas
-import crud
-
-# JWT配置
-SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
-ALGORITHM = "HS256"
-ACCESS_TOKEN_EXPIRE_MINUTES = 60
-
-# 创建FastAPI应用
-app = FastAPI(title="科研评估管理系统API")
-
-# 配置CORS
-app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"], # 在生产环境应该设置为具体的前端域名
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-# 创建静态文件目录
-STATIC_DIR = "static"
-IMAGES_DIR = os.path.join(STATIC_DIR, "images")
-os.makedirs(IMAGES_DIR, exist_ok=True)
-
-# 挂载静态文件目录
-app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
-
-# 密码处理上下文
-pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
-oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
-
-# 数据模型
-class User(BaseModel):
- username: str
- email: Optional[str] = None
- full_name: Optional[str] = None
- disabled: Optional[bool] = None
-
-class UserInDB(User):
- hashed_password: str
-
-class Token(BaseModel):
- access_token: str
- token_type: str
-
-class TokenData(BaseModel):
- username: str
-
-class Talent(BaseModel):
- id: str
- idcode: Optional[str] = None
- name: str
- gender: Optional[str] = None
- birthDate: Optional[str] = None
- title: Optional[str] = None
- position: Optional[str] = None
- education: Optional[str] = None
- address: Optional[str] = None
- academicDirection: Optional[str] = None
- talentPlan: Optional[str] = None
- officeLocation: Optional[str] = None
- email: Optional[str] = None
- phone: Optional[str] = None
- tutorType: Optional[str] = None
- papers: Optional[str] = None
- projects: Optional[str] = None
- photo: Optional[str] = None
- eduWorkHistory: Optional[str] = None
- researchDirection: Optional[str] = None
- recentProjects: Optional[str] = None
- representativePapers: Optional[str] = None
- patents: Optional[str] = None
- evaluationData: Optional[List[float]] = None
-
-class Lab(BaseModel):
- id: str
- idcode: Optional[str] = None
- name: str
- personnel: Optional[str] = None
- nationalProjects: Optional[str] = None
- otherProjects: Optional[str] = None
- achievements: Optional[str] = None
- labAchievements: Optional[str] = None
- image: Optional[str] = None
- score: Optional[int] = None
- evaluationData: Optional[List[float]] = None
-
-class DashboardData(BaseModel):
- paperCount: int
- patentCount: int
- highImpactPapers: int
- keyProjects: int
- fundingAmount: str
- researcherStats: dict
- newsData: List[dict]
-
-# URL抓取模型
-class ScrapeRequest(BaseModel):
- url: str
-
-# 保存评估数据模型
-class SaveDataRequest(BaseModel):
- data_type: str # "talent" 或 "lab"
- data: dict
-
-# 文件存储路径
-DATA_DIR = "data"
-TALENT_DATA_FILE = os.path.join(DATA_DIR, "talents.pkl")
-LAB_DATA_FILE = os.path.join(DATA_DIR, "labs.pkl")
-
-# 确保数据目录存在
-os.makedirs(DATA_DIR, exist_ok=True)
-
-# 从文件加载数据
-def load_data(file_path, default_data):
- if os.path.exists(file_path):
- try:
- with open(file_path, 'rb') as f:
- return pickle.load(f)
- except Exception as e:
- print(f"Error loading data from {file_path}: {e}")
- return default_data
-
-# 保存数据到文件
-def save_data(file_path, data):
- try:
- with open(file_path, 'wb') as f:
- pickle.dump(data, f)
- return True
- except Exception as e:
- print(f"Error saving data to {file_path}: {e}")
- return False
-
-# 加载已保存的数据
-talents = load_data(TALENT_DATA_FILE, [])
-labs = load_data(LAB_DATA_FILE, [])
-
-# 模拟数据库
-fake_users_db = {
- "admin": {
- "username": "admin",
- "full_name": "管理员",
- "email": "admin@example.com",
- "hashed_password": pwd_context.hash("123456"),
- "disabled": False,
- }
-}
-
-# 模拟人才数据
-talents = [
- {
- "id": "BLG45187",
- "name": "张三",
- "gender": "男",
- "birthDate": "1980.01.01",
- "title": "教授",
- "position": "副院长",
- "education": "博士",
- "address": "北京市海淀区中关村南大街5号",
- "academicDirection": "人工智能",
- "talentPlan": "国家杰出青年",
- "officeLocation": "理工大厦A座201",
- "email": "zhangsan@example.com",
- "phone": "13800138000",
- "tutorType": "博士生导师",
- "papers": "15篇",
- "projects": "23项",
- "photo": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%234986ff' opacity='0.2'/%3E%3Ccircle cx='50' cy='40' r='20' fill='%234986ff' opacity='0.5'/%3E%3Cpath d='M30,80 Q50,60 70,80 L70,100 L30,100 Z' fill='%234986ff' opacity='0.5'/%3E%3C/svg%3E",
- "eduWorkHistory": "2005年毕业于北京理工大学,获博士学位\n2005-2010年在清华大学从事博士后研究\n2010年至今在北京理工大学任教",
- "researchDirection": "机器学习、深度学习、计算机视觉",
- "recentProjects": "国家自然科学基金重点项目:深度学习在计算机视觉中的应用研究\n国家重点研发计划项目:智能机器人视觉感知系统研发",
- "representativePapers": "[1] 机器学习在自动化控制中的应用\n[2] 深度强化学习研究进展",
- "patents": "一种基于深度学习的图像识别方法\n一种智能控制系统及其控制方法",
- "evaluationData": [85, 90, 78, 82, 76, 88]
- },
- {
- "id": "BLG45188",
- "name": "李四",
- "gender": "男",
- "birthDate": "1982.05.15",
- "title": "副教授",
- "position": "系主任",
- "education": "博士",
- "address": "北京市海淀区中关村南大街5号",
- "academicDirection": "材料科学",
- "talentPlan": "青年千人计划",
- "officeLocation": "理工大厦B座305",
- "email": "lisi@example.com",
- "phone": "13900139000",
- "tutorType": "硕士生导师",
- "papers": "12篇",
- "projects": "18项",
- "photo": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%234986ff' opacity='0.2'/%3E%3Ccircle cx='50' cy='40' r='20' fill='%234986ff' opacity='0.5'/%3E%3Cpath d='M30,80 Q50,60 70,80 L70,100 L30,100 Z' fill='%234986ff' opacity='0.5'/%3E%3C/svg%3E",
- "eduWorkHistory": "2008年毕业于中国科学院,获博士学位\n2008-2013年在美国麻省理工学院从事博士后研究\n2013年至今在北京理工大学任教",
- "researchDirection": "新能源材料、纳米材料、催化材料",
- "recentProjects": "国家自然科学基金面上项目:高性能催化材料的设计与合成\n企业合作项目:新型锂电池材料开发",
- "representativePapers": "[1] 高性能催化材料的设计与合成\n[2] 纳米材料在新能源领域的应用",
- "patents": "一种高效催化材料的制备方法\n一种纳米材料的合成工艺",
- "evaluationData": [92, 85, 76, 89, 78, 82]
- }
-]
-
-# 模拟工程研究中心数据
-labs = [
- {
- "id": "BLG45187",
- "name": "基础力学教学实验中心",
- "personnel": "30人",
- "nationalProjects": "10项",
- "otherProjects": "46项",
- "achievements": "28项",
- "labAchievements": "中心面向全校本科生开设力学类实验课程,获国家级教学成果奖1项,省部级教学成果奖2项。开发研制教学实验装置20余台套,获得国家专利15项。建设国家级精品课程2门,国家级精品资源共享课1门。",
- "image": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 300 200'%3E%3Crect width='300' height='200' fill='%234986ff' opacity='0.2'/%3E%3Crect x='20' y='40' width='260' height='120' fill='%234986ff' opacity='0.3'/%3E%3Ccircle cx='70' cy='70' r='20' fill='%234986ff' opacity='0.5'/%3E%3Crect x='120' y='50' width='140' height='40' fill='%234986ff' opacity='0.4'/%3E%3Crect x='120' y='110' width='140' height='30' fill='%234986ff' opacity='0.4'/%3E%3C/svg%3E",
- "score": 98,
- "evaluationData": [85, 90, 78, 82, 76, 88]
- },
- {
- "id": "BLG45188",
- "name": "高性能计算工程研究中心",
- "personnel": "25人",
- "nationalProjects": "8项",
- "otherProjects": "37项",
- "achievements": "22项",
- "labAchievements": "工程研究中心围绕高性能计算、并行计算、分布式系统等方向开展研究。建有超级计算机集群,计算能力达到100 TFLOPS。在国际顶级期刊和会议发表论文50余篇,获国家发明专利12项。与多家知名IT企业建立了产学研合作关系。",
- "image": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 300 200'%3E%3Crect width='300' height='200' fill='%234986ff' opacity='0.1'/%3E%3Cpath d='M60,20 L240,20 L240,180 L60,180 Z' fill='%234986ff' opacity='0.2'/%3E%3Cpath d='M80,40 L140,40 L140,160 L80,160 Z' fill='%234986ff' opacity='0.3'/%3E%3Cpath d='M160,40 L220,40 L220,160 L160,160 Z' fill='%234986ff' opacity='0.3'/%3E%3Cline x1='80' y1='60' x2='140' y2='60' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='80' y1='80' x2='140' y2='80' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='80' y1='100' x2='140' y2='100' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='80' y1='120' x2='140' y2='120' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='80' y1='140' x2='140' y2='140' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='160' y1='60' x2='220' y2='60' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='160' y1='80' x2='220' y2='80' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='160' y1='100' x2='220' y2='100' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='160' y1='120' x2='220' y2='120' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3Cline x1='160' y1='140' x2='220' y2='140' stroke='%23fff' stroke-width='1' opacity='0.5' /%3E%3C/svg%3E",
- "score": 94,
- "evaluationData": [92, 85, 76, 89, 78, 82]
- }
-]
-
-# 模拟仪表盘数据
-dashboard_data = {
- "paperCount": 3500,
- "patentCount": 2000,
- "highImpactPapers": 100,
- "keyProjects": 50,
- "fundingAmount": "500万元",
- "researcherStats": {
- "professor": 120,
- "associateProfessor": 180,
- "assistantProfessor": 150,
- "postdoc": 90,
- "phd": 250,
- "master": 400
- },
- "newsData": [
- {"title": "北京理工大学获批国家重点研发计划项目", "date": "2023-09-15"},
- {"title": "我校教授在Nature期刊发表重要研究成果", "date": "2023-08-30"},
- {"title": "北京理工大学举办2023年学术科技节", "date": "2023-08-20"},
- {"title": "我校研究团队在量子计算领域取得突破性进展", "date": "2023-08-10"},
- {"title": "北京理工大学与华为公司签署战略合作协议", "date": "2023-07-25"},
- {"title": "北京理工大学新增两个国家重点工程研究中心", "date": "2023-07-15"},
- {"title": "我校研究生在国际大赛中获一等奖", "date": "2023-07-01"},
- {"title": "北京理工大学成功研发新型纳米材料", "date": "2023-06-20"},
- {"title": "我校科研团队获2023年度国家科技进步奖提名", "date": "2023-06-10"},
- {"title": "北京理工大学举办人工智能与未来教育论坛", "date": "2023-05-25"}
- ]
-}
-
-# 工具函数
-def verify_password(plain_password, hashed_password):
- return pwd_context.verify(plain_password, hashed_password)
-
-def get_password_hash(password):
- return pwd_context.hash(password)
-
-def get_user(db, username: str):
- if username in db:
- user_dict = db[username]
- return UserInDB(**user_dict)
- return None
-
-def authenticate_user(fake_db, username: str, password: str):
- user = get_user(fake_db, username)
- if not user:
- return False
- if not verify_password(password, user.hashed_password):
- return False
- return user
-
-def create_access_token(data: dict, expires_delta: timedelta = None):
- to_encode = data.copy()
- if expires_delta:
- expire = datetime.utcnow() + expires_delta
- else:
- expire = datetime.utcnow() + timedelta(minutes=15)
- to_encode.update({"exp": expire})
- encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
- return encoded_jwt
-
-async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
- credentials_exception = HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Could not validate credentials",
- headers={"WWW-Authenticate": "Bearer"},
- )
- try:
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
- if username is None:
- raise credentials_exception
- token_data = TokenData(username=username)
- except JWTError:
- raise credentials_exception
-
- user = crud.get_user(db, username=token_data.username)
- if user is None:
- raise credentials_exception
- return user
-
-async def get_current_active_user(current_user: models.User = Depends(get_current_user)):
- if current_user.disabled:
- raise HTTPException(status_code=400, detail="Inactive user")
- return current_user
-
-# 下载并保存图片
-def download_and_save_image(image_url, url_base=""):
- try:
- # 设置请求头,模拟浏览器访问
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
- 'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
- 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
- 'Connection': 'keep-alive',
- 'Referer': url_base
- }
-
- # 如果URL是相对路径,则与基础URL合并
- if image_url.startswith('../') or image_url.startswith('./'):
- image_url = urllib.parse.urljoin(url_base, image_url)
-
- # 发送请求获取图片
- response = requests.get(image_url, stream=True, headers=headers, timeout=30)
- response.raise_for_status()
-
- # 获取图片扩展名
- content_type = response.headers.get('Content-Type', '')
- if 'image/jpeg' in content_type or image_url.lower().endswith('.jpg') or image_url.lower().endswith('.jpeg'):
- ext = '.jpg'
- elif 'image/png' in content_type or image_url.lower().endswith('.png'):
- ext = '.png'
- elif 'image/gif' in content_type or image_url.lower().endswith('.gif'):
- ext = '.gif'
- else:
- ext = '.png' # 默认使用PNG格式
-
- # 生成唯一文件名
- image_filename = f"{uuid.uuid4().hex}{ext}"
- image_path = os.path.join(IMAGES_DIR, image_filename)
-
- # 保存图片到本地
- with open(image_path, 'wb') as out_file:
- shutil.copyfileobj(response.raw, out_file)
-
- # 返回图片的静态访问地址
- return f"/static/images/{image_filename}"
-
- except Exception as e:
- print(f"Error downloading image: {e}")
- return None
-
-# 路由
-@app.post("/token", response_model=Token)
-async def login_for_access_token(
- form_data: OAuth2PasswordRequestForm = Depends(),
- db: Session = Depends(get_db)
-):
- user = crud.authenticate_user(db, form_data.username, form_data.password)
- if not user:
- raise HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Incorrect username or password",
- headers={"WWW-Authenticate": "Bearer"},
- )
- access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
- access_token = create_access_token(
- data={"sub": user.username}, expires_delta=access_token_expires
- )
- return {"access_token": access_token, "token_type": "bearer"}
-
-@app.get("/users/me", response_model=User)
-async def read_users_me(current_user: User = Depends(get_current_active_user)):
- return current_user
-
-@app.get("/dashboard")
-async def get_dashboard(db: Session = Depends(get_db)):
- dashboard = crud.get_dashboard(db)
- if dashboard:
- # 获取相关新闻
- news = db.query(models.News).filter(models.News.dashboard_id == dashboard.id).all()
-
- # 构建响应
- result = {
- "paperCount": dashboard.paperCount,
- "patentCount": dashboard.patentCount,
- "highImpactPapers": dashboard.highImpactPapers,
- "keyProjects": dashboard.keyProjects,
- "fundingAmount": dashboard.fundingAmount,
- "researcherStats": dashboard.researcherStats,
- "newsData": [{"title": n.title, "date": n.date} for n in news]
- }
- return result
-
- raise HTTPException(status_code=404, detail="Dashboard data not found")
-
-@app.get("/talents", response_model=List[schemas.Talent])
-async def get_talents(db: Session = Depends(get_db)):
- talents = crud.get_talents(db)
- return talents
-
-@app.post("/talents", response_model=schemas.Talent)
-async def create_talent(
- talent: schemas.TalentCreate,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- return crud.create_talent(db, talent)
-
-@app.get("/labs", response_model=List[schemas.Lab])
-async def get_labs(db: Session = Depends(get_db)):
- labs = crud.get_labs(db)
- return labs
-
-@app.post("/labs", response_model=schemas.Lab)
-async def create_lab(
- lab: schemas.LabCreate,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- return crud.create_lab(db, lab)
-
-# 健康检查接口
-@app.get("/health")
-async def health_check():
- return {"status": "healthy"}
-
-# 调试接口 - 检查用户状态
-@app.get("/debug/users")
-async def debug_users(db: Session = Depends(get_db)):
- try:
- users = db.query(models.User).all()
- user_list = []
- for user in users:
- user_list.append({
- "username": user.username,
- "email": user.email,
- "full_name": user.full_name,
- "disabled": user.disabled,
- "has_password": bool(user.hashed_password)
- })
- return {
- "total_users": len(users),
- "users": user_list
- }
- except Exception as e:
- return {"error": str(e)}
-
-# 调试接口 - 重新创建默认用户
-@app.post("/debug/create-admin")
-async def debug_create_admin(db: Session = Depends(get_db)):
- try:
- # 检查是否已经存在admin用户
- existing_user = db.query(models.User).filter(models.User.username == "admin").first()
-
- if existing_user:
- return {"message": "Admin用户已存在", "user": existing_user.username}
-
- # 创建admin用户
- hashed_password = get_password_hash("123456")
- default_user = models.User(
- username="admin",
- email="admin@example.com",
- full_name="系统管理员",
- hashed_password=hashed_password,
- disabled=False
- )
- db.add(default_user)
- db.commit()
- db.refresh(default_user)
-
- return {"message": "Admin用户创建成功", "user": default_user.username}
- except Exception as e:
- db.rollback()
- return {"error": str(e)}
-
-# URL抓取接口 - 更新版本
-@app.post("/api/scrape-url")
-async def scrape_url(request: schemas.ScrapeRequest):
- try:
- # 设置请求头,模拟浏览器访问
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
- 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
- 'Connection': 'keep-alive',
- 'Upgrade-Insecure-Requests': '1',
- 'Cache-Control': 'max-age=0'
- }
-
- # 发送HTTP请求获取页面内容,增加超时时间
- response = requests.get(request.url, headers=headers, timeout=30)
- response.raise_for_status() # 如果请求失败,抛出异常
-
- # 设置编码以正确处理中文字符
- response.encoding = 'utf-8'
-
- # 解析HTML
- soup = BeautifulSoup(response.text, 'html.parser')
-
- # 获取基础URL用于解析相对路径
- url_parts = urllib.parse.urlparse(request.url)
- base_url = f"{url_parts.scheme}://{url_parts.netloc}"
-
- # 初始化数据字典
- teacher_data = {
- "id": f"BLG{random.randint(10000, 99999)}",
- "photo": f"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='120' viewBox='0 0 100 120'%3E%3Crect width='100' height='120' fill='%234986ff' opacity='0.3'/%3E%3Ccircle cx='50' cy='45' r='25' fill='%234986ff' opacity='0.6'/%3E%3Ccircle cx='50' cy='95' r='35' fill='%234986ff' opacity='0.6'/%3E%3C/svg%3E",
- "evaluationData": [
- round(min(100, max(60, 70 + 20 * (0.5 - random.random())))) for _ in range(6)
- ]
- }
-
- # 从教师信息表提取基本信息
- info_table = soup.find('div', class_='wz_teacher')
- if info_table:
- table = info_table.find('table')
- if table:
- rows = table.find_all('tr')
-
- # 提取姓名、性别、出生年月
- if len(rows) > 0:
- cells = rows[0].find_all('td')
- if len(cells) >= 6:
- teacher_data["name"] = cells[1].text.strip()
- teacher_data["gender"] = cells[3].text.strip()
- teacher_data["birthDate"] = cells[5].text.strip()
-
- # 提取职称、职务、最高学历
- if len(rows) > 1:
- cells = rows[1].find_all('td')
- if len(cells) >= 6:
- teacher_data["title"] = cells[1].text.strip()
- position = cells[3].text.strip()
- teacher_data["position"] = position if position else ""
- teacher_data["education"] = cells[5].text.strip()
-
- # 提取学科方向
- if len(rows) > 2:
- cells = rows[2].find_all('td')
- if len(cells) >= 2:
- teacher_data["academicDirection"] = cells[1].text.strip()
-
- # 提取人才计划和办公地点
- if len(rows) > 3:
- cells = rows[3].find_all('td')
- if len(cells) >= 6:
- talent_plan = cells[1].text.strip()
- teacher_data["talentPlan"] = talent_plan if talent_plan else ""
- teacher_data["officeLocation"] = cells[5].text.strip()
-
- # 提取电子邮件和联系方式
- if len(rows) > 4:
- cells = rows[4].find_all('td')
- if len(cells) >= 6:
- email = cells[1].text.strip()
- teacher_data["email"] = email if email else ""
- phone = cells[5].text.strip()
- teacher_data["phone"] = phone if phone else ""
-
- # 提取通讯地址
- if len(rows) > 5:
- cells = rows[5].find_all('td')
- if len(cells) >= 2:
- teacher_data["address"] = cells[1].text.strip()
-
- # 提取导师类型
- if len(rows) > 6:
- cells = rows[6].find_all('td')
- if len(cells) >= 2:
- teacher_data["tutorType"] = cells[1].text.strip()
-
- # 提取照片
- photo_element = soup.select_one('.teacherInfo .img img')
- if photo_element and photo_element.get('src'):
- img_src = photo_element['src']
-
- # 处理相对路径,构建完整的图片URL
- if img_src.startswith('../../../'):
- # 移除 '../../../' 前缀,直接拼接到基础URL
- img_relative = img_src[9:] # 移除 '../../../'
- img_url = f"{base_url}/{img_relative}"
- elif img_src.startswith('../../'):
- # 处理 '../../' 相对路径
- img_relative = img_src[6:] # 移除 '../../'
- img_url = f"{base_url}/{img_relative}"
- elif img_src.startswith('../'):
- # 处理 '../' 相对路径
- img_relative = img_src[3:] # 移除 '../'
- img_url = f"{base_url}/{img_relative}"
- else:
- img_url = urllib.parse.urljoin(base_url, img_src)
-
- # 直接保存完整的图片URL,不下载到本地
- teacher_data["photo"] = img_url
-
- # 提取详细信息部分
- content_divs = soup.select('.con01_t')
- for div in content_divs:
- heading = div.find('h3')
- if not heading:
- continue
-
- heading_text = heading.text.strip()
-
- # 获取该部分的所有段落文本
- paragraphs = [p.text.strip() for p in div.find_all('p') if p.text.strip()]
- section_content = '\n'.join(paragraphs)
-
- # 根据标题将内容映射到相应字段
- if '教育与工作经历' in heading_text:
- teacher_data["eduWorkHistory"] = section_content
- elif '研究方向' in heading_text:
- teacher_data["researchDirection"] = section_content
- elif '近5年承担的科研项目' in heading_text or '近五年承担的科研项目' in heading_text:
- teacher_data["recentProjects"] = section_content
- # 计算项目数量
- project_count = len([p for p in paragraphs if p.strip().startswith(str(len(paragraphs) - paragraphs.index(p))+".")])
- if project_count > 0:
- teacher_data["projects"] = f"{project_count}项"
- else:
- teacher_data["projects"] = f"{len(paragraphs)}项"
- elif '代表性学术论文' in heading_text:
- teacher_data["representativePapers"] = section_content
- # 计算论文数量
- paper_count = len([p for p in paragraphs if p.strip().startswith("[")])
- if paper_count > 0:
- teacher_data["papers"] = f"{paper_count}篇"
- else:
- teacher_data["papers"] = f"{len(paragraphs)}篇"
- elif '授权国家发明专利' in heading_text or '专利' in heading_text:
- teacher_data["patents"] = section_content
-
- return teacher_data
-
- except Exception as e:
- print(f"抓取错误: {str(e)}")
- return JSONResponse(
- status_code=500,
- content={"error": f"抓取网页失败: {str(e)}"},
- )
-
-# 保存评估数据接口
-@app.post("/api/save-data")
-async def save_evaluation_data(
- request: schemas.SaveDataRequest,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- try:
- if request.data_type == "talent":
- # 获取人才ID
- talent_id = request.data.get("id")
-
- # 如果id不存在、为空字符串或为null,执行新增操作
- if not talent_id:
- # 确保有必要的字段
- if not request.data.get("name"):
- return JSONResponse(
- status_code=400,
- content={"error": "缺少必要字段:name"},
- )
-
- # 生成新ID
- new_id = f"BLG{random.randint(10000, 99999)}"
- # 复制数据并添加ID
- talent_data = {**request.data, "id": new_id}
-
- # 创建Talent对象并保存到数据库
- # 过滤掉数据库模型中不存在的字段
- valid_fields = {
- 'id', 'idcode', 'name', 'gender', 'birthDate', 'title', 'position',
- 'education', 'educationBackground', 'address', 'academicDirection',
- 'talentPlan', 'officeLocation', 'email', 'phone', 'tutorType',
- 'papers', 'projects', 'photo', 'eduWorkHistory', 'researchDirection',
- 'recentProjects', 'representativePapers', 'patents', 'evaluationData'
- }
- filtered_talent_data = {k: v for k, v in talent_data.items() if k in valid_fields}
- db_talent = models.Talent(**filtered_talent_data)
- db.add(db_talent)
- db.commit()
- db.refresh(db_talent)
-
- return {"success": True, "message": "人才评估数据已新增", "id": new_id}
- else:
- # 执行更新操作
- talent = crud.update_talent(db, talent_id, request.data)
- if not talent:
- return JSONResponse(
- status_code=404,
- content={"error": f"未找到ID为 {talent_id} 的人才"},
- )
-
- return {"success": True, "message": "人才评估数据已更新"}
-
- elif request.data_type == "lab":
- # 获取工程研究中心ID
- lab_id = request.data.get("id")
-
- # 如果id不存在、为空字符串或为null,执行新增操作
- if not lab_id:
- # 确保有必要的字段
- if not request.data.get("name"):
- return JSONResponse(
- status_code=400,
- content={"error": "缺少必要字段:name"},
- )
-
- # 生成新ID
- new_id = f"BLG{random.randint(10000, 99999)}"
- # 复制数据并添加ID
- lab_data = {**request.data, "id": new_id}
-
- # 创建Lab对象并保存到数据库
- db_lab = models.Lab(**lab_data)
- db.add(db_lab)
- db.commit()
- db.refresh(db_lab)
-
- return {"success": True, "message": "工程研究中心评估数据已新增", "id": new_id}
- else:
- # 执行更新操作
- lab = crud.update_lab(db, lab_id, request.data)
- if not lab:
- return JSONResponse(
- status_code=404,
- content={"error": f"未找到ID为 {lab_id} 的工程研究中心"},
- )
-
- return {"success": True, "message": "工程研究中心评估数据已更新"}
-
- else:
- return JSONResponse(
- status_code=400,
- content={"error": f"不支持的数据类型: {request.data_type}"},
- )
-
- except Exception as e:
- return JSONResponse(
- status_code=500,
- content={"error": f"保存数据失败: {str(e)}"},
- )
-
-# 添加抓取教师信息和头像的新接口
-@app.post("/api/fetch-teacher-data")
-async def fetch_teacher_data(db: Session = Depends(get_db)):
- try:
- # 抓取网页内容
- url = "https://ac.bit.edu.cn/szdw/jsml/kzllykzgcyjs1/c6533e24f85749578699deca43c38b40.htm"
-
- # 设置请求头,模拟浏览器访问
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
- 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
- 'Connection': 'keep-alive',
- 'Upgrade-Insecure-Requests': '1',
- 'Cache-Control': 'max-age=0'
- }
-
- response = requests.get(url, headers=headers, timeout=30)
- response.raise_for_status()
-
- # 设置编码以正确处理中文字符
- response.encoding = 'utf-8'
-
- # 解析HTML
- soup = BeautifulSoup(response.text, 'html.parser')
-
- # 获取基础URL用于解析相对路径
- url_parts = urllib.parse.urlparse(url)
- base_url = f"{url_parts.scheme}://{url_parts.netloc}"
-
- # 初始化教师数据
- teacher_data = {
- "id": f"BLG{random.randint(10000, 99999)}",
- "evaluationData": [
- round(min(100, max(60, 70 + 20 * (0.5 - random.random())))) for _ in range(6)
- ]
- }
-
- # 从教师信息表提取基本信息
- info_table = soup.find('div', class_='wz_teacher')
- if info_table:
- table = info_table.find('table')
- if table:
- rows = table.find_all('tr')
-
- # 提取姓名、性别、出生年月
- if len(rows) > 0:
- cells = rows[0].find_all('td')
- if len(cells) >= 6:
- teacher_data["name"] = cells[1].text.strip()
- teacher_data["gender"] = cells[3].text.strip()
- teacher_data["birthDate"] = cells[5].text.strip()
-
- # 提取职称、职务、最高学历
- if len(rows) > 1:
- cells = rows[1].find_all('td')
- if len(cells) >= 6:
- teacher_data["title"] = cells[1].text.strip()
- position = cells[3].text.strip()
- teacher_data["position"] = position if position else ""
- teacher_data["education"] = cells[5].text.strip()
-
- # 提取学科方向
- if len(rows) > 2:
- cells = rows[2].find_all('td')
- if len(cells) >= 2:
- teacher_data["academicDirection"] = cells[1].text.strip()
-
- # 提取人才计划和办公地点
- if len(rows) > 3:
- cells = rows[3].find_all('td')
- if len(cells) >= 6:
- talent_plan = cells[1].text.strip()
- teacher_data["talentPlan"] = talent_plan if talent_plan else ""
- teacher_data["officeLocation"] = cells[5].text.strip()
-
- # 提取电子邮件和联系方式
- if len(rows) > 4:
- cells = rows[4].find_all('td')
- if len(cells) >= 6:
- email = cells[1].text.strip()
- teacher_data["email"] = email if email else ""
- phone = cells[5].text.strip()
- teacher_data["phone"] = phone if phone else ""
-
- # 提取通讯地址
- if len(rows) > 5:
- cells = rows[5].find_all('td')
- if len(cells) >= 2:
- teacher_data["address"] = cells[1].text.strip()
-
- # 提取导师类型
- if len(rows) > 6:
- cells = rows[6].find_all('td')
- if len(cells) >= 2:
- teacher_data["tutorType"] = cells[1].text.strip()
-
- # 提取详细信息部分
- content_divs = soup.select('.con01_t')
- for div in content_divs:
- heading = div.find('h3')
- if not heading:
- continue
-
- heading_text = heading.text.strip()
-
- # 获取该部分的所有段落文本
- paragraphs = [p.text.strip() for p in div.find_all('p') if p.text.strip()]
- section_content = '\n'.join(paragraphs)
-
- # 根据标题将内容映射到相应字段
- if '教育与工作经历' in heading_text:
- teacher_data["eduWorkHistory"] = section_content
- elif '研究方向' in heading_text:
- teacher_data["researchDirection"] = section_content
- elif '近5年承担的科研项目' in heading_text or '近五年承担的科研项目' in heading_text:
- teacher_data["recentProjects"] = section_content
- # 计算项目数量
- project_count = len([p for p in paragraphs if p.strip().startswith(str(len(paragraphs) - paragraphs.index(p))+".")])
- if project_count > 0:
- teacher_data["projects"] = f"{project_count}项"
- else:
- teacher_data["projects"] = f"{len(paragraphs)}项"
- elif '代表性学术论文' in heading_text:
- teacher_data["representativePapers"] = section_content
- # 计算论文数量
- paper_count = len([p for p in paragraphs if p.strip().startswith("[")])
- if paper_count > 0:
- teacher_data["papers"] = f"{paper_count}篇"
- else:
- teacher_data["papers"] = f"{len(paragraphs)}篇"
- elif '授权国家发明专利' in heading_text or '专利' in heading_text:
- teacher_data["patents"] = section_content
-
- # 提取照片
- photo_element = soup.select_one('.teacherInfo .img img')
- if photo_element and photo_element.get('src'):
- img_src = photo_element['src']
-
- # 处理相对路径,构建完整的图片URL
- if img_src.startswith('../../../'):
- # 从URL获取基础路径(移除文件名和最后两级目录)
- url_parts = url.split('/')
- if len(url_parts) >= 4:
- base_path = '/'.join(url_parts[:-3])
- img_url = f"{base_path}/{img_src[9:]}" # 移除 '../../../'
- else:
- img_url = urllib.parse.urljoin(base_url, img_src)
- else:
- img_url = urllib.parse.urljoin(base_url, img_src)
-
- # 直接保存完整的图片URL,不下载到本地
- teacher_data["photo"] = img_url
-
- # 保存到数据库
- db_talent = models.Talent(**teacher_data)
- db.add(db_talent)
- db.commit()
- db.refresh(db_talent)
-
- # 返回结果
- return {"success": True, "message": "教师数据已成功抓取并保存", "data": teacher_data}
-
- except Exception as e:
- print(f"抓取教师数据错误: {str(e)}")
- return JSONResponse(
- status_code=500,
- content={"error": f"抓取失败: {str(e)}"},
- )
-
-# 添加获取单个教师详情的接口
-@app.get("/talents/{talent_id}", response_model=schemas.Talent)
-async def get_talent_detail(talent_id: str, db: Session = Depends(get_db)):
- talent = crud.get_talent(db, talent_id)
- if talent is None:
- raise HTTPException(status_code=404, detail="教师信息不存在")
- return talent
-
-# 删除单个人才
-@app.delete("/talents/{talent_id}")
-async def delete_talent(
- talent_id: str,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- # 查找要删除的人才
- talent = crud.get_talent(db, talent_id)
- if talent is None:
- raise HTTPException(status_code=404, detail="教师信息不存在")
-
- # 删除人才记录
- success = crud.delete_talent(db, talent_id)
- if not success:
- raise HTTPException(status_code=500, detail="删除失败")
-
- return {"success": True, "message": "删除成功"}
-
-# 添加获取单个工程研究中心详情的接口
-@app.get("/labs/{lab_id}", response_model=schemas.Lab)
-async def get_lab_detail(lab_id: str, db: Session = Depends(get_db)):
- lab = crud.get_lab(db, lab_id)
- if lab is None:
- raise HTTPException(status_code=404, detail="工程研究中心信息不存在")
- return lab
-
-# 获取所有维度
-@app.get("/dimensions", response_model=List[schemas.Dimension])
-async def get_dimensions(db: Session = Depends(get_db)):
- dimensions = crud.get_all_dimensions(db)
- return dimensions
-
-# 获取特定类别的维度
-@app.get("/dimensions/{category}", response_model=List[schemas.Dimension])
-async def get_dimensions_by_category(category: str, db: Session = Depends(get_db)):
- dimensions = db.query(models.Dimension).filter(models.Dimension.category == category).all()
-
- # 处理返回数据,添加subDimensions字段
- result = []
- for dim in dimensions:
- dim_dict = {
- "id": dim.id,
- "name": dim.name,
- "weight": dim.weight,
- "category": dim.category,
- "description": dim.description,
- "sub_dimensions": dim.sub_dimensions,
- "subDimensions": dim.sub_dimensions # 添加subDimensions与sub_dimensions内容相同
- }
- result.append(dim_dict)
-
- return result
-
-# 创建新维度
-@app.post("/dimensions", response_model=schemas.Dimension)
-async def create_dimension(
- dimension: schemas.DimensionCreate,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- return crud.create_dimension(
- db=db,
- name=dimension.name,
- weight=dimension.weight,
- category=dimension.category,
- description=dimension.description
- )
-
-@app.put("/dimensions/{dimension_id}", response_model=schemas.Dimension)
-async def update_dimension(
- dimension_id: int,
- dimension: schemas.DimensionCreate,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- db_dimension = crud.get_dimension(db, dimension_id)
- if not db_dimension:
- raise HTTPException(status_code=404, detail="Dimension not found")
-
- dimension_data = dimension.dict(exclude_unset=True)
- return crud.update_dimension(db, dimension_id, dimension_data)
-
-@app.delete("/dimensions/{dimension_id}", response_model=dict)
-async def delete_dimension(
- dimension_id: int,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- success = crud.delete_dimension(db, dimension_id)
- if not success:
- raise HTTPException(status_code=404, detail="Dimension not found")
- return {"success": True}
-
-# 添加新的API端点用于保存维度数据
-@app.post("/api/save-dimensions")
-async def save_dimensions(
- request: dict = Body(...),
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- try:
- dimensions = request.get("dimensions", [])
- category = request.get("category", "")
-
- if not dimensions:
- return JSONResponse(
- status_code=400,
- content={"success": False, "message": "未提供维度数据"}
- )
-
- if not category:
- return JSONResponse(
- status_code=400,
- content={"success": False, "message": "未提供分类信息"}
- )
-
- # 根据category删除现有维度并重新创建
- # 首先删除该类别的所有现有维度
- existing_dimensions = db.query(models.Dimension).filter(models.Dimension.category == category).all()
- for dim in existing_dimensions:
- db.delete(dim)
-
- # 添加新的维度
- for dim_data in dimensions:
- new_dimension = models.Dimension(
- name=dim_data.get("name", ""),
- weight=dim_data.get("weight", 1.0),
- category=category,
- description=dim_data.get("description", "")
- )
- db.add(new_dimension)
-
- db.commit()
-
- return {"success": True, "message": "维度数据保存成功"}
- except Exception as e:
- db.rollback()
- return JSONResponse(
- status_code=500,
- content={"success": False, "message": f"保存维度数据失败: {str(e)}"}
- )
-
-# 添加处理人才文档的新API
-@app.post("/api/upload-talent-document")
-async def upload_talent_document(
- file: UploadFile = File(...),
- current_user: models.User = Depends(get_current_active_user)
-):
- # 检查文件是否为支持的文档格式
- if not file.filename.endswith(('.docx', '.pdf', '.doc')):
- raise HTTPException(status_code=400, detail="只支持.docx、.pdf、.doc格式的文档")
-
- try:
- # 创建临时文件保存上传的文档
- temp_file_path = f"temp_{uuid.uuid4()}.{file.filename.split('.')[-1]}"
- with open(temp_file_path, "wb") as buffer:
- shutil.copyfileobj(file.file, buffer)
-
- # 根据文件类型解析文档
- if file.filename.endswith('.docx'):
- talent_data = extract_talent_info_from_docx(temp_file_path)
- else:
- # 对于PDF和DOC文件,暂时返回基础模板数据
- talent_data = get_default_talent_data()
- talent_data["name"] = file.filename.split('.')[0] # 使用文件名作为姓名
-
- # 删除临时文件
- if os.path.exists(temp_file_path):
- os.remove(temp_file_path)
-
- return JSONResponse(
- status_code=200,
- content={"success": True, "data": talent_data}
- )
-
- except Exception as e:
- # 确保删除任何临时文件
- if 'temp_file_path' in locals() and os.path.exists(temp_file_path):
- os.remove(temp_file_path)
-
- print(f"处理人才文档时发生错误: {str(e)}")
- raise HTTPException(status_code=500, detail=f"处理文档时发生错误: {str(e)}")
-
- finally:
- # 关闭文件
- await file.close()
-
-# 添加处理Word文档的新API
-@app.post("/api/upload-lab-document")
-async def upload_lab_document(
- file: UploadFile = File(...),
- current_user: models.User = Depends(get_current_active_user)
-):
- # 检查文件是否为Word文档
- if not file.filename.endswith(('.docx')):
- raise HTTPException(status_code=400, detail="只支持.docx格式的Word文档")
-
- try:
- # 创建临时文件保存上传的文档
- temp_file_path = f"temp_{uuid.uuid4()}.docx"
- with open(temp_file_path, "wb") as buffer:
- shutil.copyfileobj(file.file, buffer)
-
- # 解析Word文档
- lab_data = extract_lab_info_from_docx(temp_file_path)
-
- # 删除临时文件
- if os.path.exists(temp_file_path):
- os.remove(temp_file_path)
-
- return JSONResponse(
- status_code=200,
- content={"success": True, "data": lab_data}
- )
-
- except Exception as e:
- # 确保删除任何临时文件
- if os.path.exists(temp_file_path):
- os.remove(temp_file_path)
-
- print(f"处理文档时发生错误: {str(e)}")
- raise HTTPException(status_code=500, detail=f"处理文档时发生错误: {str(e)}")
-
- finally:
- # 关闭文件
- await file.close()
-
-def extract_talent_info_from_docx(file_path):
- """从Word文档中提取人才信息"""
- doc = docx.Document(file_path)
- full_text = []
-
- # 提取所有段落文本
- for para in doc.paragraphs:
- if para.text.strip():
- full_text.append(para.text.strip())
-
- # 合并文本以便于处理
- text_content = "\n".join(full_text)
-
- # 初始化人才数据
- talent_data = get_default_talent_data()
-
- # 提取姓名(假设第一行或包含"姓名"的行)
- if full_text:
- # 尝试从第一行提取姓名
- first_line = full_text[0]
- if len(first_line) <= 10 and not any(char in first_line for char in [':', ':', '简历', '履历']):
- talent_data["name"] = first_line
-
- # 使用正则表达式提取各种信息
- name_pattern = re.compile(r'姓名[::]\s*([^\n\r]+)')
- name_match = name_pattern.search(text_content)
- if name_match:
- talent_data["name"] = name_match.group(1).strip()
-
- # 提取编号
- id_pattern = re.compile(r'编号[::]\s*([A-Za-z0-9]+)')
- id_match = id_pattern.search(text_content)
- if id_match:
- talent_data["idcode"] = id_match.group(1)
-
- # 提取性别
- gender_pattern = re.compile(r'性别[::]\s*([男女])')
- gender_match = gender_pattern.search(text_content)
- if gender_match:
- talent_data["gender"] = gender_match.group(1)
-
- # 提取出生日期
- birth_pattern = re.compile(r'出生[日期年月]*[::]\s*(\d{4}[年.-]\d{1,2}[月.-]\d{1,2}|\d{4}[年.-]\d{1,2})')
- birth_match = birth_pattern.search(text_content)
- if birth_match:
- talent_data["birthDate"] = birth_match.group(1)
-
- # 提取职称
- title_pattern = re.compile(r'职称[::]\s*([^\n\r]+)')
- title_match = title_pattern.search(text_content)
- if title_match:
- talent_data["title"] = title_match.group(1).strip()
-
- # 提取学历
- education_pattern = re.compile(r'学历[::]\s*([^\n\r]+)')
- education_match = education_pattern.search(text_content)
- if education_match:
- talent_data["education"] = education_match.group(1).strip()
-
- # 提取邮箱
- email_pattern = re.compile(r'邮箱[::]?\s*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})')
- email_match = email_pattern.search(text_content)
- if email_match:
- talent_data["email"] = email_match.group(1)
-
- # 提取电话
- phone_pattern = re.compile(r'电话[::]?\s*(\d{11}|\d{3,4}-\d{7,8})')
- phone_match = phone_pattern.search(text_content)
- if phone_match:
- talent_data["phone"] = phone_match.group(1)
-
- # 提取研究方向
- research_pattern = re.compile(r'研究方向[::]\s*([^\n\r]+)')
- research_match = research_pattern.search(text_content)
- if research_match:
- talent_data["researchDirection"] = research_match.group(1).strip()
-
- # 提取工作经历(查找包含"工作经历"、"教育经历"等关键词的段落)
- work_keywords = ['工作经历', '教育经历', '学习经历', '履历']
- for i, line in enumerate(full_text):
- if any(keyword in line for keyword in work_keywords):
- # 取该行及后续几行作为工作经历
- work_history = []
- for j in range(i, min(i + 5, len(full_text))):
- if full_text[j].strip():
- work_history.append(full_text[j].strip())
- talent_data["eduWorkHistory"] = "\n".join(work_history)
- break
-
- # 提取论文信息
- papers_pattern = re.compile(r'论文[数量]*[::]\s*(\d+)')
- papers_match = papers_pattern.search(text_content)
- if papers_match:
- talent_data["papers"] = f"{papers_match.group(1)}篇"
-
- # 提取项目信息
- projects_pattern = re.compile(r'项目[数量]*[::]\s*(\d+)')
- projects_match = projects_pattern.search(text_content)
- if projects_match:
- talent_data["projects"] = f"{projects_match.group(1)}项"
-
- # 生成评估数据(基于提取到的信息)
- try:
- papers_num = int(re.search(r'\d+', talent_data["papers"] or "0").group(0) or 0)
- projects_num = int(re.search(r'\d+', talent_data["projects"] or "0").group(0) or 0)
-
- # 简单的评分算法
- work_score = min(100, 60 + len(talent_data["eduWorkHistory"]) // 10)
- research_score = min(100, 60 + papers_num * 2)
- project_score = min(100, 60 + projects_num * 3)
- paper_score = min(100, 60 + papers_num * 2.5)
- patent_score = min(100, 60 + random.randint(0, 20)) # 随机生成专利分数
- impact_score = min(100, (research_score + project_score + paper_score) // 3)
-
- talent_data["evaluationData"] = [
- work_score, # 工作经历
- research_score, # 研究方向
- project_score, # 科研项目
- paper_score, # 学术论文
- patent_score, # 专利专著
- impact_score # 学术影响
- ]
- except Exception as e:
- print(f"计算人才评分时出错: {str(e)}")
- # 出错时保留默认评分
-
- return talent_data
-
-def get_default_talent_data():
- """获取默认的人才数据模板"""
- return {
- "idcode": "",
- "name": "",
- "gender": "",
- "birthDate": "",
- "title": "",
- "position": "",
- "education": "",
- "educationBackground": "",
- "address": "",
- "academicDirection": "",
- "talentPlan": "",
- "officeLocation": "",
- "email": "",
- "phone": "",
- "tutorType": "",
- "papers": "",
- "projects": "",
- "photo": "",
- "eduWorkHistory": "",
- "researchDirection": "",
- "recentProjects": "",
- "representativePapers": "",
- "patents": "",
- "evaluationData": [60, 60, 60, 60, 60, 60] # 默认评估数据
- }
-
-def extract_lab_info_from_docx(file_path):
- """从Word文档中提取工程研究中心信息"""
- doc = docx.Document(file_path)
- full_text = []
-
- # 提取所有段落文本
- for para in doc.paragraphs:
- if para.text.strip():
- full_text.append(para.text.strip())
-
- # 合并文本以便于处理
- text_content = "\n".join(full_text)
-
- # 初始化工程研究中心数据
- lab_data = {
- "name": "",
- "idcode": "",
- "personnel": "",
- "nationalProjects": "",
- "otherProjects": "",
- "achievements": "",
- "labAchievements": "",
- "evaluationData": [60, 60, 60, 60, 60, 60] # 默认评估数据
- }
-
- # 提取工程研究中心名称(假设第一行是工程研究中心名称)
- if full_text:
- lab_data["name"] = full_text[0]
-
- # 提取工程研究中心编号(寻找带有"编号"的行)
- id_pattern = re.compile(r'编号[::]\s*([A-Za-z0-9]+)')
- id_matches = id_pattern.search(text_content)
- if id_matches:
- lab_data["idcode"] = id_matches.group(1)
-
- # 提取人员数量
- personnel_pattern = re.compile(r'人员[数量]*[::]\s*(\d+)')
- personnel_matches = personnel_pattern.search(text_content)
- if personnel_matches:
- lab_data["personnel"] = f"{personnel_matches.group(1)}人"
-
- # 提取国家级项目数量
- national_projects_pattern = re.compile(r'国家级项目[::]\s*(\d+)')
- np_matches = national_projects_pattern.search(text_content)
- if np_matches:
- lab_data["nationalProjects"] = f"{np_matches.group(1)}项"
-
- # 提取其他项目数量
- other_projects_pattern = re.compile(r'(其他|其它)项目[::]\s*(\d+)')
- op_matches = other_projects_pattern.search(text_content)
- if op_matches:
- lab_data["otherProjects"] = f"{op_matches.group(2)}项"
-
- # 提取成果数量
- achievements_pattern = re.compile(r'成果[数量]*[::]\s*(\d+)')
- ach_matches = achievements_pattern.search(text_content)
- if ach_matches:
- lab_data["achievements"] = f"{ach_matches.group(1)}项"
-
- # 提取工程研究中心成就信息(取文本的中间部分作为工程研究中心成就)
- if len(full_text) > 2:
- # 跳过第一行(标题)和最后一行,取中间的文本作为成就描述
- lab_data["labAchievements"] = "\n".join(full_text[1:-1])
-
- # 根据提取到的信息,给出一个评估评分
- # 这里可以编写更复杂的评分算法,示例中使用简单方法
- try:
- # 解析数字
- personnel_num = int(re.search(r'\d+', lab_data["personnel"] or "0").group(0) or 0)
- national_num = int(re.search(r'\d+', lab_data["nationalProjects"] or "0").group(0) or 0)
- other_num = int(re.search(r'\d+', lab_data["otherProjects"] or "0").group(0) or 0)
- ach_num = int(re.search(r'\d+', lab_data["achievements"] or "0").group(0) or 0)
-
- # 简单计算评分 (仅示例)
- innovation_score = min(100, 50 + national_num * 5)
- research_score = min(100, 50 + (national_num + other_num) * 2)
- transform_score = min(100, 50 + ach_num * 2)
- discipline_score = min(100, 50 + personnel_num * 2)
- contribution_score = min(100, 50 + (national_num + ach_num) * 1.5)
- potential_score = min(100, (innovation_score + research_score + transform_score) / 3)
-
- lab_data["evaluationData"] = [
- innovation_score, # 创新水平
- research_score, # 研究能力
- transform_score, # 成果转化
- discipline_score, # 学科建设
- contribution_score, # 行业贡献
- potential_score # 发展潜力
- ]
- except Exception as e:
- print(f"计算评分时出错: {str(e)}")
- # 出错时保留默认评分
-
- return lab_data
-
-# 添加新的API端点用于清空所有人才和工程研究中心信息
-@app.post("/api/clear-all-data")
-async def clear_all_data(
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- try:
- # 删除所有人才数据
- db.query(models.Talent).delete()
-
- # 删除所有工程研究中心数据
- db.query(models.Lab).delete()
-
- # 提交事务
- db.commit()
-
- return {"success": True, "message": "所有数据已清空"}
- except Exception as e:
- db.rollback()
- return JSONResponse(
- status_code=500,
- content={"success": False, "message": f"清空数据失败: {str(e)}"}
- )
-
-# 添加端点用于保存二级维度结构
-@app.post("/dimensions/save")
-async def save_dimensions_structure(
- request: schemas.SaveDimensionsRequest,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db)
-):
- try:
- category = request.category
- new_dimensions = request.dimensions
-
- # 先删除该类别的所有现有维度
- existing_dimensions = db.query(models.Dimension).filter(models.Dimension.category == category).all()
- for dim in existing_dimensions:
- db.delete(dim)
-
- # 添加新的维度结构
- for dimension in new_dimensions:
- sub_dimensions_data = None
- # 检查是否有子维度数据,优先使用subDimensions字段(前端使用的字段名)
- if hasattr(dimension, 'subDimensions') and dimension.subDimensions:
- sub_dimensions_data = [
- {"name": sub.name, "weight": sub.weight, "description": getattr(sub, 'description', None)}
- for sub in dimension.subDimensions
- ]
- # 兼容处理sub_dimensions字段
- elif dimension.sub_dimensions:
- sub_dimensions_data = [
- {"name": sub.name, "weight": sub.weight, "description": getattr(sub, 'description', None)}
- for sub in dimension.sub_dimensions
- ]
-
- db_dimension = models.Dimension(
- name=dimension.name,
- category=category,
- weight=0.0, # 一级维度不需要权重
- description=dimension.description,
- sub_dimensions=sub_dimensions_data
- )
- db.add(db_dimension)
-
- db.commit()
- return {"success": True, "message": f"已成功保存{len(new_dimensions)}个维度及其子维度"}
-
- except Exception as e:
- db.rollback()
- return JSONResponse(
- status_code=500,
- content={"success": False, "message": f"保存维度失败: {str(e)}"}
- )
-
-# 启动时初始化数据库
-@app.on_event("startup")
-async def startup_db_client():
- # 创建表(如果不存在)
- models.Base.metadata.create_all(bind=engine)
-
- # 运行数据库表结构修改脚本
- try:
- from alter_table import check_and_alter_table
- check_and_alter_table()
- print("数据库表结构检查完成")
- except Exception as e:
- print(f"运行表结构修改脚本时出错: {e}")
-
- # 初始化维度数据
- from init_dimensions import init_dimensions
- init_dimensions()
-
- # 初始化其他数据
- from database import SessionLocal
- db = SessionLocal()
- try:
- # 检查是否已经存在用户数据
- existing_user = db.query(models.User).filter(models.User.username == "admin").first()
-
- if not existing_user:
- print("初始化默认用户...")
- hashed_password = get_password_hash("123456")
- default_user = models.User(
- username="admin",
- email="admin@example.com",
- full_name="系统管理员",
- hashed_password=hashed_password
- )
- db.add(default_user)
- db.commit()
- print("默认用户已创建: admin/123456")
- finally:
- db.close()
-
-if __name__ == "__main__":
- import uvicorn
- uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
\ No newline at end of file
diff --git a/backend/models.py b/backend/models.py
deleted file mode 100644
index d20cd5f..0000000
--- a/backend/models.py
+++ /dev/null
@@ -1,221 +0,0 @@
-from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Float, Text, JSON
-from sqlalchemy.orm import relationship
-from database import Base
-
-class User(Base):
- __tablename__ = "users"
-
- id = Column(Integer, primary_key=True, index=True)
- username = Column(String, unique=True, index=True)
- email = Column(String, nullable=True)
- full_name = Column(String, nullable=True)
- hashed_password = Column(String)
- disabled = Column(Boolean, default=False)
-
-class Talent(Base):
- __tablename__ = "talents"
-
- id = Column(String, primary_key=True, index=True)
- idcode = Column(String, nullable=True, index=True)
- name = Column(String, index=True)
- gender = Column(String, nullable=True)
- birthDate = Column(String, nullable=True)
- title = Column(String, nullable=True)
- position = Column(String, nullable=True)
- education = Column(String, nullable=True)
- educationBackground = Column(String, nullable=True)
- address = Column(String, nullable=True)
- academicDirection = Column(String, nullable=True)
- talentPlan = Column(String, nullable=True)
- officeLocation = Column(String, nullable=True)
- email = Column(String, nullable=True)
- phone = Column(String, nullable=True)
- tutorType = Column(String, nullable=True)
- papers = Column(String, nullable=True)
- projects = Column(String, nullable=True)
- photo = Column(Text, nullable=True)
- eduWorkHistory = Column(Text, nullable=True)
- researchDirection = Column(Text, nullable=True)
- recentProjects = Column(Text, nullable=True)
- representativePapers = Column(Text, nullable=True)
- patents = Column(Text, nullable=True)
- evaluationData = Column(JSON, nullable=True)
-
-class Lab(Base):
- __tablename__ = "labs"
-
- id = Column(String, primary_key=True, index=True)
- idcode = Column(String, nullable=True, index=True)
-
- # 基本信息
- name = Column(String, index=True) # 中心名称
- center_number = Column(String, nullable=True) # 中心编号
- school = Column(String, nullable=True) # 所属学校
- department = Column(String, nullable=True) # 主管部门
- field = Column(String, nullable=True) # 所属领域
-
- # 年度数据 - 存储为JSON格式,包含多年数据
- annual_data = Column(JSON, nullable=True)
-
- # 当前年度的主要信息(用于快速查询和显示)
- current_year = Column(String, nullable=True) # 当前评估年份
-
- # 技术攻关与创新情况
- innovation_situation = Column(Text, nullable=True)
-
- # 总体情况
- overall_situation = Column(Text, nullable=True)
-
- # 工程化案例
- engineering_cases = Column(Text, nullable=True)
-
- # 行业服务情况
- industry_service = Column(Text, nullable=True)
-
- # 学科发展支撑情况
- discipline_support = Column(Text, nullable=True)
-
- # 人才培养情况
- talent_cultivation = Column(Text, nullable=True)
-
- # 研究队伍建设情况
- team_building = Column(Text, nullable=True)
-
- # 主管部门、依托单位支持情况
- department_support = Column(Text, nullable=True)
-
- # 仪器设备开放共享情况
- equipment_sharing = Column(Text, nullable=True)
-
- # 学风建设情况
- academic_style = Column(Text, nullable=True)
-
- # 技术委员会工作情况
- technical_committee = Column(Text, nullable=True)
-
- # 下一年度工作计划
- next_year_plan = Column(Text, nullable=True)
-
- # 问题与建议
- problems_suggestions = Column(Text, nullable=True)
-
- # 工程中心负责人意见
- director_opinion = Column(Text, nullable=True)
-
- # 依托单位意见
- institution_opinion = Column(Text, nullable=True)
-
- # 研究方向/学术带头人
- research_directions = Column(Text, nullable=True)
-
- # 统计数据字段
- national_awards_first = Column(Integer, default=0) # 国家级科技奖励一等奖
- national_awards_second = Column(Integer, default=0) # 国家级科技奖励二等奖
- provincial_awards_first = Column(Integer, default=0) # 省、部级科技奖励一等奖
- provincial_awards_second = Column(Integer, default=0) # 省、部级科技奖励二等奖
- valid_patents = Column(Integer, default=0) # 有效专利
- other_ip = Column(Integer, default=0) # 其他知识产权
- international_standards = Column(Integer, default=0) # 国际/国家标准
- industry_standards = Column(Integer, default=0) # 行业/地方标准
-
- # 专利转化相关
- patent_transfer_contracts = Column(Integer, default=0) # 专利转让合同项数
- patent_transfer_amount = Column(Float, default=0.0) # 专利转让合同金额
- patent_license_contracts = Column(Integer, default=0) # 专利许可合同项数
- patent_license_amount = Column(Float, default=0.0) # 专利许可合同金额
- patent_valuation_contracts = Column(Integer, default=0) # 专利作价合同项数
- patent_valuation_amount = Column(Float, default=0.0) # 专利作价金额
-
- # 项目合作
- project_contracts = Column(Integer, default=0) # 项目合同项数
- project_amount = Column(Float, default=0.0) # 项目合同金额
-
- # 学科信息
- discipline_1 = Column(String, nullable=True) # 依托学科1
- discipline_2 = Column(String, nullable=True) # 依托学科2
- discipline_3 = Column(String, nullable=True) # 依托学科3
-
- # 人才培养数据
- doctoral_students = Column(Integer, default=0) # 在读博士生
- master_students = Column(Integer, default=0) # 在读硕士生
- graduated_doctoral = Column(Integer, default=0) # 当年毕业博士
- graduated_master = Column(Integer, default=0) # 当年毕业硕士
- undergraduate_courses = Column(Integer, default=0) # 承担本科课程
- graduate_courses = Column(Integer, default=0) # 承担研究生课程
- textbooks = Column(Integer, default=0) # 大专院校教材
-
- # 人员结构
- professors = Column(Integer, default=0) # 教授人数
- associate_professors = Column(Integer, default=0) # 副教授人数
- lecturers = Column(Integer, default=0) # 讲师人数
- domestic_visitors = Column(Integer, default=0) # 国内访问学者
- foreign_visitors = Column(Integer, default=0) # 国外访问学者
- postdoc_in = Column(Integer, default=0) # 本年度进站博士后
- postdoc_out = Column(Integer, default=0) # 本年度出站博士后
-
- # 基础设施
- center_area = Column(Float, default=0.0) # 工程中心面积
- new_area = Column(Float, default=0.0) # 当年新增面积
- fixed_personnel = Column(Integer, default=0) # 固定人员
- mobile_personnel = Column(Integer, default=0) # 流动人员
-
- # 经费情况
- total_funding = Column(Float, default=0.0) # 当年项目到账总经费
- vertical_funding = Column(Float, default=0.0) # 纵向经费
- horizontal_funding = Column(Float, default=0.0) # 横向经费
-
- # 服务情况
- technical_consultations = Column(Integer, default=0) # 技术咨询次数
- training_services = Column(Integer, default=0) # 培训服务人次
-
- # 原有字段保留兼容性
- personnel = Column(String, nullable=True)
- nationalProjects = Column(String, nullable=True)
- otherProjects = Column(String, nullable=True)
- achievements = Column(String, nullable=True)
- labAchievements = Column(Text, nullable=True)
- image = Column(Text, nullable=True)
- score = Column(Integer, nullable=True)
- evaluationData = Column(JSON, nullable=True)
- sub_dimension_evaluations = Column(JSON, nullable=True)
-
-class DashboardData(Base):
- __tablename__ = "dashboard"
-
- id = Column(Integer, primary_key=True)
- paperCount = Column(Integer)
- patentCount = Column(Integer)
- highImpactPapers = Column(Integer)
- keyProjects = Column(Integer)
- fundingAmount = Column(String)
- researcherStats = Column(JSON)
-
-class News(Base):
- __tablename__ = "news"
-
- id = Column(Integer, primary_key=True, index=True)
- title = Column(String)
- date = Column(String)
- dashboard_id = Column(Integer, ForeignKey("dashboard.id"))
-
- dashboard = relationship("DashboardData", back_populates="news_items")
-
-# 添加反向关系
-DashboardData.news_items = relationship("News", back_populates="dashboard")
-
-class Dimension(Base):
- __tablename__ = "dimensions"
-
- id = Column(Integer, primary_key=True, index=True)
- name = Column(String, index=True)
- weight = Column(Float, default=1.0)
- category = Column(String, nullable=True) # talent 或 lab
- description = Column(Text, nullable=True)
- parent_id = Column(Integer, ForeignKey("dimensions.id"), nullable=True)
-
- # 自引用关系,用于树形结构
- children = relationship("Dimension", back_populates="parent", cascade="all, delete-orphan")
- parent = relationship("Dimension", back_populates="children", remote_side=[id])
-
- # 存储子维度的JSON数据
- sub_dimensions = Column(JSON, nullable=True)
\ No newline at end of file
diff --git a/backend/network_diagnostic.py b/backend/network_diagnostic.py
deleted file mode 100644
index 3b4f8fc..0000000
--- a/backend/network_diagnostic.py
+++ /dev/null
@@ -1,304 +0,0 @@
-#!/usr/bin/env python3
-"""
-网络诊断工具
-用于诊断服务器部署后访问外部网站时的网络连接问题
-"""
-
-import socket
-import requests
-import time
-import urllib.parse
-import subprocess
-import platform
-from typing import Dict, List
-
-def diagnose_network_connectivity(url: str) -> Dict:
- """
- 综合诊断网络连接问题
- """
- print(f"开始诊断网络连接: {url}")
- results = {
- 'url': url,
- 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
- 'tests': {}
- }
-
- # 解析URL
- try:
- parsed_url = urllib.parse.urlparse(url)
- hostname = parsed_url.hostname
- port = parsed_url.port or (443 if parsed_url.scheme == 'https' else 80)
- results['hostname'] = hostname
- results['port'] = port
- except Exception as e:
- results['error'] = f"URL解析失败: {e}"
- return results
-
- # 1. DNS解析测试
- results['tests']['dns_resolution'] = test_dns_resolution(hostname)
-
- # 2. TCP连接测试
- results['tests']['tcp_connection'] = test_tcp_connection(hostname, port)
-
- # 3. HTTP请求测试
- results['tests']['http_request'] = test_http_request(url)
-
- # 4. 系统网络配置检查
- results['tests']['system_info'] = get_system_network_info()
-
- # 5. 建议解决方案
- results['suggestions'] = generate_suggestions(results['tests'])
-
- return results
-
-def test_dns_resolution(hostname: str) -> Dict:
- """测试DNS解析"""
- print(f"测试DNS解析: {hostname}")
- test_result = {
- 'status': 'unknown',
- 'details': {}
- }
-
- try:
- start_time = time.time()
- ip_address = socket.gethostbyname(hostname)
- dns_time = time.time() - start_time
-
- test_result['status'] = 'success'
- test_result['details'] = {
- 'ip_address': ip_address,
- 'resolution_time': f"{dns_time:.3f}秒"
- }
- print(f"DNS解析成功: {hostname} -> {ip_address} ({dns_time:.3f}秒)")
-
- except socket.gaierror as e:
- test_result['status'] = 'failed'
- test_result['details'] = {
- 'error': str(e),
- 'error_code': e.errno if hasattr(e, 'errno') else 'unknown'
- }
- print(f"DNS解析失败: {e}")
-
- return test_result
-
-def test_tcp_connection(hostname: str, port: int) -> Dict:
- """测试TCP连接"""
- print(f"测试TCP连接: {hostname}:{port}")
- test_result = {
- 'status': 'unknown',
- 'details': {}
- }
-
- try:
- start_time = time.time()
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.settimeout(10) # 10秒超时
-
- result = sock.connect_ex((hostname, port))
- connect_time = time.time() - start_time
- sock.close()
-
- if result == 0:
- test_result['status'] = 'success'
- test_result['details'] = {
- 'connection_time': f"{connect_time:.3f}秒",
- 'port_open': True
- }
- print(f"TCP连接成功: {hostname}:{port} ({connect_time:.3f}秒)")
- else:
- test_result['status'] = 'failed'
- test_result['details'] = {
- 'connection_time': f"{connect_time:.3f}秒",
- 'port_open': False,
- 'error_code': result
- }
- print(f"TCP连接失败: {hostname}:{port} (错误代码: {result})")
-
- except Exception as e:
- test_result['status'] = 'failed'
- test_result['details'] = {
- 'error': str(e)
- }
- print(f"TCP连接测试异常: {e}")
-
- return test_result
-
-def test_http_request(url: str) -> Dict:
- """测试HTTP请求"""
- print(f"测试HTTP请求: {url}")
- test_result = {
- 'status': 'unknown',
- 'details': {}
- }
-
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
- }
-
- try:
- start_time = time.time()
- response = requests.get(
- url,
- headers=headers,
- timeout=(10, 30), # (连接超时, 读取超时)
- verify=False,
- allow_redirects=True
- )
- request_time = time.time() - start_time
-
- test_result['status'] = 'success'
- test_result['details'] = {
- 'status_code': response.status_code,
- 'request_time': f"{request_time:.3f}秒",
- 'content_length': len(response.content),
- 'encoding': response.encoding,
- 'headers_count': len(response.headers)
- }
- print(f"HTTP请求成功: {response.status_code} ({request_time:.3f}秒)")
-
- except requests.exceptions.Timeout as e:
- test_result['status'] = 'timeout'
- test_result['details'] = {
- 'error': '请求超时',
- 'timeout_type': str(type(e).__name__)
- }
- print(f"HTTP请求超时: {e}")
-
- except requests.exceptions.ConnectionError as e:
- test_result['status'] = 'connection_error'
- test_result['details'] = {
- 'error': '连接错误',
- 'error_detail': str(e)
- }
- print(f"HTTP连接错误: {e}")
-
- except Exception as e:
- test_result['status'] = 'failed'
- test_result['details'] = {
- 'error': str(e)
- }
- print(f"HTTP请求失败: {e}")
-
- return test_result
-
-def get_system_network_info() -> Dict:
- """获取系统网络信息"""
- print("收集系统网络信息...")
- info = {
- 'platform': platform.system(),
- 'platform_version': platform.version(),
- 'python_version': platform.python_version()
- }
-
- try:
- # 获取本机IP地址
- hostname = socket.gethostname()
- local_ip = socket.gethostbyname(hostname)
- info['hostname'] = hostname
- info['local_ip'] = local_ip
- except:
- info['hostname'] = 'unknown'
- info['local_ip'] = 'unknown'
-
- # 检查网络配置(仅在Linux/Unix系统上)
- if platform.system() in ['Linux', 'Darwin']:
- try:
- # 检查DNS服务器
- with open('/etc/resolv.conf', 'r') as f:
- dns_servers = []
- for line in f:
- if line.startswith('nameserver'):
- dns_servers.append(line.split()[1])
- info['dns_servers'] = dns_servers
- except:
- info['dns_servers'] = 'unavailable'
-
- try:
- # 检查默认网关
- result = subprocess.run(['ip', 'route', 'show', 'default'],
- capture_output=True, text=True, timeout=5)
- if result.returncode == 0:
- info['default_gateway'] = result.stdout.strip()
- else:
- info['default_gateway'] = 'unavailable'
- except:
- info['default_gateway'] = 'unavailable'
-
- return info
-
-def generate_suggestions(test_results: Dict) -> List[str]:
- """根据测试结果生成建议"""
- suggestions = []
-
- # DNS问题
- if test_results.get('dns_resolution', {}).get('status') == 'failed':
- suggestions.extend([
- "DNS解析失败,请检查服务器的DNS配置",
- "尝试使用公共DNS服务器 (8.8.8.8, 114.114.114.114)",
- "检查 /etc/resolv.conf 文件中的DNS服务器配置"
- ])
-
- # TCP连接问题
- if test_results.get('tcp_connection', {}).get('status') == 'failed':
- suggestions.extend([
- "TCP连接失败,可能是防火墙阻止了连接",
- "检查服务器的出站规则设置",
- "确认目标端口是否开放"
- ])
-
- # HTTP请求问题
- http_status = test_results.get('http_request', {}).get('status')
- if http_status == 'timeout':
- suggestions.extend([
- "HTTP请求超时,网络延迟较高",
- "增加超时时间设置",
- "考虑使用重试机制"
- ])
- elif http_status == 'connection_error':
- suggestions.extend([
- "HTTP连接错误,可能是网络配置问题",
- "检查代理设置",
- "确认SSL/TLS证书配置"
- ])
-
- # 通用建议
- if not suggestions:
- suggestions.append("网络连接测试基本正常,问题可能在应用层面")
-
- suggestions.extend([
- "联系系统管理员检查网络配置",
- "考虑使用VPN或代理服务器",
- "检查服务器所在云平台的安全组设置"
- ])
-
- return suggestions
-
-def main():
- """主函数,用于命令行测试"""
- test_url = "https://ac.bit.edu.cn"
-
- print("=" * 60)
- print("网络连接诊断工具")
- print("=" * 60)
-
- results = diagnose_network_connectivity(test_url)
-
- print("\n" + "=" * 60)
- print("诊断结果")
- print("=" * 60)
-
- for test_name, test_result in results['tests'].items():
- print(f"\n{test_name}: {test_result['status']}")
- if 'details' in test_result:
- for key, value in test_result['details'].items():
- print(f" {key}: {value}")
-
- print("\n" + "=" * 60)
- print("建议解决方案")
- print("=" * 60)
-
- for i, suggestion in enumerate(results['suggestions'], 1):
- print(f"{i}. {suggestion}")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/backend/quick_network_test.py b/backend/quick_network_test.py
deleted file mode 100644
index 0519ecb..0000000
--- a/backend/quick_network_test.py
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/backend/quick_test.py b/backend/quick_test.py
deleted file mode 100644
index 54f3d6f..0000000
--- a/backend/quick_test.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python3
-"""
-快速网络连接测试脚本
-"""
-
-import socket
-import time
-import requests
-
-def quick_test():
- target_url = "https://ac.bit.edu.cn"
- target_host = "ac.bit.edu.cn"
-
- print("快速网络连接测试")
- print("=" * 40)
-
- # DNS测试
- print("1. DNS解析测试...")
- try:
- ip = socket.gethostbyname(target_host)
- print(f" 成功: {target_host} -> {ip}")
- except Exception as e:
- print(f" 失败: {e}")
- return
-
- # HTTP测试
- print("\n2. HTTP请求测试...")
- try:
- start = time.time()
- response = requests.get(
- target_url,
- timeout=(30, 90),
- verify=False,
- headers={'User-Agent': 'Mozilla/5.0'}
- )
- duration = time.time() - start
- print(f" 成功: {response.status_code} ({duration:.2f}秒)")
- except requests.exceptions.Timeout:
- print(" 超时: 网络延迟过高")
- except Exception as e:
- print(f" 失败: {e}")
-
- print("\n测试完成")
-
-if __name__ == "__main__":
- quick_test()
\ No newline at end of file
diff --git a/backend/requirements.txt b/backend/requirements.txt
deleted file mode 100644
index 125c258..0000000
--- a/backend/requirements.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-fastapi==0.109.2
-uvicorn==0.27.1
-pydantic==2.6.1
-python-multipart==0.0.9
-python-jose==3.3.0
-passlib==1.7.4
-bcrypt==4.1.2
-requests==2.31.0
-beautifulsoup4==4.12.2
-sqlalchemy==2.0.26
-aiosqlite==0.19.0
-alembic==1.13.1
-python-docx==0.8.11
\ No newline at end of file
diff --git a/backend/schemas.py b/backend/schemas.py
deleted file mode 100644
index 08b336f..0000000
--- a/backend/schemas.py
+++ /dev/null
@@ -1,246 +0,0 @@
-from pydantic import BaseModel
-from typing import List, Optional, Dict, Any
-
-# 用户模型
-class UserBase(BaseModel):
- username: str
- email: Optional[str] = None
- full_name: Optional[str] = None
-
-class UserCreate(UserBase):
- password: str
-
-class User(UserBase):
- id: int
- disabled: Optional[bool] = None
-
- class Config:
- from_attributes = True
-
-# 令牌模型
-class Token(BaseModel):
- access_token: str
- token_type: str
-
-class TokenData(BaseModel):
- username: str
-
-# 人才模型
-class TalentBase(BaseModel):
- name: str
- idcode: Optional[str] = None # 新增展示用ID字段
- gender: Optional[str] = None
- birthDate: Optional[str] = None
- title: Optional[str] = None
- position: Optional[str] = None
- education: Optional[str] = None
- educationBackground: Optional[str] = None
- address: Optional[str] = None
- academicDirection: Optional[str] = None
- talentPlan: Optional[str] = None
- officeLocation: Optional[str] = None
- email: Optional[str] = None
- phone: Optional[str] = None
- tutorType: Optional[str] = None
- papers: Optional[str] = None
- projects: Optional[str] = None
- photo: Optional[str] = None
- eduWorkHistory: Optional[str] = None
- researchDirection: Optional[str] = None
- recentProjects: Optional[str] = None
- representativePapers: Optional[str] = None
- patents: Optional[str] = None
- evaluationData: Optional[List[float]] = None
-
-class TalentCreate(TalentBase):
- id: str
-
-class Talent(TalentBase):
- id: str
-
- class Config:
- from_attributes = True
-
-# 工程研究中心模型
-class LabBase(BaseModel):
- name: str
- idcode: Optional[str] = None # 新增展示用ID字段
-
- # 基本信息
- center_number: Optional[str] = None # 中心编号
- school: Optional[str] = None # 所属学校
- department: Optional[str] = None # 主管部门
- field: Optional[str] = None # 所属领域
-
- # 年度数据 - 修改为可以接受字符串或字典列表
- annual_data: Optional[str] = None # 存储为JSON字符串
- current_year: Optional[str] = None # 当前评估年份
-
- # 详细信息
- innovation_situation: Optional[str] = None # 技术攻关与创新情况
- overall_situation: Optional[str] = None # 总体情况
- engineering_cases: Optional[str] = None # 工程化案例
- industry_service: Optional[str] = None # 行业服务情况
- discipline_support: Optional[str] = None # 学科发展支撑情况
- talent_cultivation: Optional[str] = None # 人才培养情况
- team_building: Optional[str] = None # 研究队伍建设情况
- department_support: Optional[str] = None # 主管部门、依托单位支持情况
- equipment_sharing: Optional[str] = None # 仪器设备开放共享情况
- academic_style: Optional[str] = None # 学风建设情况
- technical_committee: Optional[str] = None # 技术委员会工作情况
- next_year_plan: Optional[str] = None # 下一年度工作计划
- problems_suggestions: Optional[str] = None # 问题与建议
- director_opinion: Optional[str] = None # 工程中心负责人意见
- institution_opinion: Optional[str] = None # 依托单位意见
- research_directions: Optional[str] = None # 研究方向/学术带头人
-
- # 统计数据
- national_awards_first: Optional[int] = 0 # 国家级科技奖励一等奖
- national_awards_second: Optional[int] = 0 # 国家级科技奖励二等奖
- provincial_awards_first: Optional[int] = 0 # 省、部级科技奖励一等奖
- provincial_awards_second: Optional[int] = 0 # 省、部级科技奖励二等奖
- valid_patents: Optional[int] = 0 # 有效专利
- other_ip: Optional[int] = 0 # 其他知识产权
- international_standards: Optional[int] = 0 # 国际/国家标准
- industry_standards: Optional[int] = 0 # 行业/地方标准
-
- # 专利转化相关
- patent_transfer_contracts: Optional[int] = 0 # 专利转让合同项数
- patent_transfer_amount: Optional[float] = 0.0 # 专利转让合同金额
- patent_license_contracts: Optional[int] = 0 # 专利许可合同项数
- patent_license_amount: Optional[float] = 0.0 # 专利许可合同金额
- patent_valuation_contracts: Optional[int] = 0 # 专利作价合同项数
- patent_valuation_amount: Optional[float] = 0.0 # 专利作价金额
-
- # 项目合作
- project_contracts: Optional[int] = 0 # 项目合同项数
- project_amount: Optional[float] = 0.0 # 项目合同金额
-
- # 学科信息
- discipline_1: Optional[str] = None # 依托学科1
- discipline_2: Optional[str] = None # 依托学科2
- discipline_3: Optional[str] = None # 依托学科3
-
- # 人才培养数据
- doctoral_students: Optional[int] = 0 # 在读博士生
- master_students: Optional[int] = 0 # 在读硕士生
- graduated_doctoral: Optional[int] = 0 # 当年毕业博士
- graduated_master: Optional[int] = 0 # 当年毕业硕士
- undergraduate_courses: Optional[int] = 0 # 承担本科课程
- graduate_courses: Optional[int] = 0 # 承担研究生课程
- textbooks: Optional[int] = 0 # 大专院校教材
-
- # 人员结构
- professors: Optional[int] = 0 # 教授人数
- associate_professors: Optional[int] = 0 # 副教授人数
- lecturers: Optional[int] = 0 # 讲师人数
- domestic_visitors: Optional[int] = 0 # 国内访问学者
- foreign_visitors: Optional[int] = 0 # 国外访问学者
- postdoc_in: Optional[int] = 0 # 本年度进站博士后
- postdoc_out: Optional[int] = 0 # 本年度出站博士后
-
- # 基础设施
- center_area: Optional[float] = 0.0 # 工程中心面积
- new_area: Optional[float] = 0.0 # 当年新增面积
- fixed_personnel: Optional[int] = 0 # 固定人员
- mobile_personnel: Optional[int] = 0 # 流动人员
-
- # 经费情况
- total_funding: Optional[float] = 0.0 # 当年项目到账总经费
- vertical_funding: Optional[float] = 0.0 # 纵向经费
- horizontal_funding: Optional[float] = 0.0 # 横向经费
-
- # 服务情况
- technical_consultations: Optional[int] = 0 # 技术咨询次数
- training_services: Optional[int] = 0 # 培训服务人次
-
- # 原有字段保留兼容性
- personnel: Optional[str] = None
- nationalProjects: Optional[str] = None
- otherProjects: Optional[str] = None
- achievements: Optional[str] = None
- labAchievements: Optional[str] = None
- image: Optional[str] = None
- score: Optional[int] = None
- evaluationData: Optional[List[float]] = None
- sub_dimension_evaluations: Optional[Dict[str, Any]] = None # 存储二级维度评估数据
-
-class LabCreate(LabBase):
- id: str
-
-class Lab(LabBase):
- id: str
-
- class Config:
- from_attributes = True
-
-# 新闻模型
-class NewsBase(BaseModel):
- title: str
- date: str
-
-class NewsCreate(NewsBase):
- dashboard_id: int
-
-class News(NewsBase):
- id: int
- dashboard_id: int
-
- class Config:
- from_attributes = True
-
-# 仪表盘数据模型
-class DashboardData(BaseModel):
- paperCount: int
- patentCount: int
- highImpactPapers: int
- keyProjects: int
- fundingAmount: str
- researcherStats: Dict[str, int]
- newsData: List[Dict[str, str]]
-
- class Config:
- from_attributes = True
-
-# URL抓取请求模型
-class ScrapeRequest(BaseModel):
- url: str
-
-# 保存评估数据请求模型
-class SaveDataRequest(BaseModel):
- data_type: str # "talent" 或 "lab"
- data: Dict[str, Any]
-
-# 维度模型
-class SubDimensionBase(BaseModel):
- name: str
- weight: float = 1.0
- description: Optional[str] = None
-
-class SubDimension(SubDimensionBase):
- id: Optional[int] = None
-
- class Config:
- from_attributes = True
-
-class DimensionBase(BaseModel):
- name: str
- weight: float = 0.0 # 一级维度不需要权重
- category: Optional[str] = None
- description: Optional[str] = None
- sub_dimensions: Optional[List[SubDimension]] = None
- subDimensions: Optional[List[SubDimension]] = None # 添加subDimensions作为别名,兼容前端
-
-class DimensionCreate(DimensionBase):
- pass
-
-class Dimension(DimensionBase):
- id: int
-
- class Config:
- from_attributes = True
-
-# 批量保存维度的请求模型
-class SaveDimensionsRequest(BaseModel):
- dimensions: List[DimensionBase]
- category: str # "talent" 或 "lab"
\ No newline at end of file
diff --git a/backend/scrape_url_improved.py b/backend/scrape_url_improved.py
deleted file mode 100644
index e58f90f..0000000
--- a/backend/scrape_url_improved.py
+++ /dev/null
@@ -1,256 +0,0 @@
-import requests
-import socket
-import time
-import urllib.parse
-import random
-from bs4 import BeautifulSoup
-from fastapi.responses import JSONResponse
-
-async def scrape_url_improved(request):
- """
- 改进版本的URL抓取函数,解决服务器部署后的网络连接问题
- """
- try:
- print(f"开始抓取URL: {request.url}")
-
- # 设置请求头,模拟浏览器访问
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
- 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
- 'Connection': 'keep-alive',
- 'Upgrade-Insecure-Requests': '1',
- 'Cache-Control': 'max-age=0'
- }
-
- # 添加DNS解析测试
- try:
- url_parts = urllib.parse.urlparse(request.url)
- hostname = url_parts.hostname
- print(f"正在解析域名: {hostname}")
- ip_address = socket.gethostbyname(hostname)
- print(f"域名解析成功: {hostname} -> {ip_address}")
- except socket.gaierror as dns_error:
- print(f"DNS解析失败: {dns_error}")
- return JSONResponse(
- status_code=500,
- content={"error": f"DNS解析失败,无法访问 {hostname}: {str(dns_error)}"},
- )
-
- # 使用重试机制,增加连接和读取超时时间
- max_retries = 3
- timeout_settings = (30, 90) # (连接超时, 读取超时)
-
- response = None
- last_error = None
-
- for attempt in range(max_retries):
- try:
- print(f"第 {attempt + 1} 次尝试连接...")
- start_time = time.time()
-
- # 发送HTTP请求获取页面内容,增加超时时间和重试
- response = requests.get(
- request.url,
- headers=headers,
- timeout=timeout_settings,
- verify=False, # 临时禁用SSL验证,避免证书问题
- allow_redirects=True
- )
-
- elapsed_time = time.time() - start_time
- print(f"请求成功,耗时: {elapsed_time:.2f}秒")
- response.raise_for_status() # 如果请求失败,抛出异常
- break
-
- except requests.exceptions.Timeout as timeout_error:
- last_error = timeout_error
- print(f"第 {attempt + 1} 次尝试超时: {timeout_error}")
- if attempt < max_retries - 1:
- wait_time = (attempt + 1) * 2 # 递增等待时间
- print(f"等待 {wait_time} 秒后重试...")
- time.sleep(wait_time)
- continue
-
- except requests.exceptions.ConnectionError as conn_error:
- last_error = conn_error
- print(f"第 {attempt + 1} 次尝试连接错误: {conn_error}")
- if attempt < max_retries - 1:
- wait_time = (attempt + 1) * 2
- print(f"等待 {wait_time} 秒后重试...")
- time.sleep(wait_time)
- continue
-
- except requests.exceptions.RequestException as req_error:
- last_error = req_error
- print(f"第 {attempt + 1} 次尝试请求错误: {req_error}")
- if attempt < max_retries - 1:
- wait_time = (attempt + 1) * 2
- print(f"等待 {wait_time} 秒后重试...")
- time.sleep(wait_time)
- continue
-
- # 如果所有重试都失败了
- if response is None:
- error_msg = f"经过 {max_retries} 次重试后仍然无法连接到 {request.url}"
- if last_error:
- error_msg += f",最后错误: {str(last_error)}"
- print(error_msg)
- return JSONResponse(
- status_code=500,
- content={
- "error": error_msg,
- "suggestions": [
- "检查服务器网络连接",
- "确认目标网站是否可访问",
- "检查防火墙设置",
- "考虑配置代理服务器",
- "联系系统管理员检查网络配置"
- ]
- },
- )
-
- # 设置编码以正确处理中文字符
- response.encoding = 'utf-8'
-
- # 解析HTML
- soup = BeautifulSoup(response.text, 'html.parser')
-
- # 获取基础URL用于解析相对路径
- url_parts = urllib.parse.urlparse(request.url)
- base_url = f"{url_parts.scheme}://{url_parts.netloc}"
-
- # 初始化数据字典
- teacher_data = {
- "id": f"BLG{random.randint(10000, 99999)}",
- "photo": f"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='120' viewBox='0 0 100 120'%3E%3Crect width='100' height='120' fill='%234986ff' opacity='0.3'/%3E%3Ccircle cx='50' cy='45' r='25' fill='%234986ff' opacity='0.6'/%3E%3Ccircle cx='50' cy='95' r='35' fill='%234986ff' opacity='0.6'/%3E%3C/svg%3E",
- "evaluationData": [
- round(min(100, max(60, 70 + 20 * (0.5 - random.random())))) for _ in range(6)
- ]
- }
-
- # 从教师信息表提取基本信息
- info_table = soup.find('div', class_='wz_teacher')
- if info_table:
- table = info_table.find('table')
- if table:
- rows = table.find_all('tr')
-
- # 提取姓名、性别、出生年月
- if len(rows) > 0:
- cells = rows[0].find_all('td')
- if len(cells) >= 6:
- teacher_data["name"] = cells[1].text.strip()
- teacher_data["gender"] = cells[3].text.strip()
- teacher_data["birthDate"] = cells[5].text.strip()
-
- # 提取职称、职务、最高学历
- if len(rows) > 1:
- cells = rows[1].find_all('td')
- if len(cells) >= 6:
- teacher_data["title"] = cells[1].text.strip()
- position = cells[3].text.strip()
- teacher_data["position"] = position if position else ""
- teacher_data["education"] = cells[5].text.strip()
-
- # 提取学科方向
- if len(rows) > 2:
- cells = rows[2].find_all('td')
- if len(cells) >= 2:
- teacher_data["academicDirection"] = cells[1].text.strip()
-
- # 提取人才计划和办公地点
- if len(rows) > 3:
- cells = rows[3].find_all('td')
- if len(cells) >= 6:
- talent_plan = cells[1].text.strip()
- teacher_data["talentPlan"] = talent_plan if talent_plan else ""
- teacher_data["officeLocation"] = cells[5].text.strip()
-
- # 提取电子邮件和联系方式
- if len(rows) > 4:
- cells = rows[4].find_all('td')
- if len(cells) >= 6:
- email = cells[1].text.strip()
- teacher_data["email"] = email if email else ""
- phone = cells[5].text.strip()
- teacher_data["phone"] = phone if phone else ""
-
- # 提取通讯地址
- if len(rows) > 5:
- cells = rows[5].find_all('td')
- if len(cells) >= 2:
- teacher_data["address"] = cells[1].text.strip()
-
- # 提取导师类型
- if len(rows) > 6:
- cells = rows[6].find_all('td')
- if len(cells) >= 2:
- teacher_data["tutorType"] = cells[1].text.strip()
-
- # 提取照片
- photo_element = soup.select_one('.teacherInfo .img img')
- if photo_element and photo_element.get('src'):
- img_src = photo_element['src']
-
- # 处理相对路径,构建完整的图片URL
- if img_src.startswith('../../../'):
- # 从URL获取基础路径(移除文件名和最后两级目录)
- url_parts = request.url.split('/')
- if len(url_parts) >= 4:
- base_path = '/'.join(url_parts[:-3])
- img_url = f"{base_path}/{img_src[9:]}" # 移除 '../../../'
- else:
- img_url = urllib.parse.urljoin(base_url, img_src)
- else:
- img_url = urllib.parse.urljoin(base_url, img_src)
-
- # 直接保存完整的图片URL,不下载到本地
- teacher_data["photo"] = img_url
-
- # 提取详细信息部分
- content_divs = soup.select('.con01_t')
- for div in content_divs:
- heading = div.find('h3')
- if not heading:
- continue
-
- heading_text = heading.text.strip()
-
- # 获取该部分的所有段落文本
- paragraphs = [p.text.strip() for p in div.find_all('p') if p.text.strip()]
- section_content = '\n'.join(paragraphs)
-
- # 根据标题将内容映射到相应字段
- if '教育与工作经历' in heading_text:
- teacher_data["eduWorkHistory"] = section_content
- elif '研究方向' in heading_text:
- teacher_data["researchDirection"] = section_content
- elif '近5年承担的科研项目' in heading_text or '近五年承担的科研项目' in heading_text:
- teacher_data["recentProjects"] = section_content
- # 计算项目数量
- project_count = len([p for p in paragraphs if p.strip().startswith(str(len(paragraphs) - paragraphs.index(p))+".")])
- if project_count > 0:
- teacher_data["projects"] = f"{project_count}项"
- else:
- teacher_data["projects"] = f"{len(paragraphs)}项"
- elif '代表性学术论文' in heading_text:
- teacher_data["representativePapers"] = section_content
- # 计算论文数量
- paper_count = len([p for p in paragraphs if p.strip().startswith("[")])
- if paper_count > 0:
- teacher_data["papers"] = f"{paper_count}篇"
- else:
- teacher_data["papers"] = f"{len(paragraphs)}篇"
- elif '授权国家发明专利' in heading_text or '专利' in heading_text:
- teacher_data["patents"] = section_content
-
- print(f"抓取成功,提取到教师数据: {teacher_data.get('name', '未知')}")
- return teacher_data
-
- except Exception as e:
- print(f"抓取错误: {str(e)}")
- return JSONResponse(
- status_code=500,
- content={"error": f"抓取网页失败: {str(e)}"},
- )
\ No newline at end of file
diff --git a/backend/static/images/d5e83cac900c49478a8cd5130c8a2ad9.png b/backend/static/images/d5e83cac900c49478a8cd5130c8a2ad9.png
deleted file mode 100644
index 9b9b4b7..0000000
Binary files a/backend/static/images/d5e83cac900c49478a8cd5130c8a2ad9.png and /dev/null differ
diff --git a/docker-compose.yml b/docker-compose.yml
index e7ca0e8..6c9c8ba 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,21 +8,9 @@ services:
ports:
- "48100:48100"
restart: always
- depends_on:
- - backend
- networks:
- - app-network
-
- backend:
- build:
- context: ./backend
- dockerfile: Dockerfile
- ports:
- - "48996:48996"
- restart: always
networks:
- app-network
networks:
app-network:
- driver: bridge
\ No newline at end of file
+ driver: bridge
\ No newline at end of file
diff --git a/nginx.conf b/nginx.conf
index 815f44e..802ea7c 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -1,5 +1,5 @@
server {
- listen 48100;
+ listen 48103;
server_name localhost;
location / {
diff --git a/package.json b/package.json
index 4c9e4ca..ed488a9 100644
--- a/package.json
+++ b/package.json
@@ -7,10 +7,9 @@
"dev": "vite",
"dev:local": "cross-env NODE_ENV=development vite",
"dev:prod": "cross-env NODE_ENV=production vite",
- "build": "vite build",
+ "build": "vite build --mode production",
"preview": "vite preview",
- "backend": "cd backend && python app.py",
- "start": "concurrently \"npm run dev\" \"npm run backend\""
+ "start": "concurrently \"npm run dev\""
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
diff --git a/src/components/LabDetail.vue b/src/components/LabDetail.vue
index 0431de2..348f492 100644
--- a/src/components/LabDetail.vue
+++ b/src/components/LabDetail.vue
@@ -16,13 +16,7 @@
-
+
-->