<template>
  <div class="landing">
    <nav class="auth-nav auth-nav--sticky">
      <div class="auth-nav-brand">
        <div class="auth-logo">
          <q-icon name="shield" size="26px" />
        </div>
        <span class="auth-nav-title">Dpz.Core</span>
        <span class="auth-nav-sub">认证中心</span>
      </div>
      <div class="auth-nav-end">
        <div v-if="user" class="auth-nav-actions">
          <q-btn
            v-for="item in navActions"
            :key="item.label"
            dense
            flat
            no-caps
            class="auth-nav-link"
            :icon="item.icon"
            :label="item.label"
            @click="navigate(item.to)"
          />
        </div>
        <q-btn v-if="user" flat round icon="logout" @click="signOut">
          <q-tooltip>退出登录</q-tooltip>
        </q-btn>
        <q-btn
          round
          flat
          :icon="$q.dark.isActive ? 'light_mode' : 'dark_mode'"
          @click="$q.dark.toggle()"
        >
          <q-tooltip>切换主题</q-tooltip>
        </q-btn>
      </div>
    </nav>

    <main v-if="user" class="landing-main">
      <section class="dashboard">
        <div class="dashboard-hero">
          <div class="dashboard-mesh" aria-hidden="true" />
          <div class="dashboard-glow dashboard-glow--tl" />
          <div class="dashboard-glow dashboard-glow--br" />
          <div class="dashboard-orbit" aria-hidden="true" />

          <div class="dashboard-profile">
            <q-avatar
              size="86px"
              color="primary"
              text-color="white"
              class="dashboard-avatar"
            >
              <q-icon v-if="!user.avatar" name="person" size="44px" />
              <img v-else :src="user.avatar" alt="" />
            </q-avatar>
            <div class="dashboard-profile-text">
              <div class="dashboard-kicker">
                <q-icon name="verified" size="16px" />
                <span>已通过身份验证</span>
              </div>
              <div class="dashboard-name-row">
                <span class="dashboard-name">{{ user.name }}</span>
                <q-chip
                  v-if="isAdmin"
                  dense
                  color="primary"
                  text-color="white"
                  size="sm"
                  label="管理员"
                />
              </div>
              <div class="dashboard-account">
                <span>{{ user.id }}</span>
                <span v-if="user.email">{{ user.email }}</span>
              </div>
            </div>
          </div>

          <div class="dashboard-divider" />

          <div class="dashboard-stats">
            <div
              v-for="status in dashboardStatuses"
              :key="status.label"
              class="dashboard-stat"
              :class="status.colorClass"
            >
              <div class="dashboard-stat-icon">
                <q-icon :name="status.icon" size="22px" />
              </div>
              <div>
                <div class="dashboard-stat-value">{{ status.value }}</div>
                <div class="dashboard-stat-label">{{ status.label }}</div>
              </div>
            </div>
          </div>
        </div>

        <div class="dashboard-cards">
          <div class="dashboard-cards-header">
            <div>
              <div class="section-kicker">SECURITY CONSOLE</div>
              <h3 class="dashboard-cards-title">快捷操作</h3>
            </div>
            <div class="dashboard-cards-hint">
              <q-icon name="bolt" size="18px" />
              <span>安全配置入口</span>
            </div>
          </div>
          <div class="dashboard-cards-grid">
            <q-card
              v-for="card in userCards"
              :key="card.label"
              flat
              bordered
              class="dash-card"
              @click="navigate(card.to)"
            >
              <q-card-section class="dash-card-section">
                <div class="dash-card-orbit" aria-hidden="true" />
                <div class="dash-card-icon" :class="card.colorClass">
                  <q-icon :name="card.icon" size="28px" />
                </div>
                <div class="dash-card-copy">
                  <div class="dash-card-title">{{ card.label }}</div>
                  <div class="dash-card-desc">{{ card.desc }}</div>
                </div>
                <q-icon
                  class="dash-card-arrow"
                  name="arrow_forward"
                  size="20px"
                />
              </q-card-section>
            </q-card>
          </div>
        </div>
      </section>
    </main>

    <main v-else class="landing-main">
      <section class="hero">
        <div class="hero-decor" aria-hidden="true">
          <div class="hero-grid" />
          <div class="hero-scan" />
          <div class="hero-beam hero-beam--a" />
          <div class="hero-beam hero-beam--b" />
          <svg class="hero-svg hero-svg--rings" viewBox="0 0 620 620">
            <circle
              cx="310"
              cy="310"
              r="282"
              fill="none"
              stroke="currentColor"
              stroke-width="0.6"
              opacity="0.28"
            />
            <circle
              cx="310"
              cy="310"
              r="218"
              fill="none"
              stroke="currentColor"
              stroke-width="0.75"
              opacity="0.42"
              stroke-dasharray="18 26"
            />
            <circle
              cx="310"
              cy="310"
              r="142"
              fill="none"
              stroke="currentColor"
              stroke-width="0.9"
              opacity="0.5"
            />
          </svg>
          <div class="hero-gateway">
            <div class="gateway-core">
              <q-icon name="shield" size="58px" />
            </div>
            <div class="gateway-ring gateway-ring--outer" />
            <div class="gateway-ring gateway-ring--inner" />
            <div class="gateway-node gateway-node--oauth">
              <q-icon name="vpn_key" size="18px" />
              <span>OAuth</span>
            </div>
            <div class="gateway-node gateway-node--oidc">
              <q-icon name="verified_user" size="18px" />
              <span>OIDC</span>
            </div>
            <div class="gateway-node gateway-node--passkey">
              <q-icon name="fingerprint" size="18px" />
              <span>Passkey</span>
            </div>
          </div>
        </div>

        <div class="hero-content">
          <div class="hero-badge">
            <q-icon name="verified_user" size="16px" />
            <span>OpenID Connect &middot; OAuth 2.0</span>
          </div>

          <h1 class="hero-title">
            统一认证
            <br />
            <span class="hero-title--gradient">安全入口</span>
          </h1>

          <p class="hero-desc">
            为所有应用提供安全、统一、现代的认证体验。支持密码、双因素、 Passkey
            多种方式,自动适配深色与浅色模式。
          </p>

          <div class="hero-actions">
            <q-btn
              color="primary"
              size="19px"
              icon="login"
              label="前往登录"
              href="/sign-in"
              unelevated
              padding="12px 40px"
              class="hero-btn hero-btn--primary"
            />
            <q-btn
              outline
              size="19px"
              icon="fingerprint"
              label="Passkey 登录"
              href="/sign-in"
              padding="12px 40px"
              class="hero-btn hero-btn--ghost"
            />
          </div>

          <div class="hero-stats">
            <div v-for="stat in heroStats" :key="stat.value" class="hero-stat">
              <span class="hero-stat-num">{{ stat.value }}</span>
              <span class="hero-stat-label">{{ stat.label }}</span>
            </div>
          </div>
        </div>
      </section>

      <section class="section features">
        <div class="section-header">
          <div class="section-kicker">IDENTITY CAPABILITIES</div>
          <h2 class="section-title">认证能力</h2>
          <p class="section-subtitle">
            基于 OpenIddict 构建,为企业级应用提供完整的身份认证解决方案
          </p>
        </div>
        <div class="features-grid">
          <q-card
            v-for="feat in features"
            :key="feat.title"
            flat
            bordered
            class="feature-card"
          >
            <q-card-section class="feature-card-section">
              <div class="feature-card-shine" aria-hidden="true" />
              <div class="feature-card-icon" :class="feat.colorClass">
                <q-icon :name="feat.icon" size="26px" />
              </div>
              <div class="feature-card-title">{{ feat.title }}</div>
              <div class="feature-card-desc">{{ feat.desc }}</div>
            </q-card-section>
          </q-card>
        </div>
      </section>

      <section class="section passkey-section">
        <div class="section-header passkey-section-header">
          <div class="section-kicker">PASSKEY MANAGEMENT</div>
          <h2 class="section-title">删除 Passkey 前先看这里</h2>
          <p class="section-subtitle">
            本站删除的是服务端绑定记录,不会远程删除你设备或浏览器里的通行密钥。
          </p>
        </div>

        <div class="passkey-grid">
          <q-card
            v-for="item in passkeyNotes"
            :key="item.title"
            flat
            bordered
            class="passkey-card"
          >
            <q-card-section class="passkey-card-section">
              <div class="passkey-card-top">
                <div class="passkey-card-icon" :class="item.colorClass">
                  <q-icon :name="item.icon" size="26px" />
                </div>
                <div class="passkey-card-title">{{ item.title }}</div>
              </div>
              <div class="passkey-card-desc">{{ item.desc }}</div>
              <div class="passkey-card-note">
                <q-icon name="info" size="16px" />
                <span>{{ item.note }}</span>
              </div>
            </q-card-section>
          </q-card>
        </div>

        <div class="passkey-local">
          <div class="passkey-local-head">
            <div class="passkey-local-head-icon">
              <q-icon name="travel_explore" size="24px" />
            </div>
            <div>
              <div class="passkey-local-title">常见设备上的本地删除入口</div>
              <div class="passkey-local-copy">
                系统和浏览器版本不同,入口名称可能略有差异;关键词通常是“密码”“Passkey”“通行密钥”。
              </div>
            </div>
          </div>

          <div class="passkey-local-grid">
            <div
              v-for="entry in localPasskeyEntries"
              :key="entry.title"
              class="passkey-local-entry"
            >
              <q-icon :name="entry.icon" size="21px" />
              <div>
                <div class="passkey-local-entry-title">{{ entry.title }}</div>
                <div class="passkey-local-entry-path">{{ entry.path }}</div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </main>

    <footer class="landing-footer">
      <div />
      <div class="landing-footer-text">
        Dpz.Core 认证中心 &copy; {{ currentYear }}
      </div>
      <div />
    </footer>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { useQuasar } from "quasar";
