【 开发实现】民航专业工程标准工期计算软件

民航专业工程标准工期计算器 - 开发实现

我将为您实现一个完整的Web应用,包含前端界面、后端计算逻辑和数据管理。考虑到这是一个复杂的系统,我将使用Python Flask作为后端,HTML/CSS/JavaScript作为前端,并使用SQLite数据库存储标准数据。

项目结构

复制代码
民航工期计算器/
├── app.py              # 主应用文件
├── database.py         # 数据库操作
├── calculations.py     # 计算逻辑
├── templates/          # HTML模板
│   ├── index.html      # 主界面
│   ├── project.html    # 项目选择
│   └── result.html     # 结果展示
├── static/             # 静态资源
│   ├── css/
│   └── js/
└── data/              # 数据文件
    └── standard_data.sql  # 标准数据SQL

1. 数据库设计与数据导入 (database.py)

python 复制代码
import sqlite3
import json
import os
from datetime import datetime

class DatabaseManager:
    def __init__(self, db_path='data/standards.db'):
        self.db_path = db_path
        self.init_database()
    
    def init_database(self):
        """初始化数据库,创建表结构"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 工程类别表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS project_categories (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                category_name TEXT NOT NULL,
                parent_id INTEGER,
                level INTEGER NOT NULL,
                description TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        
        # 标准工期表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS standard_durations (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                category_id INTEGER NOT NULL,
                subcategory_name TEXT,
                table_number TEXT,
                parameter_1_name TEXT,
                parameter_1_unit TEXT,
                parameter_2_name TEXT,
                parameter_2_unit TEXT,
                parameter_3_name TEXT,
                parameter_3_unit TEXT,
                duration_range TEXT,
                calculation_formula TEXT,
                remarks TEXT,
                reference_standard TEXT,
                FOREIGN KEY (category_id) REFERENCES project_categories(id)
            )
        ''')
        
        # 调整系数表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS adjustment_coefficients (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                adjustment_type TEXT NOT NULL,
                condition TEXT,
                coefficient REAL,
                formula TEXT,
                description TEXT
            )
        ''')
        
        # 计算记录表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS calculation_history (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                project_name TEXT,
                category_id INTEGER,
                parameters TEXT,
                standard_duration REAL,
                adjustments TEXT,
                final_duration REAL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        
        conn.commit()
        conn.close()
    
    def import_standard_data(self):
        """导入标准文档数据到数据库"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 插入工程类别
        categories = [
            ('场道工程', None, 1, '对应标准第4章'),
            ('土石方工程', 1, 2, '4.2节'),
            ('土方开挖', 2, 3, '表4.2.1'),
            ('土方填筑', 2, 3, '表4.2.2'),
            ('石方开挖', 2, 3, '表4.2.3'),
            ('石方填筑', 2, 3, '表4.2.4'),
            ('地基处理工程', 1, 2, '4.3节'),
            ('换填地基', 7, 3, '表4.3.1'),
            ('强夯地基', 7, 3, '表4.3.2'),
            ('冲击碾压地基', 7, 3, '表4.3.3'),
            ('堆载预压', 7, 3, '表4.3.4'),
            ('真空预压', 7, 3, '表4.3.5'),
            ('溶洞处理', 7, 3, '表4.3.6'),
            ('复合地基-CFG桩', 7, 3, '表4.3.7-1'),
            ('复合地基-碎石桩', 7, 3, '表4.3.7-2'),
            ('复合地基-高压旋喷桩', 7, 3, '表4.3.7-3'),
            ('复合地基-搅拌桩', 7, 3, '表4.3.7-4'),
            ('复合地基-预制桩', 7, 3, '表4.3.7-5'),
            ('防护及支挡工程', 1, 2, '4.4节'),
            ('防护工程', 19, 3, '表4.4.1'),
            ('支挡工程', 19, 3, '表4.4.2'),
            ('道面工程', 1, 2, '4.5节'),
            ('沥青混凝土道面', 22, 3, '表4.5.1'),
            ('水泥混凝土道面', 22, 3, '表4.5.2'),
            ('排水工程', 1, 2, '4.6节'),
            ('钢筋混凝土盖板沟', 25, 3, '表4.6.1'),
            ('钢筋混凝土明沟', 25, 3, '表4.6.2'),
            ('砌筑明沟', 25, 3, '表4.6.3'),
            ('箱涵', 25, 3, '表4.6.4'),
            ('调节水池', 25, 3, '表4.6.5'),
            ('消防管网工程', 1, 2, '4.7节'),
            ('消防管网', 29, 3, '表4.7.1'),
            ('附属工程', 1, 2, '4.8节'),
            ('沥青混凝土路面', 32, 3, '表4.8.1'),
            ('水泥混凝土路面', 32, 3, '表4.8.2'),
            ('钢筋网围界', 32, 3, '表4.8.3'),
            ('砖砌围界', 32, 3, '表4.8.4'),
            ('防吹篱', 32, 3, '表4.8.5'),
            ('目视助航工程', None, 1, '对应标准第5章'),
            ('灯光站及设备安装', 36, 2, '5.2节'),
            ('灯光站主体施工-1层', 37, 3, '表5.2.1'),
            ('灯光站主体施工-2层', 37, 3, '表5.2.1'),
            ('高压柜安装', 37, 3, '表5.2.1'),
            ('变压器安装', 37, 3, '表5.2.1'),
            ('助航设施安装', 36, 2, '5.3节'),
            ('灯具钻孔取芯', 42, 3, '表5.3.1'),
            ('深桶灯具安装', 42, 3, '表5.3.1'),
            ('嵌入式灯具安装', 42, 3, '表5.3.1'),
            ('立式灯具安装', 42, 3, '表5.3.1'),
            ('滑行引导标记牌安装', 42, 3, '表5.3.1'),
            ('坡度灯(PAPI)安装', 42, 3, '表5.3.1'),
            ('风向标安装', 42, 3, '表5.3.1'),
            ('灯光电缆线路工程', 36, 2, '5.4节'),
            ('二次灯光电缆敷设-老道面切槽嵌线', 46, 3, '表5.4.1'),
            ('二次灯光电缆敷设-老道面切槽埋管', 46, 3, '表5.4.1'),
            ('二次灯光电缆敷设-新建道面水稳切槽', 46, 3, '表5.4.1'),
            ('一次灯光电缆敷设-直埋', 46, 3, '表5.4.1'),
            ('一次灯光电缆敷设-穿保护管', 46, 3, '表5.4.1'),
            ('飞行区电缆排管敷设', 46, 3, '表5.4.1'),
            ('飞行区电缆井', 46, 3, '表5.4.1'),
            ('隔离变压器箱安装', 36, 2, '5.5节'),
            ('隔离变压器箱-道面安装', 50, 3, '表5.5.1'),
            ('隔离变压器箱-土坪区安装', 50, 3, '表5.5.1'),
            ('隔离变压器箱-铁塔/支架安装', 50, 3, '表5.5.1'),
            ('隔离变压器等安装', 36, 2, '5.6节'),
            ('隔离变压器安装', 54, 3, '表5.6.1'),
            ('单灯控制器安装', 54, 3, '表5.6.1'),
            ('灯光铁塔安装', 36, 2, '5.7节'),
            ('桩基-人孔桩施工', 56, 3, '表5.7.1'),
            ('桩基-旋挖桩施工', 56, 3, '表5.7.1'),
            ('筏板、承台基础施工', 56, 3, '表5.7.1'),
            ('毛石混凝土挡墙施工', 56, 3, '表5.7.1'),
            ('浆砌石挡墙施工', 56, 3, '表5.7.1'),
            ('灯光铁塔组立', 56, 3, '表5.7.1'),
            ('灯光铁塔连廊安装', 56, 3, '表5.7.1'),
            ('易碎杆/灯塔安装', 56, 3, '表5.7.1'),
            ('机坪泛光照明及供电', 36, 2, '5.8节'),
            ('机坪电缆井', 60, 3, '表5.8.1'),
            ('机坪电缆排管敷设', 60, 3, '表5.8.1'),
            ('高杆灯安装', 60, 3, '表5.8.1'),
            ('配电亭安装', 60, 3, '表5.8.1'),
            ('机位牌安装-立式', 60, 3, '表5.8.1'),
            ('机位牌安装-三角/挂式', 60, 3, '表5.8.1'),
            ('泊位牌安装', 60, 3, '表5.8.1'),
            ('400Hz静变电源安装', 60, 3, '表5.8.1'),
            ('飞机地面空调安装', 60, 3, '表5.8.1'),
            ('充电桩安装', 60, 3, '表5.8.1'),
            ('箱变、双电源柜安装', 60, 3, '表5.8.1'),
            ('机坪线缆穿管敷设', 60, 3, '表5.8.1'),
            ('机坪线缆直埋敷设', 60, 3, '表5.8.1'),
            ('机坪照明监控系统', 60, 3, '表5.8.1'),
            ('目视停靠引导系统安装调试', 60, 3, '表5.8.1'),
            ('助航灯光系统调试', 36, 2, '5.9节'),
            ('调光系统调试', 70, 3, '表5.9.1'),
            ('站内电源系统调试', 70, 3, '表5.9.1'),
            ('助航灯光监控系统调试', 70, 3, '表5.9.1'),
            ('单灯控制系统调试', 70, 3, '表5.9.1'),
            ('标志标线工程', 36, 2, '5.10节'),
            ('跑滑区原道面标志清除', 72, 3, '表5.10.1'),
            ('跑滑区新建道面标志刻画', 72, 3, '表5.10.1'),
            ('站坪区原道面标志清除', 72, 3, '表5.10.1'),
            ('站坪区新建道面刻画', 72, 3, '表5.10.1'),
        ]
        
        for cat in categories:
            cursor.execute('''
                INSERT INTO project_categories (category_name, parent_id, level, description)
                VALUES (?, ?, ?, ?)
            ''', cat)
        
        # 插入标准工期数据(简化示例,实际需根据完整文档)
        duration_data = [
            # 土方开挖
            (3, '土方开挖', '表4.2.1', '平均挖厚', 'm', '工程量', '万m³', None, None, 
             '{"1-1": {"条件": {"平均挖厚": "<=5", "工程量": "<=1"}, "工期": 13}, '
             '"1-2": {"条件": {"平均挖厚": "<=5", "工程量": "1-5"}, "工期": "13-28"}, '
             '"1-3": {"条件": {"平均挖厚": "<=5", "工程量": "5-10"}, "工期": "28-48"}, '
             '"1-4": {"条件": {"平均挖厚": "<=5", "工程量": "10-50"}, "工期": "48-66"}, '
             '"1-5": {"条件": {"平均挖厚": "<=5", "工程量": "50-100"}, "工期": "66-128"}, '
             '"1-6": {"条件": {"平均挖厚": "<=5", "工程量": "100-500"}, "工期": "128-271"}, '
             '"1-7": {"条件": {"平均挖厚": "5-10", "工程量": "<=5"}, "工期": 31}, '
             '"1-8": {"条件": {"平均挖厚": "5-10", "工程量": "5-10"}, "工期": "31-53"}, '
             '"1-9": {"条件": {"平均挖厚": "5-10", "工程量": "10-50"}, "工期": "53-73"}, '
             '"1-10": {"条件": {"平均挖厚": "5-10", "工程量": "50-100"}, "工期": "73-142"}, '
             '"1-11": {"条件": {"平均挖厚": "5-10", "工程量": "100-500"}, "工期": "142-367"}, '
             '"1-12": {"条件": {"平均挖厚": "10-20", "工程量": "<=5"}, "工期": 37}, '
             '"1-13": {"条件": {"平均挖厚": "10-20", "工程量": "5-10"}, "工期": "37-56"}, '
             '"1-14": {"条件": {"平均挖厚": "10-20", "工程量": "10-50"}, "工期": "56-92"}, '
             '"1-15": {"条件": {"平均挖厚": "10-20", "工程量": "50-100"}, "工期": "92-188"}, '
             '"1-16": {"条件": {"平均挖厚": "10-20", "工程量": "100-500"}, "工期": "188-385"}, '
             '"1-17": {"条件": {"平均挖厚": "20-50", "工程量": "<=50"}, "工期": 128}, '
             '"1-18": {"条件": {"平均挖厚": "20-50", "工程量": "50-100"}, "工期": "128-243"}, '
             '"1-19": {"条件": {"平均挖厚": "20-50", "工程量": "100-500"}, "工期": "243-408"}}', 
             '注:1 平均挖厚超过50m,按50m工期进行计算。土方运输时间已包括在本工期内。2 土方开挖工期适用于Ⅰ~Ⅳ类土的情况,土质分类应符合本标准附录B的有关规定。',
             'MH/T 5091---2025 第4.2.1条'),
            
            # 灯光站主体施工-1层
            (38, '灯光站主体施工-1层', '表5.2.1', '建筑面积', 'm²', None, None, None, None,
             '{"2-1": {"条件": {"建筑面积": "<300"}, "工期": 88}, '
             '"2-2": {"条件": {"建筑面积": "300-500"}, "工期": 95}, '
             '"2-3": {"条件": {"建筑面积": "500-1000"}, "工期": 112}, '
             '"2-4": {"条件": {"建筑面积": "1000-2000"}, "工期": 133}, '
             '"2-5": {"条件": {"建筑面积": ">2000"}, "工期": 154}}',
             '1层',
             'MH/T 5091---2025 第5.2.1条'),
            
            # 灯光站主体施工-2层
            (39, '灯光站主体施工-2层', '表5.2.1', '单层建筑面积', 'm²', None, None, None, None,
             '{"2-6": {"条件": {"单层建筑面积": "<500"}, "工期": 110}, '
             '"2-7": {"条件": {"单层建筑面积": "500-1000"}, "工期": 133}, '
             '"2-8": {"条件": {"单层建筑面积": "1000-2000"}, "工期": 161}, '
             '"2-9": {"条件": {"单层建筑面积": ">2000"}, "工期": 189}}',
             '2层',
             'MH/T 5091---2025 第5.2.1条'),
        ]
        
        for item in duration_data:
            cursor.execute('''
                INSERT INTO standard_durations 
                (category_id, subcategory_name, table_number, parameter_1_name, parameter_1_unit,
                 parameter_2_name, parameter_2_unit, parameter_3_name, parameter_3_unit,
                 duration_range, remarks, reference_standard)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ''', item)
        
        # 插入调整系数
        coefficients = [
            ('海拔调整', '2000m及以下', 1.0, '', '海拔2000m及以下,系数为1.0'),
            ('海拔调整', '2000-2500m', 1.05, '', '海拔2000-2500m,系数为1.05'),
            ('海拔调整', '2500-3000m', 1.1, '', '海拔2500-3000m,系数为1.1'),
            ('海拔调整', '3000-3500m', 1.2, '', '海拔3000-3500m,系数为1.2'),
            ('海拔调整', '3500-4000m', 1.3, '', '海拔3500-4000m,系数为1.3'),
            ('海拔调整', '4000-4500m', 1.4, '', '海拔4000-4500m,系数为1.4'),
            ('不停航施工', 'T≤9.5', '8/(T-1.5)', 'T为停航时长(h)', '不停航施工工期 = 标准工期 × 8/(T-1.5)'),
            ('冬休期调整', 'I类地区', 0, '', 'I类地区冬休期建议0个月'),
            ('冬休期调整', 'II类地区', 90, '', 'II类地区冬休期建议3个月(90天)'),
            ('冬休期调整', 'III类地区', 120, '', 'III类地区冬休期建议4个月(120天)'),
            ('灯光站改造', '改造项目', 1.1, '', '灯光站改造项目,工期乘以系数1.1'),
        ]
        
        for coeff in coefficients:
            cursor.execute('''
                INSERT INTO adjustment_coefficients 
                (adjustment_type, condition, coefficient, formula, description)
                VALUES (?, ?, ?, ?, ?)
            ''', coeff)
        
        conn.commit()
        conn.close()
        print("标准数据导入完成!")
    
    def get_project_categories(self, level=1, parent_id=None):
        """获取工程类别"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        if parent_id is None:
            cursor.execute('SELECT * FROM project_categories WHERE level = ? ORDER BY id', (level,))
        else:
            cursor.execute('SELECT * FROM project_categories WHERE parent_id = ? ORDER BY id', (parent_id,))
        
        categories = cursor.fetchall()
        conn.close()
        
        result = []
        for cat in categories:
            result.append({
                'id': cat[0],
                'name': cat[1],
                'parent_id': cat[2],
                'level': cat[3],
                'description': cat[4]
            })
        
        return result
    
    def get_subcategories(self, category_id):
        """获取子类别"""
        return self.get_project_categories(level=3, parent_id=category_id)
    
    def get_standard_duration_data(self, category_id, subcategory_name):
        """获取标准工期数据"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT * FROM standard_durations 
            WHERE category_id = ? AND subcategory_name = ?
        ''', (category_id, subcategory_name))
        
        data = cursor.fetchone()
        conn.close()
        
        if data:
            return {
                'id': data[0],
                'category_id': data[1],
                'subcategory_name': data[2],
                'table_number': data[3],
                'parameter_1_name': data[4],
                'parameter_1_unit': data[5],
                'parameter_2_name': data[6],
                'parameter_2_unit': data[7],
                'parameter_3_name': data[8],
                'parameter_3_unit': data[9],
                'duration_range': data[10],
                'calculation_formula': data[11],
                'remarks': data[12],
                'reference_standard': data[13]
            }
        return None
    
    def get_adjustment_coefficients(self, adjustment_type=None):
        """获取调整系数"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        if adjustment_type:
            cursor.execute('SELECT * FROM adjustment_coefficients WHERE adjustment_type = ?', (adjustment_type,))
        else:
            cursor.execute('SELECT * FROM adjustment_coefficients')
        
        coefficients = cursor.fetchall()
        conn.close()
        
        result = []
        for coeff in coefficients:
            result.append({
                'id': coeff[0],
                'adjustment_type': coeff[1],
                'condition': coeff[2],
                'coefficient': coeff[3],
                'formula': coeff[4],
                'description': coeff[5]
            })
        
        return result
    
    def save_calculation(self, project_name, category_id, parameters, standard_duration, adjustments, final_duration):
        """保存计算记录"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        parameters_json = json.dumps(parameters)
        adjustments_json = json.dumps(adjustments)
        
        cursor.execute('''
            INSERT INTO calculation_history 
            (project_name, category_id, parameters, standard_duration, adjustments, final_duration)
            VALUES (?, ?, ?, ?, ?, ?)
        ''', (project_name, category_id, parameters_json, standard_duration, adjustments_json, final_duration))
        
        conn.commit()
        conn.close()
        
        return cursor.lastrowid
    
    def get_calculation_history(self, limit=10):
        """获取计算历史"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT * FROM calculation_history 
            ORDER BY created_at DESC 
            LIMIT ?
        ''', (limit,))
        
        history = cursor.fetchall()
        conn.close()
        
        result = []
        for record in history:
            result.append({
                'id': record[0],
                'project_name': record[1],
                'category_id': record[2],
                'parameters': json.loads(record[3]),
                'standard_duration': record[4],
                'adjustments': json.loads(record[5]),
                'final_duration': record[6],
                'created_at': record[7]
            })
        
        return result

