子系统框架存档20260717

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, viewport-fit=cover"
    />
    <title>智慧水务 - 安全巡检子系统</title>
    <style>
      /* ============================================================
       1. 全局CSS变量与基础重置 —— 子系统可在此统一调整品牌色
       ============================================================ */
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      :root {
        --header-h: 56px; /* 顶栏高度 */
        --sidebar-w: 240px; /* 侧边栏宽度 */
        --tab-h: 56px; /* 底部标签栏高度 */

        /* ---- 品牌色系(水利蓝)—— 其他子系统可替换此处颜色 ---- */
        --primary: #447dd7; /* 主色 */
        --primary-light: #7dacfa;
        --primary-soft: #bbd6eb;
        --accent: #00a9ea; /* 强调色(随手拍按钮渐变用) */
        --accent-dark: #023793;
        --success: #52aeef;
        --danger: #ff4d4f;
        --warning: #faad14;

        --bg-body: #f0f4f8; /* 页面背景 */
        --bg-card: #ffffff;
        --border: #d9e2ec;
        --text1: #1d2129; /* 主要文字 */
        --text2: #4e5969; /* 次要文字 */
        --radius: 8px;
        --transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
      }

      body {
        font-family:
          -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        background: var(--bg-body);
        color: var(--text1);
        -webkit-tap-highlight-color: transparent;
        overflow: hidden; /* 彻底禁止页面级滚动条 */
        padding-top: env(safe-area-inset-top);
        padding-bottom: env(safe-area-inset-bottom);
      }

      /* 全局滚动条隐藏(但容器内部仍可滚动) */
      ::-webkit-scrollbar {
        display: none;
      }
      * {
        scrollbar-width: none;
        -ms-overflow-style: none;
      }

      /* ============================================================
       2. 页面骨架(Flex大框架)
       ============================================================ */
      .layout {
        display: flex;
        flex-direction: column;
        height: 100vh;
        overflow: hidden;
      }

      /* ---- 顶栏 ---- */
      .header {
        height: var(--header-h);
        background: var(--primary);
        color: #fff;
        display: flex;
        align-items: center;
        padding: 0 16px;
        flex-shrink: 0;
        z-index: 100;
        gap: 12px;
      }
      .menu-btn {
        display: none;
        background: none;
        border: none;
        color: #fff;
        font-size: 24px;
        cursor: pointer;
        padding: 8px;
        border-radius: 4px;
      }
      .logo {
        font-weight: 700;
        font-size: 18px;
        white-space: nowrap;
        display: flex;
        align-items: center;
        gap: 8px;
      }
      .logo-icon {
        width: 28px;
        height: 28px;
        background: #fff;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 16px;
        color: var(--primary);
      }

      /* ---- 桌面端一级导航(量水测水、配水调度等)—— 移动端隐藏 ---- */
      .top-nav {
        margin-left: auto;
        display: flex;
        align-items: center;
        gap: 4px;
      }
      .top-nav-item {
        color: rgba(255, 255, 255, 0.85);
        text-decoration: none;
        padding: 8px 16px;
        border-radius: 6px;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        white-space: nowrap;
      }
      .top-nav-item:hover,
      .top-nav-item.active {
        background: rgba(255, 255, 255, 0.2);
        color: #fff;
      }
      .top-nav-item.active {
        font-weight: 700;
      }

      /* 用户下拉菜单 */
      .user-dropdown {
        position: relative;
        cursor: pointer;
      }
      .user-dropdown-trigger {
        display: flex;
        align-items: center;
        gap: 6px;
        padding: 6px 12px;
        border-radius: 6px;
      }
      .user-dropdown-trigger:hover {
        background: rgba(255, 255, 255, 0.15);
      }
      .user-dropdown-menu {
        display: none;
        position: absolute;
        right: 0;
        top: 100%;
        margin-top: 4px;
        background: #fff;
        border-radius: 8px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        min-width: 150px;
        padding: 8px 0;
        z-index: 200;
      }
      .user-dropdown-menu a {
        display: block;
        padding: 10px 16px;
        color: var(--text1);
        text-decoration: none;
        font-size: 14px;
      }
      .user-dropdown-menu a:hover {
        background: #f5f7fa;
      }
      .show {
        display: block;
      }
      .user-avatar {
        width: 32px;
        height: 32px;
        border-radius: 50%;
        background: #fff;
        color: var(--primary);
        display: flex;
        align-items: center;
        justify-content: center;
        font-weight: bold;
      }

      /* ---- 主体区域(侧边栏 + 内容) ---- */
      .body {
        display: flex;
        flex: 1;
        min-height: 0;
        position: relative;
      }

      /* 侧边栏:子系统内部菜单 */
      .sidebar {
        width: var(--sidebar-w);
        background: #fff;
        border-right: 1px solid var(--border);
        display: flex;
        flex-direction: column;
        padding: 16px 0;
        overflow-y: auto;
        flex-shrink: 0;
        transition: transform var(--transition);
        z-index: 90;
      }
      .sidebar-title {
        padding: 0 20px 12px;
        font-size: 14px;
        color: var(--text2);
        font-weight: 600;
        border-bottom: 1px solid var(--border);
        margin-bottom: 8px;
      }
      .nav-item {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 12px 20px;
        color: var(--text2);
        font-size: 15px;
        cursor: pointer;
        min-height: 48px;
        border-left: 3px solid transparent;
      }
      .nav-item.active,
      .nav-item:hover {
        background: #f0f5ff;
        color: var(--primary);
        border-left-color: var(--primary);
        font-weight: 500;
      }

      /* 遮罩层(移动端抽屉用) */
      .overlay {
        display: none;
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.45);
        z-index: 80;
      }

      /* 主内容区 */
      .main {
        flex: 1;
        overflow-y: auto;
        padding: 20px;
        background: var(--bg-body);
      }

      /* ============================================================
       3. 移动端底部标签栏 + 中间凸起按钮
       ============================================================ */
      .tab-bar {
        display: none; /* 桌面端隐藏,移动端显示 */
        position: relative;
        height: var(--tab-h);
        background: #fff;
        border-top: 1px solid var(--border);
        flex-shrink: 0;
        z-index: 100;
        padding-bottom: env(safe-area-inset-bottom);
      }
      .tab-bar-inner {
        display: flex;
        height: 100%;
        align-items: center;
        justify-content: space-around;
        position: relative;
      }
      .tab-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        flex: 1;
        height: 100%;
        color: var(--text2);
        font-size: 12px;
        cursor: pointer;
        min-height: 44px;
      }
      .tab-item.active {
        color: var(--primary);
        font-weight: 500;
      }
      .tab-icon {
        font-size: 22px;
        line-height: 1;
      }

      /* 中间凸起按钮(随手拍) */
      .center-fab-wrapper {
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        position: relative;
        height: 100%;
      }
      .center-fab {
        position: absolute;
        top: -28px;
        width: 56px;
        height: 56px;
        border-radius: 50%;
        background: linear-gradient(135deg, var(--success), var(--accent-dark));
        color: #fff;
        border: 4px solid #fff;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 28px;
        cursor: pointer;
        z-index: 5;
        transition:
          transform 0.2s,
          box-shadow 0.2s;
      }
      .center-fab:active {
        transform: scale(0.95);
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
      }
      .center-fab-label {
        position: absolute;
        bottom: -18px;
        font-size: 11px;
        color: var(--text2);
        white-space: nowrap;
      }

      /* ============================================================
       4. 通用组件样式(按钮、标签、卡片、表单、模态框等)
       ============================================================ */
      .btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        padding: 10px 20px;
        border-radius: 6px;
        font-size: 15px;
        border: none;
        cursor: pointer;
        font-weight: 500;
        min-height: 44px;
        min-width: 44px;
        transition: opacity 0.2s;
      }
      .btn-primary {
        background: var(--primary);
        color: #fff;
      }
      .btn-danger {
        background: var(--danger);
        color: #fff;
      }
      .btn-outline {
        background: #fff;
        border: 1px solid var(--border);
        color: var(--text1);
      }
      .btn-accent {
        background: var(--accent);
        color: #fff;
      }
      .btn:active {
        opacity: 0.8;
      }
      .btn-sm {
        padding: 6px 12px;
        font-size: 13px;
        min-height: 36px;
      }

      .badge {
        display: inline-block;
        padding: 2px 8px;
        border-radius: 10px;
        font-size: 12px;
        background: #e6f7ff;
        color: var(--primary);
      }
      .badge.success {
        background: #e6fffa;
        color: #00a86b;
      }
      .badge.warning {
        background: #fff7e6;
        color: var(--warning);
      }
      .badge.danger {
        background: #fff2f0;
        color: var(--danger);
      }

      .card-grid {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 16px;
        margin-bottom: 24px;
      }
      .stat-card {
        background: #fff;
        border-radius: var(--radius);
        padding: 20px;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
      }
      .stat-title {
        font-size: 14px;
        color: var(--text2);
      }
      .stat-value {
        font-size: 28px;
        font-weight: 700;
        margin: 8px 0;
      }

      .search-bar {
        display: flex;
        flex-wrap: wrap;
        gap: 12px;
        margin-bottom: 20px;
        align-items: center;
      }
      .search-input,
      .search-select {
        border: 1px solid var(--border);
        border-radius: 6px;
        padding: 10px 14px;
        font-size: 15px;
        min-height: 44px;
        outline: none;
        flex: 1;
        min-width: 150px;
      }

      /* 表格(禁止横向滚动条,采用fixed布局适配列宽) */
      .table-wrapper {
        background: #fff;
        border-radius: var(--radius);
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
        overflow: hidden;
      }
      .data-table {
        width: 100%;
        border-collapse: collapse;
        table-layout: fixed;
      }
      .data-table th,
      .data-table td {
        padding: 12px 8px;
        text-align: left;
        border-bottom: 1px solid var(--border);
        font-size: 14px;
        word-break: break-word;
      }
      .data-table th {
        background: #f7f9fc;
        font-weight: 600;
        color: var(--text2);
      }
      /* 列宽比例可根据子系统字段调整 */
      .data-table th:nth-child(1),
      .data-table td:nth-child(1) {
        width: 15%;
      }
      .data-table th:nth-child(2),
      .data-table td:nth-child(2) {
        width: 25%;
      }
      .data-table th:nth-child(3),
      .data-table td:nth-child(3) {
        width: 15%;
      }
      .data-table th:nth-child(4),
      .data-table td:nth-child(4) {
        width: 15%;
      }
      .data-table th:nth-child(5),
      .data-table td:nth-child(5) {
        width: 15%;
      }
      .data-table th:nth-child(6),
      .data-table td:nth-child(6) {
        width: 15%;
      }

      /* 移动端卡片列表 */
      .mobile-cards {
        display: none;
      }
      .task-card {
        background: #fff;
        border-radius: var(--radius);
        padding: 16px;
        margin-bottom: 12px;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
        position: relative;
      }
      .task-card-header {
        display: flex;
        justify-content: space-between;
        margin-bottom: 8px;
      }
      .task-card-body {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 8px;
        font-size: 14px;
        color: var(--text2);
      }
      .more-btn {
        position: absolute;
        right: 12px;
        bottom: 12px;
        background: none;
        border: none;
        font-size: 20px;
        cursor: pointer;
        padding: 8px;
        color: var(--text2);
      }

      /* 模态框(新建/编辑、检查表、随手拍、维修、确认) */
      .modal-overlay {
        display: none;
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.45);
        z-index: 150;
        align-items: center;
        justify-content: center;
      }
      .modal {
        background: #fff;
        border-radius: 12px;
        padding: 24px;
        width: 90%;
        max-width: 500px;
        max-height: 80vh;
        overflow-y: auto;
      }
      .form-group {
        margin-bottom: 16px;
      }
      .form-group label {
        display: block;
        margin-bottom: 6px;
        font-weight: 500;
      }
      .form-input,
      .form-textarea {
        width: 100%;
        border: 1px solid var(--border);
        border-radius: 6px;
        padding: 10px;
        font-size: 15px;
        min-height: 44px;
      }
      .form-textarea {
        resize: vertical;
      }

      /* 检查表内样式 */
      .check-item {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        padding: 12px 0;
        border-bottom: 1px solid #f0f0f0;
      }
      .check-item .status-toggle {
        display: flex;
        gap: 16px;
      }
      .status-toggle label {
        display: flex;
        align-items: center;
        gap: 6px;
        cursor: pointer;
      }
      .abnormal-detail {
        display: none;
        width: 100%;
        margin-top: 8px;
      }

      /* Toast消息提示 */
      .toast {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: rgba(0, 0, 0, 0.8);
        color: #fff;
        padding: 12px 24px;
        border-radius: 8px;
        z-index: 300;
        font-size: 15px;
        opacity: 0;
        transition: opacity 0.3s;
        pointer-events: none;
      }
      .toast.show {
        opacity: 1;
      }

      /* ============================================================
       5. 响应式断点设计
       ============================================================ */
      @media (max-width: 1024px) {
        .card-grid {
          grid-template-columns: repeat(2, 1fr);
        }
      }
      @media (max-width: 768px) {
        .top-nav {
          display: none;
        } /* 移动端隐藏一级导航 */
        .menu-btn {
          display: block;
        } /* 显示汉堡菜单 */
        .sidebar {
          /* 侧边栏变为抽屉 */
          position: fixed;
          top: var(--header-h);
          left: 0;
          bottom: 0;
          transform: translateX(-100%);
          box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
          z-index: 100;
        }
        .sidebar.open {
          transform: translateX(0);
        }
        .overlay.show {
          display: block;
        }
        .tab-bar {
          display: block;
        } /* 显示底部标签栏 */
        .card-grid {
          grid-template-columns: 1fr;
        }
        .table-wrapper {
          display: none;
        } /* 隐藏表格,改为卡片列表 */
        .mobile-cards {
          display: block;
        }
        .main {
          padding: 12px;
          padding-bottom: calc(
            12px + var(--tab-h) + env(safe-area-inset-bottom)
          );
        }
        .search-bar {
          flex-direction: column;
        }
        .search-input,
        .search-select {
          width: 100%;
        }
      }
    </style>
  </head>
  <body>
    <!-- ============================================================
       6. HTML结构
       ============================================================ -->
    <div class="layout">
      <!-- 顶栏:logo + 桌面端一级导航 + 用户信息 -->
      <header class="header">
        <button class="menu-btn" id="menuBtn">☰</button>
        <div class="logo">
          <span class="logo-icon">💧</span><span>智慧水务</span>
        </div>
        <nav class="top-nav" id="topNav">
          <!-- 以下5个为一级导航,可根据子系统增减或修改高亮 -->
          <a class="top-nav-item" data-nav="measure">量水测水</a>
          <a class="top-nav-item" data-nav="dispatch">配水调度</a>
          <a class="top-nav-item active" data-nav="inspection">安全巡检</a>
          <a class="top-nav-item" data-nav="quality">水质水情</a>
          <a class="top-nav-item" data-nav="system">系统管理</a>
          <div class="user-dropdown" id="userDropdown">
            <div class="user-dropdown-trigger">
              <div class="user-avatar">张</div>
              <span>张工</span><span>▼</span>
            </div>
            <div class="user-dropdown-menu" id="dropdownMenu">
              <a href="#">个人中心</a><a href="#">修改密码</a
              ><a href="#" id="logoutBtn">退出登录</a>
            </div>
          </div>
        </nav>
      </header>

      <div class="overlay" id="overlay"></div>

      <div class="body">
        <!-- 侧边栏:子系统内部菜单 -->
        <aside class="sidebar" id="sidebar">
          <div class="sidebar-title">安全巡检子系统</div>
          <a class="nav-item active" data-page="home">📊 首页</a>
          <a class="nav-item" data-page="monitor">📋 监测任务</a>
          <a class="nav-item" data-page="warn">⚠️ 预警管理</a>
          <a class="nav-item" data-page="report">📈 统计分析</a>
          <a class="nav-item" data-page="settings">⚙️ 系统设置</a>
        </aside>

        <!-- 主内容区:多个面板(使用display切换) -->
        <main class="main" id="mainContent">
          <!-- 面板1:首页仪表盘 -->
          <section id="panel-home" class="panel" style="display: block">
            <h2 style="margin-bottom: 16px">巡检仪表盘</h2>
            <div class="card-grid">
              <div class="stat-card">
                <div class="stat-title">总任务</div>
                <div class="stat-value" id="statTotal">0</div>
              </div>
              <div class="stat-card">
                <div class="stat-title">进行中</div>
                <div class="stat-value" id="statOngoing">0</div>
              </div>
              <div class="stat-card">
                <div class="stat-title">已完成</div>
                <div class="stat-value" id="statDone">0</div>
              </div>
              <div class="stat-card">
                <div class="stat-title">未读预警</div>
                <div class="stat-value" id="statAlerts">0</div>
              </div>
            </div>
            <h3>最近任务</h3>
            <div id="recentTasksTable" class="table-wrapper"></div>
            <div id="recentTasksCards" class="mobile-cards"></div>
          </section>

          <!-- 面板2:监测任务 -->
          <section id="panel-monitor" class="panel" style="display: none">
            <h2>巡检监测任务</h2>
            <div class="search-bar">
              <input
                type="search"
                class="search-input"
                placeholder="搜索任务编号/巡检点"
                id="monitorSearch"
              />
              <select class="search-select" id="monitorStatus">
                <option value="">全部状态</option>
                <option value="pending">待巡检</option>
                <option value="ongoing">巡检中</option>
                <option value="defect">待修复</option>
                <option value="done">已完成</option>
              </select>
              <button class="btn btn-primary" id="monitorSearchBtn">
                搜索
              </button>
              <button class="btn btn-outline" id="monitorResetBtn">重置</button>
              <button
                class="btn btn-accent"
                style="margin-left: auto"
                id="addTaskBtn"
              >
                + 新建任务
              </button>
            </div>
            <div id="monitorTableWrapper" class="table-wrapper"></div>
            <div id="monitorMobileCards" class="mobile-cards"></div>
          </section>

          <!-- 面板3:预警管理 -->
          <section id="panel-warn" class="panel" style="display: none">
            <h2>缺陷预警管理</h2>
            <div id="defectTableWrapper" class="table-wrapper"></div>
            <div id="defectMobileCards" class="mobile-cards"></div>
          </section>

          <!-- 其他占位面板 -->
          <section id="panel-report" class="panel" style="display: none">
            <h2>统计分析</h2>
            <p>图表与报表功能开发中...</p>
          </section>
          <section id="panel-settings" class="panel" style="display: none">
            <h2>系统设置</h2>
            <p>参数配置页面...</p>
          </section>
        </main>
      </div>

      <!-- 移动端底部标签栏(含中间凸起按钮) -->
      <nav class="tab-bar" id="tabBar">
        <div class="tab-bar-inner">
          <div class="tab-item active" data-page="home">
            <span class="tab-icon">💧</span><span>首页</span>
          </div>
          <div class="tab-item" data-page="monitor">
            <span class="tab-icon">🔍</span><span>巡检</span>
          </div>
          <div class="center-fab-wrapper">
            <button class="center-fab" id="centerFab">📸</button>
            <span class="center-fab-label">随手拍</span>
          </div>
          <div class="tab-item" data-page="warn">
            <span class="tab-icon">⚠️</span><span>预警</span>
          </div>
          <div class="tab-item" data-page="profile">
            <span class="tab-icon">👤</span><span>我的</span>
          </div>
        </div>
      </nav>

      <!-- ============================================================
       7. 模态框集合(根据需要复用)
       ============================================================ -->
      <!-- 任务表单(新建/编辑) -->
      <div class="modal-overlay" id="taskModal">
        <div class="modal">
          <h3 id="taskModalTitle">新建巡检任务</h3>
          <div class="form-group">
            <label>巡检点</label><input class="form-input" id="taskSite" />
          </div>
          <div class="form-group">
            <label>计划日期</label
            ><input class="form-input" type="date" id="taskDate" />
          </div>
          <div class="form-group">
            <label>巡检人</label><input class="form-input" id="taskPerson" />
          </div>
          <div style="display: flex; gap: 12px; justify-content: flex-end">
            <button class="btn btn-outline" id="cancelTaskBtn">取消</button>
            <button class="btn btn-primary" id="saveTaskBtn">保存</button>
          </div>
        </div>
      </div>

      <!-- 检查表模态框 -->
      <div class="modal-overlay" id="checkModal">
        <div class="modal" style="max-width: 600px">
          <h3>水闸启闭机检查表</h3>
          <p style="color: var(--text2)">
            任务编号:<span id="checkTaskNo"></span> | 巡检点:<span
              id="checkTaskSite"
            ></span>
          </p>
          <div id="checkItemsContainer"></div>
          <div class="form-group">
            <label>备注</label
            ><textarea
              class="form-textarea"
              id="checkRemark"
              rows="2"
            ></textarea>
          </div>
          <div style="display: flex; gap: 12px; justify-content: flex-end">
            <button class="btn btn-outline" id="cancelCheckBtn">返回</button>
            <button class="btn btn-primary" id="submitCheckBtn">
              提交检查结果
            </button>
          </div>
        </div>
      </div>

      <!-- 随手拍模态框 -->
      <div class="modal-overlay" id="snapModal">
        <div class="modal">
          <h3>📸 随手拍 - 快速上报异常</h3>
          <div class="form-group">
            <label>部位/设施</label><input class="form-input" id="snapSite" />
          </div>
          <div class="form-group">
            <label>异常描述</label
            ><textarea class="form-textarea" id="snapDesc" rows="2"></textarea>
          </div>
          <div class="form-group">
            <button class="btn btn-outline btn-sm" id="snapPhotoBtn">
              📷 拍照 (自动水印)
            </button>
            <span style="font-size: 12px; color: var(--text2)">
              GPS自动获取</span
            >
          </div>
          <div style="display: flex; gap: 12px; justify-content: flex-end">
            <button class="btn btn-outline" id="cancelSnapBtn">取消</button>
            <button class="btn btn-primary" id="submitSnapBtn">提交</button>
          </div>
        </div>
      </div>

      <!-- 维修反馈模态框 -->
      <div class="modal-overlay" id="repairModal">
        <div class="modal">
          <h3>维修反馈</h3>
          <div class="form-group">
            <label>维修方法</label
            ><textarea
              class="form-textarea"
              id="repairMethod"
              rows="2"
            ></textarea>
          </div>
          <div class="form-group">
            <label>更换备件</label><input class="form-input" id="repairParts" />
          </div>
          <button class="btn btn-outline btn-sm" id="fakeRepairPhoto">
            📷 上传修复照片
          </button>
          <div
            style="
              display: flex;
              gap: 12px;
              justify-content: flex-end;
              margin-top: 16px;
            "
          >
            <button class="btn btn-outline" id="cancelRepairBtn">取消</button>
            <button class="btn btn-primary" id="confirmRepairBtn">
              提交维修
            </button>
          </div>
        </div>
      </div>

      <!-- 确认弹窗 -->
      <div class="modal-overlay" id="confirmModal">
        <div class="modal" style="max-width: 300px; text-align: center">
          <h3 id="confirmTitle"></h3>
          <p id="confirmMsg"></p>
          <div
            style="
              display: flex;
              gap: 12px;
              justify-content: center;
              margin-top: 20px;
            "
          >
            <button class="btn btn-outline" id="confirmCancel">取消</button>
            <button class="btn btn-danger" id="confirmOk">确定</button>
          </div>
        </div>
      </div>

      <div class="toast" id="toast"></div>
    </div>

    <!-- ============================================================
       8. JavaScript 业务逻辑
       ============================================================ -->
    <script>
      ;(function () {
        // ---------- 模拟数据(可替换为API请求)----------
        let tasks = [
          {
            id: 1,
            no: 'XJ-20260701',
            site: '水库大坝A段',
            person: '李四',
            date: '2026-07-16',
            status: 'pending',
          },
          {
            id: 2,
            no: 'XJ-20260702',
            site: '泄洪闸控制室',
            person: '王五',
            date: '2026-07-15',
            status: 'done',
            checkResult: {
              items: [
                { name: '电动机', normal: true },
                { name: '减速器', normal: true },
                { name: '钢丝绳', normal: true },
                { name: '制动器', normal: true },
                { name: '限位开关', normal: true },
              ],
            },
          },
          {
            id: 3,
            no: 'XJ-20260703',
            site: '河道护岸B区',
            person: '赵六',
            date: '2026-07-14',
            status: 'defect',
            checkResult: {
              items: [
                { name: '电动机', normal: true },
                { name: '减速器', normal: false, desc: '输入轴渗油' },
                { name: '钢丝绳', normal: true },
                { name: '制动器', normal: true },
                { name: '限位开关', normal: true },
              ],
            },
          },
          {
            id: 4,
            no: 'XJ-20260704',
            site: '泵站设备间',
            person: '李四',
            date: '2026-07-16',
            status: 'ongoing',
          },
        ]
        let defects = [
          {
            id: 1,
            taskId: 3,
            taskNo: 'XJ-20260703',
            site: '河道护岸B区',
            item: '减速器',
            desc: '输入轴渗油,油位低',
            status: 'pending_repair',
            repairMethod: '',
            repairPhoto: null,
          },
        ]

        // 状态映射(可扩展)
        const statusMap = {
          pending: '待巡检',
          ongoing: '巡检中',
          defect: '待修复',
          done: '已完成',
        }
        const statusBadge = {
          pending: 'warning',
          ongoing: '',
          defect: 'danger',
          done: 'success',
        }
        const defectStatusMap = {
          pending_repair: '待维修',
          repaired: '已维修待复核',
          closed: '已销号',
        }
        const defectBadge = {
          pending_repair: 'warning',
          repaired: '',
          closed: 'success',
        }

        let currentTaskId = null
        let currentDefectId = null
        let editingTaskId = null
        let activePanel = 'home'

        // 快捷选择器
        const $ = (s) => document.querySelector(s)
        const $$ = (s) => document.querySelectorAll(s)

        // ---------- 工具函数 ----------
        function showToast(msg) {
          const t = $('#toast')
          t.textContent = msg
          t.classList.add('show')
          setTimeout(() => t.classList.remove('show'), 2000)
        }

        // ---------- 统计与渲染 ----------
        function updateStats() {
          $('#statTotal').textContent = tasks.length
          $('#statOngoing').textContent = tasks.filter(
            (t) => t.status === 'ongoing' || t.status === 'pending',
          ).length
          $('#statDone').textContent = tasks.filter(
            (t) => t.status === 'done',
          ).length
          $('#statAlerts').textContent = defects.filter(
            (d) => d.status !== 'closed',
          ).length
        }

        // 通用表格渲染(避免横向滚动条)
        function renderTable(containerId, data, columns, actions) {
          let html = `<table class="data-table"><thead><tr>${columns.map((c) => `<th>${c}</th>`).join('')}</tr></thead><tbody>`
          data.forEach((row) => {
            html += '<tr>'
            columns.forEach((col) => {
              let val = row[col] ?? ''
              if (col === 'status')
                val = `<span class="badge ${statusBadge[row.status] || ''}">${statusMap[row.status] || val}</span>`
              html += `<td>${val}</td>`
            })
            if (actions) html += `<td>${actions(row)}</td>`
            html += '</tr>'
          })
          html += '</tbody></table>'
          document.getElementById(containerId).innerHTML = html
        }

        function renderRecentTasks() {
          const recent = tasks.slice(0, 4)
          renderTable('recentTasksTable', recent, [
            'no',
            'site',
            'person',
            'date',
            'status',
          ])
          let cards = ''
          recent.forEach((t) => {
            cards += `<div class="task-card"><div class="task-card-header"><strong>${t.no}</strong><span class="badge ${statusBadge[t.status]}">${statusMap[t.status]}</span></div><div class="task-card-body"><div>📍 ${t.site}</div><div>👤 ${t.person}</div><div>📅 ${t.date}</div></div></div>`
          })
          $('#recentTasksCards').innerHTML = cards
        }

        function renderMonitorTasks() {
          const kw = $('#monitorSearch')?.value.trim().toLowerCase() || ''
          const st = $('#monitorStatus')?.value || ''
          let filtered = tasks.filter(
            (t) =>
              (!kw ||
                t.no.includes(kw) ||
                t.site.includes(kw) ||
                t.person.includes(kw)) &&
              (!st || t.status === st),
          )
          const columns = ['no', 'site', 'person', 'date', 'status']
          const actions = (t) =>
            `${t.status !== 'done' ? `<button class="btn btn-sm btn-outline start-task" data-id="${t.id}">巡检</button>` : ''} <button class="btn btn-sm btn-outline edit-task-btn" data-id="${t.id}">✏️</button> <button class="btn btn-sm btn-outline delete-task-btn" data-id="${t.id}">🗑</button>`
          renderTable('monitorTableWrapper', filtered, columns, actions)
          let cards = ''
          filtered.forEach((t) => {
            cards += `<div class="task-card"><div class="task-card-header"><strong>${t.no}</strong><span class="badge ${statusBadge[t.status]}">${statusMap[t.status]}</span></div><div class="task-card-body"><div>📍 ${t.site}</div><div>👤 ${t.person}</div><div>📅 ${t.date}</div></div><button class="more-btn monitor-more" data-id="${t.id}">•••</button></div>`
          })
          $('#monitorMobileCards').innerHTML = cards
          attachMonitorEvents()
        }

        function renderDefects() {
          const columns = ['taskNo', 'site', 'item', 'desc', 'status']
          const actions = (d) =>
            `${d.status === 'pending_repair' ? `<button class="btn btn-sm btn-primary repair-btn" data-id="${d.id}">维修</button>` : ''} ${d.status === 'repaired' ? `<button class="btn btn-sm btn-outline review-btn" data-id="${d.id}">复核</button>` : ''}`
          renderTable('defectTableWrapper', defects, columns, actions)
          let cards = ''
          defects.forEach((d) => {
            cards += `<div class="task-card"><div class="task-card-header"><strong>${d.taskNo}</strong><span class="badge ${defectBadge[d.status]}">${defectStatusMap[d.status]}</span></div><div class="task-card-body"><div>📍 ${d.site}</div><div>⚠️ ${d.item}</div><div>📝 ${d.desc}</div></div><div style="margin-top:8px;">${d.status === 'pending_repair' ? `<button class="btn btn-sm btn-primary repair-btn" data-id="${d.id}">维修接单</button>` : ''} ${d.status === 'repaired' ? `<button class="btn btn-sm btn-outline review-btn" data-id="${d.id}">复核</button>` : ''}</div></div>`
          })
          $('#defectMobileCards').innerHTML = cards
          attachDefectEvents()
        }

        // 事件绑定(监测任务)
        function attachMonitorEvents() {
          $$('.start-task').forEach(
            (b) => (b.onclick = () => openCheckModal(parseInt(b.dataset.id))),
          )
          $$('.edit-task-btn').forEach(
            (b) => (b.onclick = () => openTaskModal(parseInt(b.dataset.id))),
          )
          $$('.delete-task-btn').forEach(
            (b) =>
              (b.onclick = () => {
                currentTaskId = parseInt(b.dataset.id)
                showConfirm('删除任务', '确认删除?', () => {
                  tasks = tasks.filter((t) => t.id !== currentTaskId)
                  refreshAll()
                  showToast('已删除')
                })
              }),
          )
          $$('.monitor-more').forEach(
            (b) =>
              (b.onclick = () => {
                currentTaskId = parseInt(b.dataset.id)
                alert('操作:开始巡检/编辑/删除') // 移动端简化处理
              }),
          )
        }

        function attachDefectEvents() {
          $$('.repair-btn').forEach(
            (b) =>
              (b.onclick = () => {
                currentDefectId = parseInt(b.dataset.id)
                openRepairModal()
              }),
          )
          $$('.review-btn').forEach(
            (b) =>
              (b.onclick = () => {
                currentDefectId = parseInt(b.dataset.id)
                showConfirm('复核确认', '确认现场复核合格?', () => {
                  const d = defects.find((d) => d.id === currentDefectId)
                  if (d) {
                    d.status = 'closed'
                    const t = tasks.find((t) => t.id === d.taskId)
                    if (
                      t &&
                      defects.filter(
                        (dd) => dd.taskId === t.id && dd.status !== 'closed',
                      ).length === 0
                    )
                      t.status = 'done'
                  }
                  refreshAll()
                  showToast('缺陷已销号')
                })
              }),
          )
        }

        function refreshAll() {
          updateStats()
          renderRecentTasks()
          if (activePanel === 'monitor') renderMonitorTasks()
          if (activePanel === 'warn') renderDefects()
          if (activePanel === 'home') {
            updateStats()
            renderRecentTasks()
          }
        }

        // ---------- 面板切换 ----------
        function switchPanel(page) {
          $$('.panel').forEach((p) => (p.style.display = 'none'))
          const panel = document.getElementById(`panel-${page}`)
          if (panel) panel.style.display = 'block'
          activePanel = page
          // 侧边栏高亮
          $$('.nav-item').forEach((i) => i.classList.remove('active'))
          document
            .querySelector(`.nav-item[data-page="${page}"]`)
            ?.classList.add('active')
          // 底部标签栏高亮
          $$('.tab-item').forEach((i) => i.classList.remove('active'))
          document
            .querySelector(`.tab-item[data-page="${page}"]`)
            ?.classList.add('active')
          if (page === 'monitor') renderMonitorTasks()
          if (page === 'warn') renderDefects()
          if (page === 'home') {
            updateStats()
            renderRecentTasks()
          }
          if (window.innerWidth <= 768) closeSidebar()
        }

        // ---------- 任务表单模态框 ----------
        function openTaskModal(id = null) {
          editingTaskId = id
          if (id) {
            const t = tasks.find((t) => t.id === id)
            if (!t) return
            $('#taskSite').value = t.site
            $('#taskDate').value = t.date
            $('#taskPerson').value = t.person
            $('#taskModalTitle').textContent = '编辑巡检任务'
          } else {
            $('#taskSite').value = ''
            $('#taskDate').value = new Date().toISOString().slice(0, 10)
            $('#taskPerson').value = '张工'
            $('#taskModalTitle').textContent = '新建巡检任务'
          }
          $('#taskModal').style.display = 'flex'
        }
        $('#addTaskBtn').onclick = () => openTaskModal()
        $('#cancelTaskBtn').onclick = () =>
          ($('#taskModal').style.display = 'none')
        $('#saveTaskBtn').onclick = () => {
          const site = $('#taskSite').value.trim(),
            date = $('#taskDate').value,
            person = $('#taskPerson').value.trim()
          if (!site || !date) return showToast('请填写完整')
          if (editingTaskId) {
            const t = tasks.find((t) => t.id === editingTaskId)
            if (t) {
              t.site = site
              t.date = date
              t.person = person
            }
          } else {
            tasks.unshift({
              id: Date.now(),
              no:
                'XJ-' +
                date.replace(/-/g, '') +
                '-' +
                Math.floor(Math.random() * 100),
              site,
              date,
              person,
              status: 'pending',
            })
          }
          $('#taskModal').style.display = 'none'
          refreshAll()
          showToast(editingTaskId ? '已更新' : '已创建')
          editingTaskId = null
        }

        // ---------- 检查表逻辑 ----------
        const checkItems = ['电动机', '减速器', '钢丝绳', '制动器', '限位开关']
        function openCheckModal(taskId) {
          const task = tasks.find((t) => t.id === taskId)
          if (!task) return
          currentTaskId = taskId
          $('#checkTaskNo').textContent = task.no
          $('#checkTaskSite').textContent = task.site
          let html = ''
          checkItems.forEach((item) => {
            html += `<div class="check-item"><strong>${item}</strong><div class="status-toggle"><label><input type="radio" name="check_${item}" value="normal" checked>正常</label><label><input type="radio" name="check_${item}" value="abnormal">异常</label></div><div class="abnormal-detail" id="detail_${item}"><input class="form-input" placeholder="异常描述" id="desc_${item}"><button class="btn btn-outline btn-sm" onclick="alert('模拟拍照')">📷</button></div></div>`
          })
          $('#checkItemsContainer').innerHTML = html
          checkItems.forEach((item) => {
            document.getElementsByName(`check_${item}`).forEach((r) =>
              r.addEventListener('change', (e) => {
                document.getElementById(`detail_${item}`).style.display =
                  e.target.value === 'abnormal' ? 'block' : 'none'
              }),
            )
          })
          $('#checkModal').style.display = 'flex'
        }
        $('#cancelCheckBtn').onclick = () =>
          ($('#checkModal').style.display = 'none')
        $('#submitCheckBtn').onclick = () => {
          const task = tasks.find((t) => t.id === currentTaskId)
          if (!task) return
          const resultItems = checkItems.map((item) => {
            const normal = document.querySelector(
              `input[name="check_${item}"][value="normal"]`,
            ).checked
            const desc = normal
              ? ''
              : document.getElementById(`desc_${item}`)?.value || ''
            return {
              name: item,
              normal,
              desc,
              photo: normal ? null : 'sim.jpg',
            }
          })
          task.checkResult = { items: resultItems }
          if (resultItems.some((r) => !r.normal)) {
            task.status = 'defect'
            resultItems
              .filter((r) => !r.normal)
              .forEach((ai) => {
                defects.push({
                  id: Date.now() + Math.random(),
                  taskId: task.id,
                  taskNo: task.no,
                  site: task.site,
                  item: ai.name,
                  desc: ai.desc || '异常',
                  status: 'pending_repair',
                  repairMethod: '',
                  repairPhoto: null,
                })
              })
            showToast('已生成缺陷通知单')
          } else {
            task.status = 'done'
            showToast('检查完成')
          }
          $('#checkModal').style.display = 'none'
          refreshAll()
        }

        // 随手拍
        $('#centerFab').onclick = () => {
          $('#snapSite').value = ''
          $('#snapDesc').value = ''
          $('#snapModal').style.display = 'flex'
        }
        $('#cancelSnapBtn').onclick = () =>
          ($('#snapModal').style.display = 'none')
        $('#snapPhotoBtn').onclick = () => alert('模拟拍照,自动叠加水印')
        $('#submitSnapBtn').onclick = () => {
          const site = $('#snapSite').value.trim(),
            desc = $('#snapDesc').value.trim()
          if (!site || !desc) return showToast('请填写部位和描述')
          const taskId = Date.now()
          const taskNo =
            'SP-' +
            new Date().toISOString().slice(0, 10).replace(/-/g, '') +
            '-' +
            Math.floor(Math.random() * 100)
          tasks.unshift({
            id: taskId,
            no: taskNo,
            site,
            person: '张工',
            date: new Date().toISOString().slice(0, 10),
            status: 'defect',
          })
          defects.unshift({
            id: Date.now() + 1,
            taskId,
            taskNo,
            site,
            item: '随手拍上报',
            desc,
            status: 'pending_repair',
            repairMethod: '',
            repairPhoto: null,
          })
          $('#snapModal').style.display = 'none'
          refreshAll()
          showToast('随手拍异常已上报')
        }

        // 维修
        function openRepairModal() {
          $('#repairMethod').value = ''
          $('#repairParts').value = ''
          $('#repairModal').style.display = 'flex'
        }
        $('#cancelRepairBtn').onclick = () =>
          ($('#repairModal').style.display = 'none')
        $('#fakeRepairPhoto').onclick = () => alert('模拟上传修复照片')
        $('#confirmRepairBtn').onclick = () => {
          const d = defects.find((d) => d.id === currentDefectId)
          if (!d) return
          d.repairMethod = $('#repairMethod').value || '已完成维修'
          d.repairPhoto = 'repair.jpg'
          d.status = 'repaired'
          $('#repairModal').style.display = 'none'
          refreshAll()
          showToast('维修完成,待复核')
        }

        // 通用确认弹窗
        function showConfirm(title, msg, cb) {
          $('#confirmTitle').textContent = title
          $('#confirmMsg').textContent = msg
          $('#confirmModal').style.display = 'flex'
          const ok = () => {
            cb()
            $('#confirmModal').style.display = 'none'
            $('#confirmOk').removeEventListener('click', ok)
          }
          $('#confirmOk').addEventListener('click', ok)
          $('#confirmCancel').onclick = () => {
            $('#confirmModal').style.display = 'none'
            $('#confirmOk').removeEventListener('click', ok)
          }
        }

        // ---------- 导航交互 ----------
        function openSidebar() {
          $('#sidebar').classList.add('open')
          $('#overlay').classList.add('show')
          document.body.style.overflow = 'hidden'
        }
        function closeSidebar() {
          $('#sidebar').classList.remove('open')
          $('#overlay').classList.remove('show')
          document.body.style.overflow = ''
        }
        $('#menuBtn').onclick = () =>
          $('#sidebar').classList.contains('open')
            ? closeSidebar()
            : openSidebar()
        $('#overlay').onclick = closeSidebar
        window.addEventListener('resize', () => {
          if (window.innerWidth > 768) closeSidebar()
        })

        // 侧边栏菜单点击
        $$('.nav-item').forEach((item) =>
          item.addEventListener('click', () => switchPanel(item.dataset.page)),
        )
        // 底部标签栏点击(排除“我的”占位)
        $$('.tab-item').forEach((item) => {
          item.addEventListener('click', () => {
            const page = item.dataset.page
            if (page === 'profile') {
              showToast('个人中心开发中')
              return
            }
            switchPanel(page)
          })
        })

        // 桌面端一级导航切换(用于演示不同子系统,实际开发中可替换为页面跳转)
        $$('.top-nav-item').forEach((item) => {
          item.addEventListener('click', (e) => {
            e.preventDefault()
            $$('.top-nav-item').forEach((i) => i.classList.remove('active'))
            item.classList.add('active')
            showToast(
              `已切换到 ${item.textContent} 模块(此为演示,实际需路由)`,
            )
          })
        })

        // 用户下拉菜单
        document
          .getElementById('userDropdown')
          .addEventListener('click', (e) => {
            e.stopPropagation()
            document.getElementById('dropdownMenu').classList.toggle('show')
          })
        document.addEventListener('click', () =>
          document.getElementById('dropdownMenu').classList.remove('show'),
        )
        $('#logoutBtn')?.addEventListener('click', (e) => {
          e.preventDefault()
          showToast('已退出登录')
        })

        // 监测搜索与重置
        $('#monitorSearchBtn')?.addEventListener('click', () =>
          renderMonitorTasks(),
        )
        $('#monitorResetBtn')?.addEventListener('click', () => {
          $('#monitorSearch').value = ''
          $('#monitorStatus').value = ''
          renderMonitorTasks()
        })

        // 启动
        switchPanel('home')
        updateStats()
        renderRecentTasks()
      })()
    </script>
  </body>
</html>

  

posted @ 2026-07-17 10:48  华腾智算  阅读(1)  评论(0)    收藏  举报
https://damo.alibaba.com/ https://tianchi.aliyun.com/course?spm=5176.21206777.J_3941670930.5.87dc17c9BZNvLL