优化个人中心

This commit is contained in:
曙光 2024-09-29 20:15:03 +08:00
parent 19f535c028
commit 1730c2f51e
10 changed files with 664 additions and 156 deletions

View File

@ -35,37 +35,3 @@ router.beforeEach(async (to, from, next) => {
}
next();
});
// import router from "@/router";
// import store from "@/store";
// import ACCESS_ENUM from "@/access/ACCESS_ENUM";
// import checkAccess from "@/access/access_check";
// router.beforeEach(async (to, from, next) => {
// console.log("登陆用户信息", store.state.user.loginUser);
// let loginUser = store.state.user.loginUser;
// // 如果之前没登陆过,自动登录
// if (!loginUser || !loginUser.userRole) {
// // 加 await 是为了等用户登录成功之后,再执行后续的代码
// await store.dispatch("user/getLoginUser");
// loginUser = store.state.user.loginUser;
// }
// const needAccess = (to.meta?.access as string) ?? ACCESS_ENUM.NOT_LOGIN;
// // 要跳转的页面必须要登陆
// if (needAccess !== ACCESS_ENUM.NOT_LOGIN) {
// // 如果没登陆,跳转到登录页面
// if (
// // !loginUser ||
// // !loginUser.userRole ||
// loginUser.userRole === ACCESS_ENUM.NOT_LOGIN
// ) {
// next(`/user/login?redirect=${to.fullPath}`);
// return;
// }
// // 如果已经登陆了,但是权限不足,那么跳转到无权限页面
// if (!checkAccess(loginUser, needAccess)) {
// next("/noAuth");
// return;
// }
// }
// next();
// });

View File

@ -62,7 +62,7 @@
<!-- <p>已登录</p> -->
</a-avatar>
<template #content>
<a-doption>
<a-doption @click="gotoCenter()">
<a-button type="text">
<icon-user style=" font-size: 20px;" />
个人中心
@ -167,7 +167,10 @@ const doMenuClick = (key: string) => {
path: key,
});
};
//
const gotoCenter=()=>{
router.push('/user/center')
}
</script>
<style>

View File

