- 创建项目目录
mkdir testproject && cd testproject
- 安装 vue-cli
yarn add vue-cli
- 创建项目
vue create testproject
- 进入项目目录
cd testproject/
- 启动项目
yarn serve
- 安装 element-plus
yarn add element-plus --save
- 更新main.js,增加element-plus导入
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app=createApp(App)
app.use(ElementPlus)
app.mount('#app')
- 安装路由
yarn add vue-router --save
-
在src目录下新疆router文件夹,再新建index.js
-
main.js 中配置路由
import router from './router/index.js'
app.use(router)
- App.vue中加入路由模块
// 删除原有示例代码
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
// 替换为路由组件
<router-view></router-view>
- 安装 axios
yarn add axios --save
-
配置axios
- src下新建文件夹 utils -> http
- 在http下建 axios.js 和 http.js 文件
-
axios.js代码
import axios from "axios";
import { ElMessage } from "element-plus";
// 1. 创建axios实例
const instance = axios.create({
// 接口
baseURL: '/api',
// 超时时间
timeout: 50000
});
// 2.请求拦截
instance.interceptors.request.use(
config => {
let token = sessionStorage.getItem('token0')
if (token) {
config.headers['token'] = token
}
return config
},
error => {
}
)
// 3. 响应拦截
instance.interceptors.response.use(
res => {
return res
},
error => {
if (error && error.response){
const status = error.response.status
switch (status){
}
}else{
if (JSON.stringify(err).includes('timeout')){
ElMessage.error('服务器响应超时,请刷新页面');
}
ElMessage.error('链接服务器失败');
}
return Promise.reject(error);
}
)
// 4.导出 axios 实例
export default instance;
- http.js代码
import { reject, resolve } from "core-js/fn/promise";
import { de } from "element-plus/es/locale";
import instance from "./axios";
const post = (url, data) => {
return new Promise((resolve, reject) => {
instance.post(url, data).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
const get = (url, data) => {
return new Promise((resolve, reject) => {
instance.get(url, { params: data }).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
const put = (url, data) => {
return new Promise((resolve, reject) => {
instance.put(url, data).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
const del = (url, data) => {
return new Promise((resolve, reject) => {
instance.delete(url, { params: data }).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
export default { post, get, put, del }
- 配置跨域
vite.config.js
...
新建文件夹和文件 src -> assets -> css -> style.css style.css编辑
html, body {
padding: 0px;
margin: 0px;
height: 100%;
background: #fff;
}
/* 表格统一样式 */
.el-table .el-table__header-wrapper thead th,
.el-table .el-table__fixed-header-wrapper thead th {
padding: 0px;
background-color: #f9f9f9;
height: 50px;
line-height: 40px;
color: #283543;
}
main.js 中导入
import '@/assets/css/style.css'
在src同级目录下新建 .eslintrc.js
文件
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": "plugin:vue/vue3-essential",
"overrides": [
],
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"vue"
],
"rules": {
'vue/no-reserved-component-names': 'off', //不允许在组件定义中使用保留名称
'vue/multi-word-component-names': 'off', //要求组件名称始终为多字
'vue/no-deprecated-slot-attribute': 'off',
"vue/no-unused-components": "off",
'no-unused-vars': 0
}
}
// main.js
import local from 'element-plus/lib/locale/zh-cn'
app.use(ElementPlus, {local})
- 在
src
目录下新建api
文件夹 - 在
api
目录中新建user.js
用于存储 用户接口 - 编辑
user.js
import http from '../utils/http/http.js'
const login=(data)=>{
return http.post('/user/login', data)
}
const getUserList=(data)=>{
return http.get('/user/list', data)
}
const saveUser=(data)=>{
return http.post('/user/save', data)
}
const delUser=(data)=>{
return http.del('/user/delete', data)
}
const getUserDetail=(data)=>{
return http.get('/user/detail', data)
}
export default {
login, getUserList, saveUser, delUser, getUserDetail
}
- 进入
src/router/index.js
设置页面路由
import { createRouter, createWebHashHistory } from "vue-router";
const routes = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'login',
meta: {
title: '登录'
},
component: () => import('../view/login.vue')
},
{
path: '/home',
name: 'index',
meta:{title:'index path'},
component: ()=>import('../view/Home.vue'),
redirect: '/index',
children: [
{
path: '/index',
meta: {title: 'index path'},
component: ()=>import('../view/Welcome.vue')
},
{
path: '/user/list',
meta: {title: '用户管理'},
component: ()=> import('../view/user/Index.vue'),
},
{
path:'/user/detail',
meta: {title: '用户详情'},
component: ()=> import('../view/user/Detail.vue')
},
]
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;
script
js代码要用<script setup>
方式, 和以前的export default
方式不一样. 用该方式是将属性和方法都写在setup函数里,逻辑比较清晰setup
方式定义的属性要用到vue
的ref
和reactive
属性ref
和reactive
区别:- 相同:创建一个响应式对象。
- 不同:
- reactive 接受入参必须是对象形式,而 ref 可以是对象形式,也可以是一个单值。
- 读取/赋值不一样,ref 必须从
.value
属性中读取值 - ref存在异步问题
- 配置路由 (上一步已完成)
src/view
目录下新建login.vue
文件- 增加登陆代码
<template>
<div class="login-container">
<el-form class="login-form" :model="form" :rules="rules" ref="ruleFormRef">
<h1 class="login-title">Login</h1>
<el-form-item prop="username">
<el-input v-model="form.username" placeholder="Username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
v-model="form.password"
placeholder="Password"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm()">Login</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import router from "@/router";
import userApi from "../api/user";
import { ElMessage } from "element-plus";
import { reactive, ref } from "vue";
const form = reactive({
username: "",
password: "",
});
const ruleFormRef = ref();
const rules = reactive({
username: [
{ required: true, message: "Please input your username", trigger: "blur" },
],
password: [
{ required: true, message: "Please input your password", trigger: "blur" },
],
});
const submitForm = () => {
router.push("/home");
if (!ruleFormRef.value) return;
ruleFormRef.value.validate(async(valid) => {
if (valid) {
const res = await userApi.login(form);
if (res.data) {
if (res.data.success) {
router.push("/home");
} else {
ElMessage.error(res.data.message);
}
} else {
ElMessage.error("服务器错误");
}
} else {
return false;
}
});
}
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
}
.login-form {
width: 400px;
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.login-title {
font-size: 24px;
text-align: center;
margin-bottom: 20px;
}
</style>
- 配置路由 (上一步已完成)
src/view
目录下新建Home.vue
文件- 增加代码
<template>
<div class="main-container">
<el-header class="header">
<div class="logo">创斯维</div>
<el-button class="logout-btn" type="text" @click="logout()"
>退出系统</el-button
>
</el-header>
<el-container>
<el-aside class="aside">
<el-menu
default-active="1"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="1" @click="saveActiveNav('/index')"
><i class="el-icon-s-home"></i>首页</el-menu-item
>
<el-submenu index="2">
<template #title>
<i class="el-icon-setting"></i>
<span>系统设置</span>
</template>
<el-menu-item index="2-1" @click="saveActiveNav('/index')"
>基本设置</el-menu-item
>
<el-menu-item index="2-2" @click="saveActiveNav('/index')"
>高级设置</el-menu-item
>
</el-submenu>
<el-menu-item index="3" @click="saveActiveNav('/user/index')"
><i class="el-icon-user"></i>用户管理</el-menu-item
>
</el-menu>
</el-aside>
<el-main class="main">
<router-view></router-view>
</el-main>
</el-container>
</div>
</template>
<script setup>
import { onBeforeMount, ref } from "vue";
import { useRouter } from 'vue-router'
const router = useRouter();
onBeforeMount(() => {
activePath.value = sessionStorage.getItem("activePath")
? sessionStorage.getItem("activePath")
: "/index";
});
let activePath = ref('');
const handleOpen = (key, keyPath) => {
console.log(key, keyPath);
};
const handleClose = (key, keyPath) => {
console.log(key, keyPath);
};
const saveActiveNav = (path) => {
sessionStorage.setItem("activePath", path);
activePath.value = path;
};
const logout = () => {
sessionStorage.clear();
router.push("/login");
};
</script>
<style scoped>
.main-container {
height: 100%;
display: flex;
flex-direction: column;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #409eff;
color: #fff;
padding: 0 20px;
}
.logo {
font-size: 24px;
font-weight: bold;
}
.logout-btn {
color: #fff;
}
.aside {
background-color: #f0f0f0;
}
.main {
padding: 20px;
}
</style>
- 配置路由 (上一步已完成)
src/view
目录下新建Welcome.vue
文件- 增加代码(具体业务逻辑待实现)
<template>
<div class="main">
</div>
</template>
<style scoped>
.main{
text-align: center;
padding: 5px;
background: white;
height: 100%;
background-repeat: no-repeat;
background-size: 100% 100%;
background-position-y: bottom;
}
</style>
- 配置路由 (上一步已完成)
src/view
目录下新建user/Index.vue
目录和文件- 增加代码
<template>
<div>
<el-card>
<el-input style="width:440px;" @clear="searchUser" clearable v-model="searchForm.name" placeholder="请输入用户姓名" class="input-with-select">
<template>
<el-button @click="searchUser"><i class="el-icon-s-home"></i></el-button>
</template>
</el-input>
<el-table :data="tableData" border style="width: 100%; margin-top: 20px;">
<el-table-column prep="name" label="姓名" width="180"/>
<el-table-column prep="age" label="年龄" width="180"/>
<el-table-column label="操作" width="330">
<template #default="scope">
<el-button type="danger" size="small" @click="deleteUser(scope.row.id)">删除</el-button>
<el-button size="small" @click="() => router.push('/user/detail', {id: scope.row.id})">详情</el-button>
</template>
</el-table-column>
</el-table>
<!--分页-->
<el-pagination style="margin-top: 20px;" :current_page="searchForm.current" :page-size="searchForm.size" :page-sizes="[10,20,30,40]" layout="->,total, sizes, prev,next, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
</el-card>
</div>
</template>
<script setup>
import userApi from '../../api/user'
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
onMounted(()=>{
getUserList()
})
let tableData = ref([]);
let total = ref(0);
const searchForm = reactive({
current: 1,
size: 10,
name: ''
})
const getUserList = async()=>{
const res = await userApi.getUserList(searchForm);
tableData.value = res.data.data.records;
total.value = res.data.data.total
}
const handleSizeChange = (size)=>{
searchForm.size = size;
getUserList()
}
const handleCurrentChange = (current) => {
searchForm.current = current;
getUserList()
}
const searchUser = ()=>{
searchForm.current=1;
getUserList()
}
const deleteUser = (id)=>{
ElMessageBox.confirm(
'确定要删除该用户吗?',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(async()=>{
const res = await userApi.delUser({id: id});
if (res.data.success){
ElMessage.success('删除成功')
getUserList()
}else{
ElMessage.error('删除失败')
}
}).catch(()=>{
ElMessage({
type:'info',
message: '取消删除'
})
})
}
</script>
<style scoped>
</style>
- 配置路由 (上一步已完成)
src/view/user
目录下新建Detail.vue
文件- 增加代码
<template>
<div>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>用户详情</span>
<el-button size="mini" style="float: right" @click="router.get(-1)">返回</el-button>
</div>
</template>
<el-form>
<el-from-item label="姓名:">
{{ userDetail.name }}
</el-from-item>
<el-from-item label="年纪:">
{{ userDetail.age }}
</el-from-item>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { onBeforeMount, reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router'
import userApi from '../../api/user'
const route = useRoute();
const router = useRouter();
const userDetail = reactive({
id: ''
})
onBeforeMount(async() => {
if (route.query.id){
const res = await userApi.getUserDetail({id: route.query.id});
Object.assign(userDetail, res.data.data);
}
})
</script>
<style scoped>
</style>
- vue3 中没有
Prototype
属性,使用app.config.globalProperties
去替代 proxy
可以理解为 vue 的代理对象getCurrentInstance
方法获取当前组件实例- 新建全局函数,
assets
目录下创建文件夹和文件js/common.js
import router from "@/router";
export default {
// 路由跳转
changeView(url, queryParams){
router.push({
path: url,
query: queryParams
})
}
}
main.js
中配置全局函数
import commonJs from './assets/js/common'
app.config.globalProperties.$commonJs = commonJs
- 页面中导入和使用(以
login.vue
为例)
import { getCurrentInstance, reactive, ref } from "vue";
const {proxy} = getCurrentInstance();
// 验证成功跳转该为:
//router.push("/home");
proxy.$commonJs.changeView('/home')