Skip to content

Commit

Permalink
feat: aside
Browse files Browse the repository at this point in the history
Signed-off-by: ZTL-UwU <zhangtianli2006@163.com>
  • Loading branch information
ZTL-UwU committed May 23, 2024
1 parent e8a63a2 commit 2125f12
Show file tree
Hide file tree
Showing 35 changed files with 574 additions and 29 deletions.
29 changes: 27 additions & 2 deletions components/layout/Aside.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,37 @@
<aside
class="fixed top-16 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block overflow-y-auto"
>
<UiScrollArea orientation="vertical" class="relative overflow-hidden h-full py-6 pr-6" type="auto">
<LayoutAsideTree :links="navigation" />
<UiScrollArea orientation="vertical" class="relative overflow-hidden h-full py-6 pr-6 text-sm" type="auto">
<ul class="pb-3 border-b">
<li v-for="link in navigation" :key="link.id">
<NuxtLink
:to="link._path"
class="px-2 py-2 mb-1 hover:bg-muted rounded-md w-full block transition-all"
:class="[
path.startsWith(link._path) && 'bg-muted hover:bg-muted font-semibold',
]"
>
{{ link.title }}
</NuxtLink>
</li>
</ul>
<LayoutAsideTree :links="tree" :level="0" class="pl-2" />
</UiScrollArea>
</aside>
</template>

<script setup lang="ts">
const { navDirFromPath } = useContentHelpers();
const { navigation } = useContent();
const tree = computed(() => {
const route = useRoute();
const path = route.path.split('/');
const leveledPath = path.splice(0, 2).join('/');
const dir = navDirFromPath(leveledPath, navigation.value);
return dir ?? [];
});
const path = useRoute().path;
</script>
14 changes: 8 additions & 6 deletions components/layout/AsideTree.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<template>
<ul>
<li v-for="link in links" :key="link._id">
{{ link.title }}
<LayoutAsideTree v-if="link.children" :links="link.children" />
</li>
<ul :class="[level > 0 ? 'border-l' : 'py-2', level <= 1 && 'mt-2']">
<template v-for="link in links" :key="link._id">
<LayoutAsideTreeItem
:link="link"
:level="level"
/>
</template>
</ul>
</template>

Expand All @@ -13,5 +14,6 @@ import type { NavItem } from '@nuxt/content/types';
defineProps<{
links: NavItem[];
level: number;
}>();
</script>
48 changes: 48 additions & 0 deletions components/layout/AsideTreeItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<template>
<li class="border-l-red">
<div
class="rounded-md transition-all underline-offset-4"
:class="[level > 0 && 'pl-4']"
>
<UiCollapsible v-if="link.children" v-model:open="isOpen">
<UiCollapsibleTrigger class="w-full text-left" :class="[level > 0 ? 'py-1.5 px-2' : 'pt-2']">
<div class="w-full flex">
{{ link.title }}
<Icon
:name="isOpen ? 'lucide:chevrons-down-up' : 'lucide:chevrons-up-down'"
size="12"
class="ml-auto self-center"
:class="level === 0 && 'mr-2'"
/>
</div>
</UiCollapsibleTrigger>
<UiCollapsibleContent>
<LayoutAsideTree :links="link.children" :level="level + 1" />
</UiCollapsibleContent>
</UiCollapsible>
<NuxtLink
v-else
:to="link._path"
class="w-full block hover:underline text-muted-foreground"
:class="[
isActive && 'font-semibold text-primary',
level > 0 ? 'py-1.5 px-2' : 'pt-2',
]"
>
{{ link.title }}
</NuxtLink>
</div>
</li>
</template>

<script setup lang="ts">
import type { NavItem } from '@nuxt/content/types';
const props = defineProps<{
link: NavItem;
level: number;
}>();
const isOpen = ref(props.level < 1);
const isActive = computed(() => props.link._path === useRoute().path);
</script>
40 changes: 21 additions & 19 deletions components/layout/Toc.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
<template>
<div class="hidden xl:block">
<UiScrollArea orientation="vertical" class="h-[calc(100vh-6.5rem)] z-30 md:block overflow-y-auto" type="hover">
<p class="mb-2 text-base font-semibold">
On This Page
</p>
<LayoutTocTree :links="toc.links" :level="0" />
</UiScrollArea>
</div>

<div class="block xl:hidden mb-6">
<UiCollapsible>
<UiCollapsibleTrigger>
<UiButton variant="outline">
<template v-if="toc?.links">
<div class="hidden xl:block">
<UiScrollArea orientation="vertical" class="h-[calc(100vh-6.5rem)] z-30 md:block overflow-y-auto" type="hover">
<p class="mb-2 text-base font-semibold">
On This Page
</UiButton>
</UiCollapsibleTrigger>
<UiCollapsibleContent class="text-sm mt-4 border-l pl-4">
</p>
<LayoutTocTree :links="toc.links" :level="0" />
</UiCollapsibleContent>
</UiCollapsible>
</div>
</UiScrollArea>
</div>