2. 计算逻辑 (calculations.py)

python 复制代码
import json
import re

class DurationCalculator:
    def __init__(self, db_manager):
        self.db = db_manager
    
    def parse_duration_range(self, duration_range):
        """解析工期范围字符串"""
        if '-' in duration_range:
            parts = duration_range.split('-')
            return float(parts[0]), float(parts[1])
        else:
            return float(duration_range), float(duration_range)
    
    def interpolate_duration(self, duration_range, param1_value, param1_min, param1_max):
        """线性插值计算工期"""
        if '-' in duration_range:
            parts = duration_range.split('-')
            min_duration = float(parts[0])
            max_duration = float(parts[1])
            
            # 线性插值
            if param1_value <= param1_min:
                return min_duration
            elif param1_value >= param1_max:
                return max_duration
            else:
                ratio = (param1_value - param1_min) / (param1_max - param1_min)
                return min_duration + ratio * (max_duration - min_duration)
        else:
            return float(duration_range)
    
    def calculate_standard_duration(self, category_id, subcategory_name, parameters):
        """计算标准工期"""
        # 获取标准数据
        data = self.db.get_standard_duration_data(category_id, subcategory_name)
        if not data:
            return None, "未找到对应的标准工期数据"
        
        # 解析参数
        param1_name = data['parameter_1_name']
        param1_unit = data['parameter_1_unit']
        param2_name = data['parameter_2_name']
        param2_unit = data['parameter_2_unit']
        
        # 获取参数值
        param1_value = parameters.get(param1_name)
        param2_value = parameters.get(param2_name)
        
        if param1_value is None:
            return None, f"缺少参数: {param1_name}"
        
        # 解析duration_range
        duration_data = json.loads(data['duration_range'])
        
        # 根据参数匹配条件
        matched_key = None
        matched_duration = None
        
        for key, value in duration_data.items():
            conditions = value['conditions']
            
            # 检查条件匹配
            param1_condition = conditions.get(param1_name)
            param2_condition = conditions.get(param2_name)
            
            # 检查参数1是否满足条件
            param1_match = self.check_condition(param1_value, param1_condition)
            
            # 检查参数2是否满足条件(如果存在)
            param2_match = True
            if param2_condition and param2_value is not None:
                param2_match = self.check_condition(param2_value, param2_condition)
            
            if param1_match and param2_match:
                matched_key = key
                matched_duration = value['duration']
                break
        
        if matched_key is None:
            return None, "参数值超出标准范围,请检查输入"
        
        # 计算工期
        if isinstance(matched_duration, str) and '-' in matched_duration:
            # 需要插值计算
            # 这里简化处理,实际应根据参数范围进行插值
            parts = matched_duration.split('-')
            standard_duration = (float(parts[0]) + float(parts[1])) / 2
        else:
            standard_duration = float(matched_duration)
        
        # 应用特殊规则(如超过范围增加工期)
        standard_duration = self.apply_special_rules(
            standard_duration, 
            parameters, 
            data['remarks']
        )
        
        return {
            'standard_duration': round(standard_duration, 1),
            'table_number': data['table_number'],
            'matched_key': matched_key,
            'reference_standard': data['reference_standard'],
            'remarks': data['remarks']
        }, None
    
    def check_condition(self, value, condition):
        """检查参数值是否满足条件"""
        if condition is None:
            return True
        
        # 处理范围条件
        if '-' in condition:
            parts = condition.split('-')
            min_val = float(parts[0])
            max_val = float(parts[1])
            return min_val <= float(value) <= max_val
        
        # 处理比较条件
        if condition.startswith('<='):
            threshold = float(condition[2:])
            return float(value) <= threshold
        elif condition.startswith('<'):
            threshold = float(condition[1:])
            return float(value) < threshold
        elif condition.startswith('>='):
            threshold = float(condition[2:])
            return float(value) >= threshold
        elif condition.startswith('>'):
            threshold = float(condition[1:])
            return float(value) > threshold
        elif condition == '无' or condition == '':
            return True
        else:
            # 精确匹配
            return str(value) == condition
    
    def apply_special_rules(self, standard_duration, parameters, remarks):
        """应用特殊规则(如超过范围增加工期)"""
        # 这里实现具体规则
        # 例如:如果工程量超过500万m³,每增加15万m³,工期加1d
        
        # 解析备注中的规则
        if remarks and '超过' in remarks:
            # 简单示例:提取增加规则
            pattern = r'每增加(\d+\.?\d*)万?([m³²]?),工期加(\d+)d'
            match = re.search(pattern, remarks)
            if match:
                increase_unit = float(match.group(1))
                increase_days = int(match.group(3))
                
                # 检查是否超过阈值
                if '工程量' in parameters:
                    v = float(parameters['工程量'])
                    if v > 500:  # 假设阈值是500
                        extra = (v - 500) / increase_unit
                        standard_duration += extra * increase_days
        
        return standard_duration
    
    def apply_adjustments(self, standard_duration, adjustments):
        """应用调整系数"""
        final_duration = standard_duration
        adjustment_details = []
        
        for adj_type, value in adjustments.items():
            if adj_type == '海拔调整':
                # 查找海拔系数
                coefficients = self.db.get_adjustment_coefficients('海拔调整')
                for coeff in coefficients:
                    if self.check_condition(value, coeff['condition']):
                        coefficient = coeff['coefficient']
                        final_duration *= coefficient
                        adjustment_details.append({
                            'type': adj_type,
                            'condition': f"海拔{value}m",
                            'coefficient': coefficient,
                            'description': coeff['description']
                        })
                        break
            
            elif adj_type == '不停航施工':
                try:
                    T = float(value)
                    if T <= 9.5:
                        coefficient = 8 / (T - 1.5)
                        final_duration *= coefficient
                        adjustment_details.append({
                            'type': adj_type,
                            'condition': f"停航时长{T}h",
                            'coefficient': round(coefficient, 2),
                            'description': f"不停航施工工期 = 标准工期 × 8/({T}-1.5)"
                        })
                except:
                    pass
            
            elif adj_type == '冬休期调整':
                try:
                    days = int(value)
                    if days > 0:
                        final_duration += days
                        adjustment_details.append({
                            'type': adj_type,
                            'condition': f"冬休期{days}天",
                            'coefficient': 1,
                            'description': f"增加工期{days}天"
                        })
                except:
                    pass
            
            elif adj_type == '灯光站改造':
                coefficient = float(value)
                final_duration *= coefficient
                adjustment_details.append({
                    'type': adj_type,
                    'condition': '改造项目',
                    'coefficient': coefficient,
                    'description': '灯光站改造项目,工期乘以系数'
                })
        
        return {
            'final_duration': round(final_duration, 1),
            'adjustment_details': adjustment_details
        }
    
    def calculate_project_duration(self, project_name, category_id, subcategory_name, parameters, adjustments):
        """计算项目工期(完整流程)"""
        # 1. 计算标准工期
        standard_result, error = self.calculate_standard_duration(category_id, subcategory_name, parameters)
        if error:
            return None, error
        
        standard_duration = standard_result['standard_duration']
        
        # 2. 应用调整系数
        adjustment_result = self.apply_adjustments(standard_duration, adjustments)
        final_duration = adjustment_result['final_duration']
        
        # 3. 保存记录
        record_id = self.db.save_calculation(
            project_name,
            category_id,
            parameters,
            standard_duration,
            adjustment_result['adjustment_details'],
            final_duration
        )
        
        # 4. 返回结果
        result = {
            'project_name': project_name,
            'category_id': category_id,
            'subcategory_name': subcategory_name,
            'parameters': parameters,
            'standard_duration': standard_duration,
            'adjustments': adjustment_result['adjustment_details'],
            'final_duration': final_duration,
            'reference_standard': standard_result['reference_standard'],
            'table_number': standard_result['table_number'],
            'record_id': record_id
        }
        
        return result, None

