/*==============================================================
  船员证书到期预警系统
  --------------------------------------------------------------
  课程：面向对象程序设计课程设计
  环境：Dev-C++ 5.11 / VC 6.0（C++98 写法，可直接编译）
  存储：文本文件（crew.txt / cert.txt / user.txt）
  技术：封装、继承、多态、虚析构、组合、运算符重载、函数模板
==============================================================*/

#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
#include <vector>
#include <map>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <io.h>
#include <conio.h>

using namespace std;

/*==================== 全局常量 ====================*/
const int  DAYS_URGENT = 30;          // 紧急阈值：30 天内到期
const int  DAYS_WARN   = 90;          // 提醒阈值：90 天内到期
const char CREW_FILE[] = "crew.txt";  // 船员档案
const char CERT_FILE[] = "cert.txt";  // 证书数据
const char USER_FILE[] = "user.txt";  // 用户账号

/*==================== 通用小工具 ====================*/
string trim(const string& s) {
    string::size_type b = s.find_first_not_of(" \t\r\n");
    if (b == string::npos) return "";
    string::size_type e = s.find_last_not_of(" \t\r\n");
    return s.substr(b, e - b + 1);
}

bool strToInt(const string& s, int& value) {
    if (s.empty()) return false;
    istringstream iss(s);
    int v; char extra;
    if (!(iss >> v)) return false;
    if (iss >> extra) return false;
    value = v;
    return true;
}

// 中文在 GBK 编码下一个字占 2 字节，正好等于 2 个显示宽度，
// 所以按字节补齐就能对齐表格。
string pad(const string& s, int width) {
    if ((int)s.size() >= width) return s;
    return s + string(width - (int)s.size(), ' ');
}

void printLine(int n, char c) {
    cout << string(n, c) << endl;
}

void printTitle(const string& title) {
    printLine(72, '=');
    cout << "  " << title << endl;
    printLine(72, '=');
}

// 输入被重定向时（自动化测试）自动跳过"按回车"，避免卡住
bool stdinIsConsole() {
    return _isatty(_fileno(stdin)) != 0;
}

void clearScreen() {
    if (stdinIsConsole()) system("cls");
}

void waitEnter() {
    if (!stdinIsConsole()) return;
    cout << "\n按回车键继续...";
    string dummy;
    getline(cin, dummy);
}

string readLine(const string& prompt) {
    cout << prompt;
    string s;
    getline(cin, s);
    return trim(s);
}

// 密码输入：控制台下手动屏蔽回显；输入被重定向时按普通字符串读取
string readPassword(const string& prompt) {
    cout << prompt;
    string s;
    if (!stdinIsConsole()) {
        getline(cin, s);
        return trim(s);
    }
    for (;;) {
        int ch = _getch();
        if (ch == '\r' || ch == '\n') break;
        if (ch == '\b') {
            if (!s.empty()) {
                s.erase(s.size() - 1);
                cout << "\b \b";
            }
            continue;
        }
        if (ch == 0 || ch == 224) { _getch(); continue; }   // 方向键等
        if (ch >= 32 && ch < 127) { s += (char)ch; cout << '*'; }
    }
    cout << endl;
    return s;
}

int readInt(const string& prompt, int defValue) {
    string s = readLine(prompt);
    int v;
    if (!strToInt(s, v)) return defValue;
    return v;
}

// 直接回车表示"保持原值"
string readKeep(const string& prompt, const string& current) {
    string s = readLine(prompt + "[" + current + "]: ");
    if (s.empty()) return current;
    return s;
}

bool readYesNo(const string& prompt) {
    string s = readLine(prompt);
    return (s == "y" || s == "Y" || s == "yes" || s == "是");
}

string nowText() {
    time_t t = time(NULL);
    struct tm* lt = localtime(&t);
    char buf[40];
    sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d",
            lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
            lt->tm_hour, lt->tm_min, lt->tm_sec);
    return string(buf);
}
string timeStamp() {
    time_t t = time(NULL);
    struct tm* lt = localtime(&t);
    char buf[32];
    sprintf(buf, "%04d%02d%02d_%02d%02d%02d",
            lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
            lt->tm_hour, lt->tm_min, lt->tm_sec);
    return string(buf);
}

/*==================== 日期类 ====================*/
class Date {
private:
    int y, m, d;

    static bool isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }
    static int daysInMonth(int year, int month) {
        int dm[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        if (month == 2 && isLeapYear(year)) return 29;
        if (month < 1 || month > 12) return 0;
        return dm[month];
    }

public:
    Date() : y(1900), m(1), d(1) {}
    Date(int year, int month, int day) : y(year), m(month), d(day) {}

    int getYear()  const { return y; }
    int getMonth() const { return m; }
    int getDay()   const { return d; }

    bool isValid() const {
        if (y < 1900 || y > 2200) return false;
        if (m < 1 || m > 12) return false;
        if (d < 1 || d > daysInMonth(y, m)) return false;
        return true;
    }

    // 解析 "YYYY-MM-DD"
    static bool parse(const string& text, Date& out) {
        if (text.size() != 10) return false;
        if (text[4] != '-' || text[7] != '-') return false;
        for (int i = 0; i < 10; ++i) {
            if (i == 4 || i == 7) continue;
            if (text[i] < '0' || text[i] > '9') return false;
        }
        int yy = (text[0] - '0') * 1000 + (text[1] - '0') * 100
               + (text[2] - '0') * 10 + (text[3] - '0');
        int mm = (text[5] - '0') * 10 + (text[6] - '0');
        int dd = (text[8] - '0') * 10 + (text[9] - '0');
        Date t(yy, mm, dd);
        if (!t.isValid()) return false;
        out = t;
        return true;
    }

    static Date today() {
        time_t t = time(NULL);
        struct tm* lt = localtime(&t);
        return Date(lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday);
    }

    string toString() const {
        ostringstream oss;
        oss << setfill('0') << setw(4) << y << '-' << setw(2) << m
            << '-' << setw(2) << d << setfill(' ');
        return oss.str();
    }

    // 转成"自 1970-01-01 起的天数"，两个日期相减即为相差天数
    int toDays() const {
        int yy = y, mm = m;
        yy -= (mm <= 2);
        int era = (yy >= 0 ? yy : yy - 399) / 400;
        int yoe = yy - era * 400;
        int doy = (153 * (mm + (mm > 2 ? -3 : 9)) + 2) / 5 + d - 1;
        int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
        return era * 146097 + doe - 719468;
    }

    int  operator-(const Date& o) const { return toDays() - o.toDays(); }
    bool operator<(const Date& o) const  { return toDays() < o.toDays(); }
    bool operator<=(const Date& o) const { return toDays() <= o.toDays(); }
    bool operator==(const Date& o) const { return y == o.y && m == o.m && d == o.d; }
};

