Compare commits

...

2 Commits
master ... view

Author SHA1 Message Date
“zhuzihan” 
a8127a4bfa 二期 2025-06-13 16:21:35 +08:00
“zhuzihan” 
89130eaef2 暂存 2025-06-12 10:35:31 +08:00
29 changed files with 106 additions and 4202 deletions

1
.env.prod Normal file
View File

@ -0,0 +1 @@
NODE_ENV=production

View File

@ -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"]

View File

@ -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

View File

@ -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()

View File

@ -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

Binary file not shown.

Binary file not shown.

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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()

View File

@ -1 +0,0 @@

View File

@ -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()

View File

@ -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

View File

@ -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"

View File

@ -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)}"},
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

View File

@ -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
driver: bridge

View File

@ -1,5 +1,5 @@
server {
listen 48100;
listen 48103;
server_name localhost;
location / {

View File

@ -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",

View File

@ -12,7 +12,7 @@
<div class="title-line"></div>
</h1>
</div>
<div class="year-selector">
<!-- <div class="year-selector">
<el-dropdown>
<span class="el-dropdown-link">
2025 <el-icon class="el-icon--right"><arrow-down /></el-icon>
@ -25,7 +25,7 @@
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div> -->
</header>
<!-- 仪表盘内容 - 三列布局 -->
@ -42,19 +42,19 @@
<div class="research-stats">
<div class="stat-card">
<h3>论文数量</h3>
<div class="stat-value"><span class="stat-prefix">累计</span>3500</div>
<div class="stat-value"><span class="stat-prefix">累计</span>0</div>
</div>
<div class="stat-card">
<h3>专利数量</h3>
<div class="stat-value"><span class="stat-prefix">本年</span>2000</div>
<div class="stat-value"><span class="stat-prefix">本年</span>0</div>
</div>
<div class="stat-card">
<h3>高影响力论文</h3>
<div class="stat-value"><span class="stat-prefix">累计</span>100</div>
<div class="stat-value"><span class="stat-prefix">累计</span>0</div>
</div>
<div class="stat-card">
<h3>科研项目数量</h3>
<div class="stat-value"><span class="stat-prefix">国家重点</span>50<span></span></div>
<div class="stat-value"><span class="stat-prefix">国家重点</span>0<span></span></div>
</div>
</div>
</div>
@ -357,16 +357,12 @@
// API
const fetchDashboardData = async () => {
try {
const response = await fetch(`${getApiBaseUrl()}/dashboard`, {
headers: {
'Authorization': `Bearer ${getToken()}`
}
});
const response = await fetch(`${getApiBaseUrl()}/admin-api/pg/J-dashboard/dashboard`);
if (response.ok) {
const data = await response.json();
console.log("仪表盘数据:", data);
dashboardData.value = data;
dashboardData.value = data.data;
// UI
if (data) {

View File

@ -16,13 +16,7 @@
<!-- 主内容区域 -->
<div class="content-container">
<!-- 左侧维度设置 -->
<div class="dimension-sidebar">
<div class="sidebar-header">
<h1 class="sidebar-title">
<span class="home-link" @click="jumpToDashboard">首页</span>&nbsp;>&nbsp;工程研究中心评估
</h1>
</div>
<!-- <div class="dimension-sidebar">
<div class="dimension-content">
<h2 class="dimension-section-title">评估维度设置</h2>
@ -37,7 +31,6 @@
</div>
</div>
<!-- 二级维度列表 -->
<div class="sub-dimensions">
<div v-for="(subDim, subIndex) in dim.subDimensions" :key="`${index}-${subIndex}`" class="dimension-item sub-dimension">
<div class="dimension-name">
@ -57,12 +50,17 @@
</div>
</div>
</div>
</div>
</div> -->
<!-- 右侧内容区 -->
<div class="main-content">
<!-- 搜索和操作栏 -->
<div class="action-bar">
<div class="sidebar-header">
<h1 class="sidebar-title">
<span class="home-link" @click="jumpToDashboard">首页</span>&nbsp;>&nbsp;工程研究中心评估
</h1>
</div>
<div class="search-box">
<input
type="text"
@ -77,14 +75,15 @@
</button>
</div>
<button class="add-evaluation-btn" @click="openAddEvaluationDrawer">
<!-- <button class="add-evaluation-btn" @click="openAddEvaluationDrawer">
新增评估
</button>
</button> -->
</div>
<!-- 工程研究中心卡片列表 -->
<div class="lab-card-grid custom-scrollbar">
<div v-for="(lab, index) in filteredLabs" :key="index" class="lab-card" style="height: 440px;" @click="openLabDetail(lab)">
<div v-for="(lab, index) in filteredLabs" :key="index" class="lab-card" style="height: 440px;">
<!-- <div v-for="(lab, index) in filteredLabs" :key="index" class="lab-card" style="height: 440px;" @click="openLabDetail(lab)"> -->
<div class="card-header">
<span class="lab-id">ID: {{ lab.idcode || lab.id }}</span>
<!-- <span class="total-score">综合评估分数: <span class="score-value">{{ lab.score }}</span></span> -->
@ -241,8 +240,18 @@ function assignUniqueLabImages() {
onMounted(async () => {
try {
//
const response = await axios.get(`${getApiBaseUrl()}/dimensions/lab`);
dimensions.value = response.data;
const response = await axios.get(`${getApiBaseUrl()}/admin-api/pg/J-dimensions/lab`);
dimensions.value = response.data.data;
dimensions.value.forEach(dim => {
if (typeof dim.subDimensions === 'string') {
try {
dim.subDimensions = JSON.parse(dim.subDimensions);
} catch (error) {
console.error(`解析 subDimensions 失败: ${dim.name}`, error);
dim.subDimensions = []; //
}
}
});
//
await loadLabs();
// handleSearchloadLabs
@ -255,8 +264,8 @@ onMounted(async () => {
//
const loadLabs = async () => {
try {
const response = await axios.get(`${getApiBaseUrl()}/labs`);
labs.value = response.data;
const response = await axios.get(`${getApiBaseUrl()}/admin-api/pg/J-labs/labs`);
labs.value = response.data.data;
//
assignUniqueLabImages();

View File

@ -16,12 +16,7 @@
<!-- 主内容区域 -->
<div class="content-container">
<!-- 左侧维度设置 -->
<div class="dimension-sidebar">
<div class="sidebar-header">
<h1 class="sidebar-title">
<span class="home-link" @click="jumpToDashboard">首页</span>&nbsp;>&nbsp;教师科研人才评估
</h1>
</div>
<!-- <div class="dimension-sidebar">
<div class="dimension-content">
<h2 class="dimension-section-title">评估维度设置</h2>
@ -43,34 +38,36 @@
</div>
</div>
</div>
</div>
</div> -->
<!-- 右侧内容区 -->
<div class="main-content">
<!-- 搜索和操作栏 -->
<div class="action-bar">
<div class="sidebar-header">
<h1 class="sidebar-title">
<span class="home-link" @click="jumpToDashboard">首页</span>&nbsp;>&nbsp;教师科研人才评估
</h1>
</div>
<div class="search-box">
<input
type="text"
placeholder="请输入教师姓名或ID"
v-model="searchQuery"
@input="handleSearch"
/>
<input type="text" placeholder="请输入教师姓名或ID" v-model="searchQuery" @input="handleSearch" />
<button class="search-button">
<svg class="search-icon" viewBox="0 0 24 24" width="20" height="20">
<path fill="white" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
<path fill="white"
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</svg>
</button>
</div>
<button class="add-evaluation-btn" @click="openAddEvaluationDrawer">
<!-- <button class="add-evaluation-btn" @click="openAddEvaluationDrawer">
新增评估
</button>
</button> -->
</div>
<!-- 教师卡片列表 -->
<div class="teacher-card-grid custom-scrollbar">
<div v-for="(teacher, index) in filteredTeachers" :key="index" class="teacher-card" style="height: 300px;" @click="openTeacherDetail(teacher)">
<div v-for="(teacher, index) in filteredTeachers" :key="index" class="teacher-card" style="height: 300px;"
@click="openTeacherDetail(teacher)">
<div class="card-top">
<div class="teacher-left">
<div class="teacher-photo">
@ -86,7 +83,7 @@
</div>
<div class="info-row">
<span class="info-label">教育背景:</span>
<span class="info-value">{{ teacher.education }}</span>
</div>
<div class="info-row">
@ -110,7 +107,8 @@
</div>
<!-- 自定义维度对话框 -->
<el-dialog v-model="dimensionDialogVisible" :title="isEditingDimension ? '编辑维度' : '添加维度'" width="30%" custom-class="dimension-dialog">
<el-dialog v-model="dimensionDialogVisible" :title="isEditingDimension ? '编辑维度' : '添加维度'" width="30%"
custom-class="dimension-dialog">
<el-form :model="dimensionForm" :rules="dimensionRules" ref="dimensionFormRef" label-position="top">
<el-form-item label="维度名称" prop="name">
<el-input v-model="dimensionForm.name" placeholder="请输入维度名称" />
@ -128,19 +126,10 @@
</template>
</el-dialog>
<TalentDrawerDetail
v-model:visible="drawerVisible"
:is-edit="isEditMode"
:dimensions="dimensions"
:teacher-data="selectedTeacher"
@save="handleSaveEvaluation"
/>
<DimensionDrawer
v-model:visible="dimensionDrawerVisible"
:dimensions="dimensions"
category="talent"
@save="handleSaveDimensions"
/>
<TalentDrawerDetail v-model:visible="drawerVisible" :is-edit="isEditMode" :dimensions="dimensions"
:teacher-data="selectedTeacher" @save="handleSaveEvaluation" />
<DimensionDrawer v-model:visible="dimensionDrawerVisible" :dimensions="dimensions" category="talent"
@save="handleSaveDimensions" />
</template>
<script setup>
@ -183,24 +172,11 @@ const dimensionDrawerVisible = ref(false);
const showAddDimensionDialog = () => {
// 使dialog
// dimensionDialogVisible.value = true;
// 使drawer
dimensionDrawerVisible.value = true;
};
//
const handleSaveDimensions = (updatedDimensions) => {
//
dimensions.value = updatedDimensions;
//
updateAllRadarCharts();
dimensionDrawerVisible.value = false;
//
ElMessage.success('维度设置保存成功');
};
// Function to open the drawer for adding a new evaluation
const openAddEvaluationDrawer = () => {
@ -252,8 +228,8 @@ const dimensions = ref([]); // 改为空数组从API获取数据
onMounted(async () => {
try {
//
const response = await axios.get(`${getApiBaseUrl()}/dimensions/talent`);
dimensions.value = response.data;
const response = await axios.get(`${getApiBaseUrl()}/admin-api/pg/J-dimensions/talent`);
dimensions.value = response.data.data;
//
await loadTeachers();
//
@ -267,48 +243,8 @@ onMounted(async () => {
//
const loadTeachers = async () => {
try {
const response = await axios.get(`${getApiBaseUrl()}/talents`);
teachers.value = response.data;
//
teachers.value.forEach(teacher => {
// ID
if (!teacher.id) {
teacher.id = 'T' + Math.floor(Math.random() * 10000).toString();
}
// 使使
if (!teacher.photo) {
teacher.photo = '/image/人1.png';
}
//
if (!teacher.educationBackground) {
const eduOptions = [
"清华大学 博士",
"北京大学 博士",
"北京理工大学 博士",
"上海交通大学 博士",
"中国科学院 博士",
"哈尔滨工业大学 博士",
"华中科技大学 博士",
"武汉大学 博士"
];
teacher.educationBackground = eduOptions[Math.floor(Math.random() * eduOptions.length)];
}
//
const baseValue = teacher.id ? parseInt(teacher.id.slice(-2)) : Math.floor(Math.random() * 30);
// dimensions.value
if (dimensions.value.length > 0) {
teacher.evaluationData = dimensions.value.map((_, dimIndex) => {
const offset = (dimIndex * 7 + baseValue) % 35;
return Math.min(95, Math.max(20, Math.floor(Math.random() * 40) + 30 + offset));
});
}
});
const response = await axios.get(`${getApiBaseUrl()}/admin-api/pg/J-talents/talents`);
teachers.value = response.data.data;
handleSearch();
} catch (error) {
console.error('获取教师数据失败:', error);
@ -343,7 +279,7 @@ const editDimension = (dim, index) => {
//
const saveDimension = async () => {
if (!dimensionFormRef.value) return;
await dimensionFormRef.value.validate((valid) => {
if (valid) {
if (isEditingDimension.value && currentDimensionIndex.value >= 0) {
@ -354,7 +290,7 @@ const saveDimension = async () => {
dimensions.value.push({ ...dimensionForm.value, enabled: true });
}
dimensionDialogVisible.value = false;
//
updateAllRadarCharts();
}
@ -366,7 +302,7 @@ const deleteDimension = () => {
if (currentDimensionIndex.value >= 0) {
dimensions.value.splice(currentDimensionIndex.value, 1);
dimensionDialogVisible.value = false;
//
updateAllRadarCharts();
}
@ -395,15 +331,15 @@ const handleSearch = () => {
filteredTeachers.value = teachers.value;
} else {
filteredTeachers.value = teachers.value.filter(teacher =>
teacher.name.includes(searchQuery.value) ||
teacher.id.includes(searchQuery.value)
teacher.name.includes(searchQuery.value) ||
teacher.id.includes(searchQuery.value)
);
}
};
//
const initRadarCharts = () => {
filteredTeachers.value.forEach((teacher, index) => {
filteredTeachers.value?.forEach((teacher, index) => {
const chartDom = document.getElementById(`chart-${index}`);
if (!chartDom) return;
@ -419,7 +355,7 @@ const initRadarCharts = () => {
//
const baseValue = teacher.id ? parseInt(teacher.id.slice(-2)) : Math.floor(Math.random() * 30);
//
let evaluationData = teacher.evaluationData || [];
if (evaluationData.length !== dimensions.value.length) {
@ -506,7 +442,8 @@ const openTeacherDetail = (teacher) => {
flex-direction: column;
background-color: #0c1633;
color: white;
overflow: hidden; /* 防止页面整体出现滚动条 */
overflow: hidden;
/* 防止页面整体出现滚动条 */
}
.dashboard-header {
@ -541,7 +478,7 @@ const openTeacherDetail = (teacher) => {
}
.title-line {
border: 2px solid rgba(73,134,255,1);
border: 2px solid rgba(73, 134, 255, 1);
width: 150px;
}
@ -584,7 +521,8 @@ const openTeacherDetail = (teacher) => {
width: 280px;
display: flex;
flex-direction: column;
max-height: calc(100vh - 100px); /* 限制最大高度 */
max-height: calc(100vh - 100px);
/* 限制最大高度 */
}
.dimension-content {
@ -593,7 +531,8 @@ const openTeacherDetail = (teacher) => {
border-radius: 10px;
display: flex;
flex-direction: column;
overflow: hidden; /* 加上这个防止内容溢出 */
overflow: hidden;
/* 加上这个防止内容溢出 */
}
.dimension-section-title {
@ -601,7 +540,7 @@ const openTeacherDetail = (teacher) => {
font-size: 16px;
text-align: center;
padding-bottom: 10px;
border-bottom: 1px solid rgba(73,134,255,0.3);
border-bottom: 1px solid rgba(73, 134, 255, 0.3);
}
.dimension-list {
@ -609,8 +548,10 @@ const openTeacherDetail = (teacher) => {
display: flex;
flex-direction: column;
gap: 15px;
overflow-y: auto; /* 允许列表滚动 */
flex: 1; /* 让列表占满剩余空间 */
overflow-y: auto;
/* 允许列表滚动 */
flex: 1;
/* 让列表占满剩余空间 */
}
.dimension-item {
@ -618,15 +559,16 @@ const openTeacherDetail = (teacher) => {
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background-color: rgba(73,134,255,0.1);
background-color: rgba(73, 134, 255, 0.1);
border-radius: 4px;
border-left: 3px solid #4986ff;
cursor: pointer; /* 添加指针样式,提示可点击 */
cursor: pointer;
/* 添加指针样式,提示可点击 */
transition: background-color 0.2s;
}
.dimension-item:hover {
background-color: rgba(73,134,255,0.2);
background-color: rgba(73, 134, 255, 0.2);
}
.dimension-checkbox {
@ -658,7 +600,7 @@ const openTeacherDetail = (teacher) => {
align-items: center;
justify-content: center;
padding: 10px;
background-color: rgba(73,134,255,0.1);
background-color: rgba(73, 134, 255, 0.1);
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
@ -666,7 +608,7 @@ const openTeacherDetail = (teacher) => {
}
.dimension-add:hover {
background-color: rgba(73,134,255,0.2);
background-color: rgba(73, 134, 255, 0.2);
}
.add-icon {
@ -687,7 +629,8 @@ const openTeacherDetail = (teacher) => {
flex-direction: column;
padding: 0 10px;
overflow: hidden;
max-height: calc(100vh - 100px); /* 限制最大高度 */
max-height: calc(100vh - 100px);
/* 限制最大高度 */
}
/* 搜索和操作栏 */
@ -702,7 +645,7 @@ const openTeacherDetail = (teacher) => {
display: flex;
align-items: center;
width: 300px;
background-color: rgba(255,255,255,0.1);
background-color: rgba(255, 255, 255, 0.1);
border-radius: 20px;
overflow: hidden;
}
@ -717,7 +660,7 @@ const openTeacherDetail = (teacher) => {
}
.search-box input::placeholder {
color: rgba(255,255,255,0.5);
color: rgba(255, 255, 255, 0.5);
}
.search-button {
@ -735,9 +678,9 @@ const openTeacherDetail = (teacher) => {
}
.add-evaluation-btn {
background-color: rgba(14,62,167,1);
color: rgba(255,255,255,1);
border: 1px solid rgba(73,134,255,1);
background-color: rgba(14, 62, 167, 1);
color: rgba(255, 255, 255, 1);
border: 1px solid rgba(73, 134, 255, 1);
border-radius: 10px;
padding: 8px 15px;
font-size: 14px;
@ -751,7 +694,8 @@ const openTeacherDetail = (teacher) => {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
overflow-y: auto; /* 允许卡片区域滚动 */
overflow-y: auto;
/* 允许卡片区域滚动 */
flex: 1;
padding: 20px;
background-color: #262F50;
@ -767,27 +711,27 @@ const openTeacherDetail = (teacher) => {
:deep(.dimension-dialog .el-dialog__header) {
color: white;
border-bottom: 1px solid rgba(73,134,255,0.3);
border-bottom: 1px solid rgba(73, 134, 255, 0.3);
}
:deep(.dimension-dialog .el-dialog__body) {
color: white;
}
:deep(.dimension-dialog .el-input__inner),
:deep(.dimension-dialog .el-input__inner),
:deep(.dimension-dialog .el-input-number__decrease),
:deep(.dimension-dialog .el-input-number__increase) {
background-color: rgba(255,255,255,0.1);
border-color: rgba(73,134,255,0.3);
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(73, 134, 255, 0.3);
color: white;
}
:deep(.dimension-dialog .el-form-item__label) {
color: rgba(255,255,255,0.8);
color: rgba(255, 255, 255, 0.8);
}
:deep(.dimension-dialog .el-dialog__footer) {
border-top: 1px solid rgba(73,134,255,0.3);
border-top: 1px solid rgba(73, 134, 255, 0.3);
}
@media (max-width: 1200px) {

View File

@ -3,10 +3,10 @@ const env = import.meta.env.MODE || 'development';
const config = {
development: {
apiBaseUrl: 'http://127.0.0.1:48996',
apiBaseUrl: 'http://192.168.18.25:48080',
},
production: {
apiBaseUrl: 'http://36.103.199.107:48996',
apiBaseUrl: 'http://36.103.203.89:48089',
}
};