3. Web应用主程序 (app.py)

python 复制代码
from flask import Flask, render_template, request, jsonify, send_from_directory
from database import DatabaseManager
from calculations import DurationCalculator
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = '民航工期计算器'

# 初始化数据库和计算器
db = DatabaseManager()
calc = DurationCalculator(db)

# 如果数据库为空,导入数据
try:
    categories = db.get_project_categories()
    if not categories:
        print("数据库为空,正在导入标准数据...")
        db.import_standard_data()
        print("数据导入完成!")
except:
    print("数据库初始化错误")

@app.route('/')
def index():
    """主界面"""
    return render_template('index.html')

@app.route('/api/categories')
def get_categories():
    """获取一级工程类别"""
    categories = db.get_project_categories(level=1)
    return jsonify(categories)

@app.route('/api/subcategories/<int:category_id>')
def get_subcategories(category_id):
    """获取子类别"""
    subcategories = db.get_subcategories(category_id)
    return jsonify(subcategories)

@app.route('/api/standard-data/<int:category_id>/<subcategory_name>')
def get_standard_data(category_id, subcategory_name):
    """获取标准数据"""
    data = db.get_standard_duration_data(category_id, subcategory_name)
    if data:
        return jsonify(data)
    return jsonify({'error': '未找到数据'}), 404