ostream& operator<<(ostream& os, const Date& dt) {
    return os << dt.toString();
}

/*==================== 证书类族 ====================*/
// 抽象基类：三种证书共同的属性 + 多态接口
class Certificate {
protected:
    string certNo;      // 证书编号
    string crewId;      // 所属船员工号
    string certName;    // 证书名称
    string issuer;      // 签发机关
    Date   issueDate;   // 发证日期
    Date   expireDate;  // 到期日期

public:
    Certificate(const string& no, const string& cid, const string& name,
                const string& iss, const Date& issue, const Date& expire)
        : certNo(no), crewId(cid), certName(name), issuer(iss),
          issueDate(issue), expireDate(expire) {}

    virtual ~Certificate() {}   // 虚析构：通过基类指针删除派生类对象

    /*---- 纯虚函数：由派生类给出各自实现 ----*/
    virtual int    typeCode() const = 0;   // 类别编码
    virtual string typeName() const = 0;   // 类别名称
    virtual string extra1()   const = 0;   // 附加字段 1（用于存文件）
    virtual string extra2()   const = 0;   // 附加字段 2
    virtual void   printExtra() const = 0; // 打印附加信息
    virtual bool   validate(string& msg) const;  // 合法性校验

    /*---- 通用访问接口 ----*/
    const string& getCertNo() const     { return certNo; }
    const string& getCrewId() const     { return crewId; }
    const string& getCertName() const   { return certName; }
    const string& getIssuer() const     { return issuer; }
    const Date&   getIssueDate() const  { return issueDate; }
    const Date&   getExpireDate() const { return expireDate; }

    void setCertName(const string& v)   { certName = v; }
    void setIssuer(const string& v)     { issuer = v; }
    void setIssueDate(const Date& v)    { issueDate = v; }
    void setExpireDate(const Date& v)   { expireDate = v; }

    /*---- 业务逻辑 ----*/
    int remainDays() const { return expireDate - Date::today(); }
    bool isExpired() const { return remainDays() < 0; }

    string statusText() const {
        int r = remainDays();
        if (r < 0)            return "已过期";
        if (r <= DAYS_URGENT) return "紧急(30天内)";
        if (r <= DAYS_WARN)   return "提醒(90天内)";
        return "正常";
    }

    // 序列化成一行，供写入文件
    string toLine() const {
        ostringstream oss;
        oss << certNo << '\t' << crewId << '\t' << typeCode() << '\t'
            << certName << '\t' << issueDate.toString() << '\t'
            << expireDate.toString() << '\t' << issuer << '\t'
            << extra1() << '\t' << extra2();
        return oss.str();
    }
};

bool Certificate::validate(string& msg) const {
    if (certNo.empty()) { msg = "证书编号不能为空"; return false; }
    if (certName.empty()) { msg = "证书名称不能为空"; return false; }
    if (!issueDate.isValid() || !expireDate.isValid()) {
        msg = "日期不合法（格式应为 YYYY-MM-DD）";
        return false;
    }
    if (!(issueDate < expireDate)) {
        msg = "到期日期必须晚于发证日期";
        return false;
    }
    return true;
}

ostream& operator<<(ostream& os, const Certificate& c) {
    os << c.getCertNo() << "  " << c.typeName() << "  "
       << c.getExpireDate().toString() << "  剩余" << c.remainDays()
       << "天  " << c.statusText();
    return os;
}

/*---- 派生类 1：适任证书 ----*/
class CompetencyCert : public Certificate {
private:
    string rank;      // 职务：船长 / 大副 / 轮机长 ...
    string shipType;  // 适用船型：远洋 / 沿海 / 内河
public:
    CompetencyCert(const string& no, const string& cid, const string& name,
                   const string& iss, const Date& issue, const Date& expire,
                   const string& r, const string& st)
        : Certificate(no, cid, name, iss, issue, expire), rank(r), shipType(st) {}

    int    typeCode() const { return 1; }
    string typeName() const { return "适任证书"; }
    string extra1()   const { return rank; }
    string extra2()   const { return shipType; }
    void   printExtra() const { cout << "职务:" << rank << "  适用船型:" << shipType; }

    void setRank(const string& v)     { rank = v; }
    void setShipType(const string& v) { shipType = v; }

    bool validate(string& msg) const {
        if (!Certificate::validate(msg)) return false;
        if (rank.empty()) { msg = "职务不能为空"; return false; }
        if (shipType.empty()) { msg = "适用船型不能为空"; return false; }
        return true;
    }
};

/*---- 派生类 2：健康证 ----*/
class HealthCert : public Certificate {
private:
    string hospital;  // 体检机构
    string result;    // 体检结论：合格 / 不合格
public:
    HealthCert(const string& no, const string& cid, const string& name,
               const string& iss, const Date& issue, const Date& expire,
               const string& h, const string& r)
        : Certificate(no, cid, name, iss, issue, expire), hospital(h), result(r) {}

    int    typeCode() const { return 2; }
    string typeName() const { return "健康证"; }
    string extra1()   const { return hospital; }
    string extra2()   const { return result; }
    void   printExtra() const { cout << "体检机构:" << hospital << "  结论:" << result; }

    void setHospital(const string& v) { hospital = v; }
    void setResult(const string& v)   { result = v; }

    bool validate(string& msg) const {
        if (!Certificate::validate(msg)) return false;
        if (hospital.empty()) { msg = "体检机构不能为空"; return false; }
        if (result != "合格") { msg = "健康证体检结论必须为“合格”"; return false; }
        // 健康证有效期一般不超过 2 年
        if (getExpireDate() - getIssueDate() > 730) {
            msg = "健康证有效期不能超过 2 年";
            return false;
        }
        return true;
    }
};

/*---- 派生类 3：培训合格证 ----*/
class TrainingCert : public Certificate {
private:
    string item;      // 培训项目
public:
    TrainingCert(const string& no, const string& cid, const string& name,
                 const string& iss, const Date& issue, const Date& expire,
                 const string& it)
        : Certificate(no, cid, name, iss, issue, expire), item(it) {}

    int    typeCode() const { return 3; }
    string typeName() const { return "培训合格证"; }
    string extra1()   const { return item; }
    string extra2()   const { return ""; }
    void   printExtra() const { cout << "培训项目:" << item; }

    void setItem(const string& v) { item = v; }

