PGXApp.vue 8.76 KB
<template>
  <div class="pgx-app">
    <!-- 顶部 Header -->
    <header class="pgx-header">
      <div class="header-left">
        <div class="brand">
          <el-icon class="brand-icon" :size="22"><Opportunity /></el-icon>
          <span class="brand-name">嘉宝仁和</span>
          <span class="brand-sub">样本流转系统 vendorPGX</span>
        </div>
      </div>
      <div class="header-center">
        <el-tag v-if="pgxStore.currentRole === 'sampler'" type="success" size="large" effect="light">
          <el-icon style="margin-right:4px"><OfficeBuilding /></el-icon>医院送检方
        </el-tag>
        <el-tag v-else type="warning" size="large" effect="light">
          <el-icon style="margin-right:4px"><Cpu /></el-icon>实验室检测人员
        </el-tag>
      </div>
      <div class="header-right">
        <span class="role-switch-label">角色切换:</span>
        <el-button-group>
          <el-button
            :type="pgxStore.currentRole === 'sampler' ? 'primary' : 'default'"
            size="small"
            @click="pgxStore.switchRole('sampler')"
          ><el-icon><OfficeBuilding /></el-icon> 送检方</el-button>
          <el-button
            :type="pgxStore.currentRole === 'checkPointer' ? 'primary' : 'default'"
            size="small"
            @click="pgxStore.switchRole('checkPointer')"
          ><el-icon><Cpu /></el-icon> 实验室</el-button>
        </el-button-group>
        <el-divider direction="vertical" />
        <el-button size="small" plain @click="$router.push('/pgx/settings')">
          <el-icon><Setting /></el-icon> 设置
        </el-button>
        <el-button size="small" type="danger" plain @click="handleLogout">
          <el-icon><SwitchButton /></el-icon> 退出
        </el-button>
      </div>
    </header>

    <div class="pgx-body">
      <!-- 左侧导航 -->
      <aside class="pgx-sidebar">
        <div class="sidebar-section">
          <div class="section-title">概览</div>
          <div
            class="nav-item"
            :class="{ active: isActive('/pgx/dashboard') }"
            @click="$router.push('/pgx/dashboard')"
          ><el-icon class="ni"><House /></el-icon><span>工作台</span></div>
        </div>

        <div class="sidebar-section">
          <div class="section-title">送检流程</div>
          <div
            v-if="pgxStore.currentRole === 'sampler'"
            class="nav-item"
            :class="{ active: isActive('/pgx/register') }"
            @click="$router.push('/pgx/register')"
          ><el-icon class="ni"><EditPen /></el-icon><span>信息登记</span></div>
          <div
            class="nav-item"
            :class="{ active: isActive('/pgx/orders') }"
            @click="$router.push('/pgx/orders')"
          ><el-icon class="ni"><Document /></el-icon><span>申请单管理</span></div>
          <div
            class="nav-item"
            :class="{ active: isActive('/pgx/tracking') }"
            @click="$router.push('/pgx/tracking')"
          ><el-icon class="ni"><Compass /></el-icon><span>状态追踪</span></div>
          <div
            class="nav-item"
            :class="{ active: isActive('/pgx/post') }"
            @click="$router.push('/pgx/post')"
          ><el-icon class="ni"><Van /></el-icon><span>邮寄查询</span></div>
        </div>

        <template v-if="pgxStore.currentRole === 'checkPointer'">
          <div class="sidebar-section">
            <div class="section-title">实验室操作</div>
            <div
              class="nav-item"
              :class="{ active: isActive('/pgx/lims') }"
              @click="$router.push('/pgx/lims')"
            ><el-icon class="ni"><Finished /></el-icon><span>样本核对接收</span></div>
            <div
              class="nav-item"
              :class="{ active: isActive('/pgx/samples') }"
              @click="$router.push('/pgx/samples')"
            ><el-icon class="ni"><DataLine /></el-icon><span>样本检测状态</span></div>
            <div
              class="nav-item"
              :class="{ active: isActive('/pgx/batch') }"
              @click="$router.push('/pgx/batch')"
            ><el-icon class="ni"><Files /></el-icon><span>批量订单</span></div>
          </div>

          <div class="sidebar-section">
            <div class="section-title">分析与报告</div>
            <div
              class="nav-item"
              :class="{ active: isActive('/pgx/bio') }"
              @click="$router.push('/pgx/bio')"
            ><el-icon class="ni"><TrendCharts /></el-icon><span>生信分析平台</span></div>
            <div
              class="nav-item"
              :class="{ active: isActive('/pgx/report-gen') }"
              @click="$router.push('/pgx/report-gen')"
            ><el-icon class="ni"><Memo /></el-icon><span>报告生成审核</span></div>
          </div>
        </template>

        <div class="sidebar-bottom">
          <div
            class="nav-item nav-item-bottom"
            :class="{ active: isActive('/pgx/settings') }"
            @click="$router.push('/pgx/settings')"
          ><el-icon class="ni"><UserFilled /></el-icon><span>账号设置</span></div>
          <div class="version-tag">vendorPGX v1.0.0</div>
        </div>
      </aside>

      <!-- 主内容区 -->
      <main class="pgx-main">
        <router-view v-slot="{ Component }">
          <transition name="fade" mode="out-in">
            <component :is="Component" />
          </transition>
        </router-view>
      </main>
    </div>
  </div>
