nodejs版本要求:node-v18.16.0-x64
nodejs基础配置
npm -v
node -v
npm config set prefix "D:\software\nodejs\node_global"
npm config set cache "D:\software\nodejs\node_cache"
npm config get registry
npm config set registry https://registry.npm.taobao.org
安装Vue3
npm install @vue/cli -g
vue --version
#npm install @vue/[email protected] -g 安装指定版本
#npm uninstall @vue/cli -g
使用Vue创建前端项目
npm create vue@latest
√ Project name: ...web
√ Add TypeScript? ... No
√ Add JSX Support? ... No
√ Add Vue Router for Single Page Application development? ... Yes
√ Add Pinia for state management? ... Yes
√ Add Vitest for Unit Testing? ... No
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... Yes
√ Add Prettier for code formatting? ... Yes
启动前端项目
cd web
npm install
npm run dev
浏览器访问:http://localhost:5173
修改端口号,修改配置 vite.config.js
export default defineConfig({
server: {
port: 9000
},
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
再次访问:http://localhost:9000/
https://antdv.com/components/overview-cn
Ant Design Vue官方文档
安装Ant Design Vue
npm install ant-design-vue --save
npm install --save @ant-design/icons-vue
#自动按需引入组件
npm install unplugin-vue-components -D
修改配置文件vite.config.js
import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite';
import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
server: {
port: 9000
},
plugins: [
vue(),
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
修改main.js
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import Antd from 'ant-design-vue';
import App from './App.vue'
import router from './router'
import 'ant-design-vue/dist/reset.css';
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(Antd)
app.mount('#app')
添加一个测试页面
This is an about page
测试
Antd栅格把页面平均分成24份
col
col-12
col-12
col-8
col-8
col-8
col-6
col-6
col-6
col-6
刷新页面,Pinia中的数据会丢失,使用Pinia插件做数据持久化
npm install --save zipson
npm install --save pinia-plugin-persistedstate
修改main.js
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import Antd from 'ant-design-vue';
import App from './App.vue'
import router from './router'
import 'ant-design-vue/dist/reset.css';
const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate);//pinia数据持久化
app.use(pinia)
app.use(router)
app.use(Antd)
app.mount('#app')
使用Pinia保存用户状态,添加文件src/stores/user.js
import {reactive} from 'vue'
import {defineStore} from 'pinia'
import {stringify, parse} from 'zipson'
const MEMBER = "MEMBER"
export const useUserStore = defineStore('user', () => {
const userInfo = reactive({
id: '',
mobile: '',
token: ''
})
function setUserInfo({id, mobile, token}) {
userInfo.id = id
userInfo.mobile = mobile
userInfo.token = token
}
function clearUserInfo() {
userInfo.id = ''
userInfo.mobile = ''
userInfo.token = ''
}
return {userInfo, setUserInfo, clearUserInfo}
}, {
persist: {
key: MEMBER,
storage: sessionStorage,
// paths: ['count'],
serializer: {
deserialize: parse,
serialize: stringify
},
beforeRestore: (ctx) => {
console.log(`about to restore '${ctx.store.$id}'`)
},
afterRestore: (ctx) => {
console.log(`just restored '${ctx.store.$id}'`)
},
debug: true,
}
})
https://vitejs.cn/vite3-cn/guide/env-and-mode.html#env-variables
官方配置文档
在根目录创建文件 .env.development
NODE_ENV=development
#自定义变量需要以VITE_开头
VITE_APP_BASE_URL=http://localhost:8000
生产环境 .env.production
NODE_ENV=production
VITE_APP_BASE_URL=http://train.intmall.com
使用环境变量
axios.defaults.baseURL = import.meta.env.VITE_APP_BASE_URL;
console.log(process.env.NODE_ENV)
console.log(import.meta.env.VITE_APP_BASE_URL)
npm install axios --save
封装网络请求工具类 src/utils/request.js
import axios from 'axios'
import {notification} from 'ant-design-vue';
import {useUserStore} from '@/stores/user';
import router from '@/router'
const {userInfo, clearUserInfo} = useUserStore()
export const serverUrl = import.meta.env.VITE_APP_BASE_URL
const service = axios.create({
baseURL: serverUrl,
timeout: 5000
})
// Add a request interceptor 全局请求拦截
service.interceptors.request.use(
function (config) {
// Do something before request is sent
const token = userInfo.token
if (token) {
config.headers['token'] = token
}
// 此处还可以设置token
return config
},
function (error) {
// Do something with request error
return Promise.reject(error)
}
)
// Add a response interceptor 全局相应拦截
service.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
// 如果是固定的数据返回模式,此处可以做继续完整的封装
const resData = response.data || {}
if (resData.success) {
return resData
}
notification.error({description: resData.message});
return Promise.reject(resData.message)
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
// 此处需要对返回的状态码或者异常信息作统一处理
console.log('error', error)
const response = error.response;
const status = response.status;
if (status === 401) {
// 判断状态码是401 跳转到登录页
console.log("未登录或登录超时,跳到登录页");
clearUserInfo()
notification.error({description: "未登录或登录超时"});
router.push('/login')
}
return Promise.reject(error)
}
)
export const get = (url, params) => {
return service.get(url, {
params
})
}
export const post = (url, data) => service.post(url, data)
export const put = (url, data) => service.put(url, data)
export const del = (url, data) => service.delete(url)
遇到的问题:
useRouter失效,router无法跳转页面
https://blog.csdn.net/qq_57700056/article/details/133530562
后台接口调用示例: src/api/userApi.js
import { get, post, put, del } from "../utils/request";
// 用户登录
export async function login(data) {
return post('/member/member/login', data)
}
export async function sendCode(data) {
return post('/member/member/sendCode', data)
}
export async function getUserCount() {
return get('/member/member/count')
}
export async function savePassenger(data) {
return post('/member/passenger/save', data)
}
export async function queryPassengerList(data) {
return post('/member/passenger/queryList', data)
}
export async function deletePassenger(id) {
return del(`/member/passenger/delete/${id}`)
}
// 导出 userApi 方法
export default {
login,
sendCode,
getUserCount,
savePassenger,
deletePassenger,
queryPassengerList
}
增加路由防卫,判断要跳转的页面是否需要登录
src/router/index.js
由于router挂载比pinia要早,守卫在在使用pinia时,pinia还没有挂载,把pinia写在守卫里面即可解决问题
import {createRouter, createWebHistory} from 'vue-router'
import {notification} from 'ant-design-vue';
import {useUserStore} from '@/stores/user';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'main',
component: () => import('../views/MainView.vue'),
children: [
{
path: '/welcome',
name: 'welcome',
component: () => import('../views/main/WelcomeView.vue')
}, {
path: '/passenger',
name: 'passenger',
component: () => import('../views/main/PassengerView.vue')
}
]
},
{
path: '/login',
name: 'login',
component: () => import('../views/LoginView.vue'),
meta: {
noToken: true
}
}, {
path: '',
redirect: '/welcome'
}
]
})
// 路由登录拦截
router.beforeEach((to, from, next) => {
// 要不要对meta.noToken属性做监控拦截
if (to.matched.some(function (item) {
console.log(item, "是否不需要登录校验:", item.meta.noToken || false);
return !item.meta.noToken
})) {
const {userInfo} = useUserStore()
console.log("页面登录校验开始:", userInfo);
if (!userInfo.token) {
console.log("用户未登录或登录超时!");
notification.error({description: "未登录或登录超时"});
next('/login');
} else {
next();
}
} else {
next();
}
});
export default router
模拟12306售票系统
获取验证码
登录
刷新
新增
编辑
删除
{{item.desc}}
{{ item.desc }}
前后端分离项目,前端在请求后台接口时会出现跨域问题
这个后端项目使用到了gateway,在配置文件中加入:
# 允许请求来源(老版本叫allowedOrigin)
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOriginPatterns=*
# 允许携带的头信息
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedHeaders=*
# 允许的请求方式
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedMethods=*
# 是否允许携带cookie
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowCredentials=true
# 跨域检测的有效期,会发起一个OPTION请求
spring.cloud.gateway.globalcors.cors-configurations.[/**].maxAge=3600
#CommonPageParam.java
@Data
public class CommonPageParam {
@NotNull(message = "页码不能为空")
private Integer page;
@NotNull(message = "每页数量不能为空")
@Max(value = 100, message = "分页条数不能超过100")
private Integer limit;
}
#PassengerQueryReq.java
@Data
public class PassengerQueryReq extends CommonPageParam {
private Long memberId;
}
#CommonPageResp.java
@Data
@NoArgsConstructor
public class CommonPageResp {
/** 默认每页的条数 */
public static final int PAGE_SIZE_DEFAULT = 10;
/**
* 业务上的成功或失败
*/
private boolean success = true;
/**
* 返回信息
*/
private String message;
/**
* 返回泛型数据,自定义类型
*/
private List data;
/**
* 总数
*/
private Long count;
/**
* 页码
*/
private Integer page;
/**
* 每页数量
*/
private Integer limit;
public Integer getPage() {
if (page == null || page < 1) {
return 1;
}
return page;
}
public Integer getLimit() {
if (limit == null) {
return PAGE_SIZE_DEFAULT;
}
return limit;
}
public static CommonPageResp SUCCESS(String message, List data, PageInfo pageInfo) {
return new CommonPageResp<>(true, message, data, pageInfo.getTotal(), pageInfo.getPageNum(), pageInfo.getPageSize());
}
public CommonPageResp(boolean success, String message, List data, Long count, Integer page, Integer limit) {
this.success = success;
this.message = message;
this.data = data;
this.count = count;
this.page = page;
this.limit = limit;
}
}
#PassengerService.java
@Service
@Slf4j
public class PassengerService {
@Resource
private PassengerMapper passengerMapper;
public CommonPageResp queryList(PassengerQueryReq req) {
PassengerExample passengerExample = new PassengerExample();
passengerExample.setOrderByClause("id desc");
PassengerExample.Criteria criteria = passengerExample.createCriteria();
if (ObjectUtil.isNotNull(req.getMemberId())) {
criteria.andMemberIdEqualTo(req.getMemberId());
}
PageHelper.startPage(req.getPage(), req.getLimit());
List passengerList = passengerMapper.selectByExample(passengerExample);
PageInfo pageInfo = new PageInfo<>(passengerList);
List list = BeanUtil.copyToList(passengerList, PassengerQueryResp.class);
return CommonPageResp.SUCCESS("", list, pageInfo);
}
}
完整代码参考:
https://gitee.com/galen.zhang/train