    bool validate(string& msg) const {
        if (!Certificate::validate(msg)) return false;
        if (item.empty()) { msg = "培训项目不能为空"; return false; }
        return true;
    }
};

// 简单工厂：按类别编码创建对应派生类对象
Certificate* createCertificate(int type, const string& no, const string& cid,
                               const string& name, const string& iss,
                               const Date& issue, const Date& expire,
                               const string& e1, const string& e2) {
    if (type == 1) return new CompetencyCert(no, cid, name, iss, issue, expire, e1, e2);
    if (type == 2) return new HealthCert(no, cid, name, iss, issue, expire, e1, e2);
    if (type == 3) return new TrainingCert(no, cid, name, iss, issue, expire, e1);
    return NULL;
}

/*==================== 船员类 ====================*/
class Crew {
private:
    string id;      // 工号
    string name;    // 姓名
    string gender;  // 性别
    Date   birth;   // 出生日期
    string rank;    // 职务
    string phone;   // 联系电话
    vector<Certificate*> certs;   // 组合：船员"拥有"若干证书

    Crew(const Crew&);             // 禁止拷贝，避免证书指针被重复释放
    Crew& operator=(const Crew&);

public:
    Crew(const string& i, const string& n, const string& g,
         const Date& b, const string& r, const string& p)
        : id(i), name(n), gender(g), birth(b), rank(r), phone(p) {}

    ~Crew() {
        for (size_t i = 0; i < certs.size(); ++i) delete certs[i];
        certs.clear();
    }

    const string& getId() const     { return id; }
    const string& getName() const   { return name; }
    const string& getGender() const { return gender; }
    const Date&   getBirth() const  { return birth; }
    const string& getRank() const   { return rank; }
    const string& getPhone() const  { return phone; }

    void setName(const string& v)   { name = v; }
    void setGender(const string& v) { gender = v; }
    void setBirth(const Date& v)    { birth = v; }
    void setRank(const string& v)   { rank = v; }
    void setPhone(const string& v)  { phone = v; }

    void addCert(Certificate* c) { certs.push_back(c); }

    const vector<Certificate*>& getCerts() const { return certs; }

    Certificate* findCert(const string& certNo) const {
        for (size_t i = 0; i < certs.size(); ++i)
            if (certs[i]->getCertNo() == certNo) return certs[i];
        return NULL;
    }

    bool removeCert(const string& certNo) {
        for (size_t i = 0; i < certs.size(); ++i) {
            if (certs[i]->getCertNo() == certNo) {
                delete certs[i];
                certs.erase(certs.begin() + i);
                return true;
            }
        }
        return false;
    }

    int certCount() const { return (int)certs.size(); }
    int expiredCount() const {
        int n = 0;
        for (size_t i = 0; i < certs.size(); ++i)
            if (certs[i]->isExpired()) ++n;
        return n;
    }
    int warnCount() const {          // 90 天内到期（不含已过期）
        int n = 0;
        for (size_t i = 0; i < certs.size(); ++i) {
            int r = certs[i]->remainDays();
            if (r >= 0 && r <= DAYS_WARN) ++n;
        }
        return n;
    }

    int urgentCount() const {        // 30 天内到期（不含已过期）
        int n = 0;
        for (size_t i = 0; i < certs.size(); ++i) {
            int r = certs[i]->remainDays();
            if (r >= 0 && r <= DAYS_URGENT) ++n;
        }
        return n;
    }

    void printRow() const {
        ostringstream oss;
        oss << certCount();
        cout << pad(id, 8) << pad(name, 10) << pad(gender, 6)
             << pad(birth.toString(), 14) << pad(rank, 16)
             << pad(phone, 14) << pad(oss.str(), 8) << expiredCount() << endl;
    }
};

/*==================== 函数模板：选择法排序 ====================*/
// 与实验"选择法排序"同样的算法，但用模板写成通用版本，
// 比较规则由调用者传入的函数决定（函数指针）。
template <class T>
void selectionSort(vector<T>& data, bool (*less)(const T&, const T&)) {
    int n = (int)data.size();
    for (int i = 0; i < n - 1; ++i) {
        int k = i;
        for (int j = i + 1; j < n; ++j) {
            if (less(data[j], data[k])) k = j;
        }
        if (k != i) {
            T tmp = data[i];
            data[i] = data[k];
            data[k] = tmp;
        }
    }
}

/*==================== 预警条目 ====================*/
struct AlertItem {
    Crew*        crew;
    Certificate* cert;
    int          remain;   // 剩余天数
};

bool alertLess(const AlertItem& a, const AlertItem& b) {
    return a.remain < b.remain;
}

/*==================== 船员管理类 ====================*/
class CrewManager {
private:
    vector<Crew*> crews;
    string crewFile, certFile;

    CrewManager(const CrewManager&);
    CrewManager& operator=(const CrewManager&);

public:
    CrewManager(const string& cf, const string& tf) : crewFile(cf), certFile(tf) {}

    ~CrewManager() {
        for (size_t i = 0; i < crews.size(); ++i) delete crews[i];
        crews.clear();
    }

    const vector<Crew*>& allCrews() const { return crews; }

    Crew* findById(const string& id) const {
        for (size_t i = 0; i < crews.size(); ++i)
            if (crews[i]->getId() == id) return crews[i];
        return NULL;
    }

    vector<Crew*> findByName(const string& key) const {
        vector<Crew*> result;
        for (size_t i = 0; i < crews.size(); ++i)
            if (crews[i]->getName().find(key) != string::npos)
                result.push_back(crews[i]);
        return result;
    }

    Certificate* findCert(const string& certNo, Crew** owner = NULL) const {
        for (size_t i = 0; i < crews.size(); ++i) {
            Certificate* c = crews[i]->findCert(certNo);
            if (c != NULL) {
                if (owner != NULL) *owner = crews[i];
                return c;
            }
        }
        return NULL;
    }

    bool addCrew(Crew* c) {
        if (c == NULL) return false;
        if (findById(c->getId()) != NULL) return false;
        crews.push_back(c);
        return true;
    }

    bool removeCrew(const string& id) {
        for (size_t i = 0; i < crews.size(); ++i) {
            if (crews[i]->getId() == id) {
                delete crews[i];
                crews.erase(crews.begin() + i);
                return true;
            }
        }
        return false;
    }

    int crewCount() const { return (int)crews.size(); }
    int certCount() const {
        int n = 0;
        for (size_t i = 0; i < crews.size(); ++i) n += crews[i]->certCount();
        return n;
    }
    int expiredCount() const {
        int n = 0;
        for (size_t i = 0; i < crews.size(); ++i) n += crews[i]->expiredCount();
        return n;
    }
    int warnCount() const {
        int n = 0;
        for (size_t i = 0; i < crews.size(); ++i) n += crews[i]->warnCount();
        return n;
    }

