1. 概念扫盲
- Node.js是基于ChromeV8引擎,让JS在服务端运行的开发平台,就是JS的一种解释器
- WebPack就是模块打包机,把浏览器不能直接运行的拓展语言找到并打包为合适的格式给浏览器直接使用
- Vue基于WebPack构件项目的,并带有合理默认配置的,可以快速开发的完整系统
- npm就是JS的包管理工具
2. 环境准备
2.1 nodejs下载安装及配置
nodejs download
2.2 安装Vue脚手架
npm install -g @vue/cli
2.3 创建Vue3项目并运行
cd E:\project2024\shopping_car
vue create shopping_car_fore
cd shopping_car_fore
npm run serve
2.4 安装相关包
2.5 vue开发者必备vscode插件【2024最新】
https://blog.csdn.net/liyananweb/article/details/135958361
3. 一个小demo
3.1 public文件夹css,js等文件
3.2 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- Favicon and Touch Icons-->
<link rel="shortcut icon" href="xxyy.ico"/>
<link rel="stylesheet" href="bootstrap.css">
<title>数据平台</title>
</head>
<body>
<div id="app"></div>
<!-- Javascript Files -->
<script src="jquery-3.3.1.js"></script>
<script src="bootstrap.js"></script>
</body>
</html>
3.3 配置main.js
// 导入Vue
import { createApp } from 'vue'
// 导入Vue扩展插件
import axios from 'axios'
import VueAxios from 'vue-axios'
import { createRouter, createWebHistory } from 'vue-router'
// 导入组件
import App from './App.vue'
// import Product from './components/Product.vue'
import Signin from './components/Signin.vue'
// 定义路由
const routes = [
{ path: '/', component: Signin },
// { path: '/product', component: Product },
]
// 创建路由对象
const router = createRouter({
// 设置历史记录模式
history: createWebHistory(),
// routes: routes的缩写
routes,
})
// 创建Vue对象
const app = createApp(App)
// 将路由对象绑定到Vue对象
app.use(router)
// 将vue-axios与axios关联并绑定到Vue对象
app.use(VueAxios,axios)
// 挂载使用Vue对象
app.mount('#app')
3.4 app.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
3.5 Signin.vue
<template>
<div class="main-layout card-bg-1">
<div class="container d-flex flex-column">
<div class="row no-gutters text-center align-items-center justify-content-center min-vh-100">
<div class="col-12 col-md-6 col-lg-5 col-xl-4">
<h1 class="font-weight-bold">用户登录</h1>
<p class="text-dark mb-3">民主、文明、和谐、自由、平等</p>
<div class="mb-3">
<div class="form-group">
<label for="username" class="sr-only">账号</label>
<input type="text" class="form-control form-control-md" id="username" placeholder="请输入账号"
v-model="username">
</div>
<div class="form-group">
<label for="password" class="sr-only">密码</label>
<input type="password" class="form-control form-control-md" id="password"
placeholder="请输入密码" v-model="password">
</div>
<button class="btn btn-primary btn-lg btn-block text-uppercase font-weight-semibold" type="submit"
@click="login()">登录
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Signin',
data () {
return {
username: '',
password: ''
}
},
methods: {
login: function () {
// 判断是否输入账号
if (this.username.length > 0 && this.password.length > 0) {
// 向后端发送POST请求
let data = new FormData();
data.append('username',this.username);
data.append('password',this.password);
this.axios.post('http://127.0.0.1:8000/', data).then((res)=> {
// POST请求发送成功则获取响应结果的result
// 如果result为true,则说明存在此用户
if (res.data.result) {
// 将访问路由chat,并设置参数
this.$router.push({
path: '/product'
})
} else {
// 当前用户不存在后端的数据库
window.alert('账号不存在或异常')
// 清空用户输入的账号和密码
this.username = ''
this.password = ''
}}).catch(function () {
// PSOT请求发送失败
window.alert('账号获取失败')
// 清空用户输入的账号和密码
this.username = ''
this.password = ''
})
} else {
// 提示没有输入账号或密码
window.alert('请输入账号或密码')
}
}
}
}
</script>
<style scoped>
.text-center {
text-align: center!important;
}
.min-vh-100 {
min-height: 100vh!important;
}
.align-items-center {
align-items: center!important;
}
.justify-content-center {
justify-content: center!important;
}
.no-gutters {
margin-right: 0;
margin-left: 0;
}
.row {
display: flex;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
*, :after, :before {
box-sizing: border-box;
}
</style>
3.6 如碰到报错
error Component name “index” should always be multi-word vue/multi-word-component-names
解决方案
其一、在项目的根目录找到vue.config.js文件,没有就新创建;
其二、需要添加的代码为:
3.7 运行效果
3.8 总结vue运行原理
4. 正式开始项目
4.1 创建Vue3项目并运行
cd E:\project2024\shopping_car
vue create shopping_car_fore
cd shopping_car_fore
npm run serve
4.2配置vue.config.js
const path = require('path')
module.exports = {
publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
productionSourceMap: false,
devServer: {
hot: true,
port: 8010,
open: true,
proxy: {
'/': {
target: 'http://127.0.0.1:8000/',
changeOrigin: true,
pathRewrite: { '^/': '' },
},
},
},
configureWebpack: {
name: 'system',
resolve: {
alias: {
"~@": __dirname,
"@": path.resolve(__dirname, "./src")
}
}
},
}
4.3 public文件夹中放入文件:css,js,img,layui
4.4 src文件夹中创建项目文件夹
axios:配置vue-axios
router:配置和定义Vue的路由信息
store:配置vuex,实现vue状态管理
sytle.css:编写异常页面的样式,仅在异常页面使用,因此不能放在public的css文件夹
4.4 配置Axios和Vuex
4.4.1 安装
npm i axios vue-axios
4.4.2 axios中创建index.js
import axios from 'axios'
axios.defaults.baseURL = '/' //设置HTTP请求地址
axios.defaults.headers.post["Content-Type"] = 'application/json' //设置POST请求的数据类型
axios.defaults.timeout = 60000 //设置HTTP请求超时,单位是毫秒
axios.defaults.withCredentials = true; //默认false,代表跨域请求不提供凭据
export default axios
4.5 实现vue数据持久化
4.5.1 安装
npm i vuex vuex-persistedstate
4.5.2 store中创建index.js
import {createStore} from 'vuex' //从Vuex导入函数createStore并实例化生成store对象
import createPersistedState from "vuex-persistedstate";
const store = createStore({
state: { //设置Vuex需要保存的数据
lookImgUrl: 'http://127.0.0.1:8000',
username: '',
last_login: ''
},
mutations: { //修改参数state的数据,并且只能同步执行
setUserName(state, username){
state.username = username
},
setLastLogin(state, last_login){
state.last_login = last_login
},
},
actions: {}, // 解决参数mutations无法执行的异步问题
modules: {}, // 用于模块化处理
// 所有数据缓存到本地
plugins: [createPersistedState()], //用于为Vuex引入插件
})
export default store
4.6 编写路由
4.6.1 安装vue-router
npm i vue-router
4.6.2 编写router/index.js
import {createRouter, createWebHashHistory} from 'vue-router'
// import Home from '../components/Home.vue'
// import Commodity from '../components/Commodity.vue'
// import Detail from '../components/Detail.vue'
// import Shopper from '../components/Shopper.vue'
import Login from '../components/Login.vue'
// import Shopcart from '../components/Shopcart.vue'
// import Error from '../components/Error.vue'
// 定义路由
const routes = [
// {path: '/', component: Home, meta: {title: '首页'}},
// {path: '/commodity', component: Commodity, meta: {title: '商品列表页'}},
// // :id是设置路由变量
// {path: '/commodity/detail/:id', component: Detail, meta: {title: '商品详细页'}},
// {path: '/shopper', component: Shopper, meta: {title: '个人中心页'}},
{path: '/shopper/login', component: Login, meta: {title: '用户登录页'}},
// {path: '/shopper/shopcart', component: Shopcart, meta: {title: '我的购物车'}},
// // 路由匹配
// {path: '/:pathMatch(.*)*', component: Error, meta: {title: '页面丢失'}},
]
// 创建路由对象
const router = createRouter({
// 设置历史记录模式
history: createWebHashHistory(),
// routes: routes的缩写
routes,
})
export default router
4.7 编写组件
4.7.1 基础组件base.vue
<template>
<div class="header">
<div class="headerLayout w1200">
<div class="headerCon">
<h1 class="mallLogo">
<a href="/" title="首页">
<img src="img/logo.png">
</a>
</h1>
<div class="mallSearch">
<div class="layui-form">
<input type="text" v-model="search" required lay-verify="required" autocomplete="off"
class="layui-input"
placeholder="请输入需要的商品">
<button class="layui-btn" lay-submit lay-filter="formDemo" @click="mySearch">
<i class="layui-icon layui-icon-search"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="content content-nav-base" :class="activation">
<div class="main-nav">
<div class="inner-cont0">
<div class="inner-cont1 w1200">
<div class="inner-cont2">
<router-link :to="`/`" :class="activation == '' ?'active':''">首页</router-link>
<router-link :to="`/commodity`" :class="activation == 'commodity' ?'active':''">所有商品
</router-link>
<router-link :to="`/shopper/shopcart`" :class="activation == 'shopcart' ?'active':''">购物车
</router-link>
<router-link :to="`/shopper`" :class="activation == 'shopper' ?'active':''">个人中心</router-link>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
search: ''
}
},
props: {
activation: {
type: String,
default: ''
},
},
methods: {
// 搜索商品
mySearch: function () {
this.$router.push({path: '/commodity', query: {search: this.search, page: 1}})
},
}
}
</script>
<style scoped>
</style>
4.7.2 底边栏footer.vue
<template>
<div class="footer">
<div class="ng-promise-box">
<div class="ng-promise w1200">
<p class="text">
<a class="icon1" href="javascript:;">7天无理由退换货</a>
<a class="icon2" href="javascript:;">满99元全场免邮</a>
<a class="icon3" style="margin-right: 0" href="javascript:;">100%品质保证</a>
</p>
</div>
</div>
<div class="mod_help w1200">
<p>
<a href="javascript:;">关于我们</a>
<span>|</span>
<a href="javascript:;">帮助中心</a>
<span>|</span>
<a href="javascript:;">售后服务</a>
<span>|</span>
<a href="javascript:;">母婴资讯</a>
<span>|</span>
<a href="javascript:;">关于货源</a>
</p>
</div>
</div>
</template>
<script>
export default {
name: "Footer"
}
</script>
<style scoped>
</style>
4.8 实例化vue对象main.js
import { createApp } from 'vue'
import VueAxios from 'vue-axios'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from './axios'
import base from './components/Base'
import footer from './components/Footer'
// 创建Vue对象
const app = createApp(App)
// 注册组件
app.component('base-page', base)
app.component('footer-page', footer)
// 将路由对象绑定到Vue对象
app.use(router)
app.use(store)
// 将vue-axios与axios关联并绑定到Vue对象
app.use(VueAxios, axios)
// 挂载使用Vue对象
app.mount('#app')
4.9 根组件app.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
4.10 默认首页index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>母婴商城</title>
<link rel="stylesheet" type="text/css" href="<%= BASE_URL %>css/main.css">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" type="text/css" href="<%= BASE_URL %>layui/css/layui.css">
<script type="text/javascript" src="<%= BASE_URL %>layui/layui.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
4.11 业务组件
4.11.1 用户注册和登陆页面,login.vue
<template>
<base-page :activation="activation"></base-page>
<div class="login-bg">
<div class="login-cont w1200">
<div class="form-box">
<div class="layui-form">
<legend>手机号注册登录</legend>
<div class="layui-form-item">
<div class="layui-inline iphone">
<div class="layui-input-inline">
<i class="layui-icon layui-icon-cellphone iphone-icon"></i>
<input name="username" id="username" v-model="username"
lay-verify="required|phone" placeholder="请输入手机号"
class="layui-input">
</div>
</div>
<div class="layui-inline iphone">
<div class="layui-input-inline">
<i class="layui-icon layui-icon-password iphone-icon"></i>
<input id="password" type="password" v-model="password"
name="password" lay-verify="required|password"
placeholder="请输入密码" class="layui-input">
</div>
</div>
</div>
<p>{{ msg }}</p>
<div class="layui-form-item login-btn">
<div class="layui-input-block">
<button class="layui-btn" lay-submit="" @click="loginAndRegister">注册/登录</button>
</div>
</div>
</div>
</div>
</div>
</div>
<footer-page></footer-page>
</template>
<script>
export default {
name: "Login",
data() {
return {
activation: "login",
msg: "",
username: "",
password: ""
}
},
methods: {
loginAndRegister: function () {
console.log(this.username)
console.log(this.password)
this.axios.post('/api/v1/index/login/', {username: this.username, password: this.password}
).then(response => {
this.msg = response.data.msg
if (response.data.state === 'success') {
// 登录成功跳转个人主页
this.$store.commit('setUserName',this.username)
this.$store.commit('setLastLogin',response.data.last_login)
console.log(this.state)
this.$router.push({path: '/shopper'})
}
})
.catch(function (error) {
console.log(error)
})
}
}
}
</script>
<style scoped>
</style>
4.11.2 用户详情页面,shopper.vue
<template>
<base-page :activation="activation"></base-page>
<div class="info-list-box">
<div class="info-list">
<div class="item-box layui-clear">
<div class="item">
<div class="img">
<img src="img/portrait.png">
</div>
<div class="text">
<h4>用户:{{ username }}</h4>
<p class="data">登录时间:{{ last_login }}</p>
<div class="left-nav">
<div class="title">
<router-link :to="`/shopper/shopcart`">我的购物车</router-link>
</div>
<div class="title" @click="logout"><a>退出登录</a></div>
</div>
</div>
</div>
<div class="item1">
<div class="cart">
<div class="cart-table-th">
<div class="th th-items">
<div class="th-inner">
订单编号
</div>
</div>
<div class="th th-price">
<div class="th-inner">
订单价格
</div>
</div>
<div class="th th-amount">
<div class="th-inner">
购买时间
</div>
</div>
<div class="th th-sum">
<div class="th-inner">
订单状态
</div>
</div>
</div>
<div class="OrderList">
<div class="order-content" id="list-cont">
<ul class="item-contents layui-clear" v-for="(o, key) in orders" :key="key">
<li class="th th-items">{{ o.id }}</li>
<li class="th th-price">¥{{ o.price }}</li>
<li class="th th-amount">{{ o.created }}</li>
<li class="th th-sum">{{ o.state }}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div style="text-align: center;">
<div class="layui-box layui-laypage layui-laypage-default" id="layui-laypage-1">
<a href="javascript:;" class="layui-laypage-prev">上一页</a>
<a href="javascript:;">1</a>
<span class="layui-laypage-curr"><em class="layui-laypage-em"></em><em>2</em></span>
<a href="javascript:;">3</a>
<a href="javascript:;" class="layui-laypage-next">下一页</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Shopper",
data() {
return {
activation: 'shopper',
orders: [{}],
username: this.$store.state.username,
last_login: this.$store.state.last_login,
}
},
mounted: function () {
if (this.$store.state.username === '') {
this.$router.push({path: '/shopper/login'})
}
this.getcode(); //页面加载时自动执行
},
methods: {
getcode: function () {
var url = '/api/v1/shopper/home/'
var href = window.location.href.split('?')[1]
var t = new URLSearchParams('?' + href).get('t')
if (t !== null){
url += '?t=' + t
}
console.log(url)
this.axios.get(url).then(response => {
this.orders = response.data.data.orders
if (typeof(this.orders) == "undefined") {
this.orders = [{}]
}
console.log(this.orders)
})
.catch(function (error) {
console.log(error)
})
},
logout: function () {
this.axios.post('/api/v1/shopper/logout/').then(response => {
if (response.data.state === 'success') {
// 退出登录跳转个人主页
this.$store.commit('setUserName','')
this.$store.commit('setLastLogin','')
console.log(this.state)
this.$router.push({path: '/'})
}
})
.catch(function (error) {
console.log(error)
})
}
},
}
</script>
<style scoped>
</style>
4.11.3 报错页面,error.vue
<template>
<nav>
<div class="menu">
<p class="website_name">母婴商城</p>
</div>
</nav>
<div class="wrapper">
<div class="container">
<div id="scene" class="scene" data-hover-only="false">
<div class="circle" data-depth="1.2"></div>
<div class="one" data-depth="0.9">
<div class="content">
<span class="piece"></span>
<span class="piece"></span>
<span class="piece"></span>
</div>
</div>
<div class="two" data-depth="0.60">
<div class="content">
<span class="piece"></span>
<span class="piece"></span>
<span class="piece"></span>
</div>
</div>
<div class="three" data-depth="0.40">
<div class="content">
<span class="piece"></span>
<span class="piece"></span>
<span class="piece"></span>
</div>
</div>
<p class="p404" data-depth="0.50">404</p>
<p class="p404" data-depth="0.10">404</p>
</div>
<div class="text">
<article>
<button><router-link :to="`/`">返回首页</router-link></button>
</article>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Error"
}
</script>
<style src="@/assets/style.css" scoped>
</style>
4.12 测试页面
4.12.1 测试链接
http://localhost:8010/#/shopper/login
5. 常见报错
5.1 如果后台一直报错
“GET /ws HTTP/1.1”
则修改vue.config.js
5.2 后台接收不到数据
前端是这样的形式
报错要改成绿色方框的内容