创建项目
问题:main.ts文件爆红
在vite-env.d.ts文件里面加入以下代码即可解决,没有就创建
1 2 3 4 5 6 7
| declare module "*.vue" { import type { DefineComponent } from "vue";
const vueComponent: DefineComponent<{}, {}, any>;
export default vueComponent; }
|
配置别名
1 2 3 4 5 6 7 8
| import path from "path"; resolve: { // 配置路径别名 alias: { '@': path.resolve(__dirname, './src'), }, },
|
scss
1 2
| pnpm install -D sass pnpm install sass sass-loader -d
|
在vite.config.ts加上下面
1 2 3 4 5 6 7 8 9 10
| css: { preprocessorOptions: { scss: { additionalData: '@import "./src/assets/style/mixin.scss";' } } }
|
测试
router
1
| npm install vue-router@4
|
创建router文件夹,然后创建index.ts文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { createRouter, createWebHistory } from 'vue-router'
const routerHistory = createWebHistory() const router = createRouter({ history: routerHistory, routes: [ { path: '/', name:'Login', component: () => import('../view/Login.vue') }, { path: '/index', name:'Index', component: ()=> import('@/view/Index.vue') }, ] }) export default router
|
在main.ts引入
xxxxxxxxxx package cn.zou.webjwt.controller;import cn.hutool.crypto.digest.BCrypt;import cn.zou.webjwt.config.JwtUtil;import cn.zou.webjwt.entity.UserEntity;import cn.zou.webjwt.mapper.UserMapper;import cn.zou.webjwt.result.CommonResult;import cn.zou.webjwt.result.CommonResultEnum;import cn.zou.webjwt.valid.AddGroup;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import lombok.RequiredArgsConstructor;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.*;import java.util.List;@RestController@RequiredArgsConstructor@RequestMapping(value=”user”,name=”用户”)public class UserController { private final UserMapper userMapper; private final JwtUtil jwtUtil; @PostMapping(value = “register” ,name = “注册”) public CommonResult register(@Validated(AddGroup.class) @RequestBody UserEntity userEntity){ //1.查询用户名是否重复 LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(UserEntity::getUsername,userEntity.getUsername()); UserEntity user = userMapper.selectOne(wrapper); if(user != null){ return CommonResultEnum.USER_EXIT_ERROR.getResult(); } //2.密码加密 userEntity.setPassword(BCrypt.hashpw(userEntity.getPassword())); //3.注册用户 userMapper.insert(userEntity); return CommonResultEnum.REGISTER_SUCCESS.getResult(); } @PostMapping(value = “login” ,name = “登录”) public CommonResult login(@Validated(AddGroup.class) @RequestBody UserEntity userEntity){ //1.查询用户是否存在 LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(UserEntity::getUsername, userEntity.getUsername()); UserEntity user = userMapper.selectOne(wrapper); if(user == null){ return CommonResultEnum.USER_NOT_EXIST_ERROR.getResult(); } //2.查询密码是否正确 if(!BCrypt.checkpw(userEntity.getPassword(),user.getPassword())){ return CommonResultEnum.PASSWORD_ERROR.getResult(); } //3.生成token String jwt = jwtUtil.createJwt(user); String bearerToken = “Bearer “+jwt; return CommonResultEnum.LOGIN_SUCCESS.setData(bearerToken); } @PostMapping(value = “get” ,name = “用户列表”) public CommonResult<List> get(){ LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .select(UserEntity::getId, UserEntity::getUsername); List users = userMapper.selectList(wrapper); return CommonResultEnum.SUCCESS.setData(users); }}java
element plus
1
| npm install element-plus --save
|
直接引入
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus) app.mount('#app')
|
按需导入推荐
下载插件
1
| npm install -D unplugin-vue-components unplugin-auto-import
|
把下面代码复制到vite.config.ts里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { defineConfig } from 'vite' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({ plugins: [ AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], })
|
element plus 图标
1
| npm install @element-plus/icons-vue
|
1 2 3 4 5
| import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) }
|
中文语言
1 2 3 4 5
| import zhLocale from 'element-plus/es/locale/lang/zh-cn' app.use(ElementPlus, { locale: zhLocale }) app.use(ElementPlus)
|
axios
main.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @import './base.css';
body, html { margin: 0; padding: 0; width: 100%; height: 100%; }
#app { width: 100%; height: 100%; }
a { text-decoration: none; }
ul li { list-style: none; }
|
tailwind
安装
1 2 3 4
| pnpm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
|
生成配置文件后会多这两个文件
在tailwind.config.js指定作用目录,并增加对 vue 文件的识别
1 2 3 4 5 6 7 8 9 10 11
| export default { content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx,vue}", ], theme: { extend: {}, }, plugins: [], }
|
在项目的公共 css 文件(src/style.css)中添加以下内容,用 @tailwind 指令添加 Tailwind 功能模块。
1 2 3
| @tailwind base; @tailwind components; @tailwind utilities;
|
测试
登录页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
| <script setup lang='ts'> import { ref, reactive, onBeforeMount, onMounted } from 'vue' import { useRouter } from 'vue-router' import { registerApi, loginApi } from '@/api/user' import { useMessage } from 'naive-ui' const msg = useMessage() const router = useRouter()
onMounted(() => {
})
const isShow = ref(true)
const loginForm = ref( { username: "admin", password: "123123", code: '' } )
const registerForm = ref( { name: "", password: "", confirmPassword: "" } )
const loginBtn = async () => { const res: any = await loginApi(loginForm.value) console.log(res); if (res.code == 200) { localStorage.setItem('token', res.data) msg.success(res.msg) router.push('/home') } else { msg.error(res.msg) } }
const register = async () => { const res: any = await registerApi(registerForm.value) console.log(res); if (res.code == 200) { msg.success(res.msg) } else { msg.error(res.msg) } } </script> <template> <div class='main'> <el-card class="card" v-if="isShow"> <h2 style="text-align: center;">后台登录</h2> <el-form :model="loginForm" label-position="top"> <el-form-item label="请输入用户名"> <el-input v-model="loginForm.username" placeholder="请输入用户名" /> </el-form-item> <el-form-item label="请输入用户密码"> <el-input v-model="loginForm.password" placeholder="请输入用户密码" /> </el-form-item> <el-form-item class="item"> <el-input class="input" v-model="loginForm.code" placeholder="请输入验证码" /> <div class="vercode"> <img src="http://localhost:9090/capture" alt=""> </div> </el-form-item> <div class="goRegister" @click="isShow = !isShow">去注册</div> <el-form-item> <el-button type="primary" class="loginBtn" @click="loginBtn">登录</el-button> </el-form-item> </el-form> </el-card> <el-card class="card" v-else> <h2 style="text-align: center;">后台注册</h2> <el-form :model="registerForm" label-position="top"> <el-form-item label="请输入用户名"> <el-input v-model="registerForm.name" /> </el-form-item> <el-form-item label="请输入用户密码"> <el-input v-model="registerForm.password" /> </el-form-item> <el-form-item label="请再次输入用户密码"> <el-input v-model="registerForm.confirmPassword" /> </el-form-item> <div class="goLogin" @click="isShow = !isShow">去登录</div> <el-form-item> <el-button type="primary" class="registerBtn" @click="register">注册</el-button> </el-form-item> </el-form> </el-card> </div> </template>
<style scoped lang='scss'> .main { width: 100%; height: 100%; background-image: url('../assets/img/bg.jpg'); background-size: cover; background-position: center; display: flex; align-items: center;
.card { width: 600px; margin: 0 auto; opacity: 0.6; border-radius: 15px // margin-top: 100px; ;
.goRegister, .goLogin { text-align: right; margin: 10px 0; }
.loginBtn, .registerBtn { width: 100%; }
.item { display: flex; align-items: center; }
.input { flex: 1; margin-right: 10px; }
.vercode { width: 180px; } } } </style>
|