import { PageContext } from "../../services/PageContext";
import {
  UserPermissions,
  type UserSecurityBindingStatus,
  type VmUserInfo,
} from "../../models/AuthModels";
import { UserSecurityService } from "../../services/UserSecurityService";

const $q = useQuasar();
$q.dark.set("auto");

const context = PageContext.read();
const security = new UserSecurityService();
const securityStatus = ref<UserSecurityBindingStatus | null>(null);
const currentYear = computed(() => new Date().getFullYear());
const user = computed<VmUserInfo | null>(() => context.user ?? null);
const isAdmin = computed(() =>
  user.value?.permissions != null
    ? (user.value.permissions & UserPermissions.System) ===
      UserPermissions.System
    : false,
);

onMounted(async () => {
  if (!user.value) {
    return;
  }
  const result = await security.bindings();
  securityStatus.value = result.success ? (result.data ?? null) : null;
});

const heroStats = [
  { value: "OAuth 2.0", label: "授权协议" },
  { value: "OIDC", label: "认证标准" },
  { value: "PKCE", label: "安全流程" },
  { value: "Passkey", label: "无密码" },
];

const dashboardStatuses = [
  {
    icon: "verified_user",
    value: "OpenID Connect",
    label: "统一身份",
    colorClass: "ds--blue",
  },
  {
    icon: "fingerprint",
    value: "Passkey",
    label: "无密码凭证",
    colorClass: "ds--green",
  },
  {
    icon: "phonelink_lock",
    value: "双因素",
    label: "高风险保护",
    colorClass: "ds--purple",
  },
];

