当前位置: 首页 > backend >正文

前端之vue3创建基本工程,基本登录、注册等功能的完整过程

此文也是为了做一个基本学习用的vue3创建项目的过程,包含基本的登录页面、登出页面、基本的router跳转、axios调用、登录验证等内容。与项目:
https://gitee.com/rainpet/java-web-demo/tree/master/spring-security01
可以配套使用。
如下为主要过程。

1、node.js下载地址:
https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/
下载一个LTS版本的 v20和v22都是
https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/v20.19.2/
https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/v22.15.1/
这里下载v20的。
2、解压到路径,我这里用的是:
D:\wamp\node-v20
设置系统path变量
设置好后,可以通过
node --version
来查看版本是否生效。
3、全局安装基本的包:工程工具(vite)、包管理工具(yarn、cnpm)
npm install -g yarn
npm install -g cnpm
npm install -g vite
4、项目创建,咱们的目标是要创建一个spa(single page application)程序,这也是vue的主流应用方式。
切换到工程目录:
d:
cd D:\java\workspace>
创建工程命令
npm create vite@latest
输入工程名称:vuedemo01
选择框架:Vue
选择类型:JavaScript
完成创建。
5、安装vscode,安装相应的扩展:
Vue - Official
6、进入到vscode,打开终端,使用cmd模式。
安装本项目的依赖
yarn add axios
yarn add element-plus
yarn add vue-router
yarn add @element-plus/icons-vue
yarn add vite-plugin-vue-devtools
7、打开项目,修改vie.config.js文件,增加后台api的代理设置,确认有如下的server的相关内容。

import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'export default defineConfig({plugins: [vue(),vueDevTools(),],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))},},server: {port: 8080,proxy: {'/api': {target: 'http://localhost:8086',changeOrigin: true,rewrite: (path) => path.replace(/^\/api/, ''),//去掉原始api的前缀},},},
})

8、src下,创建目录:router,目录中增加文件:index.js

import { createRouter, createWebHistory } from 'vue-router'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: '/',name: 'home',//component: Home,component: () => import('../views/HomeView.vue'),}],
})
export default router

9、修改入口的js文件,src/main.js,将组建加入到Vue的上下文:
由:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'createApp(App).mount('#app')

改为:

import { createApp } from 'vue'
//import { createPinia } from 'pinia'import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'const app = createApp(App)//app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.mount('#app')

10、创建组件-Header.vue:

<template><el-header class="header"><div class="logo"><h2>Vue Demo</h2></div><div class="header-right"><el-dropdown><span class="el-dropdown-link">Admin<el-icon class="el-icon--right"><arrow-down /></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item @click="handleCommand('info')">关于</el-dropdown-item><el-dropdown-item @click="handleCommand('logout')">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div></el-header>
</template><script setup>
import { ArrowDown } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'const router = useRouter()const handleCommand = (command) => {switch (command) {case 'info':router.push('/about')breakcase 'logout':// 这里可以添加退出登录的逻辑console.log('退出登录')router.push('/logout')break}
}
</script><style scoped>
.header {background-color: #409EFF;color: white;display: flex;align-items: center;justify-content: space-between;padding: 0 20px;
}.logo h2 {margin: 0;
}.header-right {display: flex;align-items: center;
}.el-dropdown-link {color: white;cursor: pointer;display: flex;align-items: center;
}
</style> 

11、组件 Footer.vue

<template><el-footer class="footer"><p>© 2024 Vue Demo. All rights reserved.</p></el-footer>
</template><script setup>
</script><style scoped>
.footer {background-color: #f5f7fa;color: #606266;text-align: center;padding: 20px 0;border-top: 1px solid #e4e7ed;
}.footer p {margin: 0;
}
</style> 

12、组件:SideMenu.vue

<template><el-menuclass="side-menu el-menu-vertical":default-active="activeIndex":collapse="isCollapse"router><el-menu-item index="/"><el-icon><HomeFilled /></el-icon><template #title>首页</template></el-menu-item><el-sub-menu index="/about"><template #title><el-icon><Document /></el-icon><span>功能</span></template><el-menu-item index="/order/list">订单</el-menu-item></el-sub-menu></el-menu>
</template><script setup>
import { ref } from 'vue'
import { HomeFilled, Document, Setting, InfoFilled } from '@element-plus/icons-vue'const activeIndex = ref('/')
const isCollapse = ref(false)
</script><style scoped>
.side-menu {height: 100%;border-right: none;
}.el-menu-vertical:not(.el-menu--collapse) {width: 200px;
}
</style> 

