Merge pull request 'cys' (#28) from cys into main

Reviewed-on: #28
This commit is contained in:
朱浩 2024-07-17 14:26:44 +08:00
commit 40dca2d49b
10 changed files with 528 additions and 3 deletions

View File

@ -22,8 +22,8 @@ export default defineConfig({
server: { server: {
proxy: { proxy: {
'/dev-api': { '/dev-api': {
target: 'http://27.128.240.72:7865', // target: 'http://27.128.240.72:7865',
// target: 'http://192.168.2.52:7863', target: 'http://192.168.2.52:7863',
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '') rewrite: (p) => p.replace(/^\/dev-api/, '')
}, },

View File

@ -29,6 +29,7 @@
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
"vue-cropper": "^1.1.3",
"vue-router": "^4.4.0" "vue-router": "^4.4.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -0,0 +1,40 @@
import request from '@/utils/request'
// 查询用户个人信息
export function getUserProfile() {
return request({
url: '/system/user/profile',
method: 'get'
})
}
// 修改用户个人信息
export function updateUserProfile(data) {
return request({
url: '/system/user/profile',
method: 'put',
data: data
})
}
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword) {
const data = {
oldPassword,
newPassword
}
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
params: data
})
}
// 用户头像上传
export function uploadAvatar(data) {
return request({
url: '/system/user/profile/avatar',
method: 'post',
data: data
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -29,7 +29,7 @@
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item>个人中心</el-dropdown-item> <el-dropdown-item @click="changePage('/profile')">个人中心</el-dropdown-item>
<el-dropdown-item divided command="logout"> <el-dropdown-item divided command="logout">
<span>退出登录</span> <span>退出登录</span>
</el-dropdown-item> </el-dropdown-item>

View File

@ -31,6 +31,12 @@ export const constantRoutes = [
component: () => import('@/views/teach/index.vue'), component: () => import('@/views/teach/index.vue'),
name: 'teach', name: 'teach',
meta: {title: '授课'} meta: {title: '授课'}
},
{
path: '/profile',
component: () => import('@/views/profile/index.vue'),
name: 'profile',
meta: {title: '个人中心'}
} }
] ]
}, },

View File

@ -0,0 +1,139 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :span="6" :xs="24">
<el-card class="box-card">
<template v-slot:header>
<div class="clearfix">
<span>个人信息</span>
</div>
</template>
<div>
<div class="text-center">
<userAvatar />
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<div class="left-align">
<UserFilled class="UserFilled"/>
<span>用户名称</span>
</div>
<div class="right-align">{{ state.user.userName }}</div>
</li>
<li class="list-group-item">
<div class="left-align">
<Cellphone class="Cellphone"/>
<span>手机号码</span>
</div>
<div class="right-align">{{ state.user.phonenumber }}</div>
</li>
<li class="list-group-item">
<div class="left-align">
<Message class="Message"/>
<span>用户邮箱</span>
</div>
<div class="right-align">{{ state.user.email }}</div>
</li>
<li class="list-group-item">
<div class="left-align">
<Suitcase class="Suitcase"/>
<span>所属部门</span>
</div>
<div class="right-align" v-if="state.user.dept">
{{ state.user.dept.deptName }} / {{ state.postGroup }}
</div>
</li>
<li class="list-group-item">
<div class="left-align">
<Avatar class="Avatar"/>
<span>所属角色</span>
</div>
<div class="right-align">{{ state.roleGroup }}</div>
</li>
<li class="list-group-item">
<div class="left-align">
<Calendar class="Calendar"/>
<span>创建日期</span>
</div>
<div class="right-align">{{ state.user.createTime }}</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18" :xs="24">
<el-card>
<template v-slot:header>
<div class="clearfix">
<span>基本资料</span>
</div>
</template>
<el-tabs v-model="activeTab">
<el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="state.user" />
</el-tab-pane>
<el-tab-pane label="修改密码" name="resetPwd">
<resetPwd />
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="Profile">
import { UserFilled, Cellphone, Message, Suitcase, Avatar, Calendar } from '@element-plus/icons-vue'
import { ref, reactive } from 'vue'
import userAvatar from './userAvatar.vue'
import userInfo from './userInfo.vue'
import resetPwd from './resetPwd.vue'
import { getUserProfile } from '@/api/system/user'
const activeTab = ref('userinfo')
const state = reactive({
user: {},
roleGroup: {},
postGroup: {}
})
function getUser() {
getUserProfile().then((response) => {
state.user = response.data
state.roleGroup = response.roleGroup
state.postGroup = response.postGroup
})
}
getUser()
</script>
<style lang="scss" scoped>
.clearfix {
text-align: left;
}
.text-center {
margin-bottom: 15px;
border-bottom: 1px solid #dddddd;
}
.list-group-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
border-bottom: 1px solid #dddddd;
padding-bottom: 10px;
font-size: 14px;
}
.left-align {
display: flex;
align-items: center;
}
.right-align {
align-items: right;
}
.UserFilled, .Cellphone, .Message, .Suitcase, .Avatar, .Calendar {
width: 1em;
height: 1em;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input
v-model="user.oldPassword"
placeholder="请输入旧密码"
type="password"
show-password
/>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input
v-model="user.newPassword"
placeholder="请输入新密码"
type="password"
show-password
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
v-model="user.confirmPassword"
placeholder="请确认新密码"
type="password"
show-password
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { updateUserPwd } from '@/api/system/user'
// const { proxy } = getCurrentInstance();
const user = reactive({
oldPassword: undefined,
newPassword: undefined,
confirmPassword: undefined
})
const equalToPassword = (rule, value, callback) => {
if (user.newPassword !== value) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
}
const rules = ref({
oldPassword: [{ required: true, message: '旧密码不能为空', trigger: 'blur' }],
newPassword: [
{ required: true, message: '新密码不能为空', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
{ required: true, validator: equalToPassword, trigger: 'blur' }
]
})
/** 提交按钮 */
function submit() {
proxy.$refs.pwdRef.validate((valid) => {
if (valid) {
updateUserPwd(user.oldPassword, user.newPassword).then((response) => {
proxy.$modal.msgSuccess('修改成功')
})
}
})
}
</script>

View File

@ -0,0 +1,175 @@
<template>
<div class="user-info-head" @click="editCropper()">
<img :src="options.img" title="点击上传头像" class="img-circle" />
<el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<vue-cropper
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixedBox="options.fixedBox"
:outputType="options.outputType"
@realTime="realTime"
v-if="visible"
/>
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div class="avatar-upload-preview">
<img :src="options.previews.url" :style="options.previews.img" />
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :md="2">
<el-upload
action="#"
:http-request="requestUpload"
:show-file-list="false"
:before-upload="beforeUpload"
>
<el-button>
选择
<el-icon class="el-icon--right"><Upload /></el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button :icon="Plus" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="Minus" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" @click="uploadImg()"> </el-button>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script setup>
import { Upload, Plus, Minus, RefreshLeft, RefreshRight } from '@element-plus/icons-vue'
import { ref, reactive } from 'vue'
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
import { uploadAvatar } from "@/api/system/user";
import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
// const { proxy } = getCurrentInstance();
const open = ref(false);
const visible = ref(false);
const title = ref("修改头像");
//
const options = reactive({
img: userStore.avatar, //
autoCrop: true, //
autoCropWidth: 200, //
autoCropHeight: 200, //
fixedBox: true, //
outputType: "png", // PNG
previews: {} //
});
/** 编辑头像 */
function editCropper() {
open.value = true;
}
/** 打开弹出层结束时的回调 */
function modalOpened() {
visible.value = true;
}
/** 覆盖默认上传行为 */
function requestUpload() {}
/** 向左旋转 */
function rotateLeft() {
proxy.$refs.cropper.rotateLeft();
}
/** 向右旋转 */
function rotateRight() {
proxy.$refs.cropper.rotateRight();
}
/** 图片缩放 */
function changeScale(num) {
num = num || 1;
proxy.$refs.cropper.changeScale(num);
}
/** 上传预处理 */
function beforeUpload(file) {
if (file.type.indexOf("image/") == -1) {
proxy.$modal.msgError("文件格式错误,请上传图片类型,如JPGPNG后缀的文件。");
} else {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
options.img = reader.result;
};
}
}
/** 上传图片 */
function uploadImg() {
proxy.$refs.cropper.getCropBlob(data => {
let formData = new FormData();
formData.append("avatarfile", data);
uploadAvatar(formData).then(response => {
open.value = false;
options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl;
userStore.avatar = options.img;
proxy.$modal.msgSuccess("修改成功");
visible.value = false;
});
});
}
/** 实时预览 */
function realTime(data) {
options.previews = data;
}
/** 关闭窗口 */
function closeDialog() {
options.img = userStore.avatar;
options.visible = false;
}
</script>
<style lang='scss' scoped>
.user-info-head {
position: relative;
display: inline-block;
height: 120px;
}
.user-info-head:hover:after {
content: "+";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #eee;
background: rgba(0, 0, 0, 0.5);
font-size: 24px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
line-height: 110px;
border-radius: 50%;
}
.img-circle {
height: 120px;
width: auto
}
</style>

View File

@ -0,0 +1,89 @@
<template>
<el-form ref="userRef" :model="user" :rules="rules" label-width="80px">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="user.nickName" maxlength="30" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="user.phonenumber" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="user.email" maxlength="50" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group >
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="任教学科">
<el-radio-group v-model="user.edusubject">
<template v-for="item in subjectList">
<el-radio v-if="item.edustage==user.edustage" :label="item.itemtitle">{{ item.itemtitle }}</el-radio>
</template>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="primary" @click="handleclick1">测验</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref } from 'vue'
import { updateUserProfile } from "@/api/system/user";
import { listEvaluation } from "@/api/subject/index";
const props = defineProps({
user: {
type: Object
}
});
// const { proxy } = getCurrentInstance();
const rules = ref({
nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
email: [{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, { type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phonenumber: [{ required: true, message: "手机号码不能为空", trigger: "blur" }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
});
const subjectList = ref([]);
function handleclick1() {
console.log(props.user.edudegree)
}
//
listEvaluation({ itemkey: "subject", pageSize: 500 }).then((res) => {
subjectList.value = res.rows;
});
setTimeout(() => {
if (props.user.edudegree != '') {
var upcaseName = ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级', '初一', '初二', '初三', '高一', '高二', '高三'];
var d = parseInt(props.user.edudegree.replace('年级', ''));
if (d <= 6) {
props.user.edustage = '小学';
} else if (d >= 7 && d <= 9) {
props.user.edustage = '初中';
} else if (d >= 9 && d <= 12) {
props.user.edustage = '高中';
}
props.user.stagelabel = upcaseName[d-1];
}
}, 1000);
/** 提交按钮 */
function submit() {
proxy.$refs.userRef.validate(valid => {
if (valid) {
console.log(props.user);
updateUserProfile(props.user).then(response => {
proxy.$modal.msgSuccess("修改成功");
});
}
});
};
</script>