Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a8127a4bfa | ||
![]() |
89130eaef2 |
@ -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"]
|
@ -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
|
@ -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()
|
181
backend/crud.py
181
backend/crud.py
@ -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.
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
1556
backend/main.py
1556
backend/main.py
File diff suppressed because it is too large
Load Diff
@ -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)
|
@ -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()
|
@ -1 +0,0 @@
|
||||
|
@ -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()
|
@ -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
|
@ -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"
|
@ -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 |
@ -8,18 +8,6 @@ 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
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
server {
|
||||
listen 48100;
|
||||
listen 48103;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
@ -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> > 工程研究中心评估
|
||||
</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> > 工程研究中心评估
|
||||
</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();
|
||||
// 在此不需要调用handleSearch,因为loadLabs中已经调用了
|
||||
@ -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();
|
||||
|
@ -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> > 教师科研人才评估
|
||||
</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> > 教师科研人才评估
|
||||
</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">
|
||||
@ -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>
|
||||
@ -188,19 +177,6 @@ const showAddDimensionDialog = () => {
|
||||
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);
|
||||
@ -403,7 +339,7 @@ const handleSearch = () => {
|
||||
|
||||
// 初始化雷达图
|
||||
const initRadarCharts = () => {
|
||||
filteredTeachers.value.forEach((teacher, index) => {
|
||||
filteredTeachers.value?.forEach((teacher, index) => {
|
||||
const chartDom = document.getElementById(`chart-${index}`);
|
||||
if (!chartDom) return;
|
||||
|
||||
@ -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,7 +711,7 @@ 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) {
|
||||
@ -777,17 +721,17 @@ const openTeacherDetail = (teacher) => {
|
||||
: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) {
|
||||
|
@ -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',
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user