    int urgentCount() const {
        int n = 0;
        for (size_t i = 0; i < crews.size(); ++i) n += crews[i]->urgentCount();
        return n;
    }

    vector<AlertItem> collectAlerts(int maxDays, int minDays = -100000) const {
        vector<AlertItem> list;
        for (size_t i = 0; i < crews.size(); ++i) {
            const vector<Certificate*>& cs = crews[i]->getCerts();
            for (size_t j = 0; j < cs.size(); ++j) {
                int r = cs[j]->remainDays();
                if (r >= minDays && r <= maxDays) {
                    AlertItem item;
                    item.crew = crews[i];
                    item.cert = cs[j];
                    item.remain = r;
                    list.push_back(item);
                }
            }
        }
        selectionSort<AlertItem>(list, alertLess);   // 按剩余天数升序
        return list;
    }

    /*---- 从文本文件读入 / 写出 ----*/
    bool load() {
        ifstream fin(crewFile.c_str());
        if (!fin) return false;

        string line;
        while (getline(fin, line)) {
            if (trim(line).empty()) continue;
            istringstream iss(line);
            string id, name, gender, birth, rank, phone;
            if (!(iss >> id >> name >> gender >> birth >> rank >> phone)) continue;
            Date b;
            if (!Date::parse(birth, b)) b = Date();
            crews.push_back(new Crew(id, name, gender, b, rank, phone));
        }
        fin.close();

        ifstream fc(certFile.c_str());
        if (!fc) return true;   // 证书文件可以暂时为空

        while (getline(fc, line)) {
            if (trim(line).empty()) continue;
            istringstream iss(line);
            string no, cid, typeStr, cname, issueStr, expireStr, isser, e1, e2;
            if (!(iss >> no >> cid >> typeStr >> cname >> issueStr >> expireStr >> isser)) continue;
            iss >> e1 >> e2;

            int type = 0;
            strToInt(typeStr, type);
            Date issue, expire;
            Date::parse(issueStr, issue);
            Date::parse(expireStr, expire);

            Crew* owner = findById(cid);
            if (owner == NULL) continue;   // 找不到船员则跳过该条证书

            Certificate* c = createCertificate(type, no, cid, cname, isser, issue, expire, e1, e2);
            if (c != NULL) owner->addCert(c);
        }
        fc.close();
        return true;
    }

    bool save() const {
        ofstream fc(crewFile.c_str());
        if (!fc) return false;
        for (size_t i = 0; i < crews.size(); ++i) {
            Crew* c = crews[i];
            fc << c->getId() << '\t' << c->getName() << '\t' << c->getGender()
               << '\t' << c->getBirth().toString() << '\t' << c->getRank()
               << '\t' << c->getPhone() << '\n';
        }
        fc.close();

        ofstream ft(certFile.c_str());
        if (!ft) return false;
        for (size_t i = 0; i < crews.size(); ++i) {
            const vector<Certificate*>& cs = crews[i]->getCerts();
            for (size_t j = 0; j < cs.size(); ++j)
                ft << cs[j]->toLine() << '\n';
        }
        ft.close();
        return true;
    }
};

/*==================== 报表导出类 ====================*/
class Exporter {
public:
    static bool saveLines(const vector<string>& lines, const string& path) {
        ofstream out(path.c_str());
        if (!out) return false;
        for (size_t i = 0; i < lines.size(); ++i) out << lines[i] << "\r\n";
        out.close();
        return true;
    }
    static string fileName(const string& prefix) {
        return prefix + "_" + timeStamp() + ".txt";
    }
};

/*==================== 用户与登录 ====================*/
class User {
private:
    string account;
    string password;
    int    role;          // 1 = 管理员，2 = 操作员
public:
    User() : role(2) {}
    User(const string& a, const string& p, int r) : account(a), password(p), role(r) {}

    const string& getAccount() const { return account; }
    const string& getPassword() const { return password; }
    int getRole() const { return role; }
    string roleName() const { return role == 1 ? "管理员" : "操作员"; }
    bool isAdmin() const { return role == 1; }

    bool check(const string& a, const string& p) const {
        return account == a && password == p;
    }
    void setPassword(const string& p) { password = p; }
};

class AuthManager {
private:
    vector<User> users;
    string userFile;
public:
    AuthManager(const string& f) : userFile(f) {}

    bool load() {
        ifstream in(userFile.c_str());
        if (!in) return false;
        string line;
        while (getline(in, line)) {
            if (trim(line).empty()) continue;
            istringstream iss(line);
            string a, p, r;
            if (!(iss >> a >> p >> r)) continue;
            int role = 2;
            strToInt(r, role);
            users.push_back(User(a, p, role));
        }
        in.close();
        return true;
    }

    bool save() const {
        ofstream out(userFile.c_str());
        if (!out) return false;
        for (size_t i = 0; i < users.size(); ++i) {
            out << users[i].getAccount() << '\t' << users[i].getPassword()
                << '\t' << users[i].getRole() << '\n';
        }
        out.close();
        return true;
    }

    User* find(const string& account) {
        for (size_t i = 0; i < users.size(); ++i)
            if (users[i].getAccount() == account) return &users[i];
        return NULL;
    }

    // 登录：最多三次机会
    User* login() {
        cout << endl;
        printTitle("用户登录");
        for (int i = 0; i < 3; ++i) {
            string acc = readLine("账号：");
            string pwd = readPassword("密码：");
            for (size_t k = 0; k < users.size(); ++k) {
                if (users[k].check(acc, pwd)) {
                    cout << "\n登录成功，欢迎您，" << users[k].getAccount()
                         << "（" << users[k].roleName() << "）！" << endl;
                    return &users[k];
                }
            }
            cout << "账号或密码错误，剩余 " << (2 - i) << " 次机会。\n" << endl;
        }
        return NULL;
    }

    bool addUser(const string& a, const string& p, int role) {
        if (find(a) != NULL) return false;
        users.push_back(User(a, p, role));
        return true;
    }
};

/*==================== 界面：船员档案 ====================*/
void printCrewHeader() {
    cout << pad("工号", 8) << pad("姓名", 10) << pad("性别", 6)
         << pad("出生日期", 14) << pad("职务", 16) << pad("电话", 14)
         << pad("证书数", 8) << "已过期" << endl;
    printLine(78, '-');
}

