Skip to content

VitePress 系列教程:自定义页面 #6

Tailwind css

安装依赖

shell
npm install -D tailwindcss postcss autoprefixer

初始化配置文件

shell
npx tailwindcss init -p

跳转配置文件内容

js
/** @type {import('tailwindcss').Config} */
module.exports = {
    content: [
        "./.vitepress/theme/components/*.vue",
        "./**/*.md",
    ],
    theme: {
        extend: {},
    },
    plugins: [],
}

新增 custom.css

@tailwind base;
@tailwind components;
@tailwind utilities;

.vitepress/theme/comfig.js 中导入使用

import './css/custom.css'

集成 element-plus

安装依赖

shell
npm install element-plus
shell
npm install @element-plus/icons-vue

.vitepress/theme/nav.js 中导入使用

js
import "element-plus/dist/index.css";
import ElementPlus from "element-plus";

export default {
    extends: Theme,
    Layout: () => {
        return h(Theme.Layout, null, {});
    },
    enhanceApp({app, router, siteData}) {
        // ...
        app.use(ElementPlus);
    },
};

首页显示登录头像

theme/components/Avatar.vue

vue

<script setup>
  import {onMounted, ref} from "vue";
  import {ElMessage} from "element-plus";

  const is_login = ref(false);
  onMounted(() => {
    is_login.value = !!localStorage.getItem("jwt");
    if (!is_login.value) {
      ElMessage({
        message: "请先登录",
        type: "warning",
      });
    } else {
      url.value =
          "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png";
    }
  });
  const url = ref(
      "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
  );

  const handleCommand = (command) => {
    ElMessage(`click on item ${command}`);
    switch (command) {
      case "login":
        location.href = "/login";
        break;
      case "logout":
        localStorage.removeItem("jwt");
        location.reload();
        break;
    }
  };
</script>

<template>
  <ClientOnly>
    <Teleport to=".VPSocialLink:last-child">
      <div class="VPAvatar avatar">
        <el-dropdown @command="handleCommand">
          <el-avatar :size="20" :src="url"/>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item command="login">登录</el-dropdown-item>
              <el-dropdown-item command="logout">退出</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
    </Teleport>
  </ClientOnly>
</template>

<style scoped>
  .VPAvatar {
    display: flex;
    align-items: center;
  }

  .avatar::before {
    margin-right: 8px;
    margin-left: 8px;
    width: 1px;
    height: 24px;
    background-color: var(--vp-c-divider);
    content: "";
  }

  .el-dropdown-link {
    cursor: pointer;
    color: var(--el-color-primary);
    display: flex;
    align-items: center;
  }

  :deep(:focus-visible) {
    outline: none;
  }
</style>

挂载使用

js
import Avatar from './components/Avatar.vue'

export default {
    extends: Theme,
    Layout: () => {
        return h(Theme.Layout, null, {
            'nav-bar-content-after': () => h(Avatar),
        })
    },
    enhanceApp({app, router, siteData}) {
        // ...
    }
}

自定义登录页

在 login.md 页面中使用