const features = [
  {
    icon: "security",
    title: "OAuth 2.0 / OIDC",
    desc: "Authorization Code + PKCE 流程,支持机密与公共客户端。",
    colorClass: "fc--blue",
  },
  {
    icon: "phonelink_lock",
    title: "双因素认证",
    desc: "基于 TOTP 的双因素验证,为高安全场景提供额外保护。",
    colorClass: "fc--green",
  },
  {
    icon: "fingerprint",
    title: "Passkey 通行密钥",
    desc: "WebAuthn/FIDO2 标准,支持指纹、面部、安全密钥。",
    colorClass: "fc--purple",
  },
  {
    icon: "manage_accounts",
    title: "统一授权管理",
    desc: "集中管理授权范围与策略,用户可随时撤销客户端访问。",
    colorClass: "fc--orange",
  },
  {
    icon: "palette",
    title: "自适应主题",
    desc: "自动跟随系统深色/浅色模式,也可手动切换。",
    colorClass: "fc--teal",
  },
  {
    icon: "devices",
    title: "全设备响应式",
    desc: "适配桌面、平板、手机等所有屏幕尺寸。",
    colorClass: "fc--red",
  },
];

const passkeyNotes = [
  {
    icon: "delete",
    title: "站点内删除",
    desc: "进入 Passkey 管理页删除凭证后,这个 Passkey 将不再能用于本系统登录或敏感操作。",
    note: "当前已登录会话通常仍会保留,直到你主动退出或会话过期。",
    colorClass: "pk--blue",
  },
  {
    icon: "devices",
    title: "本地仍保留",
    desc: "服务端没有权限远程擦除你电脑、手机或浏览器里的通行密钥。",
    note: "若要彻底移除本地 Passkey,需要到系统、浏览器或密码管理器里手动删除。",
    colorClass: "pk--green",
  },
  {
    icon: "warning",
    title: "删除后的后果",
    desc: "之后该凭证无法再用于登录,也不能再完成依赖该 Passkey 的验证。",
    note: "建议先保留至少一个备用方式,例如 2FA 或另一个 Passkey。",
    colorClass: "pk--amber",
  },
];