首页架子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| <script setup lang="ts"> import { ref, reactive, onBeforeMount, onMounted, } from 'vue' import { RouterView, useRouter } from 'vue-router'
const navData = reactive([ { index: '/home/userinfo', name: '首页', }, { index: '/home/userlist', name: '用户管理', }, { index: '/home/category', name: '文章分类', }, { index: '/home/article', name: '文章列表', }, { index: '/home/log', name: '日志记录', }, ] )
const circleUrl = ref('https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',)
const router = useRouter(); const logout = () => { localStorage.removeItem('token') router.push('/login')
} </script>
<template> <div class="main"> <el-container class="layout-container"> <el-header> <div class="title">后台管理系统</div> <div class="box"> <div class="avatar"> <el-avatar :size="50" :src="circleUrl" /> </div> <div class="logout" @click="logout"><el-button type="primary">退出</el-button></div> </div> </el-header> <el-container> <el-aside width="200px"> <el-menu default-active="1" class="el-menu-vertical-demo" router active-text-color="#ffd04b" background-color="#545c64" text-color="#fff"> <el-menu-item :index="item.index" v-for="(item, index) in navData" :key="index"> <el-icon><icon-menu /></el-icon> <span>{{ item.name }}</span> </el-menu-item> </el-menu> </el-aside> <el-main> <RouterView /> </el-main> </el-container> </el-container>
</div> </template>
<style scoped lang="scss"> .main { width: 100%; height: 100%;
.el-header { color: #fff; background-color: #545c64; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #fff;
.box { display: flex; align-items: center;
.avatar { margin: 0 20px;
} }
}
.el-aside { background-color: #545c64; height: 100%; }
.el-menu { border-right: none; }
.layout-container { height: 100%; } } </style>
|
路由
请求拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import axios from 'axios'
const service = axios.create({ timeout: 2000, baseURL: "/api" })
service.interceptors.request.use( config => {
return config }, error => { console.log(error); return Promise.reject(error) } ) service.interceptors.response.use( config => { return config.data }, error => { return Promise.reject(error) } )
export default service;
|
api封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', redirect: '/home', },
{ path: '/login', name: 'login', component: () => import('../views/Login.vue') }, { path: '/home', name: 'home', component: () => import('../views/Home.vue'), children: [ { path: 'userinfo', name: 'userinfo', component: () => import('../views/UserInfo.vue') }, { path: 'userlist', name: 'userlist', component: () => import('../views/UserList.vue') }, { path: 'category', name: 'category', component: () => import('../views/Category.vue') }, { path: 'article', name: 'article', component: () => import('../views/Article.vue') }, { path: 'add', name: 'add', component: () => import('../views/Add.vue') }, { path: 'log', name: 'log', component: () => import('../views/Log.vue') }, ] },
] })
export default router
|
列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
| <script setup lang='ts'> import { ref, reactive, onBeforeMount, onMounted } from 'vue' import { addApi, deleteApi, updateApi, getPageApi } from '@/api/category' import { useMessage } from 'naive-ui' const msg = useMessage()
onMounted(() => { getPage(params) console.log(categoryData);
})
const params = reactive({ name: '', pageIndex: 1, pageSize: 3, })
const total = ref(null)
let categoryData = ref([])
const addDialog = ref(false)
const updateDialog = ref(false)
const addForm = reactive({ name: '', })
let updateForm = reactive({ name: '' })
const handleSizeChange = (val: number) => { console.log(`${val} items per page`) params.pageSize = val getPage(params) }
const handleCurrentChange = (val: number) => { console.log(`current page: ${val}`) params.pageIndex = val getPage(params) }
const queryBtn = () => { getPage(params) }
const addBtn = () => { addDialog.value = true }
const updateClick = (index, val) => { updateDialog.value = true; updateForm = val console.log(val);
}
const deleteClick = (val, index) => { console.log(val);
}
const getPage = async (val) => { const res: any = await getPageApi(val) if (res.code == 200) { console.log(res.data.records); categoryData.value = res.data.records total.value = res.data.total } else { msg.error(res.msg) } }
const updateSubmit = async () => { const res: any = await updateApi(updateForm) console.log(res); if (res.data == 200) { msg.success(res.msg) } else { msg.error(res.msg) } updateDialog.value = false getPage(params)
}
const deleteSubmit = async (index, row) => { const res: any = await deleteApi(row) console.log(res); if (res.data == 200) { msg.success(res.msg) } else { msg.error(res.msg) } getPage(params)
}
const addSubmit = async () => { const res: any = await addApi(addForm); console.log(res); if (res.data == 200) { msg.success(res.msg) } else { msg.error(res.msg) } addDialog.value = false }
</script> <template> <div class='main'> <el-card class="box-card"> <template #header> <div class="card-header"> <span>文章分类</span>
</div> </template>
<!-- 头部区域 --> <div class="demo-input-suffix"> <el-form label-width="120px"> <el-row :gutter="20"> <el-input v-model="params.name" class="w-50 m-2" placeholder="模糊查询"> <template #suffix> <el-icon class="el-input__icon"> <search /> </el-icon> </template> </el-input> <el-button class="queryBtn" @click="queryBtn" type="info" plain>go</el-button> <el-button class="queryBtn" type="info" @click="addBtn" plain>insert</el-button> </el-row> </el-form> </div> <!-- 列表区域 --> <el-table :data="categoryData" stripe style="width: 100%"> <el-table-column type="selection" width="55" /> <el-table-column prop="name" label="图书分类" width="300" /> <el-table-column label="操作"> <template #default="scope"> <el-button size="small" @click="updateClick(scope.$index, scope.row)">修改</el-button> <el-popconfirm title="是否删除该用户?" @confirm="deleteSubmit(scope.$index, scope.row)"> <template #reference> <el-button size="small" type="danger" @click="deleteClick">删除</el-button> </template> </el-popconfirm> </template> </el-table-column> </el-table> <!-- 底部区域 --> <div class="pagination"> <el-pagination v-model:current-page="params.pageIndex" v-model:page-size="params.pageSize" :page-sizes="[2, 4, 6, 8]" :small="false" :disabled="false" :background="false" layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </el-card> <!-- 新增弹框 --> <el-dialog v-model="addDialog" title="新增分类" width="30%"> <el-form :model="addForm"> <el-form-item> <el-input v-model="addForm.name" placeholder="请输入分类名称" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="addDialog = false">取消</el-button> <el-button type="primary" @click="addSubmit"> 确定 </el-button> </span> </template> </el-dialog> <!-- 修改弹框 --> <el-dialog v-model="updateDialog" title="新增分类" width="30%"> <el-form :model="updateForm"> <el-form-item> <el-input v-model="updateForm.name" placeholder="请输入分类名称" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="updateDialog = false">取消</el-button> <el-button type="primary" @click="updateSubmit"> 确定 </el-button> </span> </template> </el-dialog> </div> </template>
<style scoped lang='scss'> .main { .queryBtn { margin-left: 20px; }
.pagination { margin-top: 20px; display: flex; justify-content: center; } } </style>
|
其他
路由跳转顶部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { createRouter, createWebHistory } from 'vue-router';
const routes = [ // 路由配置 ];
const router = createRouter({ history: createWebHistory(), routes, scrollBehavior(to, from, savedPosition) { return { top: 0 }; }, });
export default router;
|
模块的声明
在vite-env.d.ts下面添加下面的代码
1
| declare module "@/hooks/bgColor.js"
|
切换黑白模式
在src下面建hooks文件夹,然后建bgColor.ts文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { ref } from 'vue'; const useThem = () => { const isDarkThem = ref(false); const changeThem = () => { if (isDarkThem.value) { console.log('isDarkThem');
document.getElementsByTagName('body')[0].style.setProperty('--font-color', '#fff'); document.getElementsByTagName('body')[0].style.setProperty('--bg-color', '#000'); isDarkThem.value = false } else { console.log(11);
document.getElementsByTagName('body')[0].style.setProperty('--font-color', '#000'); document.getElementsByTagName('body')[0].style.setProperty('--bg-color', '#fff'); isDarkThem.value = true } } return { isDarkThem, changeThem } }
export { useThem };
|
然后在用到的地方引入
1 2 3 4 5 6 7 8 9
| import {useThem} from '@/hooks/bgColor.ts' const { isDarkThem, changeThem } = useThem();
<div class="switch"> <el-switch v-model="value1" class="ml-2" inline-prompt :active-action-icon="Moon" :inactive-action-icon="Sunny" style="--el-switch-on-color: #8109d1; --el-switch-off-color: #8109d1" active-text="黑" @change="changeThem" inactive-text="白" /> </div>
|
网络请求
接口封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| import axios from 'axios'
const service = axios.create({ timeout: 2000, baseURL: "http://127.0.0.1:7001/api" })
service.interceptors.request.use( config => { const token = window.localStorage.getItem("token") if (token) { config.headers['Authorization'] = `Bearer ${token}` }
return config }, error => { console.log(error); return Promise.reject(error) } ) service.interceptors.response.use( config => { return config.data }, error => { return Promise.reject(error) } )
export default service; import axios from '../util/http'
export function registerApi(data: any) { return axios({ url: '/user/register', method: 'post', data }) } export function loginApi(data: any) { return axios({ url: '/user/login', method: 'post', data }) }
export function getUserInfoApi() { return axios({ url: '/user/getinfo', method: 'get', }) }
export function updateUserApi(data: any) { return axios({ url: '/user/update', method: 'put', data }) }
export function deleteApi(data: any) { return axios({ url: '/user/delete', method: 'delete', data }) }
export function userGetpage(params: any) { return axios({ url: '/user/getpage', method: 'post', params }) }
|
消息封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import { ElNotification, ElMessage, ElMessageBox } from 'element-plus'
export const info = (msgInfo: string) => { ElMessage({ type: 'info', showClose: true, dangerouslyUseHTMLString: true, message: msgInfo, }) }
export const success = (msgInfo: string) => { ElMessage({ type: 'success', showClose: true, dangerouslyUseHTMLString: true, message: msgInfo, }) }
export const error = (msgInfo: string) => { ElMessage({ type: 'error', showClose: true, dangerouslyUseHTMLString: true, message: msgInfo, }) }
export const warn = (msgInfo: string) => { ElMessage({ type: 'warning', showClose: true, dangerouslyUseHTMLString: true, message: msgInfo, }) }
export const alertBox = (msg: string, btnName: string, type: any, title?: string,) => { let confirmName = btnName == '确定' ? '确定' : '是' return ElMessageBox.alert(msg, title || '提示', { type: type || 'warning', confirmButtonText: confirmName, buttonSize: "default", dangerouslyUseHTMLString: true });
}
export const confirmBox = (msg: string, btnName: string, type: any, title?: string,) => { let confirmName = btnName == '确定' ? '确定' : '是' let cancelsName = btnName == '确定' ? '取消' : '否' return ElMessageBox.confirm(msg, title || '提示', { type: type || 'warning', confirmButtonText: confirmName, cancelButtonText: cancelsName, buttonSize: "default", closeOnClickModal: false, closeOnPressEscape: false, dangerouslyUseHTMLString: true }) }
|