vue

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>正心全栈编程 - 文档站</div>
        <div>此站点是正心的全栈编程文档站点</div>
      </div>
    </el-col>
    <el-col :lg="8" :md="12" class="right">
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <el-form class="w-[250px]" ref="formRef" v-bind:model="form" :rules="rules">
        <el-form-item prop="mobile">
          <el-input placeholder="请输入手机号" v-model="form.mobile">
            <template #prefix>
              <el-icon>
                <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728="">
                  <path fill="currentColor"
                        d="M512 512a192 192 0 1 0 0-384 192 192 0 0 0 0 384zm0 64a256 256 0 1 1 0-512 256 256 0 0 1 0 512zm320 320v-96a96 96 0 0 0-96-96H288a96 96 0 0 0-96 96v96a32 32 0 1 1-64 0v-96a160 160 0 0 1 160-160h448a160 160 0 0 1 160 160v96a32 32 0 1 1-64 0z"></path>
                </svg>
              </el-icon>
            </template>
          </el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input type="password" show-password placeholder="请输入密码" v-model="form.password">
            <template #prefix>
              <el-icon>
                <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728="">
                  <path fill="currentColor"
                        d="M224 448a32 32 0 0 0-32 32v384a32 32 0 0 0 32 32h576a32 32 0 0 0 32-32V480a32 32 0 0 0-32-32H224zm0-64h576a96 96 0 0 1 96 96v384a96 96 0 0 1-96 96H224a96 96 0 0 1-96-96V480a96 96 0 0 1 96-96z"></path>
                  <path fill="currentColor"
                        d="M512 544a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V576a32 32 0 0 1 32-32zm192-160v-64a192 192 0 1 0-384 0v64h384zM512 64a256 256 0 0 1 256 256v128H256V320A256 256 0 0 1 512 64z"></path>
                </svg>
              </el-icon>
            </template>
          </el-input>
        </el-form-item>
        <el-form-item label="记住账号">
          <el-switch v-model="remember"/>
        </el-form-item>
        <el-form-item>
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit">登 录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
  import {reactive, ref, watch, onMounted} from "vue"
  import {ElNotification} from "element-plus";


  // 定义登录表单的数据
  const form = reactive({
    mobile: "",
    password: ""
  })

  // 表单的引用对象
  const formRef = ref(null)

  // 校验规则
  const rules = {
    mobile: [
      {
        required: true,
        message: "用户名不能为空",
        trigger: "blur"
      }
    ],
    password: [
      {
        required: true,
        message: "密码不能为空",
        trigger: "blur"
      }
    ]
  }
  // 点击登录的事件
  // localStorage.setItem('jwt', 'Bearer xasdjssalsdfsadfasdf')
  // location.href = '/'
  // 点击登录的事件
  const onSubmit = () => {
    // 实现校验
    formRef.value.validate((valid) => {
      if (!valid) {
        return false
      }
      fetch(`/login`,
          {
            method: 'post',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(form)
          }
      ).then(response => response.json()).then(ret => {
        console.log(ret)
        if (ret.status === 'success') {
          ElNotification({
            title: '成功',
            message: '登录成功,2s 之后进行跳转',
            type: 'success',
          })
          setTimeout(function () {
            localStorage.setItem('jwt', 'Bearer xasdjssalsdfsadfasdf')
            location.href = ret?.next ? ret.next : '/'
          }, 2000)
        } else {
          ElNotification({
            title: '失败',
            message: ret.message,
            type: 'error',
          })
        }
      }).catch(error => {
        ElNotification({
          title: '错误',
          message: '网络有问题或者是服务器出问题,请联系管理员',
          type: 'error',
        })
      })
    })
  }

  const remember = ref(true)
  watch(form, (old) => {
    if (remember.value) {
      localStorage.setItem('info', JSON.stringify(form))
    } else {
      localStorage.removeItem('info')
    }
  })

  onMounted(() => {
    Object.assign(form, JSON.parse(localStorage.getItem('info') || '{}'))
  })
</script>

<style scoped>
  .login-container {
    min-height: 100vh;
    background-color: #667eea;
  }

  .login-container .left,
  .login-container .right {
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .login-container .right {
    background-color: #ffffff;
    flex-direction: column;
  }

  .left > div > div:first-child {
    color: #ffffff;
    font-size: 3rem;
    line-height: 1;
    font-weight: 700;

  }

  .left > div > div:last-child {
    color: #E5E7EB;
    font-size: 0.875rem;
  }

  .right .title {
    font-size: 1.875rem;
    line-height: 2.25rem;
    font-weight: 700;
  }

  .right > div {
    display: flex;
    margin-top: 1.25rem;
    margin-bottom: 1.25rem;
    color: #D1D5DB;
    justify-content: center;
    align-items: center;
  }

  .right .line {
    width: 4rem;
    height: 1px;
    background-color: #edf2f7;
  }
</style>