JP3-4-MyClub后台前端(二)
Java道经 - 项目 - MyClub - 后台前端(二)
文章目录
- S02. 登录业务模块
- E01. 系统登录模块
- 1. 系统登录页面
- E02. 系统主体页面
- 1. 系统主体页面
- 2. 系统仪表盘
- 3. 查看个人信息
- 4. 修改个人信息
S02. 登录业务模块
E01. 系统登录模块
武技:在 router/index.js 文件中开发全部相关页面路由配置
import Login from "../views/Login.vue";
import Main from "../views/Main.vue";
import Dashboard from "../views/Dashboard.vue";
import Personal from "../views/personal/Personal.vue";
import PersonalUpdate from "../views/personal/PersonalUpdate.vue";const router = createRouter({history: createWebHashHistory(),routes: [{path: '/', name: 'Login', component: Login},{path: '/Main', name: 'Main', component: Main,redirect: '/Dashboard',children: [{path: '/Dashboard', name: 'Dashboard', component: Dashboard},{path: '/Personal', name: 'Personal', component: Personal},{path: '/PersonalUpdate', name: 'PersonalUpdate', component: PersonalUpdate},]}]
});
1. 系统登录页面
心法:系统登录页面
武技:开发系统登录页面 views/Login.vue
<script setup>
import router from "../router";
import vuex from "../vuex";
import {onMounted, shallowReactive, shallowRef} from "vue";
import {PROJECT_INFO, RULE} from "../const";
import {ElMessage, ElNotification} from "element-plus";
import {loginByAccountApi} from "../api/emp.js";
import {getResponseData} from "../request/index.js";/* ==================== 员工登录 ==================== */
// 表单 + 表单数据 + 表单规则 todo: 上线后将默认值删除
let loginForm = shallowRef();
let loginFormData = shallowReactive({username: 'admin', password: 'admin'});
let loginFormRules = {username: RULE.USERNAME, password: RULE.PASSWORD};function login() {loginForm.value.validate(valid => {if (valid) {// 同步发送登录请求loginByAccountApi(loginFormData).then(res => {let data = getResponseData(res);if (data) {ElMessage.success('登录成功!');let token = data['token'];let loginEmp = JSON.stringify(data['emp']);vuex.dispatch('setLoginFlag', true);sessionStorage.setItem('token', token);sessionStorage.setItem('loginEmp', loginEmp);router.push('/Main');}});}});
}/* ==================== 重置表单 ==================== */function resetForm() {loginForm.value.resetFields();
}/* ==================== 忘记密码 ==================== */function forgetPassword() {ElNotification.info('员工测试账号: admin / admin');
}/* ==================== 加载函数 ==================== */onMounted(() => {sessionStorage.clear();vuex.dispatch('setLoginFlag', false);
});
</script><template><section class="login-body"><el-card class="login-card" :header="PROJECT_INFO.title"><el-form class="login-form" ref="loginForm" status-icon :model="loginFormData" :rules="loginFormRules"><el-form-item prop="username" required><el-input v-model="loginFormData['username']" suffix-icon="User" clearable placeholder="输入账号 .."/></el-form-item><el-form-item prop="password" required><el-input v-model="loginFormData['password']" suffix-icon="Lock" clearable placeholder="输入密码 .." show-password/></el-form-item><el-button class="login-btn" type="primary" @click="login">员工登录</el-button><el-checkbox class="remember-cbx" label="记住账号" size="small"/><el-button class="forget-btn" link size="small" @click="forgetPassword">忘记密码</el-button><el-button class="reset-btn" link size="small" type="warning" @click="resetForm">重置内容</el-button></el-form></el-card></section>
</template><style scoped lang="scss">
.login-body {height: 100vh; // 高度background: url("../assets/image/loginBackground.png") no-repeat; // 背景图片(不平铺)background-size: 100% 100%; // 上下 左右padding-top: 200px; // 上内边距box-sizing: border-box; // 忽略内边距影响.login-card {margin: auto; // 自居中width: 50vh; // 宽度opacity: 0.95; // 透明度}.login-btn {width: 100%; // 宽度margin: 0 auto 10px; // 外边距letter-spacing: 2px; // 字母间距}.forget-btn, .reset-btn {float: right; // 右浮动line-height: 18px; // 行高}
}
</style>
E02. 系统主体页面
1. 系统主体页面
心法:系统主体页面
武技:开发开发系统主体页面 views/Main.vue
<script setup>
import router from "../router";
import MyIcon from "../components/MyIcon.vue";
import {ElNotification} from "element-plus";
import {PROJECT_INFO, PROJECT_SKILLS} from "../const";
import {onMounted, ref, shallowRef} from "vue";
import {listByEmpId} from "../api/menu.js";
import {getResponseData} from "../request";// 当前登录的员工信息
const loginEmp = JSON.parse(sessionStorage.getItem('loginEmp'));
// 当前登录的员工头像
const avatar = PROJECT_INFO.minioHost + '/avatar/' + loginEmp['avatar'];
// 项目LOGO
const logo = PROJECT_INFO.minioHost + '/logo.jpg';/* ==================== 功能菜单 ==================== */// 当前选中菜单的index值:默认选中当前路由路径
let currentMenuIndex = shallowRef(router.currentRoute.value['path']);
// 左侧菜单列表是否折叠:向左收缩
const isCollapse = shallowRef(false);
// 当前登录的员工的菜单列表
const menus = ref();/* ==================== 项目信息 ==================== */const projectInfoDrawer = shallowRef();function openProjectInfoDrawer() {projectInfoDrawer.value = true;
}/* ==================== 项目技术栈 ==================== */const projectSkillDrawer = shallowRef();function openProjectSkillDrawer() {projectSkillDrawer.value = true;
}/* ==================== 系统日历 ==================== */const calendarDrawer = shallowRef();
let calendarData = shallowRef();function openCalendarDrawer() {calendarData.value = new Date();calendarDrawer.value = true;
}/* ==================== 系统通知 ==================== */function notify() {ElNotification.info({title: '通知列表', message: '暂无通知消息', position: 'bottom-right'});
}/* ==================== 路径跳转 ==================== */function toPersonal() {router.push('/Personal');
}function toPersonalUpdate() {router.push('/PersonalUpdate');
}/* ==================== 退出登录 ==================== */function logout() {router.push('/');
}/* ==================== 加载函数 ==================== */onMounted(() => {/*** 构建菜单树,每个父菜单都包含 subMenus 子菜单** @param menus 菜单列表* @returns object[] 菜单树(父菜单数组)*/function buildMenuTree(menus) {// 使用 JSON 缓存菜单,格式 {id : menu数据}const menuObj = {};// 父菜单数组(仅存放 pid 为 0 的菜单)const parentMenus = [];// 初始化menus.forEach(menu => {// 缓存全部菜单到 menuObj 中menuObj[menu['id']] = menu;// 将父菜单分离到父菜单数组 rootMenus 中if (menu['pid'] === 0) {menu['subMenus'] = [];parentMenus.push(menu);}});// 将子菜单关联到父菜单menus.forEach(menu => {// 仅子菜单需要找到并关联到自己的父菜单if (menu['pid'] !== 0) {// 从 JSON 中获取父菜单const parent = menuObj[menu['pid']];// 将自己关联到自己的父菜单if (parent) parent['subMenus'].push(menu);}});return parentMenus;}listByEmpId(loginEmp['id']).then(res => {let data = getResponseData(res);if (data) menus.value = buildMenuTree(data);});
});</script><template><el-container class="main-body" v-if="menus"><el-aside class="main-body-left" width="collapse" max-width="200px"><el-menu class="menus-menu el-menu-vertical-demo" unique-opened router :collapse="isCollapse" :default-active="currentMenuIndex"><el-image class="logo" :src="logo"/><el-menu-item class="house-item" index="/DashBoard" title="回到后台项目首页"><my-icon icon="House" label="DashBoard"/></el-menu-item><el-sub-menu class="menus" v-for="(menu, i) in menus" :key="menu['id']" :index="i.toString()" :title="menu['info']"><template #title><my-icon :icon="menu['icon']" :label="menu['title']"/></template><el-menu-item class="sub-menus" v-for="subMenu in menu['subMenus']" :key="subMenu['id']" :index="subMenu['url']" :title="subMenu['info']"><my-icon :icon="subMenu['icon']" :label="subMenu['title']"/></el-menu-item></el-sub-menu></el-menu></el-aside><el-container class="main-body-right"><el-header class="main-body-right-head"><el-row class="is-align-middle"><el-col class="fold-expand" :span="2"><el-radio-group v-model="isCollapse"><el-radio-button :label="!isCollapse"><my-icon size="20" :icon="!isCollapse ? 'Fold' : 'Expand'"/></el-radio-button></el-radio-group></el-col><el-col class="project-title-col" :span="7"><el-popover width="500" :content="PROJECT_INFO.info" placement="bottom-start" trigger="click"><template #reference>{{ PROJECT_INFO.title }}</template></el-popover></el-col><el-col class="operation-btn-col" :span="6" :offset="6"><el-divider direction="vertical"/><el-tooltip content="全局搜索"><el-button icon="search" size="small" round @click=""/></el-tooltip><el-tooltip content="系统通知"><el-button icon="bell" size="small" round @click="notify"/></el-tooltip><el-tooltip content="项目基本信息"><el-button icon="list" size="small" round @click="openProjectInfoDrawer"/></el-tooltip><el-tooltip content="项目技术信息"><el-button icon="management" size="small" round @click="openProjectSkillDrawer"/></el-tooltip><el-tooltip content="系统日历"><el-button icon="calendar" size="small" round @click="openCalendarDrawer"/></el-tooltip><el-divider direction="vertical"/></el-col><el-col class="emp-realname-col" :span="2" v-if="loginEmp['realname']">{{ loginEmp['realname'] }}</el-col><el-col class="emp-avatar-col" :span="1" v-if="loginEmp['avatar']"><el-dropdown trigger="click"><span class="el-dropdown-link"><el-avatar class="emp-avatar" :size="45" :src="avatar"/></span><template #dropdown><el-dropdown-menu><el-dropdown-item icon="InfoFilled" @click="toPersonal">查看个人信息</el-dropdown-item><el-dropdown-item icon="Edit" @click="toPersonalUpdate">修改个人信息</el-dropdown-item><el-dropdown-item icon="WarnTriangleFilled" @click="logout"><el-text type="danger">退出登录</el-text></el-dropdown-item></el-dropdown-menu></template></el-dropdown></el-col></el-row></el-header><el-main class="main-body-right-main"><router-view/></el-main></el-container></el-container><el-drawer title="项目系统信息" v-model="projectInfoDrawer" size="50%"><el-descriptions border column="1"><el-descriptions-item v-for="(v, k) in PROJECT_INFO" :key="k" :label="k">{{ v }}</el-descriptions-item></el-descriptions></el-drawer><el-drawer title="项目技术栈信息" v-model="projectSkillDrawer" size="50%"><el-descriptions border column="1"><el-descriptions-item v-for="item in PROJECT_SKILLS" :key="item['label']" :label="item['label']">{{ item['value'] }}({{item['version']}})</el-descriptions-item></el-descriptions></el-drawer><el-drawer title="系统日历" v-model="calendarDrawer" size="50%"><el-calendar v-model="calendarData"/></el-drawer>
</template><style scoped lang="scss">
.main-body-left {height: 100vh; // 高度border-right: 1px solid #cccccc; // 右边框.logo {padding: 10px; // 内边距}.el-menu-vertical-demo:not(.el-menu--collapse) {width: 200px; // 宽度height: 100vh; // 高度letter-spacing: 2px; // 字间距}.el-icon {margin: 0 10px; // 上下外边距 左右外边距}
}.main-body-right-head {.project-title-col {font-weight: bolder; // 加粗font-size: 1.5rem; // 字号倍率}.emp-realname-col {text-align: right; // 右对齐height: 50px; // 高度display: inline-block; // 内联块text-shadow: 2px 2px 2px gray; // 文字阴影line-height: 50px; // 行高}.emp-avatar-col {text-align: right; // 右对齐}.emp-avatar {margin: 10px; // 外边距outline: 1px solid #854040; // 边框border: 1px solid #854040; // 边框}
}
</style>
2. 系统仪表盘
心法:系统仪表盘
仪表盘自主设计,这里简单放一张图片即可。
武技:开发系统仪表盘 views/DashBoard.vue
<script setup></script><template><div class="dashboard-body"></div>
</template><style scoped lang="scss">
.dashboard-body {height: 100%; // 高度background: url("../assets/image/dashboard.jpg") no-repeat; // 背景图片(不平铺)background-size: 100% 100%; // 上下 左右
}
</style>
3. 查看个人信息
心法:查看个人信息页面
武技:开发查看个人信息页面 views/personal/Personal.vue
<script setup>
import {PROJECT_INFO} from "../../const";
import {dateFormat, genderFormat} from "../../util";
import MyNav from "../../components/MyNav.vue";// 当前登录的员工信息
const loginEmp = JSON.parse(sessionStorage.getItem('loginEmp'));
// 当前登录的员工头像
const avatar = PROJECT_INFO.minioHost + '/avatar/' + loginEmp['avatar'];// 路径导航
const navItems = [{icon: 'House', label: 'DashBoard', url: '/DashBoard'},{icon: 'View', label: '个人信息'},
];</script><template><my-nav :items="navItems"/><el-divider/><div class="personal-body"><el-row :gutter="20"><el-col :span="6"><el-image :src="avatar"></el-image><el-divider/><el-descriptions column="1"><el-descriptions-item label="登录账号">{{ loginEmp['username'] }}</el-descriptions-item><el-descriptions-item label="真实姓名">{{ loginEmp['realname'] }}</el-descriptions-item><el-descriptions-item label="入职时间">{{ dateFormat(loginEmp['hiredate']) }}</el-descriptions-item><el-descriptions-item label="创建时间">{{ dateFormat(loginEmp['created']) }}</el-descriptions-item><el-descriptions-item label="修改时间">{{ dateFormat(loginEmp['updated']) }}</el-descriptions-item></el-descriptions></el-col><el-col :span="18"><el-descriptions title="当前员工详细信息" border column="2"><el-descriptions-item label="所属部门" width="50">{{ loginEmp['dept'] ? loginEmp['dept']['title'] : '暂未分配部门' }}</el-descriptions-item><el-descriptions-item label="员工年龄" width="50">{{ loginEmp['age'] }}</el-descriptions-item><el-descriptions-item label="员工性别">{{ genderFormat(loginEmp['gender']) }}</el-descriptions-item><el-descriptions-item label="所属省份">{{ loginEmp['province'] }}</el-descriptions-item><el-descriptions-item label="手机号码">{{ loginEmp['phone'] }}</el-descriptions-item><el-descriptions-item label="微信号码">{{ loginEmp['wechat'] }}</el-descriptions-item><el-descriptions-item label="电子邮件">{{ loginEmp['email'] }}</el-descriptions-item><el-descriptions-item label="身份证号">{{ loginEmp['idcard'] }}</el-descriptions-item><el-descriptions-item label="现居住地" :span="2"><el-card>{{ loginEmp['address'] }}</el-card></el-descriptions-item><el-descriptions-item label="员工描述" :span="2"><el-card style="height: 200px">{{ loginEmp['info'] }}</el-card></el-descriptions-item></el-descriptions></el-col></el-row></div>
</template><style scoped lang="scss">
.personal-body {margin: 20px auto 0; // 20px上外边距,自动水平居中
}
</style>
4. 修改个人信息
心法:修改个人信息页面
武技:开发修改个人信息页面 views/personal/PersonalUpdate.vue
<script setup>
import MyForm from "../../components/MyForm.vue";
import MyNav from "../../components/MyNav.vue";
import MyUpload from "../../components/MyUpload.vue";
import router from "../../router";
import {getResponseData} from "../../request";
import {onMounted, reactive, ref} from "vue";
import {listApi, updateApi} from "../../api/axios.js";
import {GENDER_OPTIONS, PROVINCE_OPTIONS, RULE} from "../../const";
import {ElMessage} from "element-plus";
import {updatePasswordApi, UPLOAD_AVATAR_URL} from "../../api/emp.js";// 获取当前登录的员工记录
const loginEmp = JSON.parse(sessionStorage.getItem('loginEmp'));
// 所在部门下拉菜单选项
let deptOptions = ref([]);// 路径导航
const navItems = [{icon: 'House', label: 'DashBoard', url: '/DashBoard'},{icon: 'Edit', label: '修改个人信息'},
];/* ==================== 修改基本信息 ==================== */// 表单项 + 表单值 + 表单规则
let items = ref([{label: '登录账号', prop: 'username', disabled: true, span: 12},{label: '登录密码', prop: 'password', disabled: true, hidden: true, span: 12},{label: '真实姓名', prop: 'realname', span: 12},{label: '员工年龄', prop: 'age', span: 12, type: 'number'},{label: '手机号码', prop: 'phone', span: 12},{label: '身份证号', prop: 'idcard', span: 12},{label: '微信号码', prop: 'wechat', span: 12},{label: '电子邮件', prop: 'email', span: 12},{label: '入职日期', prop: 'hiredate', type: 'date', span: 12},{label: '员工性别', prop: 'gender', type: 'select', options: GENDER_OPTIONS, span: 12},{label: '所在省份', prop: 'province', type: 'select', options: PROVINCE_OPTIONS, span: 12},{label: '所在部门', prop: 'fkDeptId', type: 'select', options: deptOptions.value, span: 12},{label: '详细地址', prop: 'address', type: 'textarea', rows: 2},{label: '员工描述', prop: 'info', type: 'textarea', rows: 7},
]);
let params = reactive(loginEmp);
let rules = {realname: RULE.REALNAME,phone: RULE.PHONE,email: RULE.EMAIL,province: RULE.PROVINCE,address: RULE.ADDRESS,idcard: RULE.IDCARD,info: RULE.INFO,
};/* ==================== 修改个人密码 ==================== */// 表单项 + 表单值 + 表单规则
let updatePasswordItems = ref([{label: '原密码', prop: 'oldPassword', type: 'password', required: true, placeholder: '请输入原密码'},{label: '新密码', prop: 'newPassword', type: 'password', required: true, placeholder: '请输入新密码'},{label: '确认密码', prop: 'rePassword', type: 'password', required: true, placeholder: '请确认新密码'},
]);
let updatePasswordData = reactive({id: loginEmp['id']});
let updatePasswordRules = {oldPassword: RULE.PASSWORD,newPassword: [RULE.PASSWORD[0], {validator: (rule, value, callback) => {if (value === updatePasswordData['oldPassword']) callback('新旧密码不能相同');else callback();},trigger: ['blur', 'input']}],rePassword: [RULE.PASSWORD[0], {validator: (rule, value, callback) => {if (value !== updatePasswordData['newPassword']) callback('两次密码不一致');else callback();},trigger: ['blur', 'input']}],
};/* ==================== 修改成功后 ==================== */function updateSuccess() {ElMessage('修改个人信息后需要重新登录!');setTimeout(() => router.push('/'), 1000);
}/* ==================== 加载函数 ==================== */onMounted(async () => {// 查询全部部门并添加到下拉菜单选项中Object.values(getResponseData(await listApi({module: 'dept'}))).forEach(dept => {deptOptions.value.push({label: dept['title'], value: dept['id']});});
});</script><template><my-nav :items="navItems"/><el-row :gutter="20" class="personal-update-body"><el-col :span="16"><el-card class="update-card" header="修改基本信息"><my-form v-if="deptOptions.length > 0" type="update" :items="items" :params="params" :rules="rules" :api="updateApi" :args="{'module': 'emp'}" :callback="updateSuccess"/></el-card></el-col><el-col :span="8"><el-card class="upload-avatar-card" header="上传个人头像"><my-upload :url="UPLOAD_AVATAR_URL + '/' + loginEmp['id']" name="avatarFile" :callback="updateSuccess" :autoUpload="true"/></el-card><el-card class="update-password-card" header="修改个人密码"><my-form type="update" :items="updatePasswordItems" :params="updatePasswordData" :rules="updatePasswordRules" :api="updatePasswordApi" :callback="updateSuccess"/></el-card></el-col></el-row>
</template><style scoped lang="scss">
.personal-update-body {margin-top: 22px; // 上外边距height: 620px; // 高度overflow-y: scroll; // Y轴溢出滚动.update-password-card {margin-top: 37px; // 上外边距}
}
</style>
Java道经 - 项目 - MyClub - 后台前端(二)