feat: add layout of chat and login

This commit is contained in:
Quicy
2026-01-29 14:57:14 +08:00
parent 041f1afde0
commit e7500e5a12
18 changed files with 507 additions and 153 deletions
+2
View File
@@ -9,6 +9,8 @@
"start": "vite preview"
},
"dependencies": {
"@jamescoyle/vue-icon": "^0.1.2",
"@mdi/js": "^7.4.47",
"@memoh/shared": "workspace:*",
"@memoh/ui": "workspace:*",
"@tailwindcss/vite": "^4.1.18",
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+1 -3
View File
@@ -1,10 +1,8 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
import { Alert, Button } from '@memoh/ui'
</script>
<template>
<RouterView />
<Alert>Hello</Alert>
<Button>Click me</Button>
</template>
@@ -0,0 +1,13 @@
<template>
<section>
<h1 @click="open=!open">
<!-- 主体 -->
</h1>
</section>
</template>
<script setup lang="ts">
import { inject } from 'vue'
const open=inject('sideBarIsOpen')
</script>
@@ -0,0 +1,103 @@
<template>
<aside>
<SidebarProvider :open="open as boolean">
<Sidebar>
<SidebarHeader>
<img
src="../../../public/logo.png"
width="80"
class="m-auto"
alt="logo.png"
>
<h4 class="scroll-m-20 text-xl font-semibold tracking-tight text-center text-muted-foreground">
Memoh
</h4>
<!-- <SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg">
<div
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"
>
<GalleryVerticalEnd class="size-4" />
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">Acme Inc</span>
<span class="truncate text-xs">Enterprise</span>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu> -->
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel class="text-base">
对话操作
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem
v-for="sidebarItem in sidebarInfo"
:key="sidebarItem.title"
>
<SidebarMenuButton
as-child
>
<section class="flex">
<svg-icon
type="mdi"
:path="sidebarItem.icon"
/>
<span>{{ sidebarItem.title }}</span>
</section>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter />
<SidebarRail />
</Sidebar>
</SidebarProvider>
</aside>
</template>
<script setup lang="ts">
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
} from '@memoh/ui'
import { reactive, inject } from 'vue'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiCogOutline, mdiChatOutline, mdiCogBox, mdiListBox } from '@mdi/js'
const open=inject('sideBarIsOpen')
const sidebarInfo = reactive([{
title: '创建对话',
path: '/',
icon: mdiChatOutline
}, {
title: '模型配置',
path: '/',
icon: mdiCogOutline
}, {
title: '环境设置',
path: '/',
icon: mdiCogBox
}, {
title: '调度规则',
path: '/',
icon: mdiListBox
}])
</script>
@@ -0,0 +1,7 @@
<template>
<section class="flex">
<slot name="sidebar" />
<slot name="main" />
</section>
</template>
-1
View File
@@ -4,7 +4,6 @@ import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import i18n from './i18n'
import '@memoh/ui/style.css'
createApp(App)
.use(createPinia())
+22
View File
@@ -0,0 +1,22 @@
<template>
<section>
<MainLayout>
<template #sidebar>
<SideBar />
</template>
<template #main>
<MainContainer />
</template>
</MainLayout>
</section>
</template>
<script setup lang="ts">
import MainLayout from '@/layout/mainLayout/index.vue'
import SideBar from '@/components/Sidebar/index.vue'
import MainContainer from '@/components/MainContainer/index.vue'
import { provide,ref } from 'vue'
provide('sideBarIsOpen',ref(true))
</script>
+97
View File
@@ -0,0 +1,97 @@
<template>
<section
class="w-screen h-screen flex *:m-auto bg-linear-to-t from-[#BFA4A0] to-[#7784AC] "
>
<section class="w-full max-w-sm flex flex-col gap-10 ">
<section>
<img
src="../../../public/logo.png"
width="100"
alt="logo.png"
class="m-auto"
>
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight text-white text-center">
Memoh
</h3>
</section>
<Card class="py-14">
<CardContent>
<form>
<div class="grid w-full items-center gap-4 [&_input]:py-5">
<div class="flex flex-col space-y-1.5 gap-2">
<Label
for="email"
class=""
>Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
/>
</div>
<div class="flex flex-col space-y-1.5 gap-2">
<div class="flex items-center ">
<Label for="password">Password</Label>
</div>
<Input
id="password"
type="password"
/>
</div>
</div>
<div class="flex">
<a
href="#"
class="ml-auto inline-block text-sm underline mt-2"
>
Forgot your password?
</a>
</div>
</form>
</CardContent>
<CardFooter class="flex flex-col gap-4">
<Button
class="w-full"
@click="login"
>
登录
</Button>
<Button
variant="outline"
class="w-full"
>
注册
</Button>
</CardFooter>
</Card>
</section>
</section>
</template>
<script setup lang="ts">
import {
Card,
CardContent,
CardFooter,
Input,
Label,
Button
} from '@memoh/ui'
import { useRouter } from 'vue-router'
const router = useRouter()
const login = async () => {
// 先模拟一下数据
localStorage.setItem('token','afewfewf')
await router.push('/chat')
console.log('登录')
}
</script>
+28 -1
View File
@@ -1,8 +1,35 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
redirect: '/login'
},
{
name: 'Login',
path: '/login',
component: () => import('@/pages/login/index.vue')
}, {
name: 'Chat',
path: '/chat',
component: () => import('@/pages/chat/index.vue'),
}
]
const router = createRouter({
history: createWebHistory(),
routes: [],
routes,
})
router.beforeEach((to) => {
const token = localStorage.getItem('token')
if (to.fullPath !== '/login') {
return token ? true : { name: 'Login' }
} else {
return token ? { name: 'Chat' } : true
}
})
export default router
+121 -1
View File
@@ -1,2 +1,122 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.65rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.546 0.245 262.881);
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.623 0.214 259.815);
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
@source "../../ui/src";
+1 -1
View File
@@ -12,5 +12,5 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
}
+1
View File
@@ -25,6 +25,7 @@ export default defineConfig({
resolve: {
alias: {
'#': fileURLToPath(new URL('../ui/src', import.meta.url)),
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})