13、IndexView.vue

<template><div class="dashboard-container"><el-row :gutter="20"><el-col :span="6"><el-card shadow="hover"><template #header><div class="card-header"><span>访问量</span></div></template><div class="card-content"><h2>1,234</h2></div></el-card></el-col><el-col :span="6"><el-card shadow="hover"><template #header><div class="card-header"><span>用户数</span></div></template><div class="card-content"><h2>567</h2></div></el-card></el-col><el-col :span="6"><el-card shadow="hover"><template #header><div class="card-header"><span>订单数</span></div></template><div class="card-content"><h2>890</h2></div></el-card></el-col><el-col :span="6"><el-card shadow="hover"><template #header><div class="card-header"><span>收入</span></div></template><div class="card-content"><h2>¥12,345</h2></div></el-card></el-col></el-row></div></template><script setup></script><style scoped>.dashboard-container {padding: 20px;}.card-header {display: flex;justify-content: space-between;align-items: center;}.card-content {text-align: center;}.card-content h2 {margin: 10px 0;color: #409EFF;}</style>

14、HomeView.vue

<template><el-container class="layout-container"><Header /><el-container class="main-container"><el-aside width="200px" class="aside"><SideMenu /></el-aside><el-container class="content-container"><el-main><router-view><IndexView /></router-view></el-main><Footer /></el-container></el-container></el-container>
</template><script setup>
import Header from '@/components/Header.vue'
import Footer from '@/components/Footer.vue'
import SideMenu from '@/components/SideMenu.vue'
import IndexView from '@/views/IndexView.vue'
</script><style scoped>
.layout-container {height: 100vh;display: flex;flex-direction: column;
}.main-container {flex: 1;
}.aside {background-color: #fff;border-right: 1px solid #e6e6e6;
}.content-container {flex: 1;display: flex;flex-direction: column;
}.el-main {flex: 1;background-color: #f5f7fa;padding: 20px;
}
</style> 

15、AboutView.vue

<template><el-container class="layout-container"><Header /><el-container class="main-container"><el-aside width="200px" class="aside"><SideMenu /></el-aside><el-container class="content-container"><el-main><router-view><div class="about-content"><el-descriptions :column="1" border><el-descriptions-item label="系统名称">Vue Demo System</el-descriptions-item><el-descriptions-item label="版本">1.0.0</el-descriptions-item><el-descriptions-item label="技术栈">Vue 3 + Element Plus + Vue Router</el-descriptions-item><el-descriptions-item label="开发团队">Demo Team</el-descriptions-item></el-descriptions></div></router-view></el-main><Footer /></el-container></el-container></el-container></template><script setup>
import Header from '@/components/Header.vue'
import Footer from '@/components/Footer.vue'
import SideMenu from '@/components/SideMenu.vue'</script><style scoped>
.about-container {padding: 20px;
}.about-card {max-width: 800px;margin: 0 auto;
}.card-header {display: flex;justify-content: space-between;align-items: center;
}.card-header h2 {margin: 0;color: #409EFF;
}.about-content {margin-top: 20px;
}
.layout-container {height: 100vh;display: flex;flex-direction: column;
}.main-container {flex: 1;
}.aside {background-color: #fff;border-right: 1px solid #e6e6e6;
}.content-container {flex: 1;display: flex;flex-direction: column;
}.el-main {flex: 1;background-color: #f5f7fa;padding: 20px;
}
</style> 

16、App.vue 最终修改为:

<script setup></script><template><router-view></router-view>
</template><style>
html, body {margin: 0;padding: 0;height: 100%;width: 100%;
}#app {height: 100%;width: 100%;
}
</style>

17、登录LoginView.vue

