1、创建项目,使用vite
npm init vite@latest 项目名 选择 vue3 选择 javascript cd 项目名 :进入项目文件夹 npm i : 安装依赖包 npm run dev :运行项目
2、项目架构搭建
2.1 创建对应文件夹 如下图

2.2 下载如下安装包: 单独安装包: npm i axios vuex vue-router (同时安装多个包,写上安装包的名称,以空格分隔即可)
2.3 路由与main.js的关联 router/index.js 文件内容:
import { createRouter, createWebHashHistory } from 'vue-router'
import Login from '../views/Login.vue'
const routes = [
{
path: '/',
component: Login
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
main.js中引用路由
import router from './router'
createApp(App).use(router).use(store).use(ElementPlus).mount('#app')
2.4 store与main.js的关联 store/index.js 文件内容
import { createStore } from 'vuex'
import admin from './admin'
export default createStore({
state:{},
getters: {},
mutations: {},
actions: {},
modules: {
admin
}
})
main.js中引入store
import store from './store'
createApp(App).use(router).use(store).use(ElementPlus).mount('#app') 2.5 axios 的封装 将 axios 封装在 utils/axios.js 文件中
import axios from 'axios'
import store from './../store'
const ConfigBaseURL = 'http://localhost:9999';
// 让ajax携带cookie(自动携带本地所有cookie)s
axios.defaults.withCredentials=true;
// 使用create方法创建axios实例
const Service = axios.create({
baseURL: ConfigBaseURL, //1. 设置默认地址
timeout: 7000 // 2. 请求超时时间
})
//3. 给POST请求添加请求头设置(不同项目,值不一样)
Service.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8';
//4.1 添加请求拦截器
Service.interceptors.request.use(config => {
return config
})
//4.2 添加响应拦截器
Service.interceptors.response.use(response => {
console.log("axios 的响应拦截")
return response
}, error => {
return Promise.reject(error)
})
export default Service;
3、session 登录前端与后端设置 本项目采用session登录 ,需要进行相关设置,才可使用,配置如下 : 参考链接:https://bugshouji.com/mybug3/t23251
3.1 前端设置 axios文件中,设置 axios.defaults.withCredentials=true; 已经在axios.js中添加
// 让ajax携带cookie(自动携带本地所有cookie)s
axios.defaults.withCredentials=true;
// 使用create方法创建axios实例
const Service = axios.create({
baseURL: ConfigBaseURL, //1. 设置默认地址
timeout: 7000 // 2. 请求超时时间
})
3.2 后端设置(nodejs) 跨域设置,且设置Access-Control-Allow-Credentials 为true;
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://localhost:5173");
res.header("Access-Control-Max-Age", "3600");
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "X-Requested-With,X_Requested_With,Content-Type");
res.header("Access-Control-Allow-Methods", 'PUT, POST, GET, DELETE, OPTIONS');
// res.header("Access-Control-Allow-Credentials", "true");
next();
}); 除此之外注意:当前端配置withCredentials=true时, 后端配置Access-Control-Allow-Origin不能为*, 必须是相应地址否则报错:Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiat
4、element-plus引入
下载
npm install element-plus
main.js中引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(router).use(store).use(ElementPlus).mount('#app')
5、element-plus el-tableV2 虚拟化表格
5.1 columns 列名的配置信息 在设置columns属性时,其中的宽度字段(width)必须设置值(只能是数字类型)且每一列都要设置,不然会出现数据不显示或是只显示一列的情况。
const columns = [
{
key: "id",
dataKey: "id",//需要渲染当前列的数据字段,如{id:9527,name:'Mike'},则填id
title: "id",//显示在单元格表头的文本
width: 80,//当前列的宽度,必须设置
fixed: true//是否固定列
},
{
key: "name",
dataKey: "name",//需要渲染当前列的数据字段,如{id:9527,name:'Mike'},则填name
title: "姓名",
width: 100,
fixed: true
}]
5.2 自定义单元格内容
自定义单元格渲染器的字段是cellRenderer,类型为VueComponent /(props: CellRenderProps) => VNode
方法一:主要实现 h 函数: 示例:直接从UI框架中引入,然后在h函数的第一个参数中传入组件,需要注意的是,如果第一个参数直接传入字符串如’ElTag’,是渲染无效的,普通的html标签是可以的,第三个参数如果传入的数据是字符串文本,控制台会有警告信息,提示换为函数形式更佳
import { ref, h } from "vue";
import { ElMessageBox, ElMessage, ElButton ,ElTag} from "element-plus";
//columns 是一个数组,里面的值为每一列的配置信息
const columns = [
{
key: "state",
dataKey: "state",
title: "状态",
width: 80,
cellRenderer: ({ cellData }) =>
h(
ElTag,
{ type: cellData == "1" ? "success" : "danger" },
{ default: () => cellData == "1" ? "有效" : "无效" }//也可以写成字符串如'这是标签内容',但控制台会有警告
)
}
]
方法二:使用jsx的方式就比较简捷了,需要在script标签设置lang属性等于jsx,在cellRenderer函数中可以直接使用jsx的语法和UI组件(还有自定义组件), 示例如下:
const columns = [{
key: "handle",
title: "操作",
width: 200,
align: "center",
cellRenderer: ({rowData}) => (
<>
<el-button
type="danger"
icon={Delete}
onClick={handleDelete.bind(this, rowData.admin_no)}
>
删除
</el-button>
<el-button
type="primary"
icon={Edit}
onClick={handleUpdate.bind(this, rowData.admin_no)}
>
修改
</el-button>
</>
),
}]
使用 jsx 的配置参考链接:https://bugshouji.com/shareweb/t23249
5.3 删除的实现 需要提示,需要使用 import 引入对应的插件 import { ElMessage, ElMessageBox } from "element-plus";
//删除操作
const handleDelete = (data) => {
ElMessageBox.confirm(`确定删除 ${data.rowData.name}?`, "提 示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
tableData.value.splice(data.rowIndex, 1);
ElMessage({
type: "success",
message: "删除成功",
});
})
.catch(() => {
ElMessage({
type: "info",
message: "取消删除",
});
});
};
5.4 icon 图标的正常显示配置
参考:https://bugshouji.com/shareweb/t23250 因element-plus/icons改变成了svg,故显示 icon 需要进行如下配置: 1.安装 @element-plus/icons-vue npm install @element-plus/icons-vue 2. 使用 在引用图标的页面中将要使用的图标引入,作为js对象,代码如下:
<script>
import { Edit,Share } from "@element-plus/icons-vue";
export default {
setup() {
return {
Edit,
Share
}
}
}
</script> 使用方式和elemunt-ui3官网一致
<div class="flex">
<el-button type="primary" :icon="Edit" />
<el-button type="primary" :icon="Share" />
<el-button type="primary">
Upload<el-icon class="el-icon--right"><Upload /></el-icon>
</el-button>
</div> 或者采用 jsx 的语法,使用 {} 显示变量
const columns = [
{
key: "handle",
title: "操作",
width: 200,
align: "center",
cellRenderer: (data) => (
<>
<el-button
type="danger"
icon={Delete}
onClick={handleDelete.bind(this, data)}
>
删除
</el-button>
<el-button
type="primary"
icon={Edit}
onClick={handleDelete.bind(this, data)}
>
修改
</el-button>
</>
),
}
]
6、el-pagination 分页组件
<el-pagination background
layout="prev, pager, next"
:total="total"
@current-change="handleCurrentChange"
:current-page.sync="currentPage"
:page-size="pageSize"
/> total , currentPage,pageSize 均为计算属性, 代码如下:
const total = computed(()=> getters['admin/getTotal']); //获取总条数
const currentPage = computed(()=> getters['admin/getCurrentPage']); //获取总条数
const pageSize = computed(()=> getters['admin/getPageSize']); //获取总条数
@current-change="handleCurrentChange",当页发生变化 ,执行handleCurrentChange方法
//点击页码按钮事件
const handleCurrentChange =(page)=>{
dispatch("admin/setCurrentPage",page); //修改vuex中存储的currentPage的值
getAdminListBySearchAndPage(); //页码变化重新获取数据
} store/admin.js
var state = {
currentPage:1, //当前页
pageSize:4, //每页显示条数
totalSize:0, //总条数
totalPage:0, //总页码
data:[], //用户显示table的数据列表
searchName:"" //搜索的姓名
}
// 定义getters 读取状态
var getters = {
getList(state){
return [...state.data]
},
getTotal(state){
return state.totalSize;
},
getPageSize(state){
return state.pageSize;
},
getCurrentPage(state){
return state.currentPage;
},
getSearchName(state){
return state.searchName;
}
}
// 定义action,要执行的操作,如流程判断,异步请求等
var actions = {
setAdminData({commit},data){
commit('setAdminData',data);
},
setCurrentPage({commit},currentPage){
commit('setCurrentPage',currentPage);
},
setTotalSize({commit},num){
commit('setTotalSize',num);
},
setSearchName({commit},name){
commit('setSearchName',name);
}
}
// 处理状态的改变
var mutations = {
setAdminData(state,data){
state.data=[...data];
},
setTotalSize(state,num){
state.totalSize= num;
state.totalPage= Math.ceil(num/state.pageSize);
},
setSearchName(state,name){
state.searchName = name;
},
setCurrentPage(state,currentPage){
state.currentPage = currentPage;
}
}
export default {
namespaced:true, //命名空间
state,
getters,
actions,
mutations
}
7、service 对增删改查实现中,需要的请求 注: axios()方法,data传递的参数以body的形式接收,params传递的参数,会显示在url后面,使用query的形式接收。 service/admin.js
import axios from './../utils/axios'
// 获取管理员数据 根据页面与管理员姓名
function getAdminListBySearchAndPage({currentPage,pageSize,searchName}){
return axios({
url:"/getAdminListBySearchAndPage",
method:"post",
data:{
currentPage,
pageSize,
searchName
}
})
}
// 获取管理员的总条数 根据页面与管理员姓名
function getAdminListBySearchCount({searchName}){
return axios({
url:"/getAdminListBySearchCount",
method:"post",
data:{
searchName
}
})
}
//删除管理员
function delAdminById(id){
return axios({
url:"/delAdminById",
method:"get",
params:{
id
}
})
}
//获取管理员数据,根据管理员id
function getAdminInfoById(id){
return axios({
url:"/getAdminInfoById",
method:"get",
params:{
id
}
})
}
//更新管理员数据 ,根据管理员id
function updateAdminInfoById(userInfo){
return axios({
url:"/updateAdminInfoById",
method:"post",
data:userInfo
})
}
//添加管理员
function add_admin({user,pwd,rePwd,name,type}){
return axios({
url:"/addAdmin",
method:"post",
data:{
user,pwd,rePwd,name,type
}
})
}
//管理员登录
function admin_login(user,pwd){
return axios({
url:"/admin_login",
method:"post",
data:{
user,pwd
}
})
}
export default {
getAdminListBySearchAndPage,
getAdminListBySearchCount,
delAdminById,
updateAdminInfoById,
getAdminInfoById,
admin_login,
add_admin
}
service/index.js 组合所有的service文件
import admin from './admin'
export default {
admin
}
8、hooks 实现增删改查所有业务的文件
8.1 路由的相关hook 1. useRouter() 获取路由对象
import { useRouter } from 'vue-router' //引入
const router = useRouter(); //获取router对象
router.push("/login"); //跳转
router.push({ //跳转带参数
name: 'adminmodify',
query: {
id
}
});
2. useRoute() 获取当前路由对象
import { onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
const route = useRoute();
onMounted(() => {
//加载时,显示对应的数据
const id = route.query.id;
});
8.2 useStore 使用vuex的hook
import { useStore } from 'vuex' const { getters, dispatch } = useStore(); 完整代码:
import service from './../service/index'
import { ElMessage } from "element-plus";
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
function adminManage() {
const router = useRouter();
const store = useStore();
async function getAdminInfoById(id) {
const {data} = await service.admin.getAdminInfoById(id);
console.log("data",data);
if (data.code == 200) {
return data.data;
} else if (data.code == 201) {
router.push("/login"); // 跳转到登录
return null;
} else {
//提示错误信息
ElMessage({
message: data.message,
type: 'error',
});
return null;
}
}
//获取数据,根据searchName,currentPage,pageSize
function getAdminListBySearchAndPage() {
service.admin.getAdminListBySearchAndPage({
currentPage: store.state.admin.currentPage,
pageSize: store.state.admin.pageSize,
searchName: store.state.admin.searchName
}).then(({ data }) => {
if (data.code == 200) {
console.log(data.data);
store.dispatch("admin/setAdminData", data.data);
} else if (data.code == 201) {
console.log(data.message);
router.push("/login"); // 跳转到登录
} else {
console.log(data);
//提示错误信息
ElMessage({
message: data.message,
type: 'error',
});
}
})
}
// 根据searchName获取总条数
function getAdminListBySearchCount() {
service.admin.getAdminListBySearchCount({
searchName: store.state.admin.searchName
}).then(({ data }) => {
if (data.code == 200) {
store.dispatch("admin/setTotalSize", data.data);
} else if (data.code == 201) {
router.push("/login"); // 跳转到登录
} else {
//提示错误信息
ElMessage({
message: data.message,
type: 'error',
});
}
})
}
// 删除方法,根据id
function Handle_del(id) {
//发起请求
service.admin.delAdminById(id).then(({ data }) => {
if (data.code == 200) { //获取成功
ElMessage({
message: '删除成功',
type: 'success',
});
// 重新获取数据,与总条数
getAdminListBySearchAndPage();
getAdminListBySearchCount();
} else if (data.code == 201) {
router.push("/login"); // 跳转到登录
} else {
//提示错误信息
ElMessage({
message: data.message,
type: 'error',
});
}
})
}
// 管理员登录
function adminLogin(user, pwd) {
service.admin.admin_login(user, pwd).then(({ data }) => {
if (data.code == 200) {
router.push("/index"); // 跳转到登录
//存储登录用户的信息在vuex中
} else {
ElMessage({
message: data.message,
type: 'error',
});
}
})
}
// 添加管理员
function add_admin(userInput) {
service.admin.add_admin(userInput).then(({ data }) => {
if (data.code == 200) {
ElMessage({
message: data.message,
type: 'success',
});
router.push("/index"); //跳到列表页
}
else if (data.code == 201) {
router.push("/login"); // 跳转到登录
}
else {
ElMessage({
message: data.message,
type: 'error',
});
}
})
}
//更新管理
function update_admin(userInput){
service.admin.updateAdminInfoById(userInput).then(({ data }) => {
if (data.code == 200) {
ElMessage({
message: data.message,
type: 'success',
});
router.push("/index"); //跳到列表页
}
else if (data.code == 201) {
router.push("/login"); // 跳转到登录
}
else {
ElMessage({
message: data.message,
type: 'error',
});
}
})
}
return {
getAdminListBySearchAndPage,
getAdminListBySearchCount,
getAdminInfoById,
Handle_del,
adminLogin,
add_admin,
update_admin
}
}
export default adminManage
9、组件结构与路由设置

注:某个默认路径设置显示的组件 ,将path设置为空字符串即可,代码如下: { path: '', name: 'adminList', component: () => import('../components/admin/list.vue'), },
router/index.js 文件
完整如下:
import { createRouter, createWebHashHistory } from 'vue-router'
import Login from '../views/Login.vue'
const routes = [
{
path: '/',
component: Login
},
{
path: '/index',
name: 'index',
component: () => import('../views/AdminView.vue'),
children:[
{
path: '',
name: 'adminList',
component: () => import('../components/admin/list.vue'),
},
{
path: 'add',
name: 'adminAdd',
component: () => import('../components/admin/add.vue'),
},
{
path: 'update',
name: 'adminmodify',
component: () => import('../components/admin/modify.vue'),
}
]
},
{
path: '/login',
name: 'login',
component: Login
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
10、添加与修改相关知识点
10.1 使用reactive 结合toRefs绑定数据
<script setup>
import { reactive, toRefs } from "vue";
const data = reactive({
user: "",
pwd: "",
rePwd: "",
name: "",
type: "超级管理员",
});
const { user, pwd, rePwd, name, type } = toRefs(data);
// 此处的user, pwd, rePwd, name, type 即可进行数据绑定
10.2 绑定单选按钮 将一组的单选按钮绑定同一个变量, 当变量的值与单选按钮的value值相同时就会被选中。 对应变量的值,为最后被选中按钮的value值
<label for="radStudent">学生</label>
<input id="radStudent" type="radio" value="student" v-model="role">
<label for="radAdmin">管理员</label>
<input id="radAdmin" type="radio" value="admin" v-model="role">
<script setup>
import {reactive,toRefs} from 'vue';
const data = reactive({
user:"",
pwd:"",
role:"admin"
})
const {user,pwd,role} = toRefs(data);
10.3 绑定下拉列表 注意: 1. isDelete的类型,要为字符串,如果数据为number 需要进行转换 2. 使用 label 为显示数据,不要写在el-option之间;如:<el-option value="0">删除</el-option>绑定数据会出错 3. 绑定的数据 isDetele 是对应项的value值
&lt;el-select v-model=&quot;isDelete&quot; &gt;
&lt;el-option label=&quot;删除&quot; value=&quot;0&quot;&gt;&lt;/el-option&gt;
&lt;el-option label=&quot;恢复&quot; value=&quot;1&quot;&gt;&lt;/el-option&gt;
&lt;/el-select&gt;
苟有恒 , 何必三更眠五更起
关注我,一起学习吧