一、创建项目
1.创建项目
2.安装各种依赖
npm install
@highlightjs/vue-plugin
@moefe/vue-aplayer
aplayer
axios
docx-preview
dplayer
element-plus
highlight.js
js-md5
sass
sass-loader
spark-md5
vue-clipboard3
vue-cookies
vue-pdf-embed
vue-router
vue3-pdfjs
xlsx
--save
3.修改端口号
vite.config.js
server: {
port: 1024,
hmr: true,
proxy: {
'/api': {
target: 'http://localhost:7090',
changeOrigin: true,
pathRewrite: {
'^api': '/api',
},
},
},
},
4.引入各项安装
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
//引入element plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//图标图标在附件中
import '@/assets/icon/iconfont.css'
import '@/assets/base.scss'
// 引入cookies
import { VueCookies } from 'vue-cookies'
const app = createApp(App)
app.use(router)
app.use(ElementPlus)
app.mount('#app')
index.html
<script src="/easypan-front/public/hls.min.js"></script>
<title>uu云盘</title>
二、登录注册页面
可以免费下载图片: undraw
(需要前后端源码的可以评论或者私信),这一部分记录的不全面
src/views/Login.vue
<template>
<div class="login-body">
<div class="bg"></div>
<div class="login-panel">
<el-form
class="login-register"
:model="formData"
:rules="rules"
ref="formDataRef"
@submit.prevent>
<div class="login-title">uu云盘</div>
<!-- input输入 -->
<el-form-item prop="email">
<el-input
clearable
size="large"
placeholder="请输入邮箱"
v-model.trim="formData.email"
maxLength="150"
>
<!-- 插入图标 -->
<template #prefix>
<span class="iconfont icon-account"></span>
</template>
</el-input>
</el-form-item>
<!--登录密码 -->
<el-form-item prop="password" v-if="opType==1">
<el-input
type="password"
size="large"
v-model="formData.password"
placeholder="请输入密码"
show-password
>
<!-- 插入图标 -->
<template #prefix>
<span class="iconfont icon-password"></span>
</template>
</el-input>
</el-form-item>
<!-- 注册 -->
<div v-if="opType==0||opType==2">
<!-- 邮箱验证码 -->
<el-form-item prop="emailCode">
<div class="send-email-panel">
<el-input v-model.trim="formData.emailCode" placeholder="请输入邮箱验证码" size="large" clearable>
<template #prefix>
<span class="iconfont icon-checkcode"></span>
</template>
</el-input>
<el-button class="send-mail-btn" type="primary" size="large" @click="getEmailCode">获取验证码</el-button>
</div>
<!-- 气泡框 -->
<el-popover placement="left" :width="500" trigger="click">
<div>
<p>1、在垃圾箱中查找邮箱验证码</p>
<p>2、在邮箱中头像->设置->反垃圾->白名单->设置邮件地址白名单</p>
<p>
3、将邮箱【laoluo@wuhancoder.com】添加到白名单不知道怎么设置?
</p>
</div>
<template #reference>
<span class="a-link" :style="{ 'font-size': '14px' }"
>未收到邮箱验证码?</span
>
</template>
</el-popover>
</el-form-item>
<!-- 昵称,注册时0才有昵称 -->
<el-form-item prop="nickName" v-if="opType == 0">
<el-input
size="large"
clearable
placeholder="请输入昵称"
v-model.trim="formData.nickName"
maxLength="20"
>
<template #prefix>
<span class="iconfont icon-account"></span>
</template>
</el-input>
</el-form-item>
<!-- 输入密码 -->
<!-- 注册密码,找回密码 -->
<el-form-item prop="registerPassword">
<el-input
type="password"
size="large"
placeholder="请输入密码"
v-model.trim="formData.registerPassword"
show-password
>
<template #prefix>
<span class="iconfont icon-password"></span>
</template>
</el-input>
</el-form-item>
<!-- 再次输入密码 -->
<el-form-item prop="reRegisterPassword">
<el-input
type="password"
size="large"
placeholder="请再次输入密码"
v-model.trim="formData.reRegisterPassword"
show-password
>
<template #prefix>
<span class="iconfont icon-password"></span>
</template>
</el-input>
</el-form-item>
</div>
<!-- 验证码 -->
<el-form-item prop="checkCode">
<div class="check-code-panel">
<el-input
size="large"
placeholder="请输入验证码"
v-model="formData.checkCode"
@keyup.enter="doSubmit"
>
<!-- 插入图标 -->
<template #prefix>
<span class="iconfont icon-checkcode"></span>
</template>
</el-input>
<img :src="checkCodeUrl" class="check-code" @click="changeCheckCode(0)">
</div>
</el-form-item>
<!-- 登录 -->
<el-form-item v-if="opType==1">
<div class="rememberme-panel">
<el-checkbox v-model="formData.rememberMe">记住我</el-checkbox>
</div>
<div class="no-account">
<a href="javascript:void(0)" class="a-link" @click="showPanel(2)">忘记密码?</a>
<a href="javascript:void(0)" class="a-link" @click="showPanel(0)">没有账号?</a>
</div>
</el-form-item>
<!-- 找回密码时2想起密码,去登陆?点击去登陆1 -->
<el-form-item v-if="opType == 2">
<a href="javascript:void(0)" class="a-link" @click="showPanel(1)">去登陆?</a>
</el-form-item>
<!-- 注册时0,想起已有账号?点击去登陆1 -->
<el-form-item v-if="opType == 0">
<a href="javascript:void(0)" class="a-link" @click="showPanel(1)">已有账号?</a>
</el-form-item>
<!-- 注册登录重置按钮 -->
<el-form-item>
<el-button class="op-btn" type="primary" size="large" @click="doSubmit">
<span v-if="opType == 0">注册</span>
<span v-if="opType == 1">登录</span>
<span v-if="opType == 2">重置密码</span>
</el-button>
</el-form-item>
<!-- qq登录 -->
<div class="login-btn-qq" v-if="opType == 1">
快捷登录<img src="@/assets/qq.png" @click="qqLogin" />
</div>
</el-form>
</div>
<Dialog
:show="dialogConfig4SendMailCode.show"
:title="dialogConfig4SendMailCode.title"
:buttons="dialogConfig4SendMailCode.buttons"
width="500px"
:showCancel="false"
@close="dialogConfig4SendMailCode.show = false">
<el-form
:model="formData4SendMailCode"
:rules="rules"
ref="formData4SendMailCodeRef"
label-width="80px"
>
<!--展示邮箱-->
<el-form-item label="邮箱">
{{ formData.email }}
</el-form-item>
<!--验证码输入-->
<el-form-item label="验证码" prop="checkCode">
<div class="check-code-panel">
<el-input
size="large"
placeholder="请输入验证码"
v-model.trim="formData4SendMailCode.checkCode"
>
<template #prefix>
<span class="iconfont icon-checkcode"></span>
</template>
</el-input>
<img
:src="checkCodeUrl4SendMailCode"
class="check-code"
@click="changeCheckCode(1)"
/>
</div>
</el-form-item>
</el-form>
</Dialog>
</div>
</template>
<script setup>
import { ref, reactive, getCurrentInstance,nextTick,onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
import md5 from "js-md5";
// import axios from 'axios';
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();
// 定义api
const api = {
checkCode:'/api/checkCode',
sendMailCode: "/sendEmailCode",
register: "/register",
login: "/login",
resetPwd: "/resetPwd",
qqlogin: "/qqlogin",
}
// 操作类型 0:注册 1:登录 2:重置密码
const opType = ref(1)
const showPanel=(type)=>{
opType.value=type
resetForm();
}
onMounted(() => {
showPanel(1);
});
// 校验再次输入的密码
const checkRePassword = (rule, value, callback) => {
if (value !== formData.value.registerPassword) {
callback(new Error(rule.message));
} else {
callback();
}
};
// 登录页面
const formData = ref({});
const formDataRef = ref();
// 校验规则
const rules = {
email: [
{ required: true, message: "请输入邮箱" },
{ validator: proxy.Verify.email, message: "请输入正确的邮箱" },
],
password: [{ required: true, message: "请输入密码" }],
emailCode: [{ required: true, message: "请输入邮箱验证码" }],
nickName: [{ required: true, message: "请输入昵称" }],
registerPassword: [
{ required: true, message: "请输入密码" },
{
validator: proxy.Verify.password,
message: "密码只能是数字,字母,特殊字符 8-18位",
},
],
reRegisterPassword: [
{ required: true, message: "请再次输入密码" },
{
validator: checkRePassword,
message: "两次输入的密码不一致",
},
],
checkCode: [{ required: true, message: "请输入图片验证码" }],
};
// 连接后台,显示验证码
const checkCodeUrl = ref(api.checkCode)
const checkCodeUrl4SendMailCode = ref(api.checkCode)
// 验证码变化
const changeCheckCode=(type)=>{
if(type==0){
checkCodeUrl.value =
api.checkCode + "?type=" + type + "&time=" + new Date().getTime();
}else{
checkCodeUrl4SendMailCode.value =
api.checkCode + "?type=" + type + "&time=" + new Date().getTime();
}
}
// 注册界面 发送邮箱验证码 定义属性
const formData4SendMailCode = ref({});
const formData4SendMailCodeRef = ref();
const dialogConfig4SendMailCode = reactive({
show: false,
title: "发送邮箱验证码",
buttons: [
{
type: "primary",
text: "发送验证码",
click: (e) => {
sendEmailCode();
// submitForm()
},
},
],
});
// 获取邮箱验证码
const getEmailCode = () => {
formDataRef.value.validateField("email", (valid) => {
if (!valid) {
return;
}
dialogConfig4SendMailCode.show = true;
// 清空验证码
nextTick(() => {
changeCheckCode(1);
formData4SendMailCodeRef.value.resetFields();
formData4SendMailCode.value = {
email: formData.value.email,
};
});
})
}
// 发送邮箱验证码
// 0:注册 1:找回密码
const sendEmailCode = () => {
formData4SendMailCodeRef.value.validate(async (valid) => {
if (!valid) {
return;
}
const params = Object.assign({}, formData4SendMailCode.value);
params.type = opType.value == 0 ? 0 : 1;
let result = await proxy.Request({
url: api.sendMailCode,
params: params,
errorCallback: () => {
changeCheckCode(1);
},
});
if (!result) {
return;
}
proxy.Message.success("验证码发送成功,请登录邮箱查看");
dialogConfig4SendMailCode.show = false;
})
}
// 重置表单(清空表单)
const resetForm = () => {
nextTick(() => {
changeCheckCode(0);
formDataRef.value.resetFields();
formData.value = {};
// 登录
if (opType.value == 1) {
const cookieLoginInfo = proxy.VueCookies.get("loginInfo");
if (cookieLoginInfo) {
formData.value = cookieLoginInfo;
}
}
});
};
// 登录、注册、重置、提交表单
const doSubmit = () => {
formDataRef.value.validate(async (valid) => {
if (!valid) {
return;
}
let params = {};
Object.assign(params, formData.value);
// 注册
if (opType.value == 0 || opType.value == 2) {
params.password = params.registerPassword;
delete params.registerPassword;
delete params.reRegisterPassword;
}
// 登录
if (opType.value == 1) {
let cookieLoginInfo = proxy.VueCookies.get("loginInfo");
let cookiePassword =
cookieLoginInfo == null ? null : cookieLoginInfo.password;
// 现在的密码和原来的密码不相等的情况下,对当前密码进行md5加密
if (params.password !== cookiePassword) {
params.password = md5(params.password);
}
}
// 发送http请求
let url = null;
if (opType.value == 0) {
url = api.register;
} else if (opType.value == 1) {
url = api.login;
} else if (opType.value == 2) {
url = api.resetPwd;
}
let result = await proxy.Request({
url: url,
params: params,
errorCallback: () => {
changeCheckCode(0);
},
});
if (!result) {
return;
}
// 注册返回
if (opType.value == 0) {
proxy.Message.success("注册成功,请登录");
showPanel(1);
} else if (opType.value == 1) {
// 检查是否点击 “记住我”
if (params.rememberMe) {
const loginInfo = {
email: params.email,
password: params.password,
rememberMe: params.rememberMe,
};
// 将存储七天
proxy.VueCookies.set("loginInfo", loginInfo, "7d");
} else {
proxy.VueCookies.remove("loginInfo");
}
proxy.Message.success("登录成功");
// 存储cookie
proxy.VueCookies.set("userInfo", result.data, 0);
// 重定向到原始页面
const redirectUrl = route.query.redirectUrl || "/";
router.push(redirectUrl);
} else if (opType.value == 2) {
// 重置密码
proxy.Message.success("重置密码成功,请登录");
showPanel(1);
}
});
};
// qq登录
const qqLogin = async () => {
let result = await proxy.Request({
url: api.qqlogin,
params: {
callbackUrl: route.query.redirectUrl || "",
},
});
if (!result) return;
proxy.VueCookies.remove("userInfo");
document.location.href = result.data;
};
</script>
<style lang="scss" scoped>
.login-body {
height: calc(100vh);
// 把背景图像扩展至足够大,以使背景图像完全覆盖背景区域。
background-size: cover;
background: url("../assets/login_bg.jpg");
display: flex;
.bg {
flex: 1;
background-size: cover;
background-position: center;
background-size: 800px;
background-repeat: no-repeat;
background-image: url("../assets/login_img.png");
}
.login-panel {
width: 430px;
margin-right: 15%;
margin-top: calc((100vh - 500px) / 2);
.login-register {
padding: 25px;
background: #fff;
border-radius: 5px;
.login-title {
text-align: center;
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
}
.send-email-panel {
display: flex;
width: 100%;
justify-content: space-between;
.send-mail-btn {
margin-left: 5px;
}
}
.rememberme-panel {
width: 100%;
}
.no-account {
width: 100%;
display: flex;
justify-content: space-between;
}
.op-btn {
width: 100%;
}
}
}
.check-code-panel {
width: 100%;
display: flex;
.check-code {
margin-left: 5px;
cursor: pointer;
}
}
.login-btn-qq {
margin-top: 20px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
img {
cursor: pointer;
margin-left: 10px;
width: 20px;
}
}
}
</style>
三、构建框架页
(1)构建基本框架
src/views/Framework.vue
<template> <div class="framework"> <!-- 头部 --> <div class="header"> <!-- 左上角logo --> <div class="logo"> <span class="iconfont icon-pan"></span> <div class="name">uu云盘</div> </div> <!-- 右侧消息弹框 --> <div class="right-panel"> <!-- 气泡框 --> <el-popover :width="800" trigger="click" :v-model:visible="showUploader" :offset="20" transition="none" :hide-after="0" :popper-style="{ padding: '0px' }"> <template #reference> <span class="iconfont icon-transfer"></span> </template> <template #default> <Uploader ref="uploaderRef" @uploadCallback="uploadCallbackHandler"></Uploader> </template> </el-popover> <!-- 下拉框 --> <el-dropdown> <!-- 用户信息 --> <div class="user-info"> <!-- 头像 --> <div class="avatar"></div> <!-- 昵称 --> <span class="nick-name">{{ userInfo.nickName }}</span> </div> <template #dropdown> <el-dropdown-menu> <el-dropdown-item>修改头像</el-dropdown-item> <el-dropdown-item>修改密码</el-dropdown-item> <el-dropdown-item>退出</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </div> <!-- 主体 --> <div class="body"> <!-- 最左侧菜单栏 一级目录 --> <div class="left-sider"> <div class="menu-list"> <div :class="['menu-item',item.menuCode==currentMenu.menuCode?'active':'']" v-for="item in menus" @click="jump(item)"> <!-- 一级菜单图标 --> <div :class="['iconfont','icon-'+item.icon]"></div> <!-- 一级菜单名字 --> <div class="text">{{ item.name }}</div> </div> </div> <!-- 二级菜单目录 --> <div class="menu-sub-list"> <div :class="[' menu-item-sub',currentPath==sub.path?'active':'']" v-for="sub in currentMenu.children" @click="jump(sub)"> <!-- 图标 --> <span :class="['iconfont', 'icon-' + sub.icon]" v-if="sub.icon"></span> <!-- 名字 --> <span class="text">{{ sub.name }}</span> </div> <div class="tips" v-if="currentMenu && currentMenu.tips">{{ currentMenu.tips }}</div> <!-- 下方空间使用 --> <div class="space-info"> <div>空间使用</div> <div class="percent"></div> </div> </div> </div> <!-- 中间主体内容 --> <div class="body-content"> <router-view v-slot="{ Component }"> <component :is="Component"></component> </router-view> </div> </div> </div> </template> <script setup> import { ref, reactive, getCurrentInstance, watch, nextTick, computed, } from "vue"; import { useRouter, useRoute } from "vue-router"; const router = useRouter(); const route = useRoute(); const {proxy} = getCurrentInstance(); const userInfo = ref({ nickName:'张三' }); // 菜单栏 const menus = [ { icon: "cloude", name: "首页", menuCode: "main", path: "/main/all", allShow: true, children: [ { icon: "all", name: "全部", category: "all", path: "/main/all", }, { icon: "video", name: "视频", category: "video", path: "/main/video", }, { icon: "music", name: "音频", category: "music", path: "/main/music", }, { icon: "image", name: "图片", category: "image", path: "/main/image", }, { icon: "doc", name: "文档", category: "doc", path: "/main/doc", }, { icon: "more", name: "其他", category: "others", path: "/main/others", }, ], }, { path: "/myshare", icon: "share", name: "分享", menuCode: "share", allShow: true, children: [ { name: "分享记录", path: "/myshare", }, ], }, { path: "/recycle", icon: "del", name: "回收站", menuCode: "recycle", tips: "回收站为你保存10天内删除的文件", allShow: true, children: [ { name: "删除的文件", path: "/recycle", }, ], }, { path: "/settings/fileList", icon: "settings", name: "设置", menuCode: "settings", allShow: false, children: [ { name: "用户文件", path: "/settings/fileList", }, { name: "用户管理", path: "/settings/userList", }, { path: "/settings/sysSetting", name: "系统设置", }, ], }, ]; const currentMenu = ref({}); const currentPath = ref(); // 点击一级菜单栏跳转事件回调 const jump = (data) =>{ // 判断如果没有路径或者点击的还是当前的路径就不跳转 if(!data.path||data.menuCode==currentMenu.value.menuCode){ return; } // 否则就跳转到data.path router.push(data.path); } // 设置当前菜单栏 const setMenu = (menuCode, path) => { const menu = menus.find((item) => { return item.menuCode === menuCode; }); currentMenu.value = menu; currentPath.value = path; }; // 监听 watch( () => route, (newVal, oldVal) => { if (newVal.meta.menuCode) { setMenu(newVal.meta.menuCode, newVal.path); } }, { immediate: true, deep: true } ); </script> <style lang="scss" scoped> .header { box-shadow: 0 3px 10px 0 rgb(0 0 0 / 6%); height: 56px; padding-left: 24px; padding-right: 24px; position: relative; z-index: 200; display: flex; align-items: center; justify-content: space-between; .logo { display: flex; align-items: center; .icon-pan { font-size: 40px; color: #1296db; } .name { font-weight: bold; margin-left: 5px; font-size: 25px; color: #05a1f5; } } .right-panel { display: flex; align-items: center; .icon-transfer { cursor: pointer; } .user-info { margin-right: 10px; display: flex; align-items: center; cursor: pointer; // 头像 .avatar { margin: 0px 5px 0px 15px; } // 昵称 .nick-name { color: #05a1f5; } } } } .body { display: flex; .left-sider { border-right: 1px solid #f1f2f4; display: flex; .menu-list { height: calc(100vh - 56px); width: 80px; box-shadow: 0 3px 10px 0 rgb(0 0 0 / 6%); border-right: 1px solid #f1f2f4; .menu-item { text-align: center; font-size: 14px; font-weight: bold; padding: 20px 0px; cursor: pointer; &:hover { background: #f3f3f3; } .iconfont { font-weight: normal; font-size: 28px; } } .active { .iconfont { color: #06a7ff; } .text { color: #06a7ff; } } } .menu-sub-list { width: 200px; padding: 20px 10px 0px; position: relative; .menu-item-sub { text-align: center; line-height: 40px; border-radius: 5px; cursor: pointer; &:hover { background: #f3f3f3; } .iconfont { font-size: 14px; margin-right: 20px; } .text { font-size: 13px; } } .active { background: #eef9fe; .iconfont { color: #05a1f5; } .text { color: #05a1f5; } } .tips { margin-top: 10px; color: #888888; font-size: 13px; } .space-info { position: absolute; bottom: 10px; width: 100%; padding: 0px 5px; .percent { padding-right: 10px; } .space-use { margin-top: 5px; color: #7e7e7e; display: flex; justify-content: space-around; .use { flex: 1; } .iconfont { cursor: pointer; margin-right: 20px; color: #05a1f5; } } } } } .body-content { flex: 1; width: 0; padding-left: 20px; } } </style>
(2)添加路由
./src/router/index.js{ path: '/', name: 'Framework', component: ()=> import('@/views/Framework.vue'), children: [{ path: '/', redirect: "/main/all" }, { path: '/main/:category', name: '首页', meta: { needLogin: true, menuCode: "main" }, component: () => import ("@/views/main/Main.vue") }, { path: '/myshare', name: '我的分享', meta: { needLogin: true, menuCode: "share" }, component: () => import ("@/views/share/Share.vue") }, { path: '/recycle', name: '回收站', meta: { needLogin: true, menuCode: "recycle" }, component: () => import ("@/views/recycle/Recycle.vue") }, { path: '/settings/sysSetting', name: '系统设置', meta: { needLogin: true, menuCode: "settings" }, component: () => import ("@/views/admin/SysSettings.vue") }, { path: '/settings/userList', name: '用户管理', meta: { needLogin: true, menuCode: "settings" }, component: () => import ("@/views/admin/UserList.vue") }, { path: '/settings/fileList', name: '用户文件', meta: { needLogin: true, menuCode: "settings" }, component: () => import ("@/views/admin/FileList.vue") }, ] },
(3)添加如下组件
(4)main.js引入使用
import Avatar from '@/components/Avatar.vue'
(5)./src/components/Avatar.vue<template> <!-- 头像组件 --> <span class="avatar"> <img :src="avatar && avatar != '' ? avatar : `${proxy.globalInfo.avatarUrl}${userId}?${timestamp}` " v-if="userId" /> </span> </template> <script setup> import { getCurrentInstance } from "vue"; // 获取当前组件实例 const { proxy } = getCurrentInstance(); const props = defineProps({ userId: { type: String, }, avatar: { type: String, }, timestamp: { type: Number, default: 0, }, width: { type: Number, default: 40, }, }); </script> <style lang="scss" scoped> .avatar { display: flex; width: 40px; height: 40px; border-radius: 50%; overflow: hidden; img { width: 100%; object-fit: cover; } } </style>
四、上传头像
(1)封装UpdateAvatar.vue组件
./src/views/UpdateAvatar.vue<template> <div> <!-- 修改头像弹出框 --> <Dialog :show="dialogConfig.show" :title="dialogConfig.title" :buttons="dialogConfig.buttons" width="500px" :showCancel="true" @close="dialogConfig.show = false" > <el-form :model="formData" ref="formDataRef" label-width="80px" @submit.prevent > <!--显示昵称--> <el-form-item label="昵称"> {{ formData.nickName }} </el-form-item> <!--显示头像--> <el-form-item label="头像"> <AvatarUpload v-model="formData.avatar"></AvatarUpload> </el-form-item> </el-form> </Dialog> </div> </template> <script setup> // 引入头像上传组件 import AvatarUpload from "@/components/AvatarUpload.vue"; import { ref, reactive, getCurrentInstance } from "vue"; import { useRouter, useRoute } from "vue-router"; const { proxy } = getCurrentInstance(); const router = useRouter(); const route = useRoute(); const api = { updateUserAvatar: "updateUserAvatar", }; const formData = ref({}); const formDataRef = ref(); const show = (data) => { formData.value = Object.assign({}, data); formData.value.avatar = { userId: data.userId, qqAvatar: data.avatar }; dialogConfig.value.show = true; }; // 子组件暴露自己的属性 // 父组件需要调用子组件的方法父组件需要调用子组件的方法, // 或者访问子组件的变量 defineExpose({ show }); // 定义弹出框的属性 const dialogConfig = ref({ show: false, title: "修改头像", buttons: [ { type: "primary", text: "确定", click: (e) => { submitForm(); }, }, ], }); // 1、在子组件中调用defineEmits并定义要发射给父组件的方法 // 2、使用defineEmits会返回一个方法,使用一个变量emit(变量名随意)去接收 // 3、在子组件要触发的方法中,调用emit并传入发射给 父组件的方法(updateAvatar) const emit = defineEmits(["updateAvatar"]); // 点击确定 提交的回调 const submitForm = async () => { // 如果上传的不是文件,将关闭弹出框 if (!(formData.value.avatar instanceof File)) { dialogConfig.value.show = false; } let result = await proxy.Request({ url: api.updateUserAvatar, params: { avatar: formData.value.avatar, }, }); if (!result) { return; } dialogConfig.value.show = false; const cookeUserInfo = proxy.VueCookies.get("userInfo"); delete cookeUserInfo.avatar; proxy.VueCookies.set("userInfo", cookeUserInfo, 0); // 在子组件要触发的方法中,调用emit并传入发射给 父组件的方法(updateAvatar) emit("updateAvatar"); }; </script> <style lang="scss"> </style>
(2)封装全局组件AvatarUpload(上传图片)
./src/commponents/AvatarUpload.vue<template> <!-- 头像上传 --> <div class="avatar-upload"> <div class="avatar-show"> <template v-if="localFile"> <img :src="localFile" /> </template> <template v-else> <img :src="`${modelValue.qqAvatar}`" v-if="modelValue && modelValue.qqAvatar" /> <img :src="`/api/getAvatar/${modelValue.userId}`" v-else /> </template> </div> <div class="select-btn"> <el-upload name="file" :show-file-list="false" accept=".png,.PNG,.jpg,.JPG,.jpeg,.JPEG,.gif,.GIF,.bmp,.BMP" :multiple="false" :http-request="uploadImage" > <el-button type="primary">选择</el-button> </el-upload> </div> </div> </template> <script setup> import { ref, reactive, getCurrentInstance } from "vue"; import { useRouter, useRoute } from "vue-router"; const { proxy } = getCurrentInstance(); const router = useRouter(); const route = useRoute(); const timestamp = ref(""); // defineProps是一个函数 定义后props可直接在模板中使用,或者在setup其他地方使用 const props = defineProps({ modelValue: { type: Object, default: null, }, }); // 本地图片 const localFile = ref(null); // 子组件向父组件传值 const emit = defineEmits(); // 上传图片 const uploadImage = async (file) => { file = file.file; let img = new FileReader(); img.readAsDataURL(file); img.onload = ({ target }) => { localFile.value = target.result; }; emit("update:modelValue", file); }; </script> <style lang="scss"> .avatar-upload { display: flex; justify-content: center; align-items: end; .avatar-show { background: rgb(245, 245, 245); width: 150px; height: 150px; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; .iconfont { font-size: 50px; color: #ddd; } img { width: 100%; height: 100%; } .op { position: absolute; color: #0e8aef; top: 80px; } } .select-btn { margin-left: 10px; vertical-align: bottom; } } </style>
(3)Framework.vue中引入使用
<!-- 修改头像组件 --> <UpdateAvatar ref="updateAvatarRef" @updateAvatar="reloadAvatar"></UpdateAvatar> import UpdateAvatar from "./UpdateAvatar.vue";
(4)添加点击事件
<el-dropdown-item @click="updateAvatar">修改头像</el-dropdown-item> ...... // 修改头像 const updateAvatarRef = ref(); // 利用defineExpose,父组件调用子组件的函数, // 将用户信息利用show传递给子组件的show函数,使得子组件更新信息 const updateAvatar = () => { updateAvatarRef.value.show(userInfo.value); }; // 重新加载最新头像,利用子组件的 emit("updateAvatar"); 传递回来的信息, const reloadAvatar = () => { userInfo.value = proxy.VueCookies.get("userInfo"); timestamp.value = new Date().getTime(); };
五、修改密码
(1)封装UpdatePassword.vue组件
./src/views/UpdatePassword.vue
<template> <div> <!-- 修改头像弹出框 --> <Dialog :show="dialogConfig.show" :title="dialogConfig.title" :buttons="dialogConfig.buttons" width="500px" :showCancel="true" @close="dialogConfig.show = false" > <el-form :model="formData" :rules="rules" ref="formDataRef" label-width="80px" @submit.prevent > <!--输入新密码--> <el-form-item label="新密码" prop="password"> <el-input type="password" size="large" placeholder="请输入密码" v-model.trim="formData.password" show-password > <template #prefix> <span class="iconfont icon-password"></span> </template> </el-input> </el-form-item> <!-- 再次输入密码 --> <!--输入新密码--> <el-form-item label="确认密码" prop="rePassword"> <el-input type="password" size="large" placeholder="请再次输入密码" v-model.trim="formData.rePassword" show-password > <template #prefix> <span class="iconfont icon-password"></span> </template> </el-input> </el-form-item> </el-form> </Dialog> </div> </template> <script setup> import AvatarUpload from "@/components/AvatarUpload.vue"; import { ref, reactive, getCurrentInstance, nextTick } from "vue"; const { proxy } = getCurrentInstance(); const api = { updatePassword: "updatePassword", }; const formData = ref({}); const formDataRef = ref(); // 校验再次输入的密码 const checkRePassword = (rule, value, callback) => { if (value !== formData.value.rePassword) { callback(new Error(rule.message)); } else { callback(); } }; const rules = { password: [ { required: true, message: "请输入密码" }, { validator: proxy.Verify.password, message: "密码只能是数字,字母,特殊字符 8-18 位", }, ], rePassword: [ { required: true, message: "请再次输入密码" }, { validator: checkRePassword, message: "两次输入的密码不一致", }, ], }; const show = () => { dialogConfig.value.show = true; nextTick(() => { formDataRef.value.resetFields(); formData.value = {}; }); }; // 子组件暴露自己的属性 // 父组件需要调用子组件的方法父组件需要调用子组件的方法, // 或者访问子组件的变量 defineExpose({ show }); const dialogConfig = ref({ show: false, title: "修改密码", buttons: [ { type: "primary", text: "确定", click: (e) => { submitForm(); }, }, ], }); const submitForm = async () => { formDataRef.value.validate(async (valid) => { if (!valid) { return; } let result = await proxy.Request({ url: api.updatePassword, params: { password: formData.value.password, }, }); if (!result) { return; } dialogConfig.value.show = false; proxy.Message.success("密码修改成功"); }); }; </script> <style lang="scss"> </style>
(2)在Framework.vue引入,使用组件
<!-- 修改密码组件 --> <UpdatePassword ref="updatePasswordRef"></UpdatePassword>
import UpdatePassword from "./UpdatePassword.vue";
(3)添加点击事件,点击事件回调
<el-dropdown-item @click="updatePassword">修改密码</el-dropdown-item>
// 修改密码 const updatePasswordRef = ref(); const updatePassword = () => { updatePasswordRef.value.show(); };
六、退出登录
(1)封装全局组件
./src/utils/Confirm.jsimport { ElMessageBox } from 'element-plus' const confirm = (message, okfun) => { ElMessageBox.confirm(message, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'info', }).then(() => { okfun() }).catch(() => {}) } export default confirm;
(2)main.js引入和配置
import Confirm from './utils/Confirm'; app.config.globalProperties.Confirm=Confirm
(3)添加api接口
const api = { // getUseSpace: "/getUseSpace", logout: "/logout", };
(4)添加点击事件
<el-dropdown-item @click="logout">退出</el-dropdown-item> // 退出登录 const logout = () => { proxy.Confirm(`你确定要删除退出吗`, async () => { let result = await proxy.Request({ url: api.logout, }); if (!result) { return; } proxy.VueCookies.remove("userInfo"); router.push("/login"); }); };