const localPasskeyEntries = [
  {
    icon: "desktop_windows",
    title: "Windows Hello",
    path: "设置 → 账户 → 通行密钥 / Passkeys → 选择本站 → 删除",
  },
  {
    icon: "language",
    title: "Chrome / Google 密码管理器",
    path: "Chrome → 更多 → 密码和自动填充 → Google 密码管理器 → Passkeys",
  },
  {
    icon: "web_asset",
    title: "Edge / Microsoft 密码管理器",
    path: "Edge → 设置 → 个人资料 → 密码 / Microsoft Password Manager → Passkeys",
  },
  {
    icon: "public",
    title: "Firefox",
    path: "Firefox 通常调用系统或外接安全密钥保存 Passkey,请到 Windows Hello、系统密码、Google 密码管理器或安全密钥管理工具里删除",
  },
  {
    icon: "phone_iphone",
    title: "iPhone / iPad / Mac",
    path: "设置或系统设置 → 密码 → 搜索本站 → 删除对应通行密钥",
  },
  {
    icon: "android",
    title: "Android",
    path: "设置 → Google → 密码管理工具 → 搜索本站 → 删除 Passkey",
  },
];

const baseCards = [
  {
    icon: "space_dashboard",
    label: "用户中心",
    desc: "查看个人资料、安全状态、授权应用和内容管理入口。",
    to: "/user",
    colorClass: "dc--green",
  },
  {
    icon: "password",
    label: "修改密码",
    desc: "更新登录密码。修改后将撤销所有现有授权并要求重新登录。",
    to: "/change-password.html",
    colorClass: "dc--blue",
  },
  {
    icon: "fingerprint",
    label: "Passkey 管理",
    desc: "绑定指纹、面部识别或安全密钥,实现无密码登录。",
    to: "/passkeys.html",
    colorClass: "dc--purple",
  },
];

const navActions = computed(() => {
  if (!user.value) {
    return [];
  }

  const twoFactorAction = securityStatus.value?.hasTwoFactor
    ? {
        icon: "phonelink_erase",
        label: "解绑 2FA",
        to: "/unbind-two-factor.html",
      }
    : {
        icon: "phonelink_lock",
        label: "绑定 2FA",
        to: "/two-factor.html",
      };

  const actions = [
    {
      icon: "password",
      label: "修改密码",
      to: "/change-password.html",
    },
    twoFactorAction,
    {
      icon: "fingerprint",
      label: "Passkey",
      to: "/passkeys.html",
    },
  ];

  if (isAdmin.value) {
    actions.push({
      icon: "admin_panel_settings",
      label: "后台",
      to: "/admin",
    });
  }

  return actions;
});

const userCards = computed(() => {
  const cards = [...baseCards];
  if (isAdmin.value) {
    cards.push({
      icon: "admin_panel_settings",
      label: "后台管理",
      desc: "管理客户端应用、授权记录、令牌与访问申请。",
      to: "/admin",
      colorClass: "dc--amber",
    });
  }
  return cards;
});

function navigate(url: string) {
  window.location.href = url;
}

function signOut() {
  $q.dialog({
    title: "退出登录",
    message: "确认退出当前认证中心会话?",
    cancel: true,
    persistent: true,
  }).onOk(() => {
    window.location.href = "/sign-out";
  });
}
</script>
评论加载中...