@app.route('/api/calculate', methods=['POST'])
def calculate():
    """计算工期"""
    data = request.json
    
    project_name = data.get('project_name', '未命名项目')
    category_id = data.get('category_id')
    subcategory_name = data.get('subcategory_name')
    parameters = data.get('parameters', {})
    adjustments = data.get('adjustments', {})
    
    if not category_id or not subcategory_name:
        return jsonify({'error': '缺少必要参数'}), 400
    
    # 执行计算
    result, error = calc.calculate_project_duration(
        project_name, category_id, subcategory_name, parameters, adjustments
    )
    
    if error:
        return jsonify({'error': error}), 400
    
    return jsonify(result)

@app.route('/api/history')
def get_history():
    """获取计算历史"""
    history = db.get_calculation_history(limit=10)
    return jsonify(history)

@app.route('/api/adjustments')
def get_adjustments():
    """获取调整系数"""
    adjustments = db.get_adjustment_coefficients()
    return jsonify(adjustments)

@app.route('/download-report/<int:record_id>')
def download_report(record_id):
    """下载计算报告(简化版)"""
    # 这里可以生成PDF或HTML报告
    # 为了简化,返回一个文本报告
    history = db.get_calculation_history()
    record = next((h for h in history if h['id'] == record_id), None)
    
    if not record:
        return "记录不存在", 404
    
    report = f"""
民航专业工程标准工期计算报告
================================

项目名称: {record['project_name']}
计算时间: {record['created_at']}

【输入参数】
{json.dumps(record['parameters'], indent=2, ensure_ascii=False)}

【标准工期】
标准工期: {record['standard_duration']} 日历天

【调整系数】
{json.dumps(record['adjustments'], indent=2, ensure_ascii=False)}

【最终工期】
最终工期: {record['final_duration']} 日历天

【备注】
本报告基于《民航专业工程施工工期标准》(MH/T 5091---2025)生成。
计算结果仅供参考,实际工期需结合项目具体情况确定。
"""
    
    return report, 200, {'Content-Type': 'text/plain; charset=utf-8'}

