# 使用 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 的首页,编写我们数据库的首页

可以看到,这个页面设计如下:

  1. 最顶部是导航栏,由数据库 logo + 导航菜单组成
  2. 导航栏下方是背景图,背景图中央是一个大标题 + 小标题 + 搜索框,用来搜索数据库中的 biomarker
  3. 背景图下方是数据统计,这里可以使用 Element Plus 的 Statistic 统计组件
  4. 数据统计下方是数据库的简介
  5. 简介下方是 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; :将文本颜色设置为白色,以便在深色背景上清晰显示。
  • width: 100%; :将搜索框的宽度设为 100%,让它填满容器的宽度。

# 数据统计部分 ( .statistics )

  • padding: 50px 0; :为数据统计部分设置上下 50px 的内边距,使内容不至于过于紧凑。
  • background-color: #f5f5f5; :设置背景颜色为浅灰色,增强层次感。

# 简介部分 ( .introduction )

  • padding: 50px 0; :为简介部分设置上下 50px 的内边距,使内容有足够的空间展示。
  • text-align: center; :将标题和内容居中对齐。
  • max-width: 800px; :限制段落宽度,使其不至于过宽影响可读性。

# 编写思路

  1. 页面结构设计:首先设计页面的大致结构,将页面分为背景部分、数据统计部分和简介部分。

  2. 响应式设计:使用 Vue 3 的 ref 和 Element Plus 的表单组件实现双向绑定,使页面可以动态响应用户的输入,未来可以进一步添加搜索功能。

  3. 栅格布局:使用 Element Plus 的栅格系统( el-rowel-col )来实现响应式布局,这使得页面在不同屏幕上能保持良好的展示效果。

  4. 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>&copy; 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 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; :使主体区域内容在超出时可滚动。
  • text-align: center; :将底部内容居中。
  • background-color: #333;color: #fff; :设置底部的背景色和文本颜色。

# 编写思路

  1. 结构设计:首先确定页面的结构,包括头部、主体和底部。使用 Element Plus 的容器组件 ( el-container , el-header , el-main , el-footer ) 来创建清晰的布局。

  2. 动态菜单:通过 Vue 的响应式 API 和 Element Plus 的菜单组件,创建可交互的导航菜单。使用 router-link 实现路由跳转。

  3. 处理逻辑:定义 activeIndex 变量和 handleSelect 方法,实现菜单的状态管理。通过更新 activeIndex 反映用户的选择。

  4. 样式设计:使用 CSS Flexbox 布局,确保页面结构灵活适应不同屏幕大小。设置固定头部,增强页面的可用性。

  5. 可读性和易用性:注意代码的可读性和注释,使代码易于理解。为后期维护和扩展提供便利。

# 启动项目

现在,在项目根目录 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/ ,即可看到我们编写好的首页。