# 使用 Vue 3 + Element Plus 从头开始写一个数据库网站 - 03 - 编写第一个页面
由于 PrismJS 尚不支持 Vue 的语法高亮,因此 Vue 代码块均先使用 HTML 的高亮
2024-09-23
由于大家可能初学 Vue 3,为了先熟悉 Vue 3 和前端开发的流程,我们先来编写一些基本的前端页面。
在之前的教程中,我们已经了解了目录结构:
ADDB/ | |
├── .vscode/ # 存放 VS Code 配置文件 | |
├── node_modules/ # 存放项目依赖包 | |
├── public/ # 存放静态资源,如 HTML、favicon | |
├── src/ # 源代码目录 | |
│ ├── assets/ # 存放静态资源,如图片、字体 | |
│ ├── components/ # 存放可复用的 Vue 组件 | |
│ ├── router/ # 存放 Vue Router 配置 | |
│ ├── stores/ # 存放状态管理配置(Pinia) | |
│ ├── views/ # 存放不同页面的 Vue 组件 | |
│ ├── App.vue # 根组件 | |
│ ├── main.js # 应用入口文件 | |
├── .eslintrc.cjs # ESLint 配置文件 | |
├── .gitignore # Git 忽略文件列表 | |
├── .prettierrc.json # Prettier 配置文件 | |
├── index.html # 主 HTML 模板文件 | |
├── jsconfig.json # JavaScript 项目配置文件 | |
├── package.json # 项目配置文件,记录依赖及脚本 | |
├── pnpm-lock.yaml # pnpm 锁定文件,确保依赖版本一致 | |
├── README.md # 项目的自述文档 | |
├── vite.config.js # Vite 配置文件 |
在 assets/
目录下有一个 base.css
文件和一个 main.css
文件:
-
base.css
:这是一个基础样式文件,通常用于定义一些基础样式(重置样式)。这些基础样式可以帮助开发者消除不同浏览器之间的默认样式差异,确保应用在不同的浏览器中外观一致。 -
main.css
:这是一个全局样式文件,它在src/main.js
文件中被引入,作为全局样式,我们可以先把它清空。
# 认识 .vue
文件
在 Vue 3 中,一个 .vue
文件是 Vue 组件的单文件组件(SFC,Single File Component),这种文件结构能够将模板、逻辑和样式集中在一个文件中,便于维护和组织。典型的 .vue
文件由三个核心部分组成,分别是 <template>
、 <script>
和 <style>
。
# Vue 文件的基本结构
一个最简单的 Vue 组件文件结构如下:
<template> | |
<!-- 模板部分 --> | |
<div> | |
<h1></h1> | |
<button @click="handleClick">点击我</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
data() { | |
return { | |
message: 'Hello, Vue 3!', | |
}; | |
}, | |
methods: { | |
handleClick() { | |
this.message = '你点击了按钮!'; | |
}, | |
}, | |
}; | |
</script> | |
<style> | |
/* 样式部分 */ | |
div { | |
text-align: center; | |
} | |
</style> |
# <template>
部分
- 这是组件的模板部分,定义了组件的 HTML 结构和 Vue 指令。
- 它是一个 HTML 代码块,用于描述组件的界面。
- 在
<template>
中,Vue 的特性如数据绑定 ({{ }}
) 和事件绑定 (@事件
) 被使用。 - 所有你想要在浏览器中展示的内容都会写在这个部分。
# <script>
部分
在 Vue 组件中, <script>
部分用来编写 JavaScript 代码,它控制着组件的行为和状态。这个部分主要包含以下几个方面:
# 导出组件
export default { | |
// 组件的配置 | |
}; |
- 这里使用
export default
导出一个对象,这个对象定义了组件的各种配置,比如数据、方法等。 - 这使得 Vue 能够识别和使用这个组件。
# 数据状态(data)
data() { | |
return { | |
message: 'Hello, Vue 3!', | |
}; | |
}, |
data
是一个函数,返回一个对象。这个对象包含了组件的状态(即数据)。- 在上面的例子中,我们定义了一个
message
属性,初始值为'Hello, Vue 3!'
。这个值在模板中通过{{ message }}
被调用。
# 方法(methods)
methods: { | |
handleClick() { | |
this.message = '你点击了按钮!'; | |
}, | |
}, |
methods
是一个对象,包含组件可以调用的函数。- 在这个例子中,我们定义了一个
handleClick
方法。当按钮被点击时,这个方法会被调用,更新message
的值为'你点击了按钮!'
。 - 使用
this
关键字,可以访问到组件的状态和其他方法。
# 更建议使用的组合式 API
上面例子使用的是 选项式 API
(Options API)写法。
在 Vue 3 中可以选择使用标准的 组合式 API
(Composition API)写法:
<script setup> | |
import { ref } from 'vue'; | |
const message = ref('Hello, Vue 3!'); | |
function handleClick() { | |
message.value = '你点击了按钮!'; | |
} | |
</script> |
# <script setup>
- 这个语法是 Vue 3 的新特性,它让编写组件变得更加简洁。
- 不需要显式地导出一个对象,所有的内容都在
<script setup>
内部定义。
# 响应式数据(ref)
import { ref } from 'vue'; | |
const message = ref('Hello, Vue 3!'); |
- 这里我们从 Vue 导入了
ref
函数,ref
用来创建一个响应式的数据。 message
现在是一个响应式引用,初始值为'Hello, Vue 3!'
。在模板中可以通过{{ message }}
使用,但访问值时要用message.value
。
# 函数(methods)
function handleClick() { | |
message.value = '你点击了按钮!'; | |
} |
handleClick
函数在这里定义,与选项式 API 中的方法类似。- 当按钮被点击时,这个函数会被调用,更新
message
的值。
# <style>
部分
- 样式部分,用于定义组件的 CSS 样式。
- 可以使用常规的 CSS,也可以使用预处理器如 Sass 或 Less。
# scoped
样式
可以通过添加 scoped
属性使样式只作用于当前组件,避免样式冲突,这也是建议的写法。例如:
<style scoped> | |
h1 { | |
color: blue; | |
} | |
</style> |
# 可选部分
<script lang="ts">
:使用 TypeScript 代替 JavaScript。
# 首页设计
现在我们来模仿 Pubchem 的首页,编写我们数据库的首页
可以看到,这个页面设计如下:
- 最顶部是导航栏,由数据库 logo + 导航菜单组成
- 导航栏下方是背景图,背景图中央是一个大标题 + 小标题 + 搜索框,用来搜索数据库中的 biomarker
- 背景图下方是数据统计,这里可以使用 Element Plus 的 Statistic 统计组件
- 数据统计下方是数据库的简介
- 简介下方是 footer
# 首页编写
我们希望导航栏和页脚在每个页面上(不只是首页)都显示,所以我们可以将其写在根组件 App.vue
中,先跳过这两部分的编写
首先在 src/views
下创建一个 HomePage.vue
文件
# 模板部分 ( <template>
)
<template> | |
<div> | |
<!-- 背景图 --> | |
<div class="hero"> | |
<div class="hero-content"> | |
<h1>Alzheimer's Disease Biomarker Database</h1> | |
<p>Your source for Alzheimer's disease biomarker information</p> | |
<el-input placeholder="Search for biomarkers" v-model="searchQuery" @input="searchBiomarkers" :prefix-icon="Search" class="hero-search"> | |
</el-input> | |
</div> | |
</div> | |
<!-- 数据统计 --> | |
<div class="statistics"> | |
<el-row :gutter="20"> | |
<el-col :span="6"> | |
<el-statistic title="Biomarkers" value="1234"></el-statistic> | |
</el-col> | |
<el-col :span="6"> | |
<el-statistic title="Genes" value="567"></el-statistic> | |
</el-col> | |
<el-col :span="6"> | |
<el-statistic title="Proteins" value="890"></el-statistic> | |
</el-col> | |
<el-col :span="6"> | |
<el-statistic title="Publications" value="345"></el-statistic> | |
</el-col> | |
</el-row> | |
</div> | |
<!-- 简介 --> | |
<div class="introduction"> | |
<h2>About the Database</h2> | |
<p> | |
The Alzheimer's Disease Biomarker Database is a comprehensive resource for information on biomarkers | |
related to Alzheimer's disease. It provides detailed data on a wide range of biomarkers, including their | |
associated genes and proteins, as well as related publications. | |
</p> | |
</div> | |
</div> | |
</template> |
第一个 <div>
为容器元素,将页面的各个部分包裹起来。
# 背景图部分 ( hero
)
<div class="hero">
:这个容器包含了背景图和页面的标题部分。它通过background-image
样式添加背景图。<div class="hero-content">
:这个容器用来居中显示文本和搜索框。<h1>
和<p>
:标题和副标题。标题是页面的核心信息,副标题是辅助信息。<el-input>
:这是 Element Plus 的输入框组件,用户可以在此搜索生物标志物。通过v-model
双向绑定输入值到searchQuery
,并通过@input
事件触发searchBiomarkers
方法,动态响应用户的输入。:prefix-icon="Search"
:这里使用了冒号加属性的写法,进行动态绑定,Search
将作为一个组件传入。为输入框添加了一个搜索前缀图标。
# 数据统计部分 ( statistics
)
<el-row>
和<el-col>
:这是 Element Plus 的栅格布局系统,用来创建响应式布局。:gutter="20"
:定义列之间的间距。- 每个
<el-col>
包含一个<el-statistic>
,用来显示数据统计信息。el-statistic
:Element Plus 提供的统计组件,展示标题和数值,分别表示数据库中生物标志物、基因、蛋白质和相关文献的数量。- 这里我们将 value 随意设置一些值。
# 简介部分 ( introduction
)
<h2>
:简单介绍数据库的功能。<p>
:详细介绍数据库的用途和它所涵盖的数据类型。文本内容位于页面中央,并且限制了最大宽度以提高可读性。
# 逻辑部分 ( <script setup>
)
<script setup> | |
import { ref } from 'vue'; | |
import { Search } from '@element-plus/icons-vue' | |
const searchQuery = ref(''); | |
function searchBiomarkers() { | |
console.log('Searching for:', searchQuery.value); | |
// Add search functionality here | |
} | |
</script> |
-
import { ref } from 'vue';
:使用 Vue 3 的组合式 API,用来定义响应式变量。ref
是用来创建响应式数据的函数,允许组件中的数据随时随地响应变化。 -
import { Search } from '@element-plus/icons-vue'
:引入搜索图标。 -
const searchQuery = ref('');
:通过ref
定义了searchQuery
变量,并初始化为空字符串。这个变量与输入框中的值进行双向绑定(v-model="searchQuery"
),用户在输入框中的输入会自动更新searchQuery
的值。 -
function searchBiomarkers()
:这是一个简单的方法,每次用户输入时都会被调用(@input="searchBiomarkers"
)。目前只是将输入值打印在控制台中,未来将添加搜索功能。
# 样式部分 ( <style scoped>
)
<style scoped> | |
.hero { | |
background-image: url('background.jpg'); | |
background-size: cover; | |
background-position: center; | |
padding: 210px 0; | |
text-align: center; | |
color: #fff; | |
} | |
.hero-content { | |
max-width: 600px; | |
margin: 0 auto; | |
} | |
.hero h1 { | |
font-size: 30px; | |
margin: 0 0 20px; | |
} | |
.hero p { | |
font-size: 18px; | |
margin: 0 0 20px; | |
} | |
.hero-search { | |
width: 100%; | |
} | |
.statistics { | |
padding: 50px 0; | |
background-color: #f5f5f5; | |
} | |
.el-col { | |
text-align: center; | |
} | |
.introduction { | |
padding: 50px 0; | |
text-align: center; | |
} | |
.introduction h2 { | |
font-size: 32px; | |
margin-bottom: 20px; | |
} | |
.introduction p { | |
font-size: 18px; | |
max-width: 800px; | |
margin: 0 auto; | |
} | |
</style> |
# 背景图部分 ( .hero
)
background-image: url('background.jpg');
:设置了背景图的路径,background.jpg
文件应位于public
目录下。background-size: cover;
:确保背景图覆盖整个容器。background-position: center;
:将背景图居中对齐。padding: 210px 0;
:为背景图部分上下添加内边距,使内容在页面中垂直居中。text-align: center;
:文本水平居中显示。color: #fff;
:将文本颜色设置为白色,以便在深色背景上清晰显示。
# 搜索框部分 ( .hero-search
)
width: 100%;
:将搜索框的宽度设为 100%,让它填满容器的宽度。
# 数据统计部分 ( .statistics
)
padding: 50px 0;
:为数据统计部分设置上下 50px 的内边距,使内容不至于过于紧凑。background-color: #f5f5f5;
:设置背景颜色为浅灰色,增强层次感。
# 简介部分 ( .introduction
)
padding: 50px 0;
:为简介部分设置上下 50px 的内边距,使内容有足够的空间展示。text-align: center;
:将标题和内容居中对齐。max-width: 800px;
:限制段落宽度,使其不至于过宽影响可读性。
# 编写思路
-
页面结构设计:首先设计页面的大致结构,将页面分为背景部分、数据统计部分和简介部分。
-
响应式设计:使用 Vue 3 的
ref
和 Element Plus 的表单组件实现双向绑定,使页面可以动态响应用户的输入,未来可以进一步添加搜索功能。 -
栅格布局:使用 Element Plus 的栅格系统(
el-row
和el-col
)来实现响应式布局,这使得页面在不同屏幕上能保持良好的展示效果。 -
CSS 样式:样式部分通过
scoped
限制了样式的作用范围,避免污染其他组件。通过简洁的样式设置,确保页面内容能清晰地展示在用户面前,并提供了良好的用户体验。
# 配置路由
打开 src/router/index.js
文件,配置路由以使 HomePage.vue
成为默认显示的页面:
import { createRouter, createWebHistory } from 'vue-router' | |
import HomePage from '../views/HomePage.vue' | |
const router = createRouter({ | |
history: createWebHistory(import.meta.env.BASE_URL), | |
routes: [ | |
{ | |
path: '/', | |
name: 'HomePage', | |
component: HomePage | |
}, | |
] | |
}) | |
export default router |
# 修改根组件 App.vue
App.vue
文件是整个 Vue 应用的根组件,所有的页面和组件都将在 App.vue
里显示。默认的 App.vue
文件可能包含一些预设的内容,比如头部、侧边栏或其他全局组件,这些内容会在每个页面上显示。
当我们创建项目时,它已经包含了默认内容,但根据我们的需要,现在来重写它。
# 模板部分 ( <template>
)
<template> | |
<!-- 定义一个容器,包含头部、主体和底部 --> | |
<el-container class="app-container"> | |
<!-- 头部 --> | |
<el-header class="header"> | |
<!-- 定义一个菜单,默认激活的菜单项为 activeIndex --> | |
<el-menu :default-active="activeIndex" class="el-menu" mode="horizontal" @select="handleSelect"> | |
<!-- 菜单中的 logo --> | |
<img src="/logo.png" alt="Logo" style="height: 50px;" /> | |
<!-- 菜单项 1 --> | |
<el-menu-item index="1"> | |
<el-icon> | |
<HomeFilled /> | |
</el-icon> | |
<router-link to="/">Home</router-link> | |
</el-menu-item> | |
<!-- 菜单项 2 --> | |
<el-menu-item index="2"> | |
<el-icon> | |
<Collection /> | |
</el-icon> | |
<router-link to="/biomarkers">Biomarkers</router-link> | |
</el-menu-item> | |
<!-- 菜单项 3 --> | |
<el-menu-item index="3"> | |
<el-icon> | |
<InfoFilled /> | |
</el-icon> | |
<router-link to="/about">About</router-link> | |
</el-menu-item> | |
<!-- 菜单项 4 --> | |
<el-menu-item index="4"> | |
<el-icon> | |
<Flag /> | |
</el-icon> | |
<router-link to="/test">Test</router-link> | |
</el-menu-item> | |
</el-menu> | |
</el-header> | |
<!-- 主体 --> | |
<el-main class="main-content"> | |
<!-- 路由视图 --> | |
<router-view></router-view> | |
</el-main> | |
<!-- 底部 --> | |
<el-footer class="footer"> | |
<p>© 2024 Alzheimer's Disease Biomarker Database. All rights reserved.</p> | |
</el-footer> | |
</el-container> | |
</template> |
<el-container>
:这是 Element Plus 提供的容器组件,用于布局页面的主要结构,包括头部、主体和底部。
# 头部部分 ( <el-header>
)
-
<el-header class="header">
:头部组件,通常用来放置导航菜单和 logo。 -
<el-menu>
:Element Plus 的菜单组件,用于显示导航项。:default-active="activeIndex"
:动态设置当前激活的菜单项,使用响应式变量activeIndex
。@select="handleSelect"
:注册菜单项选择事件,当用户点击菜单项时调用handleSelect
方法。
-
菜单项:
- 每个
<el-menu-item>
表示一个菜单项,使用<el-icon>
组件展示图标。 router-link
:用于实现路由导航,点击菜单项会跳转到对应的路由。
- 每个
# 主体部分 ( <el-main>
)
<el-main class="main-content">
:主体区域,放置路由视图。<router-view>
:这是 Vue Router 的一个内置组件,它会根据当前路由渲染匹配的组件。由于我们刚刚在src/router/index.js
中配置了路由,当访问/
时,HomePage
将通过router-view
被渲染。
# 底部部分 ( <el-footer>
)
<el-footer class="footer">
:底部组件,用于显示版权信息等内容。
# 逻辑部分 ( <script setup>
)
<script setup> | |
import { ref } from 'vue'; | |
// 导入图标组件 | |
import { HomeFilled, Collection, InfoFilled, Flag } from '@element-plus/icons-vue'; | |
// 定义一个响应式变量,表示当前激活的菜单项 | |
const activeIndex = ref('1'); | |
// 处理菜单项选择事件 | |
function handleSelect(key) { | |
// 更新当前激活的菜单项 | |
activeIndex.value = key; | |
} | |
</script> |
当用户选择不同的菜单项时, handleSelect
会被调用并更新 activeIndex
的值,以反映当前激活的菜单项。
# 样式部分 ( <style scoped>
)
<style scoped> | |
.app-container { | |
display: flex; | |
flex-direction: column; | |
} | |
.header { | |
position: fixed; | |
top: 0; | |
left: 0; | |
right: 0; | |
z-index: 1000; | |
background-color: white; | |
color: #fff; | |
display: flex; | |
align-items: center; | |
} | |
.el-menu { | |
flex-grow: 1; | |
background-color: transparent; | |
} | |
.el-main { | |
flex: 1; | |
padding-top: 50px; | |
/* Adjust based on header height */ | |
overflow-y: auto; | |
} | |
.footer { | |
text-align: center; | |
background-color: #333; | |
color: #fff; | |
} | |
a { | |
color: inherit; | |
text-decoration: none; | |
} | |
</style> |
# 容器样式 ( .app-container
)
display: flex;
和flex-direction: column;
:将容器设置为灵活盒子布局,垂直排列子元素。
# 头部样式 ( .header
)
position: fixed;
:使头部固定在页面顶部,不随内容滚动。top: 0; left: 0; right: 0;
:设置头部的定位,确保其跨越页面的整个顶部。z-index: 1000;
:确保头部在所有内容之上。background-color: white;
和color: #fff;
:设置背景色和文本颜色display: flex;
和align-items: center;
:使菜单项在头部中水平居中。
# 主体样式 ( .el-main
)
flex: 1;
:允许主体区域占据剩余的空间。padding-top: 50px;
:为主体区域添加上内边距,以避免内容被固定头部遮挡。overflow-y: auto;
:使主体区域内容在超出时可滚动。
# 底部样式 ( .footer
)
text-align: center;
:将底部内容居中。background-color: #333;
和color: #fff;
:设置底部的背景色和文本颜色。
# 编写思路
-
结构设计:首先确定页面的结构,包括头部、主体和底部。使用 Element Plus 的容器组件 (
el-container
,el-header
,el-main
,el-footer
) 来创建清晰的布局。 -
动态菜单:通过 Vue 的响应式 API 和 Element Plus 的菜单组件,创建可交互的导航菜单。使用
router-link
实现路由跳转。 -
处理逻辑:定义
activeIndex
变量和handleSelect
方法,实现菜单的状态管理。通过更新activeIndex
反映用户的选择。 -
样式设计:使用 CSS Flexbox 布局,确保页面结构灵活适应不同屏幕大小。设置固定头部,增强页面的可用性。
-
可读性和易用性:注意代码的可读性和注释,使代码易于理解。为后期维护和扩展提供便利。
# 启动项目
现在,在项目根目录 ADDB
下打开终端,执行命令
pnpm run dev |
可以看到以下输出:
> pnpm run dev | |
> addb@0.0.0 dev D:\Study\Project\GDG\ADDB\ADDB | |
> vite | |
VITE v5.4.1 ready in 3367 ms | |
➜ Local: http://localhost:5173/ | |
➜ Network: use --host to expose | |
➜ press h + enter to show help |
打开浏览器并访问 http://localhost:5173/
,即可看到我们编写好的首页。