Compare commits

...

16 Commits
报告 ... main

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

Binary file not shown.

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="Flask">
<option name="enabled" value="true" />
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/app/templates" />
</list>
</option>
</component>
</module>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/flaskProject.iml" filepath="$PROJECT_DIR$/.idea/flaskProject.iml" />
</modules>
</component>
</project>

Binary file not shown.

@ -0,0 +1,47 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import Config
import os
# 初始化扩展
db = SQLAlchemy()
login_manager = LoginManager()
def create_app(config_class=Config):
# 创建Flask应用实例
app = Flask(__name__)
app.config.from_object(config_class)
# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# 初始化扩展
db.init_app(app)
login_manager.init_app(app)
# 配置登录管理器
login_manager.login_view = 'auth.login'
login_manager.login_message = '请先登录'
login_manager.login_message_category = 'info'
# 注册蓝图
from app.routes import auth, main, triage, records, settings, chat
app.register_blueprint(auth.bp)
app.register_blueprint(main.bp)
app.register_blueprint(triage.bp)
app.register_blueprint(records.bp)
app.register_blueprint(settings.bp)
app.register_blueprint(chat.bp)
# 用户加载函数
from app.models.models import User
@login_manager.user_loader
def load_user(id):
return User.query.get(int(id))
# 创建数据库表
with app.app_context():
db.create_all()
return app

@ -0,0 +1,41 @@
from zhipuai import ZhipuAI
from flask import current_app
def extract_content(response_message):
"""提取智谱AI响应中的内容"""
content = response_message.content
return content
def ask(conte):
"""调用智谱AI接口获取回答"""
try:
client = ZhipuAI(api_key=current_app.config['ZHIPUAI_API_KEY'])
response = client.chat.completions.create(
model="glm-4-plus",
messages=[
{"role": "user", "content": conte},
],
)
result = extract_content(response.choices[0].message)
return result.encode('utf-8', 'ignore').decode('utf-8')
except Exception as e:
current_app.logger.error(f"AI服务调用失败: {str(e)}")
raise Exception("AI服务暂时不可用请稍后重试")
def get_medical_advice(symptoms):
"""获取医疗建议"""
prompt = f"""
作为一个医疗AI助手请根据以下症状提供初步建议
症状{symptoms}
请提供
1. 可能的病因
2. 建议就诊科室
3. 注意事项
请注意这只是初步建议具体诊断请以医生的专业判断为准
"""
return ask(prompt)

