|
|
@@ -1,5 +1,5 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { computed, onMounted, reactive, ref } from "vue";
|
|
|
+import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
import { useUserStore } from "@/store/modules/user";
|
|
|
import { factoryBoard, homeBlocks } from "@/api/ps";
|
|
|
@@ -41,6 +41,7 @@ const mine = reactive({
|
|
|
});
|
|
|
const site = useSiteStore();
|
|
|
const blocks = ref<HomeBlock[]>([]);
|
|
|
+const menuOpen = ref(false);
|
|
|
const brandName = computed(() => site.title);
|
|
|
const firstStepsIndex = computed(() =>
|
|
|
blocks.value.findIndex(item => item.type === "steps")
|
|
|
@@ -66,6 +67,7 @@ const heroMetrics = computed(() => {
|
|
|
});
|
|
|
|
|
|
onMounted(async () => {
|
|
|
+ window.addEventListener("resize", onResize);
|
|
|
try {
|
|
|
if (loggedIn.value) {
|
|
|
await store.loadFactory();
|
|
|
@@ -88,7 +90,31 @@ function formatNum(n: number) {
|
|
|
return String(v);
|
|
|
}
|
|
|
|
|
|
+function closeMenu() {
|
|
|
+ menuOpen.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+function toggleMenu() {
|
|
|
+ menuOpen.value = !menuOpen.value;
|
|
|
+}
|
|
|
+
|
|
|
+watch(menuOpen, open => {
|
|
|
+ document.body.style.overflow = open ? "hidden" : "";
|
|
|
+});
|
|
|
+
|
|
|
+function onResize() {
|
|
|
+ if (window.innerWidth > 900) {
|
|
|
+ closeMenu();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ document.body.style.overflow = "";
|
|
|
+ window.removeEventListener("resize", onResize);
|
|
|
+});
|
|
|
+
|
|
|
function go(path: string) {
|
|
|
+ closeMenu();
|
|
|
if (!loggedIn.value && path !== "/login" && path !== "/register" && !path.startsWith("/register")) {
|
|
|
router.push("/login");
|
|
|
return;
|
|
|
@@ -97,6 +123,7 @@ function go(path: string) {
|
|
|
}
|
|
|
|
|
|
function scrollTo(id: string) {
|
|
|
+ closeMenu();
|
|
|
document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
|
}
|
|
|
|
|
|
@@ -139,7 +166,7 @@ function isAnchor(path?: string) {
|
|
|
|
|
|
<template>
|
|
|
<div class="home" :class="{ embedded: !isPublic, public: isPublic }">
|
|
|
- <header v-if="isPublic" class="pub-nav">
|
|
|
+ <header v-if="isPublic" class="pub-nav" :class="{ open: menuOpen }">
|
|
|
<div class="wrap pub-nav-inner">
|
|
|
<div class="pub-brand">{{ brandName }}</div>
|
|
|
<nav class="pub-links">
|
|
|
@@ -165,6 +192,47 @@ function isAnchor(path?: string) {
|
|
|
<button type="button" class="btn-ghost" @click="go('/register')">注册工厂</button>
|
|
|
<button type="button" class="btn-solid" @click="go('/register?mode=join')">加入工厂</button>
|
|
|
</div>
|
|
|
+ <div class="pub-mobile">
|
|
|
+ <button type="button" class="btn-text" @click="go('/login')">登录</button>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="pub-menu-btn"
|
|
|
+ :aria-expanded="menuOpen"
|
|
|
+ :aria-label="menuOpen ? '关闭菜单' : '打开菜单'"
|
|
|
+ @click="toggleMenu"
|
|
|
+ >
|
|
|
+ <span />
|
|
|
+ <span />
|
|
|
+ <span />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-show="menuOpen" class="pub-mask" @click="closeMenu" />
|
|
|
+ <div class="pub-sheet" :class="{ open: menuOpen }">
|
|
|
+ <div class="wrap pub-sheet-inner">
|
|
|
+ <nav class="pub-sheet-links">
|
|
|
+ <button
|
|
|
+ v-if="firstStepsIndex >= 0"
|
|
|
+ type="button"
|
|
|
+ class="pub-sheet-link"
|
|
|
+ @click="scrollTo('home-flow')"
|
|
|
+ >
|
|
|
+ 怎么用
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ v-if="firstFeaturesIndex >= 0"
|
|
|
+ type="button"
|
|
|
+ class="pub-sheet-link"
|
|
|
+ @click="scrollTo('home-features')"
|
|
|
+ >
|
|
|
+ 能做什么
|
|
|
+ </button>
|
|
|
+ </nav>
|
|
|
+ <div class="pub-sheet-actions">
|
|
|
+ <button type="button" class="btn-ghost" @click="go('/register')">注册工厂</button>
|
|
|
+ <button type="button" class="btn-solid" @click="go('/register?mode=join')">加入工厂</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</header>
|
|
|
|
|
|
@@ -340,6 +408,7 @@ function isAnchor(path?: string) {
|
|
|
position: sticky;
|
|
|
top: 0;
|
|
|
z-index: 20;
|
|
|
+ padding-top: env(safe-area-inset-top);
|
|
|
background: rgba(255, 255, 255, 0.92);
|
|
|
backdrop-filter: blur(16px);
|
|
|
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
|
|
@@ -352,10 +421,14 @@ function isAnchor(path?: string) {
|
|
|
min-height: 72px;
|
|
|
}
|
|
|
.pub-brand {
|
|
|
+ min-width: 0;
|
|
|
font-size: 18px;
|
|
|
font-weight: 700;
|
|
|
letter-spacing: 0.02em;
|
|
|
color: #0f172a;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
}
|
|
|
.pub-links {
|
|
|
display: flex;
|
|
|
@@ -376,10 +449,97 @@ function isAnchor(path?: string) {
|
|
|
}
|
|
|
.pub-actions {
|
|
|
display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.pub-mobile {
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.pub-menu-btn {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ padding: 0;
|
|
|
+ border: 0;
|
|
|
+ border-radius: 12px;
|
|
|
+ background: #f1f5f9;
|
|
|
+ display: inline-flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
align-items: center;
|
|
|
+ gap: 5px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+.pub-menu-btn span {
|
|
|
+ display: block;
|
|
|
+ width: 16px;
|
|
|
+ height: 2px;
|
|
|
+ border-radius: 2px;
|
|
|
+ background: #0f172a;
|
|
|
+ transition: transform 0.18s ease, opacity 0.18s ease;
|
|
|
+}
|
|
|
+.pub-nav.open .pub-menu-btn span:nth-child(1) {
|
|
|
+ transform: translateY(7px) rotate(45deg);
|
|
|
+}
|
|
|
+.pub-nav.open .pub-menu-btn span:nth-child(2) {
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+.pub-nav.open .pub-menu-btn span:nth-child(3) {
|
|
|
+ transform: translateY(-7px) rotate(-45deg);
|
|
|
+}
|
|
|
+.pub-mask {
|
|
|
+ position: fixed;
|
|
|
+ inset: 0;
|
|
|
+ top: calc(56px + env(safe-area-inset-top));
|
|
|
+ z-index: 18;
|
|
|
+ background: rgba(15, 23, 42, 0.28);
|
|
|
+}
|
|
|
+.pub-sheet {
|
|
|
+ display: none;
|
|
|
+ position: relative;
|
|
|
+ z-index: 21;
|
|
|
+ padding: 4px 0 max(16px, env(safe-area-inset-bottom));
|
|
|
+ background: #fff;
|
|
|
+ border-top: 1px solid #eef1f6;
|
|
|
+ box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
|
|
|
+}
|
|
|
+.pub-sheet.open {
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.pub-sheet-inner {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ padding-top: 8px;
|
|
|
+ padding-bottom: 8px;
|
|
|
+}
|
|
|
+.pub-sheet-links {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+.pub-sheet-link {
|
|
|
+ border: 0;
|
|
|
+ background: none;
|
|
|
+ text-align: left;
|
|
|
+ padding: 14px 0;
|
|
|
+ color: #334155;
|
|
|
+ font-size: 15px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+.pub-sheet-actions {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
+.pub-sheet-actions .btn-ghost,
|
|
|
+.pub-sheet-actions .btn-solid {
|
|
|
+ width: 100%;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
|
|
|
.btn-solid,
|
|
|
.btn-ghost,
|
|
|
@@ -714,14 +874,26 @@ h2 {
|
|
|
}
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
- .pub-links {
|
|
|
+ .pub-links,
|
|
|
+ .pub-actions {
|
|
|
display: none;
|
|
|
}
|
|
|
+ .pub-mobile {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+ .pub-nav-inner {
|
|
|
+ min-height: 56px;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ .pub-brand {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
.wrap {
|
|
|
- width: min(1120px, calc(100% - 40px));
|
|
|
+ width: min(1120px, calc(100% - 32px));
|
|
|
}
|
|
|
.hero {
|
|
|
- padding-top: 48px;
|
|
|
+ padding-top: 36px;
|
|
|
}
|
|
|
.hero-copy {
|
|
|
padding-bottom: 36px;
|
|
|
@@ -736,16 +908,24 @@ h2 {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+@media (min-width: 901px) {
|
|
|
+ .pub-mobile,
|
|
|
+ .pub-mask,
|
|
|
+ .pub-sheet {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
@media (max-width: 768px) {
|
|
|
.home.embedded {
|
|
|
margin: -16px;
|
|
|
}
|
|
|
- .pub-nav-inner {
|
|
|
- min-height: 64px;
|
|
|
- }
|
|
|
.hero-title {
|
|
|
font-size: 26px;
|
|
|
}
|
|
|
+ .brand {
|
|
|
+ font-size: 32px;
|
|
|
+ }
|
|
|
.stage-row {
|
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
|
}
|