<template><div class="login-container"><div class="login-box"><h1>登录</h1><el-form @submit.prevent="handleLogin" label-position="top" class="login-form"><el-form-item label="用户名"><el-input v-model="username" required @keyup.enter="handleLogin"></el-input></el-form-item><el-form-item label="密码"><el-input type="password" v-model="password" required @keyup.enter="handleLogin"></el-input></el-form-item><el-form-item><el-button type="primary" @click="handleLogin" class="login-button">登录</el-button></el-form-item></el-form><p v-if="errorMessage" class="error">{{ errorMessage }}</p><p class="register-link">还没有账号?<a href="/register">注册</a></p></div></div>
</template><script setup>
import { ref } from 'vue';
import axios from 'axios';
import { useRouter } from 'vue-router';
import { ElForm, ElFormItem, ElInput, ElButton, ElMessage } from 'element-plus';const username = ref('');
const password = ref('');
const errorMessage = ref('');const router = useRouter();const handleLogin = async () => {try {const response = await axios.post('/api/user/login', {userName: username.value,password: password.value});if (response.data.code === 200) {errorMessage.value = '';localStorage.setItem('token', response.data.data.token);localStorage.setItem('userId', response.data.data.userId);localStorage.setItem('userName', response.data.data.userName);router.push('/');} else {errorMessage.value = response.data.msg;}} catch (error) {errorMessage.value = '登录失败,请检查用户名和密码';}
};
</script><style scoped>
.login-container {display: flex;justify-content: center;align-items: center;height: 100vh;/* background: linear-gradient(135deg, #6e8efb, #a777e3); */
}.login-box {width: 400px;padding: 40px;background-color: #fff;border-radius: 10px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);text-align: center;
}.login-box h1 {margin-bottom: 30px;font-size: 24px;color: #333;
}.login-form {display: flex;flex-direction: column;
}.login-form .el-form-item {margin-bottom: 20px;
}.login-button {width: 100%;background-color: #6e8efb;border-color: #6e8efb;
}.login-button:hover {background-color: #5a7de1;border-color: #5a7de1;
}.error {color: red;margin-top: 10px;
}.register-link {margin-top: 20px;color: #333;
}.register-link a {color: #6e8efb;text-decoration: none;
}.register-link a:hover {text-decoration: underline;
}
</style>

18、登出页面 LogoutView.vue

<template><div class="logout-container"><Header /><el-container class="content"><el-main><h1>您已退出登录!</h1><el-button type="primary" @click="goHome">返回首页</el-button></el-main></el-container></div>
</template><script setup>
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import Header from '@/components/Header.vue';const router = useRouter();const goHome = () => {router.push('/');
};onMounted(() => {// 退出登录逻辑,删除存储的 tokenlocalStorage.removeItem('token');
});
</script><style scoped>
.logout-container {text-align: center;padding: 2rem;
}.content {display: flex;flex-direction: column;align-items: center;justify-content: center;height: calc(100vh - 200px); /* Adjust height based on header */
}h1 {margin-bottom: 1rem;
}.el-button {margin-top: 2rem;
}
</style>

19、修改src/router/index.js,增加如下内容,以实现登录验证:

router.beforeEach((to, from, next) => {if (to.name !== 'login' && to.name !== 'logout' && to.name !=='register' && !localStorage.getItem('token')) {next({ name: 'login' })} else {next()}
})

20、增加最终的登录后测试页面 OrderView.vue:

<template><div><Header /><div class="layout"><Sidebar /><div class="content"><section class="order"><h4>订单列表</h4><div v-if="orders.length > 0"><el-table :data="orders" style="width: 100%"><el-table-column prop="fdate" label="订单日期" /><el-table-column prop="orderAmount" label="订单金额" /><el-table-column prop="status" label="订单状态" /><el-table-column prop="address" label="收货地址" /><el-table-column label="操作"><template #default="scope"><el-button type="primary" @click="placeOrder(scope.row)">支付</el-button><el-button type="info" @click="showDetails(scope.row)">详情</el-button></template></el-table-column></el-table><div class="order-total"><h3>总计: {{ formatCurrency(orderAmounts) }}</h3></div></div><div v-else><p>订单是空的。</p></div></section></div></div><Footer /><el-dialogv-model="dialogVisible"title="Tips"width="800":before-close="handleClose"><template #title><span>订单详情</span></template><el-table :data="selectedOrder.entries" style="width: 100%"><el-table-column prop="goodsName" label="商品ID" /><el-table-column prop="qty" label="数量" /><el-table-column prop="price" label="价格"><template #default="entryScope"><span>¥{{ entryScope.row.price }}</span></template></el-table-column><el-table-column prop="orderAmount" label="金额"><template #default="entryScope"><span>¥{{ entryScope.row.amount }}</span></template></el-table-column></el-table><div class="order-total"><h3>订单小计: {{ formatCurrency(detailAmount) }}</h3></div><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">关闭</el-button></span></el-dialog></div>
</template><script setup>
import { ref, computed, onMounted } from 'vue';
import Header from '@/components/Header.vue';
import Sidebar from '@/components/Sidemenu.vue';
import Footer from '@/components/Footer.vue';
import axios from 'axios';
import { useRouter } from 'vue-router';const router = useRouter();
const orders = ref([]);
const dialogVisible = ref(false);
const selectedOrder = ref({ entries: [] });const getOrdersList = async () => {try {const token = localStorage.getItem('token');const userId = localStorage.getItem('userId');const headers = {'token': `${token}`};const response = await axios.get('/api/orders/listByOwner/' + userId, { headers: headers });//console.log(response.data);console.log(response.data.data);// 检查响应数据格式if (Array.isArray(response.data.data)) {orders.value = response.data.data;} else if (response.data.code === 401) {router.push('/login');} else {console.error('服务器返回的数据格式不正确:', response.data);}} catch (error) {console.error('获取订单列表失败:', error);}
};const formatCurrency = (amount) => {return amount.toLocaleString('zh-CN', { style: 'currency', currency: 'CNY' }).replace('CNY', '');
};const detailAmount = computed(() => {return Math.round(selectedOrder.value.entries.reduce((total, entry) => total + entry.amount, 0),2);
});const orderAmounts = computed(() => {return orders.value.reduce((total, order) => {return Math.round(total + order.entries.reduce((orderTotal, entry) => orderTotal + entry.amount, 0),2);}, 0);
});const placeOrder = (order) => {// 支付逻辑alert(`订单 ${order.id} 已支付`);
};const showDetails = (order) => {console.log(`查看订单详情: ${order.id}`);selectedOrder.value = order;console.log(selectedOrder.value);dialogVisible.value = true;console.log(dialogVisible.value);
};const handleClose = (done) => {dialogVisible.value = false;
};onMounted(() => {getOrdersList();
});
</script><style scoped>
.layout {display: flex;
}
.el-dialog {display: block !important;
}
.content {flex: 1;display: flex;justify-content: center;align-items: flex-start;padding: 20px;background-color: #fff;overflow-y: auto;
}.order {max-width: 1200px;width: 100%;margin: 0 auto;padding: 2rem;
}.order-total {text-align: right;margin-top: 2rem;
}.order-total h3 {font-size: 1.5rem;color: #333;
}.el-button {margin-top: 1rem;
}
</style>

21、增加相应的router的内容

{path: '/logout',name: 'logout',component: () => import('../views/LogoutView.vue')},{path:'/login',name: 'login',component: () => import('../views/LoginView.vue')},{path: '/register',name: 'register',component: () => import('../views/RegisterView.vue')},{path: '/order/list',name: 'orderList',component: () => import('../views/OrderView.vue')}

22、最终的项目结构:
在这里插入图片描述

http://www.xdnf.cn/news/7992.html

相关文章:

  • 【IC验证】systemverilog_包
  • 自由开发者计划 001:创建一个用于查看 Jupyter Notebook 的谷歌浏览器插件 Jupyter Peek
  • 常见的LLM
  • 从零基础到最佳实践:Vue.js 系列(2/10):《模板语法与数据绑定》
  • 对抗学习(AL),生成对抗网络(GAN),强化学习,RLHF
  • 【差异分析】t-test
  • React中 lazy与 Suspense懒加载的组件
  • 26、AI 预测性维护 (燃气轮机轴承) - /安全与维护组件/ai-predictive-maintenance-turbine
  • 鸿蒙电脑系统和统信UOS都是自主可控的系统吗
  • 从零开始:Python语言基础之条件语句(if-elif-else)
  • Java虚拟机栈
  • 社会工程与信息收集
  • 左手腾讯CodeBuddy 、华为通义灵码,右手微软Copilot,旁边还有个Cursor,程序员幸福指数越来越高了
  • Human Dil-HDL,使用方法,红色荧光标记人源高密度脂蛋白
  • 循环队列分析及应用
  • 在 Qt 中实现动态切换主题(明亮和暗黑)
  • Gartner研究报告《Generative AI 赋能Digital Commerce的三种路径》学习心得
  • 笑林广记读书笔记三
  • 下一代电子电气架构(EEA)的关键技术
  • 具有思考模式模型部署:Qwen3、DeepSeek-R1-Distill、Phi-4、QWQ系列
  • 模型量化与保存
  • Python实例题:Python实现简单画板
  • 网络安全之身份验证绕过漏洞
  • 【AI+开发】什么是LLM、MCP和Agent?
  • 容器网络中的 veth pair 技术详解
  • 求无符号字符型数据乘积的高一半
  • 隧道自动化监测解决方案
  • 【攻防实战】MacOS系统上线Cobalt Strike
  • 高并发内存池|六、page cache的设计
  • 13、自动配置【源码分析】-自动包规则原理