Vue3项目实战商店后台管理系统
1.课程地址
https://www.bilibili.com/video/BV15v4y1b7Bw/?vd_source=240d9002f7c7e3da63cd9a975639409a#reply151314916928
2. 准备工作(mac)
ncpm 安装 sudo npm install -g cnpm --registry=https://registry.npm.taobao.org
全局安装 vue sudo cnpm i -g vue@next
全局安装 vue/cli sudo cnpm install -g @vue/cli
全局安装yarn sudo cnpm i yarn -g
3.创建项目
vue create shop
4. 创建仓库
在码云创建仓库
https://gitee.com/Blue_Pepsi_Cola/shop
5. reactive、toRefs
reactive 是 Vue3 中提供的实现响应式数据的方法。
链接:https://blog.csdn.net/weixin_44283432/article/details/111190176
toRefs函数的作用是将响应式对象中的所有属性转换为单独的响应式数据,对象成为普通对象,并且值是关联的。
链接:https://blog.csdn.net/lianghecai52171314/article/details/125706376
<template>
<h1>hello world!</h1>
<p v-text="name"></p>
<!-- v-text简写 -->
<p>{
{ age }}</p>
<p>{
{ name}}</p>
</template>
<script>
import {
reactive, toRefs } from "vue";
export default {
name: "home",
setup() {
// beforeCreate和created 连接声明周期
const data = reactive({
name: "小红",
age: 20,
});
return {
...toRefs(data)
};
},
};
</script>
6. v-if 等
<template>
<h1>hello world!</h1>
<p v-text="name"></p>
<!-- v-text简写 -->
<p>{
{ age }}</p>
<p>{
{ name }}</p>
<p v-html="info"></p>
<p v-bind:data="dataVal">我有属性</p>
<p :class="{ red: isRed }">我是红色的</p>
<p v-if="isTrue">我是if存在</p>
</template>
<script>
import {
reactive, toRefs } from "vue";
export default {
name: "home",
setup() {
// beforeCreate和created 连接声明周期
const data = reactive({
name: "小红",
age: 20,
info: "<i>我是斜体字</i>",
dataVal: 2,
isRed: true,
isTrue: true,
});
return {
...toRefs(data),
};
},
};
</script>
<style>
.red {
color: red;
}
</style>
7.生命周期
<template>
<div>
<h2>vue3的生命周期</h2>
<div id="dom">{
{ msg }}</div>
</div>
</template>
<script>
import {
reactive,
toRefs,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
} from "vue";
export default {
name: "about",
setup() {
const data = reactive({
msg: "你好!",
});
// 数据渲染前
onBeforeMount(() => {
console.log("onBeforeMount", document.querySelector("#dom"));
});
// 数据渲染后
onMounted(() => {
console.log("onMounted", document.querySelector("#dom"));
setTimeout(() => {
data.msg = "hello";
}, 8000);
});
// dom更新前
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
});
//dom更新后
onUpdated(() => {
console.log("onUpdated");
});
return {
...toRefs(data),
};
},
};
</script>
8. 事件绑定
8.1 点击事件
<template>
<div>
<h2>vue3的生命周期</h2>
<div id="dom">{
{ msg }} -- {
{ num }}</div>
<!-- v-on:事件名="事件方法" 绑定事件 -->
<!-- 事件及方法直接声明在 setup 内 -->
<button v-on:click="handleClick">click me</button>
</div>
</template>
<script>
import {
reactive,
toRefs,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
} from "vue";
export default {
name: "about",
setup() {
const data = reactive({
msg: "你好!",
});
// 数据渲染前
onBeforeMount(() => {
console.log("onBeforeMount", document.querySelector("#dom"));
});
// 数据渲染后
onMounted(() => {
console.log("onMounted", document.querySelector("#dom"));
setTimeout(() => {
data.msg = "hello";
}, 8000);
});
// dom更新前
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
});
//dom更新后
onUpdated(() => {
console.log("onUpdated");
});
// 事件及方法
const handleClick = () => {
alert("Nihao");
};
return {
...toRefs(data),
handleClick,
};
},
};
</script>
8.2 input事件
<template>
<div>
<h2>vue3的生命周期</h2>
<div id="dom">{
{ msg }} -- {
{ num }}</div>
<!-- v-on:事件名="事件方法" 绑定事件 简写 @:事件名="时间方法"-->
<!-- 事件及方法直接声明在 setup 内 -->
<button v-on:click="handleClick">click me</button>
<hr />
<!-- v-model 双向绑定 -->
<!-- input: 输入时间
blur:失去焦点
focus:获取焦点
change: 内容更改-->
<input type="text" placeholder="请输入姓名" v-model="userName" />
<input type="text" placeholder="请输入电话号" v-model="userPhone" @:focus="handleFocus" @blur="handleBlur" />
<textarea
placeholder="请输入您的建议"
cols="30"
rows="10"
v-model="userInput"
></textarea>
<p>{
{ userName }} ---- {
{ userInput }}</p>
<button @click="submit">提交</button>
</div>
</template>
<script>
import {
reactive,
toRefs,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
} from "vue";
export default {
name: "about",
setup() {
const data = reactive({
msg: "你好!",
mesg2: "hello world",
num: 0,
userName: "",
userInput: "",
userPhone: "",
});
// 数据渲染前
onBeforeMount(() => {
console.log("onBeforeMount", document.querySelector("#dom"));
});
// 数据渲染后
onMounted(() => {
console.log("onMounted", document.querySelector("#dom"));
setTimeout(() => {
data.msg = "hello";
}, 8000);
});
// dom更新前
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
});
//dom更新后
onUpdated(() => {
console.log("onUpdated");
});
// 事件及方法
const handleClick = () => {
alert("Nihao");
};
const submit = () => {
alert(
`${
data.userName}她${
data.userInput}她的电话号码是${
data.userPhone}`
);
};
const handleFocus = () => {
console.log("获取焦点");
};
const handleBlur = () => {
console.log("失去焦点");
};
return {
...toRefs(data),
handleClick,
submit,
handleFocus,
handleBlur
};
},
};
</script>
9.路由及路由配置
路由的位置在router下的index.js
{
path: "/about",
name: "about",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
// 懒加载引入组件
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
},
{
path: "/abc",
// 页面的名字
name: "abc",
// 组件名 通过头部引用
component: Abc,
},
其中import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
是懒加载组件,path: "/about"
是路由。
在src下的App.vue引入
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
<router-link to="/abc">Abc</router-link>
</nav>
<router-view/>
</template>
10. element-plus
1.官网 https://element-plus.gitee.io/zh-CN/
2. 安装
# 选择一个你喜欢的包管理器
# NPM
$ npm install element-plus --save
# Yarn
$ yarn add element-plus
# pnpm
$ pnpm install element-plus
3.项目引入
4.布局
<template>
<div class="common-layout">
<el-container>
<el-header>Header</el-header>
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-main>Main</el-main>
</el-container>
</el-container>
</div>
</template>
11. 新的布局及按钮添加
LayOut.vue
<template>
<div class="common-layout">
<el-container>
<el-header class="common-header flex-float">
<div class="flex">
<img class="logo" src="../../assets/logo.png" alt="" />
<h1 class="title">商铺后台管理系统</h1>
</div>
<el-button type="danger">退出</el-button>
</el-header>
<el-container>
<el-aside class="common-aside" width="200px"></el-aside>
<el-main>
<!-- router-view -->
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
export default {
name: "layout",
};
</script>
<style>
.el-container {
height: 100vh;
overflow: hidden;
}
.common-header {
background: rgb(48, 55, 65);
}
.common-aside {
background: rgb(48, 55, 65);
}
.logo {
width: 80px;
}
.title {
color: white;
}
</style>
并在APP.vue中添加
<style>
/* flex布局类名 */
.flex-float {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex {
display: flex;
align-items: center;
}
</style>
12. icon
安装icon
# 选择一个你喜欢的包管理器
# NPM
$ npm install @element-plus/icons-vue
# Yarn
$ yarn add @element-plus/icons-vue
# pnpm
$ pnpm install @element-plus/icons-vue
安装后需要全局注册图标
// main.ts
// 如果您正在使用CDN引入,请删除下面一行。
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
13. menu
地址: https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
<!-- el-menu 整个导航 background-color背景颜色-->
<el-menu background-color="none"
text-color="#fff"
:router="true">
<!-- 一级导航 -->
<el-sub-menu index="1">
<template #title>
<!-- icon -->
<el-icon><Avatar /></el-icon>
<span>账号管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="/user">账号列表</el-menu-item>
</el-menu-item-group>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>
<el-icon><Management /></el-icon>
<span>角色管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="/roles">账号列表</el-menu-item>
</el-menu-item-group>
</el-sub-menu>
</el-menu>
效果
14. 登录页面
1.form表单 https://element-plus.gitee.io/zh-CN/component/form.html
2.button
<template>
<div class="login_wrap"></div>
<div class="form_wrap">
<el-form
ref="formRef"
:model="loginData"
label-width="100px"
class="demo-dynamic"
>
<el-form-item
prop="username"
label="用户名"
:rules="[
{
required: true,
message: '此项目为必填项',
trigger: 'blur',
},
]"
>
<el-input v-model="loginData.username" />
</el-form-item>
</el-form>
<el-form
ref="formRef"
:model="loginData"
label-width="100px"
class="demo-dynamic"
>
<el-form-item
prop="password"
label="密码"
:rules="[
{
required: true,
message: '此项目为必填项',
trigger: 'blur',
},
]"
>
<el-input v-model="loginData.password" type="password"/>
</el-form-item>
</el-form>
<el-button type="primary" class="login_bt" >登录</el-button>
</div>
</template>
<script>
import {
reactive, toRefs } from "vue";
export default {
name: "login",
setup() {
const data = reactive({
loginData: {
username: "",
password: "",
},
});
return {
...toRefs(data),
};
},
};
</script>
<style scoped>
.login_wrap {
width: 100%;
height: 100vh;
background: rgb(28, 76, 166);
position: relative;
}
.form_wrap {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding:30px 50px;
border-radius: 5px;
}
.login_bt{
display: block;
margin: 10px auto;
}
</style>
15. Vuex
// 后续补充
16. 路由守卫
在router的index.js中
在store/state
的userinfo.state.js
中
在store的index.js
中
实现输入用户名密码即可登录
17. 路由守卫登录状态存储
由于原来用户的登录是通过路由守卫判断uInfo是否存在username
属性,即不为空。
但是每次刷新后uInfo为空,就又会跳转回login页面。
这里在login.vue中通过localStorage.setItem("loginData", JSON.stringify(data.loginData));
存储,刷新后不会再清空。
18.退出功能
19.封装axios
19.1 查询axios的所有版本
npm view axios versions --json
安装
import axios from "axios";
import { ElLoading } from "element-plus";
import { ElMessage } from "element-plus";
// 使用create创建axios实例
let loadingObj = null;
const Service = axios.create({
timeOut: 8000,
baseURL: "http://XXX",
headers: {
"content-type": "application/json;charset=utf-8",
},
});
// 请求拦截 增加loading,对请求做统一处理
Service.interceptors.request.use((config) => {
loadingObj = ElLoading.service({
lock: true,
text: "Loading",
background: "rgba(0, 0, 0, 0.7)",
});
return config;
});
// 响应拦截 对返回值做统一处理
Service.interceptors.response.use(
(response) => {
loadingObj.close();
return response.data;
},
(error) => {
loadingObj.close();
ElMessage({
message: "服务器错误",
type: "error",
duration: 2000,
});
}
);
20.
// TODO
附录
1.git https://gitee.com/all-about-git
2.完整项目代码 https://github.com/act27/Store-backstage