vue3实战Easy云盘(一):创建项目+登录注册页面+构建框架页+上传头像/修改密码/退出登录

一、创建项目

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-inputclearablesize="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-inputtype="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-inputsize="large"clearableplaceholder="请输入昵称"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-inputtype="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-inputtype="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-inputsize="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-inputsize="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=typeresetForm();
}
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("登录成功");// 存储cookieproxy.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.pathrouter.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-uploadname="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-inputtype="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-inputtype="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.js

import { 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");});
};

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3031567.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

redis深入理解之数据存储

1、redis为什么快 1&#xff09;Redis是单线程执行&#xff0c;在执行时顺序执行 redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回 (socket 写)等都由一个顺序串行的主线…

C++ C# 贝塞尔曲线

二阶贝塞尔曲线公式 三阶贝塞尔曲线公式 C 三维坐标点 二阶到N阶源码 //二阶公式&#xff1a; FVector BezierUtils::CalculateBezierPoint(float t, FVector startPoint, FVector controlPoint, FVector endPoint) {float t1 (1 - t) * (1 - t);float t2 2 * t * (1 - t);…

docker 容器无法直接读取宿主机文件

最近一个需求, 要在后端直接使用代码直接生成 pdf 文档, 由于使用的 apache 的工具包, 该工具包无法直接解析中文字体, 需要导入外部 中文插件包, 相关代码如下: PDPage page new PDPage(PDRectangle.A4);document.addPage(page);PDFont fontFile PDType0Font.load(document…

输出电流保护/限制方案分享

需求 对电源输出进行 过/限 流保护。 方案1 采样电流输出模拟电压 -> 电压比较器 -> 控制MOS 打开/关闭&#xff1b; 这个方案的问题是&#xff1a; 当过流MOS关断之后&#xff0c;电流采样的电压是0&#xff0c;会快速使使MOS重新打开&#xff0c;电路现象是好像没关…

大文件传输的好帮手Libarchive:功能强大的开源归档文件处理库

在数字化时代&#xff0c;文件的存储和传输对于企业的日常运作至关重要。但是&#xff0c;服务器中的压缩文件往往无法直接查看或预览&#xff0c;这给用户带来了不便。为了解决这一问题&#xff0c;在线解压功能的开发变得尤为重要。接下来&#xff0c;小编将介绍一个能够实现…

C/C++关键字:extern

文章目录 一、extern&#xff1a;声明外部变量或外部函数1.extern的作用2.代码举例①例1②例2③例3 一、extern&#xff1a;声明外部变量或外部函数 1.extern的作用 extern的作用&#xff1a;声明外部的全局变量或外部的函数&#xff0c;以实现跨文件使用其他.c/.h文件的全局…

Hive的文件存储格式

TEXTFILE 文本文件,默认的文件存储格式,内容是可以直接查看,用来保存非结构化数据; 特点:文本文件的格式一旦定义就无法改变. 除TEXTFILE外,其他文件存储格式的表不能直接从本地文件导入数据,数据要先导入到textfile格式的表中, 然后再从表中用insert导入SequenceFile,RCFile,…

微信小程序原生组件使用

1、video组件使用 <view class"live-video"><video id"myVideo" src"{{videoSrc}}" bindplay"onPlay" bindfullscreenchange"fullScreenChange" controls object- fit"contain"> </video&g…

低功耗时钟芯片RX8111CE和RX4111CE的特征和应用

爱普生RTC中的两款低功耗时钟芯片RX4111CE和RX8111CE&#xff0c;在电子设备集约化、小型化不断加速的背景之下&#xff0c;驱动着市场要求RTC时钟芯片具备高精度、小型化及低功耗的特性。爱普生实时时钟模块( RTC )相对于芯片内置RTC的产品由于具有独立供电&#xff0c;所以设…

前端动画requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画&#xff0c;并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数&#xff0c;该回调函数会在浏览器下一次重绘之前执行。 备注&#xff1a; 若你想在浏览器下次重绘…

element el-date-picker组件 输入输出格式为时间戳

<!-- 时间戳 --><el-date-pickerv-model"time"value-format"timestamp"type"date"placeholder"选择日期"/>// 把 value-format"timestamp" 加入 就可以实现时间戳格式

IDA PRO 7.7 全局修改字体大小

转到IDA的安装目录&#xff0c;以我的为例&#xff0c;IDA的安装目录是&#xff1a; C:\Program Files (x86)\IDA_Pro_7.7\打开.css文件 IDA安装路径\themes\default\theme.css拉到最下面&#xff0c;找到如图所示的位置&#xff0c;把font-size修改成你想要的大小。 保存并验…

AI技术实现口语练习

大模型技术可以用于实现英语口语练习的以下几个方面&#xff0c;使用大模型技术实现英语口语练习是一种很有前途的方法。随着大模型技术的不断发展&#xff0c;我们可以期待英语口语练习将变得更加智能、高效和个性化。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发…

10.轮转数组

文章目录 题目简介题目解答解法一&#xff1a;使用额外的数组代码&#xff1a;复杂度分析&#xff1a; 解法二&#xff1a;数组反转代码&#xff1a;复杂度分析&#xff1a; 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 轮转数组 相关的讲解&#xff01;&#…

Linux 第二十七章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

党务政务服务热线|基于SSM的党务政务服务热线平台(源码+数据库+文档)

目录 基于SprinBootvue的党务政务服务热线平台 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2后台功能模块 5.2.1管理员功能模块 5.2.2部门功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; …

【6大模型让你的沟通汇报更有条理】项目管理常见问题大揭秘 03

6大模型让你的沟通汇报更有条理 虽然头脑中构思众多&#xff0c;一开口却发现空白一片&#xff1f; 工作表现出色&#xff0c;汇报时却总是支支吾吾不知从何说起&#xff1f; 生性腼腆&#xff0c;却难以避免需要站在众人面前发言&#xff1f; 阿道掐指一算&#xff1a;你需…

代码无界,创新无限!华为云开发者日 · 广州站来了!

5月23日&#xff0c;2024年首场华为云开发者日HDC.Cloud Day将在广州盛大举行。这场技术派对将为开发者们带来一场无与伦比的技术盛宴。在这里&#xff0c;开发者们将有机会现场聆听行业专家的精彩分享&#xff0c;深度了解众多前沿产品的最新技术和功能&#xff0c;并与行业专…

SVN 合并到 Git 时有文件大于 100 M 被限制 Push

如果有文件大小大于 100M&#xff0c;GitHub 是会被限制推送到仓库中的&#xff0c;大概率情况会显示下面的错误&#xff1a; remote: Resolving deltas: 100% (3601/3601), done. remote: error: Trace: aea1f450da6f2ef7bfce457c715d0fbb9b0f6d428fdca80233aff34b601ff59b re…

【matlab基础知识代码】(十六)代数方程的图解法多项式型方程的准解析解方法

>> ezplot(exp(-3*t)*sin(4*t2)4*exp(-0.5*t)*cos(2*t)-0.5,[0 5]), line([0 5],[0 0]) 验证 >> t0.6738; >> exp(-3*t)*sin(4*t2)4*exp(-0.5*t)*cos(2*t)-0.5 ans -2.9852e-04 >> ezplot(x^2*exp(-x*y^2/2)exp(-x/2)*sin(x*y)) >> hold on; …