first commit
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import api from '../api';
|
||||
import { toast } from '../utils';
|
||||
|
||||
const router = useRouter();
|
||||
const user = ref(JSON.parse(localStorage.getItem('user') || '{}'));
|
||||
const editMode = ref(false);
|
||||
const form = ref({ nickname: '', phone: '', building: '', bio: '' });
|
||||
const pwdForm = ref({ oldPassword: '', newPassword: '', confirm: '' });
|
||||
const showPwd = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await api.get('/auth/me');
|
||||
user.value = data;
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
form.value = { nickname: data.nickname, phone: data.phone, building: data.building, bio: data.bio };
|
||||
} catch (e) {
|
||||
toast(e.message);
|
||||
}
|
||||
});
|
||||
|
||||
async function saveProfile() {
|
||||
try {
|
||||
const data = await api.put('/auth/me', form.value);
|
||||
user.value = data;
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
editMode.value = false;
|
||||
toast('保存成功');
|
||||
} catch (e) {
|
||||
toast(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function changePassword() {
|
||||
const { oldPassword, newPassword, confirm } = pwdForm.value;
|
||||
if (!oldPassword || !newPassword) return toast('请填写完整');
|
||||
if (newPassword !== confirm) return toast('两次新密码不一致');
|
||||
try {
|
||||
await api.put('/auth/password', { oldPassword, newPassword });
|
||||
toast('密码修改成功');
|
||||
pwdForm.value = { oldPassword: '', newPassword: '', confirm: '' };
|
||||
showPwd.value = false;
|
||||
} catch (e) {
|
||||
toast(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
router.push('/login');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="card profile-card">
|
||||
<div class="avatar big">{{ user.nickname?.charAt(0) }}</div>
|
||||
<div class="info">
|
||||
<div class="name">{{ user.nickname }}</div>
|
||||
<div class="meta">@{{ user.username }}</div>
|
||||
<div class="meta" v-if="user.building">📍 {{ user.building }}</div>
|
||||
<div class="meta" v-if="user.bio">{{ user.bio }}</div>
|
||||
</div>
|
||||
<span v-if="user.role === 'admin'" class="badge">管理员</span>
|
||||
</div>
|
||||
|
||||
<div class="card section">
|
||||
<div class="section-title">
|
||||
<h3>个人资料</h3>
|
||||
<button class="btn btn-ghost btn-sm" @click="editMode = !editMode">
|
||||
{{ editMode ? '取消' : '编辑' }}
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="editMode">
|
||||
<div class="form">
|
||||
<label>昵称</label>
|
||||
<input v-model="form.nickname" />
|
||||
<label>手机号</label>
|
||||
<input v-model="form.phone" />
|
||||
<label>楼栋/房号</label>
|
||||
<input v-model="form.building" />
|
||||
<label>个人简介</label>
|
||||
<textarea v-model="form.bio" rows="3"></textarea>
|
||||
<button class="btn" @click="saveProfile">保存</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="kv">
|
||||
<div class="row"><span>昵称</span><span>{{ user.nickname }}</span></div>
|
||||
<div class="row"><span>手机号</span><span>{{ user.phone || '未填写' }}</span></div>
|
||||
<div class="row"><span>楼栋/房号</span><span>{{ user.building || '未填写' }}</span></div>
|
||||
<div class="row"><span>个人简介</span><span>{{ user.bio || '未填写' }}</span></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="card section">
|
||||
<div class="section-title">
|
||||
<h3>账号安全</h3>
|
||||
<button class="btn btn-ghost btn-sm" @click="showPwd = !showPwd">
|
||||
{{ showPwd ? '收起' : '修改密码' }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="showPwd" class="form">
|
||||
<input v-model="pwdForm.oldPassword" type="password" placeholder="原密码" />
|
||||
<input v-model="pwdForm.newPassword" type="password" placeholder="新密码(至少6位)" />
|
||||
<input v-model="pwdForm.confirm" type="password" placeholder="确认新密码" />
|
||||
<button class="btn" @click="changePassword">确认修改</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-danger logout-btn" @click="logout">退出登录</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.profile-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.avatar.big {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: var(--text-light);
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form label {
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.kv .row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.kv .row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.kv .row span:first-child {
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user