void showAllCrews(const CrewManager& mgr) {
    printTitle("船员档案列表");
    if (mgr.crewCount() == 0) {
        cout << "  暂无船员记录。\n";
        return;
    }
    printCrewHeader();
    const vector<Crew*>& all = mgr.allCrews();
    for (size_t i = 0; i < all.size(); ++i) all[i]->printRow();
    printLine(78, '-');
    cout << "  共 " << mgr.crewCount() << " 名船员。" << endl;
}

void searchCrew(const CrewManager& mgr) {
    printTitle("查找船员");
    string key = readLine("请输入工号或姓名（直接回车取消）：");
    if (key.empty()) return;
    Crew* one = mgr.findById(key);
    printCrewHeader();
    if (one != NULL) {
        one->printRow();
    } else {
        vector<Crew*> list = mgr.findByName(key);
        if (list.empty()) { cout << "  未找到匹配的船员。\n"; return; }
        for (size_t i = 0; i < list.size(); ++i) list[i]->printRow();
    }
}

void addCrewUI(CrewManager& mgr) {
    printTitle("新增船员");
    string id = readLine("工号：");
    if (id.empty()) { cout << "  工号不能为空。\n"; return; }
    if (mgr.findById(id) != NULL) { cout << "  该工号已存在！\n"; return; }

    string name   = readLine("姓名：");
    string gender = readLine("性别（男/女）：");
    string birthStr = readLine("出生日期（YYYY-MM-DD）：");
    string rank   = readLine("职务：");
    string phone  = readLine("联系电话：");

    Date birth;
    if (!Date::parse(birthStr, birth)) { cout << "  出生日期格式错误！\n"; return; }

    mgr.addCrew(new Crew(id, name, gender, birth, rank, phone));
    if (mgr.save())
        cout << "  新增成功，已保存到 " << CREW_FILE << "。\n";
    else
        cout << "  保存失败，请检查文件是否被占用。\n";
}

void editCrewUI(CrewManager& mgr) {
    printTitle("修改船员信息");
    string id = readLine("请输入要修改的工号：");
    Crew* c = mgr.findById(id);
    if (c == NULL) { cout << "  未找到该船员。\n"; return; }

    cout << "  （直接回车表示保持原值）\n";
    c->setName(readKeep("姓名", c->getName()));
    c->setGender(readKeep("性别", c->getGender()));
    string bs = readKeep("出生日期", c->getBirth().toString());
    Date b;
    if (Date::parse(bs, b)) c->setBirth(b);
    c->setRank(readKeep("职务", c->getRank()));
    c->setPhone(readKeep("电话", c->getPhone()));

    if (mgr.save()) cout << "  修改已保存。\n";
    else cout << "  保存失败。\n";
}

void deleteCrewUI(CrewManager& mgr) {
    printTitle("删除船员");
    string id = readLine("请输入要删除的工号：");
    Crew* c = mgr.findById(id);
    if (c == NULL) { cout << "  未找到该船员。\n"; return; }

    cout << "  该船员名下有 " << c->certCount() << " 本证书";
    if (c->certCount() > 0) cout << "，删除后一并移除";
    cout << "。\n";
    if (!readYesNo("确定删除吗？(y/n)：")) { cout << "  已取消。\n"; return; }

    if (mgr.removeCrew(id) && mgr.save())
        cout << "  删除成功。\n";
    else
        cout << "  删除失败。\n";
}

/*==================== 界面：证书管理 ====================*/
void printCertHeader() {
    cout << pad("证书编号", 14) << pad("工号", 8) << pad("类别", 14)
         << pad("发证日期", 12) << pad("到期日期", 12)
         << pad("剩余", 6) << "状态" << endl;
    printLine(78, '-');
}

void printCertRow(const Certificate& c) {
    ostringstream oss;
    oss << c.remainDays();
    cout << pad(c.getCertNo(), 14) << pad(c.getCrewId(), 8)
         << pad(c.typeName(), 14) << pad(c.getIssueDate().toString(), 12)
         << pad(c.getExpireDate().toString(), 12) << pad(oss.str(), 6)
         << c.statusText() << endl;
}

void showCrewCerts(const CrewManager& mgr) {
    printTitle("查看船员证书");
    string id = readLine("请输入工号：");
    Crew* c = mgr.findById(id);
    if (c == NULL) { cout << "  未找到该船员。\n"; return; }

    cout << "\n  姓名：" << c->getName() << "   职务：" << c->getRank()
         << "   证书 " << c->certCount() << " 本\n\n";
    if (c->certCount() == 0) { cout << "  该船员暂无证书记录。\n"; return; }

    printCertHeader();
    const vector<Certificate*>& cs = c->getCerts();
    for (size_t i = 0; i < cs.size(); ++i) {
        printCertRow(*cs[i]);
        cout << "      ";          // 附加信息由多态函数打印
        cs[i]->printExtra();
        cout << "   签发机关：" << cs[i]->getIssuer() << endl;
    }
}

void addCertUI(CrewManager& mgr) {
    printTitle("新增证书");
    string id = readLine("船员工号：");
    Crew* c = mgr.findById(id);
    if (c == NULL) { cout << "  未找到该船员。\n"; return; }

    cout << "  证书类别：1-适任证书  2-健康证  3-培训合格证\n";
    int type = readInt("请输入类别：", 0);
    if (type < 1 || type > 3) { cout << "  类别输入有误。\n"; return; }

    string no = readLine("证书编号：");
    if (mgr.findCert(no) != NULL) { cout << "  该证书编号已存在！\n"; return; }

    string name, e1, e2;
    if (type == 1) { name = "适任证书"; }
    else if (type == 2) { name = "健康证"; }
    else { name = "培训合格证"; }
    name = readKeep("证书名称", name);

    string issueStr  = readLine("发证日期（YYYY-MM-DD）：");
    string expireStr = readLine("到期日期（YYYY-MM-DD）：");
    string issuer    = readLine("签发机关：");

    if (type == 1) {
        e1 = readLine("职务：");
        e2 = readLine("适用船型（远洋/沿海/内河）：");
    } else if (type == 2) {
        e1 = readLine("体检机构：");
        e2 = readLine("体检结论（合格/不合格）：");
    } else {
        e1 = readLine("培训项目：");
        e2 = "";
    }

    Date issue, expire;
    if (!Date::parse(issueStr, issue) || !Date::parse(expireStr, expire)) {
        cout << "  日期格式错误，应为 YYYY-MM-DD。\n";
        return;
    }

    Certificate* cert = createCertificate(type, no, id, name, issuer, issue, expire, e1, e2);
    if (cert == NULL) { cout << "  创建证书失败。\n"; return; }

    string msg;
    if (!cert->validate(msg)) {
        delete cert;
        cout << "  " << msg << endl;
        return;
    }

    c->addCert(cert);
    if (mgr.save()) cout << "  证书添加成功。\n";
    else cout << "  保存失败。\n";
}

