本仓库为远程超声诊断平台的统一代码仓库,包含前端、后端、AI质控模块以及专网实时通信中间件。 采用 Monorepo 结构管理,方便统一版本控制,支持针对不同医院/客户进行定制化开发。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

113 lines
4.9 KiB

import os
import json
from flask import Flask, render_template_string, request, redirect, url_for, flash
app = Flask(__name__)
app.secret_key = "supersecretkey"
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
VOCAB_DIR = os.path.join(BASE_DIR, "data", "vocab")
CONFIG_DIR = os.path.join(BASE_DIR, "data", "config")
FILES = {
"l1": os.path.join(VOCAB_DIR, "l1_standard.json"),
"l2": os.path.join(VOCAB_DIR, "l2_hospital.json"),
"l3": os.path.join(VOCAB_DIR, "l3_mapping.json"),
"pinyin": os.path.join(VOCAB_DIR, "pinyin_map.json"),
"scoring": os.path.join(CONFIG_DIR, "scoring_standard.json")
}
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>医疗影像AI质控 - 后台管理</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f7f6; margin: 0; padding: 20px; }
.container { max-width: 1000px; margin: auto; background: white; padding: 30px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
h1 { color: #2c3e50; text-align: center; border-bottom: 2px solid #3498db; padding-bottom: 15px; }
.nav { display: flex; justify-content: center; gap: 10px; margin-bottom: 25px; }
.nav a { text-decoration: none; color: #34495e; padding: 8px 16px; border-radius: 6px; background: #ecf0f1; transition: 0.3s; }
.nav a:hover, .nav a.active { background: #3498db; color: white; }
.editor-area { margin-top: 20px; }
textarea { width: 100%; height: 500px; font-family: monospace; font-size: 14px; padding: 15px; border: 2px solid #bdc3c7; border-radius: 8px; box-sizing: border-box; }
.btn-save { display: block; width: 100%; padding: 12px; background: #27ae60; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; margin-top: 15px; transition: 0.3s; }
.btn-save:hover { background: #219150; }
.alert { padding: 15px; margin-bottom: 20px; border-radius: 6px; }
.alert-success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.alert-error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
</style>
</head>
<body>
<div class="container">
<h1>🏥 医疗影像质控后台管理</h1>
<div class="nav">
<a href="{{ url_for('edit', file_key='l1') }}" class="{{ 'active' if current == 'l1' }}">L1 标准术语</a>
<a href="{{ url_for('edit', file_key='l2') }}" class="{{ 'active' if current == 'l2' }}">L2 本院特色</a>
<a href="{{ url_for('edit', file_key='l3') }}" class="{{ 'active' if current == 'l3' }}">L3 纠错对照</a>
<a href="{{ url_for('edit', file_key='pinyin') }}" class="{{ 'active' if current == 'pinyin' }}">拼音映射</a>
<a href="{{ url_for('edit', file_key='scoring') }}" class="{{ 'active' if current == 'scoring' }}">评分引擎配置</a>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="editor-area">
<h3>正在编辑:{{ file_name }}</h3>
<form method="POST">
<textarea name="content">{{ content }}</textarea>
<button type="submit" class="btn-save">保存修改并同步到 RuleEngine</button>
</form>
</div>
</div>
</body>
</html>
"""
@app.route('/')
def index():
return redirect(url_for('edit', file_key='l1'))
@app.route('/edit/<file_key>', methods=['GET', 'POST'])
def edit(file_key):
if file_key not in FILES:
return "File not found", 404
file_path = FILES[file_key]
if request.method == 'POST':
content = request.form.get('content')
try:
# Validate JSON
json_data = json.loads(content)
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(json_data, f, ensure_ascii=False, indent=2)
flash("保存成功!RuleEngine 已实时同步最新规则。", "success")
except Exception as e:
flash(f"保存失败:JSON 格式错误 ( {str(e)} )", "error")
return redirect(url_for('edit', file_key=file_key))
# GET
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
else:
content = "{}" if file_key != "l1" and file_key != "l2" else "[]"
return render_template_string(
HTML_TEMPLATE,
content=content,
current=file_key,
file_name=os.path.basename(file_path)
)
if __name__ == '__main__':
# Ensure directories exist
os.makedirs(VOCAB_DIR, exist_ok=True)
os.makedirs(CONFIG_DIR, exist_ok=True)
app.run(port=5005, debug=True)