</template>

<script setup lang="ts">
import { usePGXStore } from '@/stores/pgx'
import { useAuthStore } from '@/stores/auth'
import { ElMessageBox } from 'element-plus'
import {
  House, EditPen, Document, Compass, Van,
  Finished, DataLine, Files, TrendCharts, Memo,
  Setting, SwitchButton, OfficeBuilding, Cpu, UserFilled, Opportunity
} from '@element-plus/icons-vue'

const pgxStore = usePGXStore()
const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()

function isActive(path: string) {
  return route.path === path || route.path.startsWith(path + '/')
}

async function handleLogout() {
  await ElMessageBox.confirm('确定退出登录吗?', '退出确认', { type: 'warning', confirmButtonText: '退出', cancelButtonText: '取消' })
  authStore.logout?.()
  router.push('/auth')
}
</script>

<style scoped>
.pgx-app {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: #f4fafb;
  font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
  z-index: 9999;
}

/* ── Header ── */
.pgx-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  background: #fff;
  padding: 0 20px;
  flex-shrink: 0;
  border-bottom: 2px solid #00B5C8;
  box-shadow: 0 2px 8px rgba(0,181,200,0.1);
  z-index: 100;
}

.brand { display: flex; align-items: center; gap: 10px; }
.brand-icon { font-size: 24px; }
.brand-name { color: #00B5C8; font-size: 17px; font-weight: 700; letter-spacing: 0.5px; }
.brand-sub {
  color: #00B5C8;
  font-size: 12px;
  background: rgba(0,181,200,0.08);
  border: 1px solid rgba(0,181,200,0.3);
  padding: 2px 8px;
  border-radius: 4px;
}

.header-right { display: flex; align-items: center; gap: 8px; }
.role-switch-label { color: #888; font-size: 12px; white-space: nowrap; }

/* ── Body ── */
.pgx-body {
  display: flex;
  flex: 1;
  overflow: hidden;
}

/* ── Sidebar ── */
.pgx-sidebar {
  width: 220px;
  background: #fff;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
  border-right: 1px solid #e0f5f8;
}

.sidebar-section {
  padding: 12px 0 4px;
}

.section-title {
  color: #9db8bc;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: 0 14px 6px;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  margin: 1px 8px;
  border-radius: 7px;
  cursor: pointer;
  color: #5a7a80;
  font-size: 13.5px;
  transition: all 0.15s;
  white-space: nowrap;
}
.nav-item:hover { background: #edfafc; color: #00B5C8; }
.nav-item.active {
  background: rgba(0,181,200,0.1);
  color: #00B5C8;
  font-weight: 600;
  border-left: 3px solid #00B5C8;
  padding-left: 11px;
}
.ni { font-size: 16px; width: 18px; flex-shrink: 0; }
.brand-icon { color: #00B5C8; }

.sidebar-bottom {
  margin-top: auto;
  padding: 8px 0 12px;
  border-top: 1px solid #e0f5f8;
}
.nav-item-bottom { margin-top: 2px; }
.version-tag {
  text-align: center;
  color: #b0cdd1;
  font-size: 11px;
  padding: 6px 0 0;
}

/* ── Main ── */
.pgx-main {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  background: #f4fafb;
}

/* ── Transitions ── */
.fade-enter-active, .fade-leave-active { transition: opacity 0.12s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>