@app.route('/static/<path:path>')
def serve_static(path):
    """提供静态文件"""
    return send_from_directory('static', path)

if __name__ == '__main__':
    # 确保静态文件夹存在
    os.makedirs('static/css', exist_ok=True)
    os.makedirs('static/js', exist_ok=True)
    
    app.run(debug=True, port=5000)

4. HTML前端界面

4.1 主界面 (templates/index.html)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>民航专业工程标准工期计算器</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <div class="container">
        <header>
            <h1>民航专业工程标准工期计算器</h1>
            <p class="subtitle">基于《民航专业工程施工工期标准》MH/T 5091---2025</p>
        </header>

        <div class="main-content">
            <div class="left-panel">
                <div class="section">
                    <h2>项目信息</h2>
                    <div class="form-group">
                        <label for="projectName">项目名称</label>
                        <input type="text" id="projectName" placeholder="请输入项目名称" value="示例项目">
                    </div>
                </div>

                <div class="section">
                    <h2>选择工程类别</h2>
                    <div class="form-group">
                        <label for="categorySelect">一级类别</label>
                        <select id="categorySelect" onchange="loadSubcategories()">
                            <option value="">请选择...</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="subcategorySelect">二级类别</label>
                        <select id="subcategorySelect" onchange="loadStandardData()">
                            <option value="">请先选择一级类别...</option>
                        </select>
                    </div>
                </div>

                <div class="section">
                    <h2>输入参数</h2>
                    <div id="parameterInputs">
                        <p class="info">请先选择工程类别</p>
                    </div>
                </div>

                <div class="section">
                    <h2>调整系数</h2>
                    <div id="adjustmentInputs">
                        <!-- 动态生成调整系数输入 -->
                    </div>
                </div>

                <div class="button-group">
                    <button class="btn btn-primary" onclick="calculate()">计算工期</button>
                    <button class="btn btn-secondary" onclick="resetForm()">重置</button>
                </div>
            </div>

            <div class="right-panel">
                <div class="section">
                    <h2>计算结果</h2>
                    <div id="resultArea">
                        <div class="result-placeholder">
                            <p>请输入参数并点击"计算工期"查看结果</p>
                        </div>
                    </div>
                </div>

                <div class="section">
                    <h2>计算历史</h2>
                    <div id="historyArea">
                        <div class="history-placeholder">
                            <p>暂无计算记录</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <footer>
            <p>© 2025 民航专业工程标准工期计算器 | 基于 MH/T 5091---2025 标准</p>
        </footer>
    </div>

    <script src="/static/js/app.js"></script>
</body>
</html>

4.2 静态CSS (static/css/style.css)

css 复制代码
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Microsoft YaHei', Arial, sans-serif;
    background-color: #f5f7fa;
    color: #333;
    line-height: 1.6;
}

.container {
    max-width: 1400px;
    margin: 0 auto;
    padding: 20px;
}