<div class="block xl:hidden mb-6">
<UiCollapsible>
<UiCollapsibleTrigger>
<UiButton variant="outline">
On This Page
</UiButton>
</UiCollapsibleTrigger>
<UiCollapsibleContent>
<LayoutTocTree :links="toc.links" :level="0" class="text-sm pl-4 border-l mt-4" />
</UiCollapsibleContent>
</UiCollapsible>
</div>
</template>
</template>

<script setup lang="ts">
Expand Down
23 changes: 23 additions & 0 deletions content/developer/1.introduction/1.getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: 初始化工作
---

# 初始化工作

::alert{type="info"}
阅读本节前,请确认你已经配置好 Node.js 环境,并且启用 pnpm 作为包管理器。
::

## 克隆仓库并安装依赖

```shell
git clone https://github.com/sms-cosmo/sms-tree
cd sms-tree
pnpm i
```

我们推荐使用 Visual Studio Code 作为集成开发环境,你可以在打开克隆好的仓库后根据提示安装我们推荐使用的插件。

::alert{type="success"}
在开始之前,请阅读 [Vue 官方文档](https://cn.vuejs.org/guide/introduction.html),了解 Vue 的语法和工作方式。
::
26 changes: 26 additions & 0 deletions content/developer/1.introduction/2.trpc-panel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: 后端接口调试
---

# tRPC Panel

[`.env`](/developer/directory-structure/env) 中的 `NODE_ENV``development` 时,会开启 [tRPC Panel](https://github.com/iway1/trpc-panel) —— 一个后端接口调试工具。

<img src="/trpc-panel-preview.png" width="1000" alt="trpc panel 预览">

## 打开方式

`pnpm dev` 后,在浏览器中打开 `http://localhost:3000/panel`

## 登陆方式

1.`user.login` 中输入账号和密码。
<img src="/trpc-panel-login.png" width="1000" alt="trpc panel login">

2. 点击 **Execute login** 后,复制 `accessToken` 中的全部内容。
<img src="/trpc-panel-login-response.png" width="1000" alt="trpc panel login response">

1. 点开右上角的 **Headers** 按钮。点击 **Add +**。在 `Key` 中填入 `Authorization`,在 `Value` 中填入刚刚复制的 `accessToken` 的内容。填写完后点击 `Confirm`
<img src="/trpc-panel-header.png" width="1000" alt="trpc panel headers">若要保持登陆状态,请点击 `Save Headers`

1. 登陆完成。
3 changes: 3 additions & 0 deletions content/developer/1.introduction/_dir.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
title: 开始开发
icon: ph:star-duotone
navigation: true
26 changes: 26 additions & 0 deletions content/developer/2.directory-structure/1.components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
icon: ph:folder-notch-open-duotone
title: components
---

# Components 组件

::alert{type="success"}
关于 Composables 的更多信息,请参阅 [Nuxt 文档](https://nuxt.com/docs/guide/directory-structure/components)
::

`component` /kəmˈpəʊ.nənt/ n. 组件

顾名思义,该文件夹里包含了构建UI时候使用的组件。

```
.
└── components/
├── attachment # 附件相关
├── class # 班级相关
├── group # 小组相关
├── layouts # 布局相关
├── paper # 论文相关
├── user # 用户相关
└── utils # 工具组件
```
22 changes: 22 additions & 0 deletions content/developer/2.directory-structure/10.scripts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
icon: ph:folder-notch-open-duotone
title: scripts
---

# Scripts 脚本

### `createDefaultUser`

用于初始化数据库之后,创建一个身份为`admin`的账户。创建后生成的密码为随机字符串,请注意保存。

```shell
pnpm run db:createAdmin
```

### `createSampleDb`

创建一个用于测试的样例数据库。

```shell
pnpm run db:createSampleDB
```
12 changes: 12 additions & 0 deletions content/developer/2.directory-structure/11.server/1.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
icon: ph:folder-notch-open-duotone
title: api
---

# API

此文件夹下唯一的文件是 `[trpc].ts` ,它调用了来自 [`trpc-nuxt`](https://trpc-nuxt.vercel.app/)`createNuxtApiHandler` 函数,将 tRPC 服务转化为 Nuxt 原生的 API 路由。

所有 tRPC 请求都最终指向这个文件(即指向`/api/trpc/xxxx`),也就是说,这是整个 tRPC 服务的入口。

![请求示例](/api-example.png)
34 changes: 34 additions & 0 deletions content/developer/2.directory-structure/11.server/2.db.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
icon: ph:folder-notch-open-duotone
title: db
---

# db 数据库

db,即数据库( database ),储存数据的地方。这个文件夹存放了数据结构的定义,以及用于连接数据库的文件。

在本项目中,我们使用了 [`DrizzleORM`](https://orm.drizzle.team/docs/overview) 作为数据库工具。 ORM 是一种技术,用于简化与数据库之间的交互,省去编写复杂的SQL查询语句,也增加了安全性。
`DrizzleORM` 是一个基于 TypeScript 的 ORM 框架,它可以让我们使用 TypeScript 来定义数据结构,利用强大的类型系统提升开发效率、保障代码质量。

::alert{type="warning"}
当然,在使用 `DrizzleORM` 之前,你需要具备一些数据库的基础知识,比如 SQL 语句、数据库表的设计等。
::

```
.
└── db/
├── schema/
│ ├── paper.ts
│ └── ...
└── db.ts
```

## `schema`

数据结构定义文件夹。每个文件对应一个数据表。数据表的定义语法可以参考 `DrizzleORM`[文档](https://orm.drizzle.team/docs/sql-schema-declaration)

## `db.ts`

数据库连接文件。在这个文件中,我们使用 `@libsql/client` 连接数据库,随后调用 `drizzle` 函数,将数据库连接转化为 `DrizzleORM` 实例。

这个文件也导出了各个表的数据类型,以便在其他地方使用。
14 changes: 14 additions & 0 deletions content/developer/2.directory-structure/11.server/3.routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
icon: ph:folder-notch-open-duotone
title: routes
---

# Routes

::alert{type="success"}
tRPC Panel 的使用方法详见 [开始开发/后端接口调试](/developer/introduction/trpc-panel)
::

此文件夹下唯一的文件是 `panel.ts` ,它通过调用来自 [`trpc-panel`](https://github.com/iway1/trpc-panel)`renderTrpcPanel` 函数渲染 tRPC Panel(`http://localhost:3000/panel`)。

同时它控制了只在 [`.env`](/developer/directory-structure/env) 中的 `NODE_ENV``development` 时才会开启。
53 changes: 53 additions & 0 deletions content/developer/2.directory-structure/11.server/4.trpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
icon: ph:folder-notch-open-duotone
title: trpc
---

# tRPC

[tRPC](https://trpc.io) 是一个用于构建类型安全的 API 的框架。它的全称是 **TypeScript Remote Procedure Call**,即 TypeScript 远程过程调用。

::alert{type="info"}
有关 tRPC 的语法,请参阅[官方文档](https://trpc.io/docs/server/routers)
::

```
.
└── trpc/
├── controllers/
│ └── ...
├── routers/
│ └── ...
├── scripts/
│ └── ...
├── serializer/
│ └── ...
├── utils/
│ └── ...
├── context.ts
└── trpc.ts
```

## `controllers`

controller(控制器)是业务逻辑真正的实现,处理输入数据、数据库交互在这里进行。

## `routers`

routers(路由)是 tRPC 用于路由请求的入口,它负责验证用户输入是否合法,验证用户身份,并将请求传递给 controller 。

## `serializer`

serializer(序列化函数)负责筛选过滤数据库返回的数据,使用户不应看到的数据被过滤掉,使用场景如去除用户密码等。

## `utils`

utils(工具函数)是存放了一些常用的工具函数,如 JWT 的生成和校验、密码的加密和解密等。还包含一个内部使用的 Result 类,在 controller 和 routers 之间使用。

## `context.ts`

context(上下文)是 tRPC 服务的上下文,每当有一个请求被处理时,tRPC 会创建一个上下文,并将其传递给 routers 。在创建上下文时,会解密请求 headers 中包含的 JWT,将其解析为用户对象,还会将全局的 controllers 实例附在上下文,以便在 routers 中使用。

## `trpc.ts`

这是 tRPC 服务的入口,所有请求都经由前文提到过的 API Handler 传递给这里进行处理。这里也包含验证用户身份的 middleware(中间件)逻辑。
8 changes: 8 additions & 0 deletions content/developer/2.directory-structure/11.server/5.env.ts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
icon: tabler:brand-typescript
title: env.ts
---

# env 环境变量

这个文件负责处理服务端的环境变量。它通过读取 [`.env`](../.env) 文件、`process.env`来加载环境变量,并通过 `zod` 验证环境变量是否符合规则。
3 changes: 3 additions & 0 deletions content/developer/2.directory-structure/11.server/_dir.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
title: server
icon: ph:folder-notch-open-duotone
navigation.redirect: /developer/directory-structure/server/api
Loading

0 comments on commit 2125f12

Please sign in to comment.