@ -0,0 +1,78 @@
<template>
<div class="info" style="align-items: center;">
<a-space direction="vertical" size="large" :style="{ width: '600px' }">
<a-form :model="form" layout="vertical">
<a-form-item field="oldPassword" label="旧密码">
<a-input-password
v-model="form.oldPassword"
placeholder="请输入旧密码"
allow-clear
/>
</a-form-item>
<a-form-item field="newPassword" label="新密码">
<a-input-password
v-model="form.newPassword"
placeholder="请输入新密码"
allow-clear
/>
</a-form-item>
<a-form-item field="confirmPassword" label="确认新密码">
<a-input-password
v-model="form.confirmPassword"
placeholder="请再次输入新密码"
allow-clear
/>
</a-form-item>
<a-button type="primary" class="save" @click="savePassword">
保存
</a-button>
</a-form>
</a-space>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { UserControllerService } from '../../../generated';
import { useRouter } from 'vue-router';
//
const form = ref({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
//
const savePassword = async () => {
//
if (form.value.newPassword !== form.value.confirmPassword) {
Message.error('新密码和确认密码不一致');
return;
}
const res = await UserControllerService.updatePasswordUsingPost(form.value);
if (res.code === 0) {
Message.success('密码修改成功');
//
} else {
Message.error('密码修改失败: ' + res.message);
}
};
</script>
<style scoped>
.info {
display: flex;
justify-content: center;
align-items: center;
height: 100%; /* 使其占满父元素的高度 */
/* background-color: #f0f2f5; 设置背景色 */
padding: 20px; /* 内边距 */
}
.save {
margin-top: 20px; /* 按钮与表单的间距 */
}
</style>

View File

@ -1,7 +1,9 @@
<template>
<div class="info">
<div class="avatar" style="display: flex;justify-content: center;">
<a-avatar id="userAvatar" @click="onClcik" :style="{ backgroundColor: '#0A65CC' }">
<div class="info" style="align-items: center;">
<div class="avatar" style="display: flex;justify-content: center;align-items: center;width: 50%;">
<a-avatar :size="100" id="userAvatar" @click="onClcik" :style="{ backgroundColor: '#0A65CC' }">
<img v-if="form.userAvatar" class="avatar-image" alt="avatar" :src="form.userAvatar" />
<IconUser v-else />
<template #trigger-icon>

View File

@ -0,0 +1,93 @@
<template>
<a-list
class="list-demo-action-layout"
:bordered="false"
:data="dataSource"
:pagination-props="paginationProps"
>
<template #item="{ item }">
<a-list-item class="list-demo-item" action-layout="vertical">
<template #actions>
<span><icon-heart />83</span>
<span><icon-star />{{ item.index }}</span>
<span><icon-message />Reply</span>
</template>
<template #extra>
<div className="image-area">
<img alt="arco-design" :src="item.imageSrc" />
</div>
</template>
<a-list-item-meta
:title="item.title"
:description="item.description"
>
<template #avatar>
<a-avatar shape="square">
<img alt="avatar" :src="item.avatar" />
</a-avatar>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</template>
<script>
import { reactive } from 'vue'
const names = ['Socrates', 'Balzac', 'Plato'];
const avatarSrc = [
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/e278888093bef8910e829486fb45dd69.png~tplv-uwbnlip3yd-webp.webp',
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/9eeb1800d9b78349b24682c3518ac4a3.png~tplv-uwbnlip3yd-webp.webp',
];
const imageSrc = [
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/29c1f9d7d17c503c5d7bf4e538cb7c4f.png~tplv-uwbnlip3yd-webp.webp',
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/04d7bc31dd67dcdf380bc3f6aa07599f.png~tplv-uwbnlip3yd-webp.webp',
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/1f61854a849a076318ed527c8fca1bbf.png~tplv-uwbnlip3yd-webp.webp',
];
const dataSource = new Array(15).fill(null).map((_, index) => {
return {
index: index,
avatar: avatarSrc[index % avatarSrc.length],
title: names[index % names.length],
description:
'Beijing ByteDance Technology Co., Ltd. is an enterprise located in China. ByteDance has products such as TikTok, Toutiao, volcano video and Douyin (the Chinese version of TikTok).',
imageSrc: imageSrc[index % imageSrc.length],
};
});
export default {
setup() {
return {
dataSource,
paginationProps: reactive({
defaultPageSize: 3,
total: dataSource.length
})
}
},
}
</script>
<style scoped>
.list-demo-action-layout .image-area {
width: 183px;
height: 119px;
border-radius: 2px;
overflow: hidden;
}
.list-demo-action-layout .list-demo-item {
padding: 20px 0;
border-bottom: 1px solid var(--color-fill-3);
}
.list-demo-action-layout .image-area img {
width: 100%;
}
.list-demo-action-layout .arco-list-item-action .arco-icon {
margin: 0 4px;
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<a-comment author="Balzac" datetime="1 hour" align="right" v-for="item in 10">
<template #actions>
<span class="action" key="heart" @click="onLikeChange">
<span v-if="like">
<IconHeartFill :style="{ color: '#f53f3f' }" />
</span>
<span v-else>
<IconHeart />
</span>
{{ 83 + (like ? 1 : 0) }}
</span>
<span class="action" key="star" @click="onStarChange">
<span v-if="star">
<IconStarFill style="{ color: '#ffb400' }" />
</span>
<span v-else>
<IconStar />
</span>
{{ 3 + (star ? 1 : 0) }}
</span>
<span class="action" key="reply">
<IconMessage /> Reply
</span>
</template>
<template #avatar>
<a-avatar>
<img
alt="avatar"
src="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp"
/>
</a-avatar>
</template>
<template #content>
<div>
A design is a plan or specification for the construction of an object or
system or for the implementation of an activity or process, or the
result of that plan or specification in the form of a prototype, product
or process.
</div>
</template>
</a-comment>
</template>
<script>
import { ref } from 'vue';
import {
IconHeart,
IconMessage,
IconStar,
IconStarFill,
IconHeartFill,
} from '@arco-design/web-vue/es/icon';
export default {
components: {
IconHeart,
IconMessage,
IconStar,
IconStarFill,
IconHeartFill,
},
setup() {
const like = ref(false);
const star = ref(false);
const onLikeChange = () => {
like.value = !like.value;
};
const onStarChange = () => {
star.value = !star.value;
};
return {
like,
star,
onLikeChange,
onStarChange
}
},
};
</script>
<style scoped>
.action {
display: inline-block;
padding: 0 4px;
color: var(--color-text-1);
line-height: 24px;
background: transparent;
border-radius: 2px;
cursor: pointer;
transition: all 0.1s ease;
}
.action:hover {
background: var(--color-fill-3);
}
</style>

View File

@ -147,23 +147,29 @@
</div>
</div>
</div>
<!-- 登录弹框 -->
<a-modal v-model:visible="visible" @cancel="handleCancel" :footer="null" >
<UserLoginView />
</a-modal>
<div v-if="visible" class="overlay" @click.self="handleCancel" >
<div class="login-container">
<div v-if="ModelInfo=='login'">
<UserLoginView @goToReg ="UserModel" :ModelStatus="ModelStatus" />
</div>
<div v-else-if="ModelInfo=='reg'">
<UserRegView @goToLogin ="UserModel" :ModelStatus="ModelStatus" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import UserLoginView from './user/UserLoginView.vue';
import UserRegView from './user/UserRegView.vue';
const Mdvalue = ref<string>('');
const ModelStatus = ref('card'); //
const Codevalue = ref<string>('');
// /
const visible = ref(false);
const ModelInfo=ref('login')
const OnChange = (v: string) => {
Mdvalue.value = v;
console.log(Mdvalue.value);
@ -173,14 +179,16 @@ const InChnage = (v: string) => {
Codevalue.value = v;
console.log(Codevalue.value);
};
// /
const visible = ref(false);
//
const useLogin = () => {
visible.value = true;
};
const UserModel = (msg: string) => {
ModelInfo.value=msg
console.log("收到子组件传来的消息: ", msg);
//
};
//
const handleCancel = () => {
visible.value = false;
@ -196,7 +204,39 @@ const handleCancel = () => {
justify-content: center;
align-items: center;
}
/* 遮罩层样式 */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5); /* 半透明背景 */
display: flex;
justify-content: center;
align-items: center;
z-index: 1000; /* 确保遮罩层位于最顶层 */
}
/* 弹窗样式 */
.login-container {
/* background: #0055ff; */
/* border-radius: 25px; */
/* box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); */
/* animation: fadeIn 0.3s ease; 添加淡入动画 */
}
/* 淡入动画效果 */
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(-20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 打字机动画效果 */
@keyframes typing {
from {

View File

@ -1,70 +1,118 @@
<template>
<div id="UserCenter">
<div class="info center" style="display: flex;justify-content: center;align-items: center;flex-direction: row;">
<a-menu
:style="{ width: '200px', }"
:default-open-keys="['0','1','2']"
:default-selected-keys="['0_2']"
show-collapse-button
breakpoint="xl"
@collapse="onCollapse"
>
<a-sub-menu key="0">
<template #icon><icon-apps></icon-apps></template>
<template #title>我的资料</template>
<a-menu-item key="0_0">修改资料</a-menu-item>
<a-menu-item key="0_1">修改密码</a-menu-item>
</a-sub-menu>
<a-sub-menu key="1">
<template #icon><icon-bug></icon-bug></template>
<template #title>我的论坛</template>
<a-menu-item key="1_0">我的发帖</a-menu-item>
<a-menu-item key="1_1">我的评论</a-menu-item>
</a-sub-menu>
<a-sub-menu key="2">
<template #icon><icon-bulb></icon-bulb></template>
<template #title>我的题库</template>
<a-menu-item key="2_0">做题记录</a-menu-item>
</a-sub-menu>
</a-menu>
<a-card hoverable :style="{ width: '50vw' }" class="aboutCard">
<div class="content center" >
<UserInfo/>
</div>
</a-card>
<div id="UserCenter" class="user-center">
<div class="info">
<a-menu
:style="{ width: '220px' }"
:default-open-keys="['0', '1', '2']"
:default-selected-keys="['0_0']"
show-collapse-button
breakpoint="xl"
@collapse="onCollapse"
>
<a-sub-menu key="0">
<template #icon><icon-apps></icon-apps></template>
<template #title>我的资料</template>
<a-menu-item key="0_0" @click="ChangeStatus(1)">修改资料</a-menu-item>
<a-menu-item key="0_1" @click="ChangeStatus(2)">修改密码</a-menu-item>
</a-sub-menu>
<a-sub-menu key="1">
<template #icon><icon-bug></icon-bug></template>
<template #title>我的论坛</template>
<a-menu-item key="1_0" @click="ChangeStatus(3)">我的发帖</a-menu-item>
<a-menu-item key="1_1" @click="ChangeStatus(4)">我的评论</a-menu-item>
</a-sub-menu>
<a-sub-menu key="2">
<template #icon><icon-bulb></icon-bulb></template>
<template #title>我的题库</template>
<a-menu-item key="2_0" @click="gotoQuestion">做题记录</a-menu-item>
</a-sub-menu>
</a-menu>
<a-card hoverable :style="{ width: '60vw' }" class="aboutCard">
<div class="content">
<UserInfo v-if="dashStatus==1"/>
<UserChangePwd v-else-if="dashStatus==2"/>
<UserPost v-else-if="dashStatus==3"/>
<UserReview v-else-if="dashStatus==4"/>
</div>
</a-card>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import UserInfo from "@/components/User/UserInfo.vue"
import {ref} from 'vue'
import { useRouter,useRoute } from "vue-router";
import UserInfo from "@/components/User/UserInfo.vue";
import UserChangePwd from '@/components/User/UserChangePwd.vue';
import UserReview from '@/components/User/UserReview.vue';
import UserPost from '@/components/User/UserPost.vue';
const dashStatus=ref(1)
const router=useRouter()
const ChangeStatus=(v:number)=>{
dashStatus.value=v
}
const gotoQuestion=()=>{
router.push("/history/question/")
}
</script>
<style scoped>
.user-center {
padding: 20px; /* 增加整体的内边距 */
display: flex;
justify-content: center;
}
.info {
display: flex;
justify-content: space-between;
align-items: flex-start; /* 垂直对齐 */
}
.center {
display: flex;
align-items: center;
justify-content: center;
}
.content {
width: 100%;
flex-direction: column;
}
.arco-avatar {
width: 80px;
height: 80px;
margin-bottom: 10px;
}
.arco-card-size-medium {
font-size: 8px;
}
.save {
align-self: flex-end;
}
:deep(.vch__container .vch__legend) {
display: none;
.aboutCard {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); /* 加强阴影效果 */
border-radius: 12px; /* 圆角效果 */
transition: box-shadow 0.3s; /* 动画过渡效果 */
}
</style>
.aboutCard:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); /* 鼠标悬停时的阴影效果 */
}
/* 菜单样式 */
.a-menu {
background-color: #ffffff; /* 菜单背景 */
border-radius: 8px; /* 菜单圆角 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 菜单阴影 */
}
.a-menu-item:hover {
background-color: #e6f7ff; /* 菜单项悬停效果 */
}
</style>

View File

@ -37,55 +37,87 @@
</a-button>
</a-form-item>
</a-form>
<!-- 底部链接 -->
<div class="bottom-text">没有账号<a @click="goToReg">请注册</a></div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from "vue";
import { UserControllerService, UserLoginRequest } from "../../../generated";
import message from "@arco-design/web-vue/es/message";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import { reactive, defineEmits, ref, defineProps, withDefaults } from 'vue';
import { UserControllerService, UserLoginRequest } from '../../../generated';
import message from '@arco-design/web-vue/es/message';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
/**
* 表单信息
*/
const form = reactive({
userAccount: "",
userPassword: "",
userAccount: '',
userPassword: '',
} as UserLoginRequest);
//
const router = useRouter();
const store = useStore();
//
const userModelInfo = ref('user');
//
const emit = defineEmits(['goToReg']);
// props
const props = withDefaults(
defineProps<{
ModelStatus?: string;
}>(),
{
ModelStatus: 'page',
}
);
// ModelStatus
console.log('接收到的 ModelStatus:', props.ModelStatus);
/**
* 跳转到注册页面
*/
const goToReg = () => {
userModelInfo.value = 'reg';
emit('goToReg', userModelInfo.value); //
// ModelStatus
if (props.ModelStatus !== 'card') {
router.push('/user/reg'); //
}
};
/**
* 提交表单
* @param data
*/
const handleSubmit = async () => {
const res = await UserControllerService.userLoginUsingPost(form);
//
if (res.code === 0) {
await store.dispatch("user/getLoginUser");
router.push({
path: "/question",
replace: true,
});
await store.dispatch('user/getLoginUser');
router.push({ path: '/question', replace: true });
} else {
message.error("登陆失败," + res.message);
message.error('登录失败,' + res.message); //
}
};
</script>
<style scoped>
/* 样式优化 */
#userLoginView {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
@ -111,7 +143,16 @@ h2 {
flex-direction: column;
gap: 16px;
}
/* 底部文本样式 */
.bottom-text {
text-align: center;
margin-top: 16px;
}
.bottom-text a {
color: #0052d9;
cursor: pointer;
}
.login-button {
width: 100%;
}

View File

@ -1,72 +1,211 @@
<template>
<div id="userRegView">
<h2 style="margin-bottom: 16px">用户注册</h2>
<a-form
style="max-width: 480px; margin: 0 auto"
label-align="left"
auto-label-width
:model="form"
@submit="handleSubmit"
>
<a-form-item field="userAccount" label="账号">
<a-input v-model="form.userAccount" placeholder="请输入账号" />
</a-form-item>
<a-form-item field="userPassword" tooltip="密码不少于 8 位" label="密码">
<a-input-password
v-model="form.userPassword"
placeholder="请输入密码"
/>
</a-form-item>
<a-form-item field="checkPassword" tooltip="密码不少于 8 位" label="密码">
<a-input-password
v-model="form.checkPassword"
placeholder="请再次输入密码"
/>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit" style="width: 120px">
注册
</a-button>
</a-form-item>
</a-form>
<div class="register-container">
<h2>用户注册</h2>
<a-form
layout="vertical"
:model="form"
@submit="handleSubmit"
class="register-form"
>
<a-form-item field="userAccount" label="账号">
<a-input
v-model="form.userAccount"
placeholder="请输入账号"
size="large"
allow-clear
/>
</a-form-item>
<a-form-item field="userPassword" label="密码">
<a-input-password
v-model="form.userPassword"
placeholder="请输入密码"
size="large"
allow-clear
/>
</a-form-item>
<a-form-item field="checkPassword" label="确认密码">
<a-input-password
v-model="form.checkPassword"
placeholder="请再次输入密码"
size="large"
allow-clear
/>
</a-form-item>
<a-form-item>
<a-button
type="primary"
html-type="submit"
size="large"
class="register-button"
>
注册
</a-button>
</a-form-item>
</a-form>
<!-- 底部链接 -->
<div class="bottom-text">
已有账号<a @click="goToLogin">请登录</a>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from "vue";
import { UserControllerService, UserLoginRequest } from "../../../generated";
import message from "@arco-design/web-vue/es/message";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import { reactive, ref, defineEmits, defineProps, withDefaults } from 'vue';
import { UserControllerService, UserLoginRequest } from '../../../generated';
import message from '@arco-design/web-vue/es/message';
import { useRouter } from 'vue-router';
/**
* Props 定义
*/
interface Status {
ModelStatus?: string; //
}
// 使 withDefaults ModelStatus
const props = withDefaults(defineProps<Status>(), {
ModelStatus: 'page', // 'page'
});
/**
* 表单信息
*/
const form = reactive({
userAccount: "",
userPassword: "",
checkPassword:""
userAccount: '',
userPassword: '',
checkPassword: '',
} as UserLoginRequest);
//
const router = useRouter();
const store = useStore();
/**
* Emit 事件定义
*/
const emit = defineEmits(['goToLogin']);
/**
* 当前模式信息
*/
const ModelInfo = ref('login');
/**
* 提交表单
* @param data
*/
const handleSubmit = async () => {
if (!isPasswordValid()) return; //
const res = await UserControllerService.userRegisterUsingPost(form);
//
handleResponse(res); //
};
/**
* 校验密码是否一致
* @returns 是否有效
*/
const isPasswordValid = (): boolean => {
if (form.userPassword !== form.checkPassword) {
message.error('两次输入的密码不一致');
return false;
}
return true;
};
/**
* 处理响应
* @param res API 响应
*/
const handleResponse = (res: any) => {
if (res.code === 0) {
message.success(res.message);
// await store.dispatch("user/getLoginUser");
router.push({
path: "/user/login",
replace: true,
});
message.success(res.message);
router.push({ path: '/user/login', replace: true });
} else {
message.error("注册" + res.message);
message.error('注册失败,' + res.message);
}
};
/**
* 跳转到登录页面
*/
const goToLogin = () => {
if (props.ModelStatus !== 'card') {
router.push('/user/login');
}
emit('goToLogin', ModelInfo.value); //
};
</script>
<style scoped>
/* 优化样式 */
#userRegView {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.register-container {
background: #fff;
padding: 40px 30px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
max-width: 400px;
width: 100%;
animation: fadeIn 0.5s ease;
}
h2 {
text-align: center;
font-size: 24px;
color: #0052d9;
margin-bottom: 24px;
}
.register-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.register-button {
width: 100%;
}
/* 底部文本样式 */
.bottom-text {
text-align: center;
margin-top: 16px;
}
.bottom-text a {
color: #0052d9;
cursor: pointer;
}
/* 动画效果 */
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(-20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式优化 */
@media (max-width: 600px) {
.register-container {
padding: 30px 20px;
}
h2 {
font-size: 20px;
}
}
</style>