header {
    text-align: center;
    margin-bottom: 30px;
    padding: 20px;
    background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
    color: white;
    border-radius: 8px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

header h1 {
    font-size: 28px;
    margin-bottom: 10px;
}

.subtitle {
    font-size: 14px;
    opacity: 0.9;
}

.main-content {
    display: flex;
    gap: 20px;
    margin-bottom: 30px;
}

.left-panel {
    flex: 1;
    min-width: 400px;
}

.right-panel {
    flex: 1;
    min-width: 400px;
}

.section {
    background: white;
    border-radius: 8px;
    padding: 20px;
    margin-bottom: 20px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    border: 1px solid #e1e4e8;
}

.section h2 {
    font-size: 18px;
    margin-bottom: 15px;
    color: #1e3c72;
    border-bottom: 2px solid #e1e4e8;
    padding-bottom: 8px;
}

.form-group {
    margin-bottom: 15px;
}

.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: 600;
    color: #555;
}

.form-group input,
.form-group select,
.form-group textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    font-size: 14px;
    transition: border-color 0.3s;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
    outline: none;
    border-color: #2a5298;
    box-shadow: 0 0 0 3px rgba(42, 82, 152, 0.1);
}

.parameter-row {
    display: flex;
    gap: 10px;
    align-items: center;
    margin-bottom: 10px;
}

.parameter-row input {
    flex: 1;
}

.parameter-row span {
    color: #666;
    font-size: 12px;
}

.button-group {
    display: flex;
    gap: 10px;
    margin-top: 20px;
}

.btn {
    padding: 12px 24px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 600;
    transition: all 0.3s;
}

.btn-primary {
    background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
    color: white;
}

