Skip to content

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

Tailwind css

安装依赖

shell
npm install -D tailwindcss postcss autoprefixer

初始化配置文件

shell
npx tailwindcss init -p

跳转配置文件内容

/** @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/index.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>

挂载使用

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 页面中使用

<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>