@ -0,0 +1,158 @@
from app import db
from flask_login import UserMixin
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
"""用户模型"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False, index=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(128), nullable=False)
role = db.Column(db.String(20), nullable=False, default='patient') # 'patient' 或 'doctor'
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
title = db.Column(db.String(64)) # 医生职称
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# 明确指定外键关系
patient_records = db.relationship('MedicalRecord',
foreign_keys='MedicalRecord.patient_id',
backref='patient',
lazy='dynamic')
doctor_records = db.relationship('MedicalRecord',
foreign_keys='MedicalRecord.doctor_id',
backref='doctor',
lazy='dynamic')
triage_records = db.relationship('TriageRecord',
foreign_keys='TriageRecord.patient_id',
backref='patient',
lazy='dynamic')
chat_histories = db.relationship('ChatHistory', backref='user', lazy='dynamic')
department = db.relationship('Department', backref='doctors')
# 新增:发送的消息和接收的消息关系
sent_messages = db.relationship('Message',
foreign_keys='Message.sender_id',
backref='sender',
lazy='dynamic')
received_messages = db.relationship('Message',
foreign_keys='Message.receiver_id',
backref='receiver',
lazy='dynamic')
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def is_doctor(self):
return self.role == 'doctor'
class UserProfile(db.Model):
"""用户个人资料"""
__tablename__ = 'user_profiles'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
avatar = db.Column(db.String(200)) # 头像URL
nickname = db.Column(db.String(64)) # 昵称
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user = db.relationship('User', backref=db.backref('profile', uselist=False))
class Department(db.Model):
"""科室模型"""
__tablename__ = 'departments'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, nullable=False)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
medical_records = db.relationship('MedicalRecord', backref='department', lazy='dynamic')
symptom_rules = db.relationship('SymptomDepartment', backref='department', lazy='dynamic')
class MedicalRecord(db.Model):
"""病历记录模型"""
__tablename__ = 'medical_records'
id = db.Column(db.Integer, primary_key=True)
patient_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
doctor_id = db.Column(db.Integer, db.ForeignKey('users.id'))
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
chief_complaint = db.Column(db.Text) # 主诉
present_illness = db.Column(db.Text) # 现病史
past_history = db.Column(db.Text) # 既往史
diagnosis = db.Column(db.Text) # 诊断
treatment = db.Column(db.Text) # 治疗方案
prescription = db.Column(db.Text) # 处方
notes = db.Column(db.Text) # 备注
status = db.Column(db.String(20), default='pending') # pending, in_progress, completed
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class TriageRecord(db.Model):
"""分诊记录模型"""
__tablename__ = 'triage_records'
id = db.Column(db.Integer, primary_key=True)
patient_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
symptoms = db.Column(db.Text, nullable=False)
severity = db.Column(db.String(20)) # 严重程度
triage_result = db.Column(db.Text) # 分诊结果
notes = db.Column(db.Text) # 备注
status = db.Column(db.String(20), default='pending') # pending, processed
created_at = db.Column(db.DateTime, default=datetime.utcnow)
processed_at = db.Column(db.DateTime)
class SymptomDepartment(db.Model):
"""症状-科室关联模型"""
__tablename__ = 'symptom_departments'
id = db.Column(db.Integer, primary_key=True)
symptom = db.Column(db.String(128), nullable=False)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'), nullable=False)
severity_level = db.Column(db.Integer, default=1) # 1-5表示严重程度
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class ChatHistory(db.Model):
"""聊天历史记录模型"""
__tablename__ = 'chat_histories'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
message_type = db.Column(db.String(20)) # 'user' 或 'ai'
message = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class Message(db.Model):
"""医生-患者对话消息"""
__tablename__ = 'messages'
id = db.Column(db.Integer, primary_key=True)
sender_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
receiver_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
content = db.Column(db.Text, nullable=False)
read = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

@ -0,0 +1,7 @@
from . import main
from . import auth
from . import records
from . import triage
# 确保所有蓝图都被正确导入
__all__ = ['main', 'auth', 'records', 'triage']

@ -0,0 +1,106 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from app.models.models import User, Department
from app import db
bp = Blueprint('auth', __name__)
@bp.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
remember = request.form.get('remember', False)
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
login_user(user, remember=remember)
next_page = request.args.get('next')
return redirect(next_page or url_for('main.index'))
flash('用户名或密码错误')
return render_template('auth/login.html')
@bp.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
role = request.form.get('role', 'patient')
if User.query.filter_by(username=username).first():
flash('用户名已存在')
return redirect(url_for('auth.register'))
if User.query.filter_by(email=email).first():
flash('邮箱已被注册')
return redirect(url_for('auth.register'))
user = User(username=username, email=email, role=role)
user.set_password(password)
if role == 'doctor':
department_id = request.form.get('department')
title = request.form.get('title')
user.department_id = department_id
user.title = title
db.session.add(user)
db.session.commit()
flash('注册成功,请登录')
return redirect(url_for('auth.login'))
departments = Department.query.all()
return render_template('auth/register.html', departments=departments)
@bp.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('main.index'))
@bp.route('/profile', methods=['GET', 'POST'])
@login_required
def profile():
if request.method == 'POST':
email = request.form.get('email')
current_password = request.form.get('current_password')
new_password = request.form.get('new_password')
if current_password and new_password:
if current_user.check_password(current_password):
current_user.set_password(new_password)
flash('密码修改成功')
else:
flash('当前密码错误')
if email != current_user.email:
if User.query.filter_by(email=email).first():
flash('邮箱已被使用')
else:
current_user.email = email
flash('邮箱修改成功')
if current_user.is_doctor():
department_id = request.form.get('department')
title = request.form.get('title')
current_user.department_id = department_id
current_user.title = title
db.session.commit()
return redirect(url_for('auth.profile'))
departments = Department.query.all()
return render_template('auth/profile.html', departments=departments)

@ -0,0 +1,96 @@
from flask import Blueprint, render_template, request, jsonify
from flask_login import login_required, current_user
from app.models.models import Message, User
from app import db
# 将 chat_bp 改为 bp
bp = Blueprint('chat', __name__)
@bp.route('/chat')
@login_required
def chat_list():
"""聊天列表"""
if current_user.is_doctor():
# 医生查看与其对话的患者列表
chats = db.session.query(User).join(
Message,
(Message.sender_id == User.id) | (Message.receiver_id == User.id)
).filter(
(Message.sender_id == current_user.id) |
(Message.receiver_id == current_user.id)
).distinct().all()
else:
# 患者查看与其对话的医生列表
chats = User.query.filter_by(role='doctor').all()
return render_template('chat/list.html', chats=chats)
@bp.route('/chat/<int:user_id>')
@login_required
def chat_room(user_id):
"""聊天室"""
other_user = User.query.get_or_404(user_id)
# 获取聊天记录
messages = Message.query.filter(
((Message.sender_id == current_user.id) & (Message.receiver_id == user_id)) |
((Message.sender_id == user_id) & (Message.receiver_id == current_user.id))
).order_by(Message.created_at.asc()).all()
# 标记消息为已读
unread_messages = Message.query.filter_by(
receiver_id=current_user.id,
sender_id=user_id,
read=False
).all()
for msg in unread_messages:
msg.read = True
db.session.commit()
return render_template('chat/room.html', other_user=other_user, messages=messages)
@bp.route('/chat/send', methods=['POST'])
@login_required
def send_message():
"""发送消息"""
try:
receiver_id = request.form.get('receiver_id')
content = request.form.get('content')
if not all([receiver_id, content]):
return jsonify({'success': False, 'message': '参数不完整'})
message = Message(
sender_id=current_user.id,
receiver_id=receiver_id,
content=content
)
db.session.add(message)
db.session.commit()
return jsonify({
'success': True,
'message': {
'id': message.id,
'content': message.content,
'created_at': message.created_at.strftime('%Y-%m-%d %H:%M:%S')
}
})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'message': str(e)})
@bp.route('/chat/unread_count')
@login_required
def get_unread_count():
"""获取未读消息数"""
count = Message.query.filter_by(
receiver_id=current_user.id,
read=False
).count()
return jsonify({'count': count})

@ -0,0 +1,71 @@
from flask import Blueprint, render_template, jsonify, request
from flask_login import login_required, current_user
from app.models.models import ChatHistory
from app import db
from app.ai_service import ask
bp = Blueprint('main', __name__)
@bp.route('/')
def index():
return render_template('main/index.html')
@bp.route('/ai_consult', methods=['GET', 'POST'])
@login_required
def ai_consult():
if request.method == 'POST':
question = request.form.get('question')
# 保存用户问题
user_message = ChatHistory(
user_id=current_user.id,
message_type='user',
message=question
)
db.session.add(user_message)
# 获取AI回答
try:
ai_response = ask(question)
# 保存AI回答
ai_message = ChatHistory(
user_id=current_user.id,
message_type='ai',
message=ai_response
)
db.session.add(ai_message)
db.session.commit()
return jsonify({
'status': 'success',
'answer': ai_response
})
except Exception as e:
db.session.rollback()
return jsonify({
'status': 'error',
'message': str(e)
}), 500
# 获取历史聊天记录
chat_history = ChatHistory.query.filter_by(
user_id=current_user.id
).order_by(ChatHistory.created_at.desc()).limit(50).all()
return render_template('main/ai_consult.html', chat_history=chat_history)
@bp.route('/chat_history')
@login_required
def chat_history():
page = request.args.get('page', 1, type=int)
pagination = ChatHistory.query.filter_by(
user_id=current_user.id
).order_by(ChatHistory.created_at.desc()).paginate(
page=page, per_page=20, error_out=False
)
return render_template('main/chat_history.html', pagination=pagination)

@ -0,0 +1,111 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_login import login_required, current_user
from app.models.models import MedicalRecord, Department, User
from app import db
from datetime import datetime
bp = Blueprint('records', __name__)
@bp.route('/medical_records')
@login_required
def medical_records():
page = request.args.get('page', 1, type=int)
if current_user.is_doctor():
# 医生查看其创建的病历
pagination = MedicalRecord.query.filter_by(
doctor_id=current_user.id
).order_by(MedicalRecord.created_at.desc()).paginate(
page=page, per_page=10, error_out=False
)
else:
# 患者查看自己的病历
pagination = MedicalRecord.query.filter_by(
patient_id=current_user.id
).order_by(MedicalRecord.created_at.desc()).paginate(
page=page, per_page=10, error_out=False
)
return render_template('records/list.html', pagination=pagination)
@bp.route('/medical_records/new', methods=['GET', 'POST'])
@login_required
def new_record():
if not current_user.is_doctor():
flash('只有医生可以创建病历', 'warning')
return redirect(url_for('records.medical_records'))
# 获取所有患者列表(仅普通用户,非医生)
patients = User.query.filter_by(role='patient').all()
# 获取所有科室列表
departments = Department.query.all()
if request.method == 'POST':
try:
record = MedicalRecord(
patient_id=request.form.get('patient_id'),
doctor_id=current_user.id,
department_id=request.form.get('department_id', current_user.department_id),
chief_complaint=request.form.get('chief_complaint'),
present_illness=request.form.get('present_illness'),
past_history=request.form.get('past_history'),
diagnosis=request.form.get('diagnosis'),
treatment=request.form.get('treatment'),
prescription=request.form.get('prescription'),
notes=request.form.get('notes'),
status='completed'
)
db.session.add(record)
db.session.commit()
flash('病历创建成功', 'success')
return redirect(url_for('records.medical_records'))
except Exception as e:
db.session.rollback()
flash(f'病历创建失败:{str(e)}', 'danger')
return render_template('records/new.html',
patients=patients,
departments=departments)
@bp.route('/medical_records/<int:id>')
@login_required
def view_record(id):
record = MedicalRecord.query.get_or_404(id)
# 检查访问权限
if not (current_user.is_doctor() or record.patient_id == current_user.id):
flash('您没有权限查看此病历')
return redirect(url_for('records.medical_records'))
return render_template('records/view.html', record=record)
@bp.route('/medical_records/<int:id>/edit', methods=['GET', 'POST'])
@login_required
def edit_record(id):
if not current_user.is_doctor():
flash('只有医生可以编辑病历')
return redirect(url_for('records.medical_records'))
record = MedicalRecord.query.get_or_404(id)
if request.method == 'POST':
record.chief_complaint = request.form.get('chief_complaint')
record.present_illness = request.form.get('present_illness')
record.past_history = request.form.get('past_history')
record.diagnosis = request.form.get('diagnosis')
record.treatment = request.form.get('treatment')
record.prescription = request.form.get('prescription')
record.notes = request.form.get('notes')
record.updated_at = datetime.utcnow()
db.session.commit()
flash('病历更新成功')
return redirect(url_for('records.view_record', id=id))
return render_template('records/edit.html', record=record)

@ -0,0 +1,57 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
import os
from app import db
from app.models.models import UserProfile
# 将 settings_bp 改为 bp
bp = Blueprint('settings', __name__)
def allowed_file(filename):
"""检查文件类型是否允许"""
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@bp.route('/settings', methods=['GET', 'POST'])
@login_required
def settings():
"""用户设置页面"""
if request.method == 'POST':
try:
# 处理头像上传
if 'avatar' in request.files:
file = request.files['avatar']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
# 生成唯一文件名
unique_filename = f"{current_user.id}_{filename}"
file.save(os.path.join(current_app.config['UPLOAD_FOLDER'], unique_filename))
# 更新或创建用户资料
profile = UserProfile.query.filter_by(user_id=current_user.id).first()
if not profile:
profile = UserProfile(user_id=current_user.id)
db.session.add(profile)
profile.avatar = unique_filename
# 更新昵称
nickname = request.form.get('nickname')
if nickname:
profile = UserProfile.query.filter_by(user_id=current_user.id).first()
if not profile:
profile = UserProfile(user_id=current_user.id)
db.session.add(profile)
profile.nickname = nickname
db.session.commit()
flash('设置已更新', 'success')
return redirect(url_for('settings.settings'))
except Exception as e:
db.session.rollback()
flash(f'更新失败:{str(e)}', 'danger')
return render_template('settings/settings.html')

@ -0,0 +1,116 @@
from flask import Blueprint, render_template, request, jsonify, flash
from flask_login import login_required, current_user
from app.models.models import TriageRecord, SymptomDepartment, Department
from app import db
from datetime import datetime
bp = Blueprint('triage', __name__)
@bp.route('/triage', methods=['GET', 'POST'])
@login_required
def triage():
if request.method == 'POST':
symptoms = request.form.get('symptoms')
# 分析症状,获取推荐科室和严重程度
max_severity = 1
recommended_dept = None
# 将症状拆分为列表
symptom_list = [s.strip() for s in symptoms.split(',')]
# 查找匹配的症状规则
for symptom in symptom_list:
symptom_rule = SymptomDepartment.query.filter_by(
symptom=symptom
).first()
if symptom_rule and symptom_rule.severity_level > max_severity:
max_severity = symptom_rule.severity_level
recommended_dept = symptom_rule.department
# 如果没有找到匹配的科室,使用默认科室
if not recommended_dept:
recommended_dept = Department.query.filter_by(
name='普通内科'
).first()
# 创建分诊记录
triage_record = TriageRecord(
patient_id=current_user.id,
department_id=recommended_dept.id,
symptoms=symptoms,
severity=f'级别{max_severity}',
triage_result=f'建议前往{recommended_dept.name}就诊',
status='processed',
processed_at=datetime.utcnow()
)
db.session.add(triage_record)
db.session.commit()
return jsonify({
'status': 'success',
'department': recommended_dept.name,
'severity': f'级别{max_severity}',
'description': recommended_dept.description
})
return render_template('triage/triage.html')
@bp.route('/triage/history')
@login_required
def triage_history():
page = request.args.get('page', 1, type=int)
pagination = TriageRecord.query.filter_by(
patient_id=current_user.id
).order_by(TriageRecord.created_at.desc()).paginate(
page=page, per_page=10, error_out=False
)
return render_template('triage/history.html', pagination=pagination)
@bp.route('/triage/manage', methods=['GET', 'POST'])
@login_required
def manage_triage():
if not current_user.is_doctor():
flash('只有医生可以访问此页面')
return redirect(url_for('main.index'))
if request.method == 'POST':
action = request.form.get('action')
if action == 'add_rule':
symptom = request.form.get('symptom')
department_id = request.form.get('department_id')
severity = request.form.get('severity')
description = request.form.get('description')
rule = SymptomDepartment(
symptom=symptom,
department_id=department_id,
severity_level=severity,
description=description
)
db.session.add(rule)
db.session.commit()
flash('规则添加成功')
elif action == 'delete_rule':
rule_id = request.form.get('rule_id')
rule = SymptomDepartment.query.get_or_404(rule_id)
db.session.delete(rule)
db.session.commit()
flash('规则删除成功')
# 获取所有规则和科室
rules = SymptomDepartment.query.all()
departments = Department.query.all()
return render_template('triage/manage.html',
rules=rules,
departments=departments)

@ -0,0 +1,139 @@
/* 全局样式 */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--warning-color: #ffc107;
--info-color: #17a2b8;
--light-color: #f8f9fa;
--dark-color: #343a40;
}
body {
background-color: #f4f6f9;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
/* 导航栏样式 */
.navbar {
box-shadow: 0 2px 4px rgba(0,0,0,.1);
}
.navbar-brand img {
margin-right: 0.5rem;
}
/* 卡片样式 */
.card {
border: none;
margin-bottom: 1.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,.075);
}
.card-header {
background-color: #fff;
border-bottom: 1px solid rgba(0,0,0,.125);
}
/* 表单样式 */
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
}
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}
/* 表格样式 */
.table {
margin-bottom: 0;
}
.table thead th {
border-top: none;
background-color: var(--light-color);
}
/* 分页样式 */
.pagination {
margin-bottom: 0;
}
.page-link {
color: var(--primary-color);
}
.page-item.active .page-link {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
/* 聊天界面样式 */
.chat-container {
height: 400px;
overflow-y: auto;
padding: 1rem;
background-color: #fff;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
}
.chat-message {
margin-bottom: 1rem;
padding: 0.75rem;
border-radius: 0.25rem;
max-width: 80%;
}
.user-message {
background-color: #e3f2fd;
margin-left: auto;
}
.ai-message {
background-color: #f8f9fa;
margin-right: auto;
}
/* 响应式调整 */
@media (max-width: 768px) {
.card-body {
padding: 1rem;
}
.table-responsive {
margin-bottom: 1rem;
}
.chat-container {
height: 300px;
}
.chat-message {
max-width: 90%;
}
}
/* 自定义动画 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.3s ease-out;
}

@ -0,0 +1,130 @@
// 通用工具函数
const utils = {
// 显示提示消息
showAlert: function(message, type = 'info') {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
const container = document.querySelector('main.container');
container.insertBefore(alertDiv, container.firstChild);
// 5秒后自动消失
setTimeout(() => {
alertDiv.remove();
}, 5000);
},
// 格式化日期时间
formatDateTime: function(date) {
return new Date(date).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
},
// 防抖函数
debounce: function(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
};
// 表单验证
const formValidation = {
validateRequired: function(form) {
const requiredFields = form.querySelectorAll('[required]');
let valid = true;
requiredFields.forEach(field => {
if (!field.value.trim()) {
valid = false;
field.classList.add('is-invalid');
// 添加提示信息
let feedback = field.nextElementSibling;
if (!feedback || !feedback.classList.contains('invalid-feedback')) {
feedback = document.createElement('div');
feedback.className = 'invalid-feedback';
field.parentNode.appendChild(feedback);
}
feedback.textContent = '此字段不能为空';
} else {
field.classList.remove('is-invalid');
}
});
return valid;
},
validateEmail: function(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
},
validatePassword: function(password) {
return password.length >= 6;
}
};
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
// 初始化所有工具提示
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function(tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// 初始化所有弹出框
const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
popoverTriggerList.map(function(popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl);
});
// 处理表单验证
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
if (!formValidation.validateRequired(form)) {
e.preventDefault();
utils.showAlert('请填写所有必填字段', 'danger');
}
});
});
});
// 聊天相关功能
const chat = {
scrollToBottom: function(container) {
container.scrollTop = container.scrollHeight;
},
addMessage: function(message, type, container) {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${type}-message fade-in`;
const now = utils.formatDateTime(new Date());
messageDiv.innerHTML = `
<div class="message-header">
<small class="text-muted">${now}</small>
</div>
<div class="message-content">${message}</div>
`;
container.appendChild(messageDiv);
this.scrollToBottom(container);
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}登录 - {{ super() }}{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="card shadow">
<div class="card-body">
<h4 class="card-title text-center mb-4">用户登录</h4>
<form method="POST" action="{{ url_for('auth.login') }}">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">记住我</label>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">登录</button>
</div>
</form>
<div class="text-center mt-3">
<a href="{{ url_for('auth.register') }}" class="text-decoration-none">
还没有账号?立即注册
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,100 @@
{% extends "base.html" %}
{% block title %}个人资料 - {{ super() }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">个人资料</h5>
</div>
<div class="card-body">
<form method="POST" id="profileForm">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username"
value="{{ current_user.username }}" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">邮箱</label>
<input type="email" class="form-control" id="email" name="email"
value="{{ current_user.email }}" required>
</div>
{% if current_user.is_doctor() %}
<div class="mb-3">
<label for="department_id" class="form-label">所属科室</label>
<select class="form-select" id="department_id" name="department_id">
<option value="">请选择科室</option>
{% for dept in departments %}
<option value="{{ dept.id }}"
{{ 'selected' if current_user.department_id == dept.id }}>
{{ dept.name }}
</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="title" class="form-label">职称</label>
<input type="text" class="form-control" id="title" name="title"
value="{{ current_user.title or '' }}">
</div>
{% endif %}
<div class="mb-3">
<label for="new_password" class="form-label">新密码</label>
<input type="password" class="form-control" id="new_password" name="new_password"
minlength="6">
<div class="form-text">如果不修改密码,请留空</div>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">确认新密码</label>
<input type="password" class="form-control" id="confirm_password"
name="confirm_password" minlength="6">
</div>
<div class="mb-3">
<label for="current_password" class="form-label">当前密码</label>
<input type="password" class="form-control" id="current_password"
name="current_password" required>
<div class="form-text">请输入当前密码以确认修改</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary">保存修改</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('profileForm');
const newPassword = document.getElementById('new_password');
const confirmPassword = document.getElementById('confirm_password');
form.addEventListener('submit', function(e) {
if (newPassword.value || confirmPassword.value) {
if (newPassword.value !== confirmPassword.value) {
e.preventDefault();
alert('两次输入的新密码不一致!');
return;
}
if (newPassword.value.length < 6) {
e.preventDefault();
alert('新密码长度不能少于6个字符');
return;
}
}
});
});
</script>
{% endblock %}

@ -0,0 +1,112 @@
{% extends "base.html" %}
{% block title %}注册 - {{ super() }}{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow">
<div class="card-body">
<h4 class="card-title text-center mb-4">用户注册</h4>
<form method="POST" action="{{ url_for('auth.register') }}" id="registerForm">
<div class="mb-3">
<label class="form-label">注册类型</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="role" id="rolePatient"
value="patient" checked>
<label class="form-check-label" for="rolePatient">患者</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="role" id="roleDoctor"
value="doctor">
<label class="form-check-label" for="roleDoctor">医生</label>
</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">邮箱</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">确认密码</label>
<input type="password" class="form-control" id="confirm_password"
name="confirm_password" required>
</div>
<!-- 医生特有字段 -->
<div id="doctorFields" style="display: none;">
<div class="mb-3">
<label for="department" class="form-label">所属科室</label>
<select class="form-select" id="department" name="department">
{% for dept in departments %}
<option value="{{ dept.id }}">{{ dept.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="title" class="form-label">职称</label>
<select class="form-select" id="title" name="title">
<option value="主任医师">主任医师</option>
<option value="副主任医师">副主任医师</option>
<option value="主治医师">主治医师</option>
<option value="住院医师">住院医师</option>
</select>
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">注册</button>
</div>
</form>
<div class="text-center mt-3">
<a href="{{ url_for('auth.login') }}" class="text-decoration-none">
已有账号?立即登录
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const roleInputs = document.querySelectorAll('input[name="role"]');
const doctorFields = document.getElementById('doctorFields');
function toggleDoctorFields() {
const isDoctor = document.getElementById('roleDoctor').checked;
doctorFields.style.display = isDoctor ? 'block' : 'none';
}
roleInputs.forEach(input => {
input.addEventListener('change', toggleDoctorFields);
});
// 表单验证
const form = document.getElementById('registerForm');
form.addEventListener('submit', function(event) {
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirm_password').value;
if (password !== confirmPassword) {
event.preventDefault();
alert('两次输入的密码不一致!');
}
});
});
</script>
{% endblock %}

@ -0,0 +1,65 @@
{% extends "base.html" %}
{% block title %}设置新密码 - {{ super() }}{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">设置新密码</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="password" class="form-label">新密码</label>
<input type="password" class="form-control" id="password"
name="password" required minlength="6">
<div class="form-text">
密码长度至少6个字符。
</div>
</div>
<div class="mb-3">
<label for="password2" class="form-label">确认新密码</label>
<input type="password" class="form-control" id="password2"
name="password2" required minlength="6">
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">
重置密码
</button>
<a href="{{ url_for('auth.login') }}" class="btn btn-outline-secondary">
返回登录
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');
const password = document.getElementById('password');
const password2 = document.getElementById('password2');
form.addEventListener('submit', function(e) {
if (password.value !== password2.value) {
e.preventDefault();
alert('两次输入的密码不一致!');
return;
}
if (password.value.length < 6) {
e.preventDefault();
alert('密码长度不能少于6个字符');
return;
}
});
});
</script>
{% endblock %}

@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}重置密码 - {{ super() }}{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">重置密码</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="email" class="form-label">邮箱地址</label>
<input type="email" class="form-control" id="email" name="email"
required placeholder="请输入注册时使用的邮箱">
<div class="form-text">
我们将向您的邮箱发送重置密码的链接。
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">
发送重置链接
</button>
<a href="{{ url_for('auth.login') }}" class="btn btn-outline-secondary">
返回登录
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}医疗问答系统{% endblock %}</title>
<link href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="{{ url_for('main.index') }}">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo" height="30">
医疗问答系统
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
{% if current_user.is_authenticated %}
{% if current_user.role == 'patient' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('triage.triage') }}">智能分诊</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.ai_consult') }}">AI问诊</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('records.medical_records') }}">我的病历</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('chat.chat_list') }}">
找医生咨询
<span class="badge bg-danger" id="unreadCount"></span>
</a>
</li>
{% elif current_user.role == 'doctor' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('triage.manage_triage') }}">分诊管理</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('records.medical_records') }}">病历管理</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('chat.chat_list') }}">
患者咨询
<span class="badge bg-danger" id="unreadCount"></span>
</a>
</li>
{% endif %}
{% endif %}
</ul>
<ul class="navbar-nav">
{% if current_user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
data-bs-toggle="dropdown">
<img src="{{ url_for('static', filename='uploads/' + current_user.profile.avatar) if current_user.profile and current_user.profile.avatar else url_for('static', filename='images/default-avatar.png') }}"
class="rounded-circle me-2"
style="width: 30px; height: 30px; object-fit: cover;">
{{ current_user.profile.nickname if current_user.profile and current_user.profile.nickname else current_user.username }}
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="{{ url_for('settings.settings') }}">
<i class="bi bi-gear"></i> 系统设置
</a></li>
<li><a class="dropdown-item" href="{{ url_for('chat.chat_list') }}">
<i class="bi bi-chat"></i> 我的消息
<span class="badge bg-danger float-end" id="dropdownUnreadCount"></span>
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="{{ url_for('auth.logout') }}">
<i class="bi bi-box-arrow-right"></i> 退出登录
</a></li>
</ul>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">注册</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<!-- 主要内容 -->
<main class="container mt-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category or 'info' }} alert-dismissible fade show">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<!-- 页脚 -->
<footer class="footer mt-5 py-3 bg-light">
<div class="container text-center">
<span class="text-muted">© 2024 医疗问答系统. All rights reserved.</span>
</div>
</footer>
<!-- JavaScript -->
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.7.2/font/bootstrap-icons.css"></script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% if current_user.is_authenticated %}
<script>
// 获取未读消息数
async function updateUnreadCount() {
try {
const response = await fetch('{{ url_for("chat.get_unread_count") }}');
const data = await response.json();
const count = data.count;
// 更新导航栏未读消息数
const unreadCount = document.getElementById('unreadCount');
const dropdownUnreadCount = document.getElementById('dropdownUnreadCount');
if (count > 0) {
unreadCount.textContent = count;
dropdownUnreadCount.textContent = count;
unreadCount.style.display = 'inline';
dropdownUnreadCount.style.display = 'inline';
} else {
unreadCount.style.display = 'none';
dropdownUnreadCount.style.display = 'none';
}
} catch (error) {
console.error('获取未读消息数失败:', error);
}
}
// 定期更新未读消息数
updateUnreadCount();
setInterval(updateUnreadCount, 60000); // 每分钟更新一次
</script>
{% endif %}
{% block extra_js %}{% endblock %}
</body>
</html>

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block title %}消息列表 - {{ super() }}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">消息列表</h5>
</div>
<div class="card-body">
<div class="list-group">
{% for user in chats %}
<a href="{{ url_for('chat.chat_room', user_id=user.id) }}"
class="list-group-item list-group-item-action">
<div class="d-flex align-items-center">
<img src="{{ url_for('static', filename='uploads/' + user.profile.avatar) if user.profile and user.profile.avatar else url_for('static', filename='images/default-avatar.png') }}"
class="rounded-circle me-3"
style="width: 50px; height: 50px; object-fit: cover;">
<div>
<h6 class="mb-0">{{ user.profile.nickname if user.profile else user.username }}</h6>
<small class="text-muted">
{{ '医生' if user.is_doctor() else '患者' }} -
{{ user.department.name if user.is_doctor() and user.department else '' }}
</small>
</div>
</div>
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,94 @@
{% extends "base.html" %}
{% block title %}与 {{ other_user.profile.nickname if other_user.profile else other_user.username }} 的对话 - {{ super() }}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card shadow">
<div class="card-header">
<div class="d-flex align-items-center">
<img src="{{ url_for('static', filename='uploads/' + other_user.profile.avatar) if other_user.profile and other_user.profile.avatar else url_for('static', filename='images/default-avatar.png') }}"
class="rounded-circle me-3"
style="width: 40px; height: 40px; object-fit: cover;">
<h5 class="card-title mb-0">
{{ other_user.profile.nickname if other_user.profile else other_user.username }}
<small class="text-muted">
{{ '医生' if other_user.is_doctor() else '患者' }}
{% if other_user.is_doctor() and other_user.department %}
- {{ other_user.department.name }}
{% endif %}
</small>
</h5>
</div>
</div>
<div class="card-body chat-container" style="height: 400px; overflow-y: auto;" id="messageContainer">
{% for message in messages %}
<div class="d-flex mb-3 {{ 'justify-content-end' if message.sender_id == current_user.id else 'justify-content-start' }}">
<div class="message {{ 'bg-primary text-white' if message.sender_id == current_user.id else 'bg-light' }}"
style="max-width: 70%; padding: 10px; border-radius: 10px;">
{{ message.content }}
<div class="small {{ 'text-white-50' if message.sender_id == current_user.id else 'text-muted' }}">
{{ message.created_at.strftime('%H:%M') }}
</div>
</div>
</div>
{% endfor %}
</div>
<div class="card-footer">
<form id="messageForm" class="d-flex">
<input type="hidden" name="receiver_id" value="{{ other_user.id }}">
<input type="text" class="form-control me-2" name="content" placeholder="输入消息...">
<button type="submit" class="btn btn-primary">发送</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('messageForm');
const messageContainer = document.getElementById('messageContainer');
// 滚动到最新消息
messageContainer.scrollTop = messageContainer.scrollHeight;
form.addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(form);
try {
const response = await fetch('{{ url_for("chat.send_message") }}', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
// 添加新消息到界面
const messageDiv = document.createElement('div');
messageDiv.className = 'd-flex mb-3 justify-content-end';
messageDiv.innerHTML = `
<div class="message bg-primary text-white" style="max-width: 70%; padding: 10px; border-radius: 10px;">
${data.message.content}
<div class="small text-white-50">
${data.message.created_at}
</div>
</div>
`;
messageContainer.appendChild(messageDiv);
messageContainer.scrollTop = messageContainer.scrollHeight;
form.reset();
}
} catch (error) {
console.error('发送消息失败:', error);
}
});
});
</script>
{% endblock %}

@ -0,0 +1,143 @@
{% extends "base.html" %}
{% block title %}AI问诊 - {{ super() }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">AI智能问诊</h5>
</div>
<div class="card-body">
<!-- 聊天记录区域 -->
<div class="chat-container mb-3" id="chatContainer">
{% for message in chat_history %}
<div class="chat-message {% if message.message_type == 'user' %}user-message{% else %}ai-message{% endif %}">
<div class="message-header">
<small class="text-muted">
{{ message.created_at.strftime('%Y-%m-%d %H:%M:%S') }}
</small>
</div>
<div class="message-content">
{{ message.message }}
</div>
</div>
{% endfor %}
</div>
<!-- 输入区域 -->
<form id="consultForm" class="mt-3">
<div class="input-group">
<textarea class="form-control" id="questionInput" rows="2"
placeholder="请描述您的症状或健康问题..."></textarea>
<button class="btn btn-primary" type="submit">发送</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_css %}
<style>
.chat-container {
height: 400px;
overflow-y: auto;
padding: 15px;
}
.chat-message {
margin-bottom: 15px;
padding: 10px;
border-radius: 10px;
max-width: 80%;
}
.user-message {
background-color: #e3f2fd;
margin-left: auto;
}
.ai-message {
background-color: #f5f5f5;
margin-right: auto;
}
.message-header {
margin-bottom: 5px;
}
.message-content {
white-space: pre-wrap;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('consultForm');
const input = document.getElementById('questionInput');
const chatContainer = document.getElementById('chatContainer');
// 滚动到底部
function scrollToBottom() {
chatContainer.scrollTop = chatContainer.scrollHeight;
}
// 添加消息到聊天区域
function addMessage(message, type) {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${type}-message`;
const now = new Date().toLocaleString();
messageDiv.innerHTML = `
<div class="message-header">
<small class="text-muted">${now}</small>
</div>
<div class="message-content">${message}</div>
`;
chatContainer.appendChild(messageDiv);
scrollToBottom();
}
// 处理表单提交
form.addEventListener('submit', async function(e) {
e.preventDefault();
const question = input.value.trim();
if (!question) return;
// 添加用户消息
addMessage(question, 'user');
input.value = '';
try {
const response = await fetch('/ai_consult', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `question=${encodeURIComponent(question)}`
});
const data = await response.json();
if (data.status === 'success') {
addMessage(data.answer, 'ai');
} else {
addMessage('抱歉,系统出现错误,请稍后重试。', 'ai');
}
} catch (error) {
addMessage('网络错误,请检查您的网络连接。', 'ai');
}
});
// 初始滚动到底部
scrollToBottom();
});
</script>
{% endblock %}

@ -0,0 +1,124 @@
{% extends "base.html" %}
{% block title %}首页 - {{ super() }}{% endblock %}
{% block content %}
<div class="container">
<!-- 欢迎区域 -->
<div class="row mb-4">
<div class="col-12">
<div class="jumbotron bg-light p-5 rounded">
<h1 class="display-4">欢迎使用医疗问答系统</h1>
<p class="lead">我们提供智能分诊、AI问诊和电子病历等服务让您的就医体验更加便捷。</p>
{% if not current_user.is_authenticated %}
<hr class="my-4">
<p>立即注册或登录,开始使用我们的服务。</p>
<div class="d-flex gap-2">
<a class="btn btn-primary btn-lg" href="{{ url_for('auth.register') }}" role="button">注册账号</a>
<a class="btn btn-outline-primary btn-lg" href="{{ url_for('auth.login') }}" role="button">登录</a>
</div>
{% endif %}
</div>
</div>
</div>
<!-- 功能介绍 -->
<div class="row mb-4">
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-stethoscope text-primary"></i>
智能分诊
</h5>
<p class="card-text">根据您的症状,智能推荐最适合的就诊科室,让您的就医更有针对性。</p>
{% if current_user.is_authenticated and current_user.role == 'patient' %}
<a href="{{ url_for('triage.triage') }}" class="btn btn-primary">开始分诊</a>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-robot text-primary"></i>
AI问诊
</h5>
<p class="card-text">通过AI智能问答快速了解您的健康状况获取专业的医疗建议。</p>
{% if current_user.is_authenticated and current_user.role == 'patient' %}
<a href="{{ url_for('main.ai_consult') }}" class="btn btn-primary">咨询AI</a>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-file-medical text-primary"></i>
电子病历
</h5>
<p class="card-text">在线管理您的就医记录,随时查看诊断结果和治疗方案。</p>
{% if current_user.is_authenticated %}
<a href="{{ url_for('records.medical_records') }}" class="btn btn-primary">查看病历</a>
{% endif %}
</div>
</div>
</div>
</div>
<!-- 系统说明 -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title">系统说明</h5>
<div class="row">
<div class="col-md-6">
<h6>患者功能:</h6>
<ul>
<li>智能分诊服务</li>
<li>AI智能问诊</li>
<li>在线查看病历</li>
<li>查看就诊历史</li>
</ul>
</div>
<div class="col-md-6">
<h6>医生功能:</h6>
<ul>
<li>管理患者病历</li>
<li>查看分诊记录</li>
<li>更新治疗方案</li>
<li>管理科室信息</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_css %}
<style>
.jumbotron {
background-color: #f8f9fa;
border-radius: 0.5rem;
}
.card {
transition: transform 0.2s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.fas {
margin-right: 0.5rem;
}
</style>
{% endblock %}

@ -0,0 +1,104 @@
{% extends "base.html" %}
{% block title %}病历记录 - {{ super() }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">
{% if current_user.is_doctor() %}
病历管理
{% else %}
我的病历
{% endif %}
</h5>
{% if current_user.is_doctor() %}
<a href="{{ url_for('records.new_record') }}" class="btn btn-primary btn-sm">
新建病历
</a>
{% endif %}
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>编号</th>
{% if current_user.is_doctor() %}
<th>患者</th>
{% else %}
<th>主治医生</th>
{% endif %}
<th>科室</th>
<th>诊断</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for record in pagination.items %}
<tr>
<td>{{ record.id }}</td>
{% if current_user.is_doctor() %}
<td>{{ record.patient.username }}</td>
{% else %}
<td>{{ record.doctor.username if record.doctor else '待分配' }}</td>
{% endif %}
<td>{{ record.department.name if record.department else '-' }}</td>
<td>{{ record.diagnosis[:20] + '...' if record.diagnosis else '-' }}</td>
<td>
<span class="badge bg-{{ {
'pending': 'warning',
'in_progress': 'info',
'completed': 'success'
}[record.status] }}">
{{ {
'pending': '待处理',
'in_progress': '处理中',
'completed': '已完成'
}[record.status] }}
</span>
</td>
<td>{{ record.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<a href="{{ url_for('records.view_record', id=record.id) }}"
class="btn btn-info btn-sm">查看</a>
{% if current_user.is_doctor() and record.status != 'completed' %}
<a href="{{ url_for('records.edit_record', id=record.id) }}"
class="btn btn-warning btn-sm">编辑</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- 分页 -->
{% if pagination.pages > 1 %}
<nav aria-label="Page navigation" class="mt-4">
<ul class="pagination justify-content-center">
{% for page in pagination.iter_pages() %}
{% if page %}
<li class="page-item {{ 'active' if page == pagination.page else '' }}">
<a class="page-link" href="{{ url_for('records.medical_records', page=page) }}">
{{ page }}
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,117 @@
{% extends "base.html" %}
{% block title %}新建病历 - {{ super() }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">新建病历</h5>
</div>
<div class="card-body">
<form method="POST" id="newRecordForm">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="patient_id" class="form-label">患者</label>
<select class="form-select" id="patient_id" name="patient_id" required>
{% for patient in patients %}
<option value="{{ patient.id }}">{{ patient.username }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="department_id" class="form-label">就诊科室</label>
<select class="form-select" id="department_id" name="department_id" required>
{% for dept in departments %}
<option value="{{ dept.id }}"
{{ 'selected' if dept.id == current_user.department_id else '' }}>
{{ dept.name }}
</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="mb-3">
<label for="chief_complaint" class="form-label">主诉</label>
<textarea class="form-control" id="chief_complaint" name="chief_complaint"
rows="3" required></textarea>
</div>
<div class="mb-3">
<label for="present_illness" class="form-label">现病史</label>
<textarea class="form-control" id="present_illness" name="present_illness"
rows="4" required></textarea>
</div>
<div class="mb-3">
<label for="past_history" class="form-label">既往史</label>
<textarea class="form-control" id="past_history" name="past_history"
rows="3"></textarea>
</div>
<div class="mb-3">
<label for="diagnosis" class="form-label">诊断</label>
<textarea class="form-control" id="diagnosis" name="diagnosis"
rows="3" required></textarea>
</div>
<div class="mb-3">
<label for="treatment" class="form-label">治疗方案</label>
<textarea class="form-control" id="treatment" name="treatment"
rows="4" required></textarea>
</div>
<div class="mb-3">
<label for="prescription" class="form-label">处方</label>
<textarea class="form-control" id="prescription" name="prescription"
rows="4"></textarea>
</div>
<div class="mb-3">
<label for="notes" class="form-label">备注</label>
<textarea class="form-control" id="notes" name="notes" rows="2"></textarea>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary">保存病历</button>
<a href="{{ url_for('records.medical_records') }}" class="btn btn-secondary">取消</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('newRecordForm');
form.addEventListener('submit', function(e) {
const requiredFields = form.querySelectorAll('[required]');
let valid = true;
requiredFields.forEach(field => {
if (!field.value.trim()) {
valid = false;
field.classList.add('is-invalid');
} else {
field.classList.remove('is-invalid');
}
});
if (!valid) {
e.preventDefault();
alert('请填写所有必填字段');
}
});
});
</script>
{% endblock %}

@ -0,0 +1,125 @@
{% extends "base.html" %}
{% block title %}查看病历 - {{ super() }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">病历详情</h5>
<div>
{% if current_user.is_doctor() and record.status != 'completed' %}
<a href="{{ url_for('records.edit_record', id=record.id) }}"
class="btn btn-warning btn-sm">编辑病历</a>
{% endif %}
<a href="{{ url_for('records.medical_records') }}"
class="btn btn-secondary btn-sm">返回列表</a>
</div>
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-3">
<strong>病历编号:</strong>
<span>{{ record.id }}</span>
</div>
<div class="col-md-3">
<strong>患者姓名:</strong>
<span>{{ record.patient.username }}</span>
</div>
<div class="col-md-3">
<strong>主治医生:</strong>
<span>{{ record.doctor.username if record.doctor else '待分配' }}</span>
</div>
<div class="col-md-3">
<strong>就诊科室:</strong>
<span>{{ record.department.name if record.department else '-' }}</span>
</div>
</div>
<div class="medical-record-section">
<h6 class="section-title">主诉</h6>
<div class="section-content">{{ record.chief_complaint or '无' }}</div>
</div>
<div class="medical-record-section">
<h6 class="section-title">现病史</h6>
<div class="section-content">{{ record.present_illness or '无' }}</div>
</div>
<div class="medical-record-section">
<h6 class="section-title">既往史</h6>
<div class="section-content">{{ record.past_history or '无' }}</div>
</div>
<div class="medical-record-section">
<h6 class="section-title">诊断</h6>
<div class="section-content">{{ record.diagnosis or '无' }}</div>
</div>
<div class="medical-record-section">
<h6 class="section-title">治疗方案</h6>
<div class="section-content">{{ record.treatment or '无' }}</div>
</div>
<div class="medical-record-section">
<h6 class="section-title">处方</h6>
<div class="section-content">{{ record.prescription or '无' }}</div>
</div>
<div class="medical-record-section">
<h6 class="section-title">备注</h6>
<div class="section-content">{{ record.notes or '无' }}</div>
</div>
<div class="row mt-4">
<div class="col-md-4">
<strong>状态:</strong>
<span class="badge bg-{{ {
'pending': 'warning',
'in_progress': 'info',
'completed': 'success'
}[record.status] }}">
{{ {
'pending': '待处理',
'in_progress': '处理中',
'completed': '已完成'
}[record.status] }}
</span>
</div>
<div class="col-md-4">
<strong>创建时间:</strong>
<span>{{ record.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
</div>
<div class="col-md-4">
<strong>最后更新:</strong>
<span>{{ record.updated_at.strftime('%Y-%m-%d %H:%M') }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_css %}
<style>
.medical-record-section {
margin-bottom: 1.5rem;
padding: 1rem;
background-color: #f8f9fa;
border-radius: 0.25rem;
}
.section-title {
color: #495057;
margin-bottom: 0.5rem;
font-weight: 600;
}
.section-content {
white-space: pre-wrap;
color: #212529;
}
</style>
{% endblock %}

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}系统设置 - {{ super() }}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">个人设置</h5>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label for="avatar" class="form-label">头像</label>
<div class="d-flex align-items-center">
<img src="{{ url_for('static', filename='uploads/' + current_user.profile.avatar) if current_user.profile and current_user.profile.avatar else url_for('static', filename='images/default-avatar.png') }}"
class="rounded-circle me-3"
style="width: 100px; height: 100px; object-fit: cover;">
<input type="file" class="form-control" id="avatar" name="avatar" accept="image/*">
</div>
</div>
<div class="mb-3">
<label for="nickname" class="form-label">昵称</label>
<input type="text" class="form-control" id="nickname" name="nickname"
value="{{ current_user.profile.nickname if current_user.profile else '' }}"
placeholder="请输入您的昵称">
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary">保存设置</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,101 @@
{% extends "base.html" %}
{% block title %}分诊规则管理 - {{ super() }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="card shadow mb-4">
<div class="card-header">
<h5 class="card-title mb-0">添加分诊规则</h5>
</div>
<div class="card-body">
<form method="POST" id="addRuleForm">
<input type="hidden" name="action" value="add_rule">
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label for="symptom" class="form-label">症状</label>
<input type="text" class="form-control" id="symptom" name="symptom" required>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label for="department_id" class="form-label">科室</label>
<select class="form-select" id="department_id" name="department_id" required>
{% for dept in departments %}
<option value="{{ dept.id }}">{{ dept.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label for="severity" class="form-label">严重程度</label>
<select class="form-select" id="severity" name="severity" required>
<option value="1">1级 - 轻微</option>
<option value="2">2级 - 较轻</option>
<option value="3">3级 - 中等</option>
<option value="4">4级 - 严重</option>
<option value="5">5级 - 危急</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">&nbsp;</label>
<button type="submit" class="btn btn-primary d-block w-100">添加规则</button>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">现有分诊规则</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>症状</th>
<th>科室</th>
<th>严重程度</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for rule in rules %}
<tr>
<td>{{ rule.symptom }}</td>
<td>{{ rule.department.name }}</td>
<td>
<span class="badge bg-{{ ['success', 'info', 'warning', 'danger', 'dark'][rule.severity_level-1] }}">
{{ rule.severity_level }}级
</span>
</td>
<td>{{ rule.updated_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="delete_rule">
<input type="hidden" name="rule_id" value="{{ rule.id }}">
<button type="submit" class="btn btn-danger btn-sm"
onclick="return confirm('确定要删除这条规则吗?')">
删除
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,153 @@
{% extends "base.html" %}
{% block title %}智能分诊 - {{ super() }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card shadow">
<div class="card-header">
<h5 class="card-title mb-0">智能分诊</h5>
</div>
<div class="card-body">
<form id="triageForm">
<div class="mb-3">
<label for="symptoms" class="form-label">请描述您的症状</label>
<textarea class="form-control" id="symptoms" name="symptoms" rows="4"
placeholder="请详细描述您的症状,多个症状请用逗号分隔..." required></textarea>
</div>
<div class="mb-3">
<label class="form-label">常见症状快速选择:</label>
<div class="common-symptoms">
<button type="button" class="btn btn-outline-secondary btn-sm m-1" data-symptom="头痛">头痛</button>
<button type="button" class="btn btn-outline-secondary btn-sm m-1" data-symptom="发烧">发烧</button>
<button type="button" class="btn btn-outline-secondary btn-sm m-1" data-symptom="咳嗽">咳嗽</button>
<button type="button" class="btn btn-outline-secondary btn-sm m-1" data-symptom="腹痛">腹痛</button>
<button type="button" class="btn btn-outline-secondary btn-sm m-1" data-symptom="恶心">恶心</button>
<button type="button" class="btn btn-outline-secondary btn-sm m-1" data-symptom="头晕">头晕</button>
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">开始分诊</button>
</div>
</form>
<!-- 分诊结果 -->
<div id="triageResult" class="mt-4" style="display: none;">
<h5 class="border-bottom pb-2">分诊结果</h5>
<div class="result-content">
<div class="mb-3">
<strong>建议科室:</strong>
<span id="recommendedDepartment"></span>
</div>
<div class="mb-3">
<strong>严重程度:</strong>
<span id="severityLevel"></span>
</div>
<div class="mb-3">
<strong>科室说明:</strong>
<p id="departmentDescription" class="text-muted"></p>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
提示:此结果仅供参考,具体诊疗请以医生的专业判断为准。
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_css %}
<style>
.common-symptoms {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.result-content {
background-color: #f8f9fa;
padding: 20px;
border-radius: 8px;
}
.severity-badge {
padding: 5px 10px;
border-radius: 15px;
font-size: 0.9em;
}
.severity-low {
background-color: #d4edda;
color: #155724;
}
.severity-medium {
background-color: #fff3cd;
color: #856404;
}
.severity-high {
background-color: #f8d7da;
color: #721c24;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('triageForm');
const symptomsInput = document.getElementById('symptoms');
const resultDiv = document.getElementById('triageResult');
// 处理快速选择按钮
document.querySelectorAll('[data-symptom]').forEach(button => {
button.addEventListener('click', function() {
const symptom = this.dataset.symptom;
const currentSymptoms = symptomsInput.value.split(',').map(s => s.trim()).filter(s => s);
if (!currentSymptoms.includes(symptom)) {
currentSymptoms.push(symptom);
symptomsInput.value = currentSymptoms.join(', ');
}
});
});
// 处理表单提交
form.addEventListener('submit', async function(e) {
e.preventDefault();
try {
const response = await fetch('/triage', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `symptoms=${encodeURIComponent(symptomsInput.value)}`
});
const data = await response.json();
if (data.status === 'success') {
document.getElementById('recommendedDepartment').textContent = data.department;
document.getElementById('severityLevel').textContent = data.severity;
document.getElementById('departmentDescription').textContent = data.description;
resultDiv.style.display = 'block';
resultDiv.scrollIntoView({ behavior: 'smooth' });
} else {
alert('分诊失败,请稍后重试');
}
} catch (error) {
alert('网络错误,请检查您的网络连接');
}
});
});
</script>
{% endblock %}

@ -0,0 +1,19 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
# 密钥配置
SECRET_KEY = 'dev' # 在生产环境中应该使用复杂的随机字符串
# 数据库配置
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 文件上传配置
UPLOAD_FOLDER = os.path.join(basedir, 'app/static/uploads')
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 最大16MB
# 智谱AI配置
ZHIPUAI_API_KEY = "7dac79af6b3a474893d3c5aac13c087c.QJBPxMxLIaFClkLx" # 替换为您的智谱API密钥

@ -0,0 +1,36 @@
Flask==2.3.3
Werkzeug==2.3.7
Flask-Login==0.6.2
Flask-SQLAlchemy==3.0.5
SQLAlchemy==2.0.21
alembic==1.12.0
python-dotenv==1.0.0
email-validator==2.0.0.post2
pytz==2023.3.post1
requests==2.31.0
urllib3==2.0.4
cryptography==41.0.3
PyJWT==2.8.0
python-dateutil==2.8.2
pytest==7.4.2
pytest-cov==4.1.0
black==23.7.0
flake8==6.1.0
gunicorn==21.2.0
gevent==23.7.0

@ -0,0 +1,6 @@
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)

@ -0,0 +1,158 @@
from app import create_app, db
from app.models.models import User, Department, SymptomDepartment, UserProfile
from werkzeug.security import generate_password_hash
def init_departments():
"""初始化科室数据"""
departments = [
{'name': '普通内科', 'description': '处理常见内科疾病,如感冒、发烧等'},
{'name': '心内科', 'description': '专治心脏相关疾病'},
{'name': '神经内科', 'description': '治疗神经系统疾病'},
{'name': '消化内科', 'description': '治疗消化系统疾病'},
{'name': '呼吸内科', 'description': '治疗呼吸系统疾病'},
{'name': '内分泌科', 'description': '治疗内分泌系统疾病'},
{'name': '普通外科', 'description': '处理常见外科疾病'},
{'name': '骨科', 'description': '治疗骨骼相关疾病'},
{'name': '妇产科', 'description': '治疗妇科疾病和孕产相关'},
{'name': '儿科', 'description': '专门治疗儿童疾病'},
{'name': '皮肤科', 'description': '治疗皮肤相关疾病'},
{'name': '眼科', 'description': '治疗眼部相关疾病'},
{'name': '耳鼻喉科', 'description': '治疗耳鼻喉相关疾病'},
{'name': '口腔科', 'description': '治疗口腔相关疾病'},
{'name': '精神科', 'description': '治疗心理和精神相关疾病'}
]
for dept in departments:
if not Department.query.filter_by(name=dept['name']).first():
department = Department(name=dept['name'], description=dept['description'])
db.session.add(department)
db.session.commit()
def init_symptom_rules():
"""初始化症状-科室关联规则"""
rules = [
{'symptom': '发烧', 'department': '普通内科', 'severity': 2},
{'symptom': '咳嗽', 'department': '呼吸内科', 'severity': 2},
{'symptom': '胸痛', 'department': '心内科', 'severity': 4},
{'symptom': '头痛', 'department': '神经内科', 'severity': 3},
{'symptom': '腹痛', 'department': '消化内科', 'severity': 3},
{'symptom': '骨折', 'department': '骨科', 'severity': 4},
{'symptom': '皮疹', 'department': '皮肤科', 'severity': 2},
{'symptom': '视力模糊', 'department': '眼科', 'severity': 3},
{'symptom': '耳鸣', 'department': '耳鼻喉科', 'severity': 2},
{'symptom': '牙痛', 'department': '口腔科', 'severity': 2},
{'symptom': '焦虑', 'department': '精神科', 'severity': 3},
{'symptom': '高血压', 'department': '心内科', 'severity': 3},
{'symptom': '糖尿病', 'department': '内分泌科', 'severity': 3},
{'symptom': '哮喘', 'department': '呼吸内科', 'severity': 3},
{'symptom': '胃痛', 'department': '消化内科', 'severity': 2}
]
for rule in rules:
dept = Department.query.filter_by(name=rule['department']).first()
if dept and not SymptomDepartment.query.filter_by(symptom=rule['symptom']).first():
symptom_dept = SymptomDepartment(
symptom=rule['symptom'],
department_id=dept.id,
severity_level=rule['severity']
)
db.session.add(symptom_dept)
db.session.commit()
def create_test_users():
"""创建测试用户"""
# 创建管理员用户
admin = User.query.filter_by(username='admin').first()
if not admin:
admin = User(
username='admin',
email='admin@example.com',
role='doctor',
department_id=1, # 默认分配到普通内科
title='主任医师'
)
admin.set_password('admin123')
db.session.add(admin)
db.session.commit() # 先提交以获取用户ID
# 创建管理员的个人资料
admin_profile = UserProfile(
user_id=admin.id, # 现在可以安全地使用admin.id
nickname='系统管理员',
avatar='admin_default.png'
)
db.session.add(admin_profile)
db.session.commit()
# 创建测试医生
test_doctor = User.query.filter_by(username='doctor').first()
if not test_doctor:
test_doctor = User(
username='doctor',
email='doctor@example.com',
role='doctor',
department_id=1,
title='主治医师'
)
test_doctor.set_password('doctor123')
db.session.add(test_doctor)
db.session.commit() # 先提交以获取用户ID
# 创建医生的个人资料
doctor_profile = UserProfile(
user_id=test_doctor.id, # 现在可以安全地使用test_doctor.id
nickname='测试医生',
avatar='doctor_default.png'
)
db.session.add(doctor_profile)
db.session.commit()
# 创建测试患者
test_patient = User.query.filter_by(username='patient').first()
if not test_patient:
test_patient = User(
username='patient',
email='patient@example.com',
role='patient'
)
test_patient.set_password('patient123')
db.session.add(test_patient)
db.session.commit() # 先提交以获取用户ID
# 创建患者的个人资料
patient_profile = UserProfile(
user_id=test_patient.id, # 现在可以安全地使用test_patient.id
nickname='测试患者',
avatar='patient_default.png'
)
db.session.add(patient_profile)
db.session.commit()
print('测试用户创建完成')
def init_db():
"""初始化数据库"""
app = create_app()
with app.app_context():
# 创建所有表
db.create_all()
# 初始化基础数据
init_departments()
init_symptom_rules()
create_test_users()
print('数据库初始化完成!')
print('测试账号:')
print('管理员 - username: admin, password: admin123')
print('医生 - username: doctor, password: doctor123')
print('患者 - username: patient, password: patient123')
if __name__ == '__main__':
init_db()

@ -0,0 +1,25 @@
from zhipuai import ZhipuAI
def extract_content(response_message):
# 提取 content 字段的值
content = response_message.content # 获取 content 属性
return content
def ask(conte):
client = ZhipuAI(api_key="7dac79af6b3a474893d3c5aac13c087c.QJBPxMxLIaFClkLx") # 请填写您自己的APIKey
response = client.chat.completions.create(
model="glm-4-plus", # 请填写您要调用的模型名称
messages=[
{"role": "user", "content": conte},
],
)
r=extract_content(response.choices[0].message)
# print(r)
return r.encode('utf-8', 'ignore').decode('utf-8')
Loading…
Cancel
Save