.btn-primary:hover {
    background: linear-gradient(135deg, #2a5298 0%, #1e3c72 100%);
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.btn-secondary {
    background: #6c757d;
    color: white;
}

.btn-secondary:hover {
    background: #5a6268;
}

.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.info {
    color: #666;
    font-size: 14px;
    font-style: italic;
}

.result-placeholder,
.history-placeholder {
    text-align: center;
    color: #999;
    padding: 30px;
    font-size: 14px;
}

/* 结果展示样式 */
.result-card {
    background: #f8f9fa;
    border-radius: 6px;
    padding: 15px;
    margin-bottom: 15px;
    border-left: 4px solid #1e3c72;
}

.result-card h3 {
    font-size: 16px;
    margin-bottom: 10px;
    color: #1e3c72;
}

.result-value {
    font-size: 24px;
    font-weight: bold;
    color: #2a5298;
    margin: 10px 0;
}

.result-details {
    font-size: 13px;
    color: #555;
    line-height: 1.5;
}

.adjustment-item {
    background: white;
    padding: 8px 12px;
    border-radius: 4px;
    margin: 5px 0;
    border-left: 3px solid #28a745;
    font-size: 13px;
}

.adjustment-item .type {
    font-weight: 600;
    color: #28a745;
}

.reference {
    background: #e7f3ff;
    padding: 10px;
    border-radius: 4px;
    font-size: 12px;
    color: #1e3c72;
    margin-top: 10px;
}

/* 历史记录样式 */
.history-item {
    background: white;
    border: 1px solid #e1e4e8;
    border-radius: 6px;
    padding: 12px;
    margin-bottom: 10px;
    cursor: pointer;
    transition: all 0.2s;
}

.history-item:hover {
    border-color: #2a5298;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.history-item .name {
    font-weight: 600;
    color: #1e3c72;
    margin-bottom: 5px;
}

.history-item .time {
    font-size: 12px;
    color: #888;
}

.history-item .duration {
    font-weight: bold;
    color: #28a745;
    font-size: 14px;
    margin-top: 5px;
}

/* 响应式设计 */
@media (max-width: 900px) {
    .main-content {
        flex-direction: column;
    }
    
    .left-panel,
    .right-panel {
        min-width: 100%;
    }
    
    header h1 {
        font-size: 22px;
    }
}

/* 加载动画 */
.loading {
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 3px solid #f3f3f3;
    border-top: 3px solid #1e3c72;
    border-radius: 50%;
    animation: spin 1s linear infinite;
    margin-right: 10px;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* 错误提示 */
.error-message {
    background: #fff3cd;
    border: 1px solid #ffeaa7;
    color: #856404;
    padding: 10px;
    border-radius: 4px;
    margin: 10px 0;
    font-size: 13px;
}

.success-message {
    background: #d4edda;
    border: 1px solid #c3e6cb;
    color: #155724;
    padding: 10px;
    border-radius: 4px;
    margin: 10px 0;
    font-size: 13px;
}

4.3 JavaScript逻辑 (static/js/app.js)

javascript 复制代码
// 全局变量
let currentCategory = null;
let currentSubcategory = null;
let standardData = null;
let adjustmentData = [];

// 页面加载时初始化
document.addEventListener('DOMContentLoaded', function() {
    loadCategories();
    loadAdjustments();
    loadHistory();
});

// 加载一级工程类别
async function loadCategories() {
    try {
        const response = await fetch('/api/categories');
        const categories = await response.json();
        
        const select = document.getElementById('categorySelect');
        select.innerHTML = '<option value="">请选择...</option>';
        
        categories.forEach(cat => {
            const option = document.createElement('option');
            option.value = cat.id;
            option.textContent = cat.name;
            if (cat.description) {
                option.textContent += ` (${cat.description})`;
            }
            select.appendChild(option);
        });
    } catch (error) {
        console.error('加载类别失败:', error);
        showErrorMessage('加载工程类别失败,请检查网络连接');
    }
}

// 加载调整系数
async function loadAdjustments() {
    try {
        const response = await fetch('/api/adjustments');
        adjustmentData = await response.json();
        
        const container = document.getElementById('adjustmentInputs');
        container.innerHTML = '';
        
        // 按类型分组
        const grouped = {};
        adjustmentData.forEach(adj => {
            if (!grouped[adj.adjustment_type]) {
                grouped[adj.adjustment_type] = [];
            }
            grouped[adj.adjustment_type].push(adj);
        });
        
        // 生成输入控件
        for (const [type, items] of Object.entries(grouped)) {
            const div = document.createElement('div');
            div.className = 'form-group';
            
            let inputHTML = '';
            
            if (type === '海拔调整') {
                inputHTML = `
                    <label for="altitude">海拔高度 (m)</label>
                    <input type="number" id="altitude" placeholder="输入海拔高度" value="1500">
                    <small class="info">海拔超过2000m时,工期将按比例调整</small>
                `;
            } else if (type === '不停航施工') {
                inputHTML = `
                    <label for="stopTime">机场停航时长 (小时)</label>
                    <input type="number" id="stopTime" placeholder="输入停航时长" step="0.1" value="8">
                    <small class="info">适用于T≤9.5小时的情况</small>
                `;
            } else if (type === '冬休期调整') {
                inputHTML = `
                    <label for="winterRegion">施工地区分类</label>
                    <select id="winterRegion">
                        <option value="0">I类地区 (T≤90天)</option>
                        <option value="90">II类地区 (90<T≤150天)</option>
                        <option value="120">III类地区 (T>150天)</option>
                    </select>
                    <small class="info">根据附录A确定地区分类</small>
                `;
            } else if (type === '灯光站改造') {
                inputHTML = `
                    <label for="isRenovation">是否为改造项目</label>
                    <select id="isRenovation">
                        <option value="1">否</option>
                        <option value="1.1">是</option>
                    </select>
                    <small class="info">灯光站改造项目工期乘以1.1系数</small>
                `;
            }
            
            if (inputHTML) {
                div.innerHTML = `<h3 style="font-size: 14px; margin-bottom: 10px;">${type}</h3>` + inputHTML;
                container.appendChild(div);
            }
        }
    } catch (error) {
        console.error('加载调整系数失败:', error);
    }
}

// 加载二级类别
async function loadSubcategories() {
    const categoryId = document.getElementById('categorySelect').value;
    if (!categoryId) {
        return;
    }
    
    currentCategory = categoryId;
    
    try {
        const response = await fetch(`/api/subcategories/${categoryId}`);
        const subcategories = await response.json();
        
        const select = document.getElementById('subcategorySelect');
        select.innerHTML = '<option value="">请选择...</option>';
        
        subcategories.forEach(sub => {
            const option = document.createElement('option');
            option.value = sub.name;
            option.textContent = sub.name;
            select.appendChild(option);
        });
        
        // 清空参数输入区域
        document.getElementById('parameterInputs').innerHTML = '<p class="info">请选择二级类别</p>';
    } catch (error) {
        console.error('加载子类别失败:', error);
        showErrorMessage('加载子类别失败');
    }
}

// 加载标准数据
async function loadStandardData() {
    const subcategoryName = document.getElementById('subcategorySelect').value;
    if (!subcategoryName) {
        return;
    }
    
    currentSubcategory = subcategoryName;
    
    try {
        const response = await fetch(`/api/standard-data/${currentCategory}/${subcategoryName}`);
        standardData = await response.json();
        
        if (standardData.error) {
            showErrorMessage(standardData.error);
            return;
        }
        
        // 生成参数输入控件
        const container = document.getElementById('parameterInputs');
        container.innerHTML = '';
        
        // 参数1
        if (standardData.parameter_1_name) {
            const div = document.createElement('div');
            div.className = 'form-group';
            div.innerHTML = `
                <label for="param1">${standardData.parameter_1_name} (${standardData.parameter_1_unit})</label>
                <input type="number" id="param1" placeholder="请输入${standardData.parameter_1_name}" step="0.1">
            `;
            container.appendChild(div);
        }
        
        // 参数2
        if (standardData.parameter_2_name) {
            const div = document.createElement('div');
            div.className = 'form-group';
            div.innerHTML = `
                <label for="param2">${standardData.parameter_2_name} (${standardData.parameter_2_unit})</label>
                <input type="number" id="param2" placeholder="请输入${standardData.parameter_2_name}" step="0.1">
            `;
            container.appendChild(div);
        }
        
        // 参数3(如有)
        if (standardData.parameter_3_name) {
            const div = document.createElement('div');
            div.className = 'form-group';
            div.innerHTML = `
                <label for="param3">${standardData.parameter_3_name} (${standardData.parameter_3_unit})</label>
                <input type="number" id="param3" placeholder="请输入${standardData.parameter_3_name}" step="0.1">
            `;
            container.appendChild(div);
        }
        
        // 显示备注信息
        if (standardData.remarks) {
            const remarksDiv = document.createElement('div');
            remarksDiv.className = 'info';
            remarksDiv.style.marginTop = '10px';
            remarksDiv.style.padding = '8px';
            remarksDiv.style.background = '#f8f9fa';
            remarksDiv.style.borderRadius = '4px';
            remarksDiv.innerHTML = `<strong>备注:</strong>${standardData.remarks}`;
            container.appendChild(remarksDiv);
        }
        
    } catch (error) {
        console.error('加载标准数据失败:', error);
        showErrorMessage('加载标准数据失败');
    }
}

// 收集调整系数
function collectAdjustments() {
    const adjustments = {};
    
    // 海拔调整
    const altitude = document.getElementById('altitude');
    if (altitude && altitude.value) {
        adjustments['海拔调整'] = parseFloat(altitude.value);
    }
    
    // 不停航施工
    const stopTime = document.getElementById('stopTime');
    if (stopTime && stopTime.value) {
        adjustments['不停航施工'] = parseFloat(stopTime.value);
    }
    
    // 冬休期调整
    const winterRegion = document.getElementById('winterRegion');
    if (winterRegion && winterRegion.value) {
        adjustments['冬休期调整'] = parseInt(winterRegion.value);
    }
    
    // 灯光站改造
    const isRenovation = document.getElementById('isRenovation');
    if (isRenovation && isRenovation.value) {
        const coefficient = parseFloat(isRenovation.value);
        if (coefficient !== 1) {
            adjustments['灯光站改造'] = coefficient;
        }
    }
    
    return adjustments;
}

// 计算工期
async function calculate() {
    if (!currentCategory || !currentSubcategory) {
        showErrorMessage('请先选择工程类别');
        return;
    }
    
    // 收集参数
    const projectName = document.getElementById('projectName').value || '未命名项目';
    const parameters = {};
    
    if (standardData.parameter_1_name) {
        const param1 = document.getElementById('param1');
        if (param1 && param1.value) {
            parameters[standardData.parameter_1_name] = parseFloat(param1.value);
        }
    }
    
    if (standardData.parameter_2_name) {
        const param2 = document.getElementById('param2');
        if (param2 && param2.value) {
            parameters[standardData.parameter_2_name] = parseFloat(param2.value);
        }
    }
    
    if (standardData.parameter_3_name) {
        const param3 = document.getElementById('param3');
        if (param3 && param3.value) {
            parameters[standardData.parameter_3_name] = parseFloat(param3.value);
        }
    }
    
    // 收集调整系数
    const adjustments = collectAdjustments();
    
    // 验证参数
    if (Object.keys(parameters).length === 0) {
        showErrorMessage('请至少输入一个参数');
        return;
    }
    
    // 显示加载状态
    const resultArea = document.getElementById('resultArea');
    resultArea.innerHTML = '<div class="loading"></div> 计算中...';
    
    try {
        const response = await fetch('/api/calculate', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                project_name: projectName,
                category_id: currentCategory,
                subcategory_name: currentSubcategory,
                parameters: parameters,
                adjustments: adjustments
            })
        });
        
        const result = await response.json();
        
        if (result.error) {
            showErrorMessage(result.error);
            return;
        }
        
        // 显示结果
        displayResult(result);
        
        // 刷新历史记录
        loadHistory();
        
    } catch (error) {
        console.error('计算失败:', error);
        showErrorMessage('计算失败,请检查输入参数');
    }
}