void editCertUI(CrewManager& mgr) {
    printTitle("修改证书");
    string no = readLine("证书编号：");
    Crew* owner = NULL;
    Certificate* c = mgr.findCert(no, &owner);
    if (c == NULL) { cout << "  未找到该证书。\n"; return; }

    cout << "  （直接回车表示保持原值）\n";
    c->setCertName(readKeep("证书名称", c->getCertName()));
    c->setIssuer(readKeep("签发机关", c->getIssuer()));

    string s1 = readKeep("发证日期", c->getIssueDate().toString());
    string s2 = readKeep("到期日期", c->getExpireDate().toString());
    Date d1, d2;
    if (Date::parse(s1, d1)) c->setIssueDate(d1);
    if (Date::parse(s2, d2)) c->setExpireDate(d2);

    // 多态：按实际类型修改各自的附加字段
    // （用 typeCode + static_cast，避免依赖 RTTI，VC6 也能编译）
    if (c->typeCode() == 1) {
        CompetencyCert* p = static_cast<CompetencyCert*>(c);
        p->setRank(readKeep("职务", p->extra1()));
        p->setShipType(readKeep("适用船型", p->extra2()));
    } else if (c->typeCode() == 2) {
        HealthCert* p = static_cast<HealthCert*>(c);
        p->setHospital(readKeep("体检机构", p->extra1()));
        p->setResult(readKeep("体检结论", p->extra2()));
    } else {
        TrainingCert* p = static_cast<TrainingCert*>(c);
        p->setItem(readKeep("培训项目", p->extra1()));
    }

    string msg;
    if (!c->validate(msg)) { cout << "  修改未生效：" << msg << endl; return; }

    if (mgr.save()) cout << "  修改已保存。\n";
    else cout << "  保存失败。\n";
}

void deleteCertUI(CrewManager& mgr) {
    printTitle("删除证书");
    string no = readLine("证书编号：");
    Crew* owner = NULL;
    Certificate* c = mgr.findCert(no, &owner);
    if (c == NULL) { cout << "  未找到该证书。\n"; return; }

    if (!readYesNo("确定删除该证书吗？(y/n)：")) { cout << "  已取消。\n"; return; }
    owner->removeCert(no);
    if (mgr.save()) cout << "  删除成功。\n";
    else cout << "  保存失败。\n";
}

/*==================== 界面：到期预警 ====================*/
void showAlerts(const CrewManager& mgr, int maxDays, const string& title,
                int minDays = -100000) {
    printTitle(title);
    vector<AlertItem> list = mgr.collectAlerts(maxDays, minDays);
    if (list.empty()) { cout << "  没有符合条件的证书。\n"; return; }

    cout << pad("工号", 8) << pad("姓名", 10) << pad("证书编号", 14)
         << pad("类别", 14) << pad("到期日期", 12)
         << pad("剩余", 8) << "状态" << endl;
    printLine(78, '-');
    for (size_t i = 0; i < list.size(); ++i) {
        ostringstream oss;
        oss << list[i].remain;
        cout << pad(list[i].crew->getId(), 8)
             << pad(list[i].crew->getName(), 10)
             << pad(list[i].cert->getCertNo(), 14)
             << pad(list[i].cert->typeName(), 14)
             << pad(list[i].cert->getExpireDate().toString(), 12)
             << pad(oss.str(), 8)
             << list[i].cert->statusText() << endl;
    }
    printLine(78, '-');
    cout << "  共 " << list.size() << " 条记录。" << endl;
}

/*==================== 界面：统计 ====================*/
struct RankStat {
    string rank;    // 职务
    int count;      // 人数
};

bool rankStatLess(const RankStat& a, const RankStat& b) {
    return a.count > b.count;      // 人数多的排前面
}

void showOverview(const CrewManager& mgr) {
    printTitle("系统总览");
    cout << "  船员总数：" << mgr.crewCount() << " 人" << endl;
    cout << "  证书总数：" << mgr.certCount() << " 本" << endl;
    cout << "  已过期    ：" << mgr.expiredCount() << " 本" << endl;
    cout << "  30 天内到期：" << mgr.urgentCount() << " 本（含已过期则 "
         << mgr.collectAlerts(DAYS_URGENT).size() << " 本）" << endl;
    cout << "  90 天内到期：" << mgr.warnCount() << " 本（含 30 天内到期）" << endl;
    cout << "  无风险    ：" << mgr.certCount() - mgr.expiredCount() - mgr.warnCount()
         << " 本" << endl;
}

void statByRank(const CrewManager& mgr) {
    printTitle("按职务统计");
    vector<RankStat> list;
    const vector<Crew*>& all = mgr.allCrews();
    for (size_t i = 0; i < all.size(); ++i) {
        bool found = false;
        for (size_t j = 0; j < list.size(); ++j) {
            if (list[j].rank == all[i]->getRank()) {
                list[j].count++;
                found = true;
                break;
            }
        }
        if (!found) {
            RankStat s;
            s.rank = all[i]->getRank();
            s.count = 1;
            list.push_back(s);
        }
    }
    selectionSort<RankStat>(list, rankStatLess);

    cout << pad("职务", 20) << "人数" << endl;
    printLine(30, '-');
    for (size_t i = 0; i < list.size(); ++i) {
        ostringstream oss;
        oss << list[i].count;
        cout << pad(list[i].rank, 20) << oss.str() << endl;
    }
}

void statByType(const CrewManager& mgr) {
    printTitle("按证书类别统计");
    int total[4] = {0, 0, 0, 0};
    int expired[4] = {0, 0, 0, 0};
    int warn[4] = {0, 0, 0, 0};

    const vector<Crew*>& all = mgr.allCrews();
    for (size_t i = 0; i < all.size(); ++i) {
        const vector<Certificate*>& cs = all[i]->getCerts();
        for (size_t j = 0; j < cs.size(); ++j) {
            int t = cs[j]->typeCode();
            if (t < 1 || t > 3) continue;
            total[t]++;
            if (cs[j]->isExpired()) expired[t]++;
            else if (cs[j]->remainDays() <= DAYS_WARN) warn[t]++;
        }
    }

    cout << pad("类别", 18) << pad("总数", 8) << pad("已过期", 10) << "90天内到期" << endl;
    printLine(48, '-');
    const char* names[4] = {"", "适任证书", "健康证", "培训合格证"};
    for (int t = 1; t <= 3; ++t) {
        ostringstream a, b, c;
        a << total[t]; b << expired[t]; c << warn[t];
        cout << pad(names[t], 18) << pad(a.str(), 8) << pad(b.str(), 10) << c.str() << endl;
    }
}