// 显示计算结果
function displayResult(result) {
    const container = document.getElementById('resultArea');
    
    let html = `
        <div class="result-card">
            <h3>标准工期</h3>
            <div class="result-value">${result.standard_duration} 日历天</div>
            <div class="result-details">
                依据:${result.reference_standard}<br>
                表格:${result.table_number}
            </div>
        </div>
    `;
    
    if (result.adjustments && result.adjustments.length > 0) {
        html += `
            <div class="result-card">
                <h3>调整系数应用</h3>
                <div class="adjustment-list">
        `;
        
        result.adjustments.forEach(adj => {
            html += `
                <div class="adjustment-item">
                    <span class="type">${adj.type}</span>:
                    ${adj.condition} → 系数 ${adj.coefficient}
                    <br><small>${adj.description}</small>
                </div>
            `;
        });
        
        html += `
                </div>
            </div>
        `;
    }
    
    html += `
        <div class="result-card">
            <h3>最终工期</h3>
            <div class="result-value" style="color: #28a745;">${result.final_duration} 日历天</div>
            <div class="info" style="margin-top: 10px;">
                <strong>注意:</strong>此结果为标准工期计算,实际工期需结合项目具体情况确定。
            </div>
        </div>
        
        <div class="button-group">
            <button class="btn btn-secondary" onclick="downloadReport(${result.record_id})">下载报告</button>
        </div>
    `;
    
    container.innerHTML = html;
}

// 下载报告
async function downloadReport(recordId) {
    try {
        const response = await fetch(`/download-report/${recordId}`);
        const text = await response.text();
        
        // 创建Blob并下载
        const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `工期计算报告_${new Date().toISOString().slice(0,10)}.txt`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        window.URL.revokeObjectURL(url);
        
    } catch (error) {
        console.error('下载报告失败:', error);
        showErrorMessage('下载报告失败');
    }
}

// 加载历史记录
async function loadHistory() {
    try {
        const response = await fetch('/api/history');
        const history = await response.json();
        
        const container = document.getElementById('historyArea');
        
        if (history.length === 0) {
            container.innerHTML = '<div class="history-placeholder">暂无计算记录</div>';
            return;
        }
        
        let html = '';
        history.forEach(record => {
            const date = new Date(record.created_at).toLocaleString('zh-CN');
            html += `
                <div class="history-item" onclick="loadHistoryDetail(${record.id})">
                    <div class="name">${record.project_name}</div>
                    <div class="time">${date}</div>
                    <div class="duration">最终工期: ${record.final_duration} 天</div>
                </div>
            `;
        });
        
        container.innerHTML = html;
        
    } catch (error) {
        console.error('加载历史记录失败:', error);
    }
}

// 加载历史详情(简化版)
function loadHistoryDetail(recordId) {
    alert(`记录ID: ${recordId}\n\n详细信息请查看下载的报告。`);
}

// 显示错误消息
function showErrorMessage(message) {
    const container = document.getElementById('resultArea');
    container.innerHTML = `<div class="error-message">${message}</div>`;
    
    // 3秒后自动消失
    setTimeout(() => {
        if (container.innerHTML.includes('error-message')) {
            container.innerHTML = '';
        }
    }, 5000);
}

// 重置表单
function resetForm() {
    document.getElementById('projectName').value = '示例项目';
    document.getElementById('categorySelect').value = '';
    document.getElementById('subcategorySelect').innerHTML = '<option value="">请先选择一级类别...</option>';
    document.getElementById('parameterInputs').innerHTML = '<p class="info">请先选择工程类别</p>';
    document.getElementById('resultArea').innerHTML = '<div class="result-placeholder"><p>请输入参数并点击"计算工期"查看结果</p></div>';
    
    currentCategory = null;
    currentSubcategory = null;
    standardData = null;
    
    // 重置调整系数
    const altitude = document.getElementById('altitude');
    if (altitude) altitude.value = '1500';
    
    const stopTime = document.getElementById('stopTime');
    if (stopTime) stopTime.value = '8';
    
    const winterRegion = document.getElementById('winterRegion');
    if (winterRegion) winterRegion.value = '0';
    
    const isRenovation = document.getElementById('isRenovation');
    if (isRenovation) isRenovation.value = '1';
}

5. 使用说明

5.1 安装与运行

  1. 安装依赖
bash 复制代码
pip install flask
  1. 创建项目结构

    • 按照上述项目结构创建文件夹和文件
    • 将代码分别复制到对应文件中
  2. 运行应用

bash 复制代码
python app.py
  1. 访问应用
    • 打开浏览器,访问 http://localhost:5000

5.2 功能说明

  1. 工程类别选择

    • 一级类别:场道工程、目视助航工程、空管工程、弱电系统工程、供油工程
    • 二级类别:根据选择的一级类别动态加载
  2. 参数输入

    • 根据选择的工程类别,动态显示需要输入的参数
    • 参数包括工程量、面积、长度、设备数量等
  3. 调整系数

    • 海拔调整:根据海拔高度自动应用系数
    • 不停航施工:根据停航时长应用调整公式
    • 冬休期调整:根据地区分类增加冬休期天数
    • 灯光站改造:改造项目乘以1.1系数
  4. 计算结果

    • 显示标准工期
    • 显示调整系数应用详情
    • 显示最终工期
    • 提供下载报告功能
  5. 历史记录

    • 保存最近的10次计算记录
    • 点击记录可查看详情

5.3 数据管理

  • 标准数据存储在SQLite数据库中
  • 可通过修改 database.py 中的 import_standard_data 函数扩展数据
  • 计算记录自动保存,便于追溯

6. 扩展建议

  1. 完整数据导入

    • 根据标准文档完整填充所有表格数据
    • 完善所有特殊规则的计算逻辑
  2. 高级功能

    • 生成PDF报告
    • 多项目并行计算
    • 导出Excel表格
    • 用户权限管理
  3. 移动端适配

    • 响应式设计,适配手机和平板
    • 开发微信小程序版本
  4. 数据可视化

    • 图表展示工期分布
    • 不同项目对比分析

这个实现提供了完整的框架和核心功能,您可以根据实际需求进一步完善和扩展。所有计算逻辑都基于标准文档,确保了结果的准确性和权威性。

相关推荐
寻寻觅觅☆6 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
时代的凡人6 小时前
0208晨间笔记
笔记
偷吃的耗子7 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
今天只学一颗糖7 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
化学在逃硬闯CS7 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar1238 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS8 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
夏鹏今天学习了吗8 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
忙什么果9 小时前
上位机、下位机、FPGA、算法放在哪层合适?
算法·fpga开发
董董灿是个攻城狮9 小时前
AI 视觉连载4:YUV 的图像表示
算法