/*==================== 界面：报表导出 ====================*/
void exportCrewList(const CrewManager& mgr) {
    vector<string> lines;
    lines.push_back("================ 船员台账 ================");
    lines.push_back("导出时间：" + nowText());
    lines.push_back(string("工号\t姓名\t性别\t出生日期\t职务\t电话\t证书数"));

    const vector<Crew*>& all = mgr.allCrews();
    for (size_t i = 0; i < all.size(); ++i) {
        Crew* c = all[i];
        ostringstream oss;
        oss << c->getId() << '\t' << c->getName() << '\t' << c->getGender() << '\t'
            << c->getBirth().toString() << '\t' << c->getRank() << '\t'
            << c->getPhone() << '\t' << c->certCount();
        lines.push_back(oss.str());
    }
    ostringstream total;
    total << "合计：" << mgr.crewCount() << " 人";
    lines.push_back(total.str());

    string file = Exporter::fileName("船员台账");
    if (Exporter::saveLines(lines, file))
        cout << "  导出成功：" << file << endl;
    else
        cout << "  导出失败，请检查目录写权限。\n";
}

void exportCertList(const CrewManager& mgr) {
    vector<string> lines;
    lines.push_back("================ 证书清单 ================");
    lines.push_back("导出时间：" + nowText());
    lines.push_back(string("证书编号\t工号\t姓名\t类别\t发证日期\t到期日期\t剩余天数\t状态\t签发机关"));

    const vector<Crew*>& all = mgr.allCrews();
    for (size_t i = 0; i < all.size(); ++i) {
        const vector<Certificate*>& cs = all[i]->getCerts();
        for (size_t j = 0; j < cs.size(); ++j) {
            ostringstream oss;
            oss << cs[j]->getCertNo() << '\t' << cs[j]->getCrewId() << '\t'
                << all[i]->getName() << '\t' << cs[j]->typeName() << '\t'
                << cs[j]->getIssueDate().toString() << '\t'
                << cs[j]->getExpireDate().toString() << '\t'
                << cs[j]->remainDays() << '\t' << cs[j]->statusText() << '\t'
                << cs[j]->getIssuer();
            lines.push_back(oss.str());
        }
    }

    string file = Exporter::fileName("证书清单");
    if (Exporter::saveLines(lines, file))
        cout << "  导出成功：" << file << endl;
    else
        cout << "  导出失败。\n";
}

void exportAlertList(const CrewManager& mgr) {
    vector<AlertItem> list = mgr.collectAlerts(DAYS_WARN);   // 含已过期
    vector<string> lines;
    lines.push_back("========= 证书到期预警名单（含已过期） =========");
    lines.push_back("导出时间：" + nowText());
    lines.push_back(string("工号\t姓名\t证书编号\t类别\t到期日期\t剩余天数\t状态"));

    for (size_t i = 0; i < list.size(); ++i) {
        ostringstream oss;
        oss << list[i].crew->getId() << '\t' << list[i].crew->getName() << '\t'
            << list[i].cert->getCertNo() << '\t' << list[i].cert->typeName() << '\t'
            << list[i].cert->getExpireDate().toString() << '\t'
            << list[i].remain << '\t' << list[i].cert->statusText();
        lines.push_back(oss.str());
    }

    string file = Exporter::fileName("到期预警名单");
    if (Exporter::saveLines(lines, file))
        cout << "  导出成功，共 " << list.size() << " 条：" << file << endl;
    else
        cout << "  导出失败。\n";
}

void exportSummary(const CrewManager& mgr) {
    vector<string> lines;
    lines.push_back("============ 证书管理统计汇总 ============");
    lines.push_back("导出时间：" + nowText());

    ostringstream a;
    a << "船员总数：" << mgr.crewCount() << " 人";
    lines.push_back(a.str());
    ostringstream b;
    b << "证书总数：" << mgr.certCount() << " 本";
    lines.push_back(b.str());
    ostringstream c;
    c << "已过期证书：" << mgr.expiredCount() << " 本";
    lines.push_back(c.str());
    ostringstream d;
    d << "90 天内到期：" << mgr.warnCount() << " 本";
    lines.push_back(d.str());

    lines.push_back("");
    lines.push_back("---- 按职务统计 ----");
    map<string, int> table;
    const vector<Crew*>& all = mgr.allCrews();
    for (size_t i = 0; i < all.size(); ++i) table[all[i]->getRank()]++;
    map<string, int>::iterator it;
    for (it = table.begin(); it != table.end(); ++it) {
        ostringstream oss;
        oss << it->first << "\t" << it->second << " 人";
        lines.push_back(oss.str());
    }

    string file = Exporter::fileName("统计汇总");
    if (Exporter::saveLines(lines, file))
        cout << "  导出成功：" << file << endl;
    else
        cout << "  导出失败。\n";
}

/*==================== 各子菜单 ====================*/
void crewMenu(CrewManager& mgr) {
    int choice = -1;
    while (choice != 0) {
        clearScreen();
        printTitle("船员档案管理");
        cout << "  1. 浏览全部船员\n";
        cout << "  2. 按工号 / 姓名查找\n";
        cout << "  3. 新增船员\n";
        cout << "  4. 修改船员信息\n";
        cout << "  5. 删除船员\n";
        cout << "  0. 返回主菜单\n";
        printLine(72, '-');
        choice = readInt("请选择：", -1);
        switch (choice) {
        case 1: showAllCrews(mgr); break;
        case 2: searchCrew(mgr); break;
        case 3: addCrewUI(mgr); break;
        case 4: editCrewUI(mgr); break;
        case 5: deleteCrewUI(mgr); break;
        case 0: break;
        default: cout << "  输入有误，请输入 0-5。\n";
        }
        if (choice != 0) waitEnter();
    }
}

void certMenu(CrewManager& mgr) {
    int choice = -1;
    while (choice != 0) {
        clearScreen();
        printTitle("证书管理");
        cout << "  1. 查看某船员的全部证书\n";
        cout << "  2. 新增证书\n";
        cout << "  3. 修改证书\n";
        cout << "  4. 删除证书\n";
        cout << "  0. 返回主菜单\n";
        printLine(72, '-');
        choice = readInt("请选择：", -1);
        switch (choice) {
        case 1: showCrewCerts(mgr); break;
        case 2: addCertUI(mgr); break;
        case 3: editCertUI(mgr); break;
        case 4: deleteCertUI(mgr); break;
        case 0: break;
        default: cout << "  输入有误，请输入 0-4。\n";
        }
        if (choice != 0) waitEnter();
    }
}

void alertMenu(const CrewManager& mgr) {
    int choice = -1;
    while (choice != 0) {
        clearScreen();
        printTitle("证书到期预警");
        cout << "  1. 已过期证书\n";
        cout << "  2. 30 天内到期（紧急）\n";
        cout << "  3. 90 天内到期（含紧急）\n";
        cout << "  4. 全部证书按剩余天数排序\n";
        cout << "  0. 返回主菜单\n";
        printLine(72, '-');
        choice = readInt("请选择：", -1);
        switch (choice) {
        case 1: showAlerts(mgr, -1, "已过期证书"); break;
        case 2: showAlerts(mgr, DAYS_URGENT, "30 天内到期的证书", 0); break;
        case 3: showAlerts(mgr, DAYS_WARN, "90 天内到期的证书", 0); break;
        case 4: showAlerts(mgr, 100000, "全部证书（按剩余天数升序）"); break;
        case 0: break;
        default: cout << "  输入有误，请输入 0-4。\n";
        }
        if (choice != 0) waitEnter();
    }
}

void statMenu(const CrewManager& mgr) {
    int choice = -1;
    while (choice != 0) {
        clearScreen();
        printTitle("查询与统计");
        cout << "  1. 系统总览\n";
        cout << "  2. 按职务统计人数\n";
        cout << "  3. 按证书类别统计\n";
        cout << "  4. 查看某船员证书明细\n";
        cout << "  0. 返回主菜单\n";
        printLine(72, '-');
        choice = readInt("请选择：", -1);
        switch (choice) {
        case 1: showOverview(mgr); break;
        case 2: statByRank(mgr); break;
        case 3: statByType(mgr); break;
        case 4: showCrewCerts(mgr); break;
        case 0: break;
        default: cout << "  输入有误，请输入 0-4。\n";
        }
        if (choice != 0) waitEnter();
    }
}

void exportMenu(const CrewManager& mgr) {
    int choice = -1;
    while (choice != 0) {
        clearScreen();
        printTitle("报表导出");
        cout << "  1. 导出船员台账\n";
        cout << "  2. 导出证书清单\n";
        cout << "  3. 导出到期预警名单\n";
        cout << "  4. 导出统计汇总\n";
        cout << "  0. 返回主菜单\n";
        printLine(72, '-');
        choice = readInt("请选择：", -1);
        switch (choice) {
        case 1: exportCrewList(mgr); break;
        case 2: exportCertList(mgr); break;
        case 3: exportAlertList(mgr); break;
        case 4: exportSummary(mgr); break;
        case 0: break;
        default: cout << "  输入有误，请输入 0-4。\n";
        }
        if (choice != 0) waitEnter();
    }
}

void userMenu(AuthManager& auth) {
    int choice = -1;
    while (choice != 0) {
        clearScreen();
        printTitle("用户管理（管理员）");
        cout << "  1. 新增账号\n";
        cout << "  2. 修改密码\n";
        cout << "  0. 返回主菜单\n";
        printLine(72, '-');
        choice = readInt("请选择：", -1);
        switch (choice) {
        case 1: {
            string a = readLine("新账号：");
            string p = readPassword("密码：");
            int role = readInt("角色（1-管理员 2-操作员）：", 2);
            if (auth.addUser(a, p, role)) {
                auth.save();
                cout << "  账号创建成功，已写入 " << USER_FILE << "。\n";
            } else {
                cout << "  账号已存在。\n";
            }
            break;
        }
        case 2: {
            string a = readLine("账号：");
            User* u = auth.find(a);
            if (u == NULL) { cout << "  账号不存在。\n"; break; }
            string p = readPassword("新密码：");
            u->setPassword(p);
            auth.save();
            cout << "  密码已修改，已写入 " << USER_FILE << "。\n";
            break;
        }
        case 0: break;
        default: cout << "  输入有误。\n";
        }
        if (choice != 0) waitEnter();
    }
}

/*==================== 主菜单 ====================*/
void showMainMenu(const CrewManager& mgr, const User& user) {
    clearScreen();
    printTitle("船员证书到期预警系统  V1.0");
    cout << "  当前用户：" << user.getAccount() << "（" << user.roleName() << "）"
         << "    今天：" << Date::today().toString() << endl;
    cout << "  船员 " << mgr.crewCount() << " 人 | 证书 " << mgr.certCount() << " 本"
         << " | 已过期 " << mgr.expiredCount() << " 本"
         << " | 90 天内到期 " << mgr.warnCount() << " 本" << endl;
    printLine(72, '-');
    cout << "  1. 船员档案管理\n";
    cout << "  2. 证书管理\n";
    cout << "  3. 到期预警\n";
    cout << "  4. 查询与统计\n";
    cout << "  5. 报表导出\n";
    if (user.isAdmin()) cout << "  6. 用户管理\n";
    cout << "  0. 退出系统\n";
    printLine(72, '-');
}

int main() {
    clearScreen();

    // 第一步：登录
    AuthManager auth(USER_FILE);
    if (!auth.load()) {
        cout << "无法打开用户数据文件 " << USER_FILE << "，程序退出。\n";
        return 1;
    }
    User* user = auth.login();
    if (user == NULL) {
        cout << "\n登录失败次数过多，系统退出。\n";
        return 0;
    }

    // 第二步：加载业务数据
    CrewManager mgr(CREW_FILE, CERT_FILE);
    if (!mgr.load()) {
        cout << "无法打开船员档案 " << CREW_FILE << "，程序退出。\n";
        return 1;
    }

    // 第三步：主循环
    int choice = -1;
    while (choice != 0) {
        showMainMenu(mgr, *user);
        choice = readInt("请选择：", -1);
        switch (choice) {
        case 1: crewMenu(mgr); break;
        case 2: certMenu(mgr); break;
        case 3: alertMenu(mgr); break;
        case 4: statMenu(mgr); break;
        case 5: exportMenu(mgr); break;
        case 6:
            if (user->isAdmin()) userMenu(auth);
            else cout << "  权限不足。\n";
            break;
        case 0:
            cout << "\n  感谢使用，再见！\n";
            break;
        default:
            cout << "  输入有误，请输入 0-6。\n";
            waitEnter();
        }
    }
    return 0;
}
