完成头像上传
This commit is contained in:
parent
aba88b26a2
commit
915718a07d
@ -94,6 +94,12 @@
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
<!-- 对象存储Minio依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>8.3.9</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
@ -0,0 +1,28 @@
|
||||
package com.example.config;
|
||||
|
||||
import io.minio.MinioClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class MinioConfiguration {
|
||||
|
||||
@Value("${spring.minio.endpoint}")
|
||||
String endpoint;
|
||||
@Value("${spring.minio.username}")
|
||||
String username;
|
||||
@Value("${spring.minio.password}")
|
||||
String password;
|
||||
|
||||
@Bean
|
||||
public MinioClient minioClient(){
|
||||
log.info("Init minio client...");
|
||||
return MinioClient.builder()
|
||||
.endpoint(endpoint)
|
||||
.credentials(username, password)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -54,6 +54,7 @@ public class SecurityConfiguration {
|
||||
return http
|
||||
.authorizeHttpRequests(conf -> conf
|
||||
.requestMatchers("/api/auth/**", "/error").permitAll()
|
||||
.requestMatchers("/images/**").permitAll()
|
||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||
.anyRequest().hasAnyRole(Const.ROLE_DEFAULT)
|
||||
)
|
||||
|
@ -0,0 +1,35 @@
|
||||
package com.example.controller;
|
||||
|
||||
import com.example.entity.RestBean;
|
||||
import com.example.service.ImageService;
|
||||
import com.example.utils.Const;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/image")
|
||||
public class ImageController {
|
||||
|
||||
@Resource
|
||||
ImageService service;
|
||||
|
||||
@PostMapping("/avatar")
|
||||
public RestBean<String> uploadAvatar(@RequestParam("file") MultipartFile file,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int id) throws IOException {
|
||||
if(file.getSize() > 1025 * 100)
|
||||
return RestBean.failure(400, "头像图片不能大于100KB");
|
||||
log.info("正在进行头像上传操作...");
|
||||
String url = service.uploadAvatar(file, id);
|
||||
if(url != null) {
|
||||
log.info("头像上传成功,大小: " + file.getSize());
|
||||
return RestBean.success(url);
|
||||
} else {
|
||||
return RestBean.failure(400, "头像上传失败,请联系管理员!");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.example.controller;
|
||||
|
||||
import com.example.entity.RestBean;
|
||||
import com.example.service.ImageService;
|
||||
import io.minio.errors.ErrorResponseException;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class ObjectController {
|
||||
|
||||
@Resource
|
||||
ImageService service;
|
||||
|
||||
@GetMapping("/images/avatar/**")
|
||||
public void imageFetch(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
this.fetchImage(request, response);
|
||||
}
|
||||
|
||||
private void fetchImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
String imagePath = request.getServletPath().substring(7);
|
||||
ServletOutputStream stream = response.getOutputStream();
|
||||
if(imagePath.length() <= 13) {
|
||||
response.setStatus(404);
|
||||
stream.println(RestBean.failure(404, "Not found").toString());
|
||||
} else {
|
||||
try {
|
||||
service.fetchImageFromMinio(stream, imagePath);
|
||||
response.setHeader("Cache-Control", "max-age=2592000");
|
||||
} catch (ErrorResponseException e) {
|
||||
if(e.response().code() == 404) {
|
||||
response.setStatus(404);
|
||||
stream.println(RestBean.failure(404, "Not found").toString());
|
||||
} else {
|
||||
log.error("从Minio获取图片出现异常: "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,5 +22,6 @@ public class Account implements BaseData {
|
||||
String password;
|
||||
String email;
|
||||
String role;
|
||||
String avatar;
|
||||
Date registerTime;
|
||||
}
|
||||
|
@ -9,5 +9,6 @@ public class AccountVO {
|
||||
String username;
|
||||
String email;
|
||||
String role;
|
||||
String avatar;
|
||||
Date registerTime;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ public class RequestLogFilter extends OncePerRequestFilter {
|
||||
@Resource
|
||||
SnowflakeIdGenerator generator;
|
||||
|
||||
private final Set<String> ignores = Set.of("/swagger-ui", "/v3/api-docs");
|
||||
private final Set<String> ignores = Set.of("/swagger-ui", "/v3/api-docs", "/images");
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
|
@ -0,0 +1,11 @@
|
||||
package com.example.service;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface ImageService {
|
||||
String uploadAvatar(MultipartFile file, int id) throws IOException;
|
||||
void fetchImageFromMinio(OutputStream stream, String image) throws Exception;
|
||||
}
|
@ -99,7 +99,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
if(this.existsAccountByUsername(username)) return "该用户名已被他人使用,请重新更换";
|
||||
String password = passwordEncoder.encode(info.getPassword());
|
||||
Account account = new Account(null, info.getUsername(),
|
||||
password, email, Const.ROLE_DEFAULT, new Date());
|
||||
password, email, Const.ROLE_DEFAULT, null, new Date());
|
||||
if(!this.save(account)) {
|
||||
return "内部错误,注册失败";
|
||||
} else {
|
||||
|
@ -0,0 +1,62 @@
|
||||
package com.example.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.example.entity.dto.Account;
|
||||
import com.example.mapper.AccountMapper;
|
||||
import com.example.service.ImageService;
|
||||
import io.minio.GetObjectArgs;
|
||||
import io.minio.GetObjectResponse;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.PutObjectArgs;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.compress.utils.IOUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ImageServiceImpl implements ImageService {
|
||||
|
||||
@Resource
|
||||
MinioClient client;
|
||||
|
||||
@Resource
|
||||
AccountMapper mapper;
|
||||
|
||||
@Override
|
||||
public void fetchImageFromMinio(OutputStream stream, String image) throws Exception {
|
||||
GetObjectArgs args = GetObjectArgs.builder()
|
||||
.bucket("study")
|
||||
.object(image)
|
||||
.build();
|
||||
GetObjectResponse response = client.getObject(args);
|
||||
IOUtils.copy(response, stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadAvatar(MultipartFile file, int id) throws IOException {
|
||||
String imageName = UUID.randomUUID().toString().replace("-", "");
|
||||
imageName = "/avatar/" + imageName;
|
||||
PutObjectArgs args = PutObjectArgs.builder()
|
||||
.bucket("study")
|
||||
.stream(file.getInputStream(), file.getSize(), -1)
|
||||
.object(imageName)
|
||||
.build();
|
||||
try {
|
||||
client.putObject(args);
|
||||
if(mapper.update(null, Wrappers.<Account>update()
|
||||
.eq("id", id).set("avatar", imageName)) > 0) {
|
||||
return imageName;
|
||||
} else
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("图片上传出现问题: "+ e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -39,3 +39,7 @@ spring:
|
||||
origin: '*'
|
||||
credentials: false
|
||||
methods: '*'
|
||||
minio:
|
||||
endpoint: 'http://localhost:9000'
|
||||
username: 'minio'
|
||||
password: 'password'
|
||||
|
@ -43,3 +43,7 @@ spring:
|
||||
origin: '*'
|
||||
credentials: false
|
||||
methods: '*'
|
||||
minio:
|
||||
endpoint: 'http://localhost:9000'
|
||||
username: 'minio'
|
||||
password: 'password'
|
||||
|
@ -99,4 +99,4 @@ function unauthorized() {
|
||||
return !takeAccessToken()
|
||||
}
|
||||
|
||||
export { post, get, login, logout, unauthorized }
|
||||
export { post, get, login, logout, unauthorized, accessHeader }
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { defineStore } from "pinia";
|
||||
import axios from "axios";
|
||||
|
||||
export const useStore = defineStore('general', {
|
||||
state: () => {
|
||||
@ -7,8 +8,16 @@ export const useStore = defineStore('general', {
|
||||
username: '',
|
||||
email: '',
|
||||
role: '',
|
||||
avatar: null,
|
||||
registerTime: null
|
||||
}
|
||||
}
|
||||
}, getters: {
|
||||
avatarUrl() {
|
||||
if(this.user.avatar)
|
||||
return `${axios.defaults.baseURL}/images${this.user.avatar}`
|
||||
else
|
||||
return 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -59,7 +59,7 @@ function userLogout() {
|
||||
<div>{{store.user.email}}</div>
|
||||
</div>
|
||||
<el-dropdown>
|
||||
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
|
||||
<el-avatar :src="store.avatarUrl"/>
|
||||
<template #dropdown>
|
||||
<el-dropdown-item>
|
||||
<el-icon><Operation/></el-icon>
|
||||
|
@ -4,8 +4,9 @@ import Card from "@/components/Card.vue";
|
||||
import {Message, Refresh, Select, User} from "@element-plus/icons-vue";
|
||||
import {useStore} from "@/store";
|
||||
import {computed, reactive, ref} from "vue";
|
||||
import {get, post} from "@/net";
|
||||
import {accessHeader, get, post} from "@/net";
|
||||
import {ElMessage} from "element-plus";
|
||||
import axios from "axios";
|
||||
|
||||
const store = useStore()
|
||||
|
||||
@ -29,7 +30,7 @@ const emailForm = reactive({
|
||||
const validateUsername = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请输入用户名'))
|
||||
} else if(!/^[a-zA-Z0-9\u4e00-\u9fa5]+$/.test(value)){
|
||||
} else if (!/^[a-zA-Z0-9\u4e00-\u9fa5]+$/.test(value)) {
|
||||
callback(new Error('用户名不能包含特殊字符,只能是中文/英文'))
|
||||
} else {
|
||||
callback()
|
||||
@ -37,10 +38,10 @@ const validateUsername = (rule, value, callback) => {
|
||||
}
|
||||
const rules = {
|
||||
username: [
|
||||
{ validator: validateUsername, trigger: ['blur', 'change'] },
|
||||
{ min: 2, max: 10, message: '用户名的长度必须在2-10个字符之间', trigger: ['blur', 'change'] },
|
||||
{validator: validateUsername, trigger: ['blur', 'change']},
|
||||
{min: 2, max: 10, message: '用户名的长度必须在2-10个字符之间', trigger: ['blur', 'change']},
|
||||
], email: [
|
||||
{ required: true, message: '请输入邮件地址', trigger: 'blur' },
|
||||
{required: true, message: '请输入邮件地址', trigger: 'blur'},
|
||||
{type: 'email', message: '请输入合法的电子邮件地址', trigger: ['blur', 'change']}
|
||||
]
|
||||
}
|
||||
@ -52,7 +53,7 @@ const loading = reactive({
|
||||
|
||||
function saveDetails() {
|
||||
baseFormRef.value.validate(isValid => {
|
||||
if(isValid) {
|
||||
if (isValid) {
|
||||
loading.base = true
|
||||
post('/api/user/save-details', baseForm, () => {
|
||||
ElMessage.success('用户信息保存成功')
|
||||
@ -66,6 +67,7 @@ function saveDetails() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get('/api/user/details', data => {
|
||||
baseForm.username = store.user.username
|
||||
baseForm.gender = data.gender
|
||||
@ -80,18 +82,19 @@ get('/api/user/details', data => {
|
||||
const coldTime = ref(0)
|
||||
const isEmailValid = ref(true)
|
||||
const onValidate = (prop, isValid) => {
|
||||
if(prop === 'email')
|
||||
if (prop === 'email')
|
||||
isEmailValid.value = isValid
|
||||
}
|
||||
|
||||
function sendEmailCode() {
|
||||
emailFormRef.value.validate(isValid => {
|
||||
if(isValid) {
|
||||
if (isValid) {
|
||||
coldTime.value = 60
|
||||
get(`/api/auth/ask-code?email=${emailForm.email}&type=modify`, () => {
|
||||
ElMessage.success(`验证码已成功发送到邮箱:${emailForm.email},请注意查收`)
|
||||
const handle = setInterval(() => {
|
||||
coldTime.value--
|
||||
if(coldTime.value === 0) {
|
||||
if (coldTime.value === 0) {
|
||||
clearInterval(handle)
|
||||
}
|
||||
}, 1000)
|
||||
@ -102,9 +105,10 @@ function sendEmailCode() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function modifyEmail() {
|
||||
emailFormRef.value.validate(isValid => {
|
||||
if(isValid) {
|
||||
if (isValid) {
|
||||
post('/api/user/modify-email', emailForm, () => {
|
||||
ElMessage.success('邮件修改成功')
|
||||
store.user.email = emailForm.email
|
||||
@ -113,94 +117,124 @@ function modifyEmail() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function beforeAvatarUpload(rawFile) {
|
||||
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
|
||||
ElMessage.error('头像只能是 JPG/PNG 格式的')
|
||||
return false
|
||||
} else if(rawFile.size / 1024 > 100) {
|
||||
ElMessage.error('头像大小不能大于 100KB')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function uploadSuccess(response){
|
||||
ElMessage.success('头像上传成功')
|
||||
store.user.avatar = response.data
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="display: flex;max-width: 950px;margin: auto">
|
||||
<div class="settings-left">
|
||||
<card :icon="User" title="账号信息设置" desc="在这里编辑您的个人信息,您可以在隐私设置中选择是否展示这些信息"
|
||||
v-loading="loading.form">
|
||||
<el-form :model="baseForm" :rules="rules" ref="baseFormRef" label-position="top" style="margin: 0 10px 10px 10px">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="baseForm.username" maxlength="10"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别">
|
||||
<el-radio-group v-model="baseForm.gender">
|
||||
<el-radio :label="0">男</el-radio>
|
||||
<el-radio :label="1">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="baseForm.phone" maxlength="11"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="QQ号" prop="qq">
|
||||
<el-input v-model="baseForm.qq" maxlength="13"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="微信号" prop="wx">
|
||||
<el-input v-model="baseForm.wx" maxlength="20"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="个人简介" prop="desc">
|
||||
<el-input v-model="baseForm.desc" type="textarea" :rows="6" maxlength="200"/>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-button :icon="Select" @click="saveDetails" :loading="loading.base"
|
||||
type="success">保存用户信息</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</card>
|
||||
<card style="margin-top: 10px" :icon="Message" title="电子邮件设置" desc="您可以在这里修改默认绑定的电子邮件地址">
|
||||
<el-form :rules="rules" @validate="onValidate" :model="emailForm" ref="emailFormRef" label-position="top" style="margin: 0 10px 10px 10px">
|
||||
<el-form-item label="电子邮件" prop="email">
|
||||
<el-input v-model="emailForm.email"/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-row style="width: 100%" :gutter="10">
|
||||
<el-col :span="18">
|
||||
<el-input placeholder="请获取验证码" v-model="emailForm.code"/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-button type="success" style="width: 100%" :disabled="!isEmailValid || coldTime > 0"
|
||||
@click="sendEmailCode" plain>
|
||||
{{ coldTime > 0 ? `请稍后 ${coldTime} 秒` : '获取验证码'}}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-button :icon="Refresh" type="success" @click="modifyEmail">更新电子邮件</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</card>
|
||||
<div style="display: flex;max-width: 950px;margin: auto">
|
||||
<div class="settings-left">
|
||||
<card :icon="User" title="账号信息设置" desc="在这里编辑您的个人信息,您可以在隐私设置中选择是否展示这些信息"
|
||||
v-loading="loading.form">
|
||||
<el-form :model="baseForm" :rules="rules" ref="baseFormRef" label-position="top"
|
||||
style="margin: 0 10px 10px 10px">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="baseForm.username" maxlength="10"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别">
|
||||
<el-radio-group v-model="baseForm.gender">
|
||||
<el-radio :label="0">男</el-radio>
|
||||
<el-radio :label="1">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="baseForm.phone" maxlength="11"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="QQ号" prop="qq">
|
||||
<el-input v-model="baseForm.qq" maxlength="13"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="微信号" prop="wx">
|
||||
<el-input v-model="baseForm.wx" maxlength="20"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="个人简介" prop="desc">
|
||||
<el-input v-model="baseForm.desc" type="textarea" :rows="6" maxlength="200"/>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-button :icon="Select" @click="saveDetails" :loading="loading.base"
|
||||
type="success">保存用户信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</card>
|
||||
<card style="margin-top: 10px" :icon="Message" title="电子邮件设置"
|
||||
desc="您可以在这里修改默认绑定的电子邮件地址">
|
||||
<el-form :rules="rules" @validate="onValidate" :model="emailForm" ref="emailFormRef"
|
||||
label-position="top" style="margin: 0 10px 10px 10px">
|
||||
<el-form-item label="电子邮件" prop="email">
|
||||
<el-input v-model="emailForm.email"/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-row style="width: 100%" :gutter="10">
|
||||
<el-col :span="18">
|
||||
<el-input placeholder="请获取验证码" v-model="emailForm.code"/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-button type="success" style="width: 100%" :disabled="!isEmailValid || coldTime > 0"
|
||||
@click="sendEmailCode" plain>
|
||||
{{ coldTime > 0 ? `请稍后 ${coldTime} 秒` : '获取验证码' }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-button :icon="Refresh" type="success" @click="modifyEmail">更新电子邮件</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</card>
|
||||
</div>
|
||||
<div class="settings-right">
|
||||
<div style="position: sticky;top: 20px">
|
||||
<card>
|
||||
<div style="text-align: center;padding: 5px 15px 0 15px">
|
||||
<el-avatar :size="70" :src="store.avatarUrl"/>
|
||||
<div style="margin: 5px 0">
|
||||
<el-upload
|
||||
:action="axios.defaults.baseURL + '/api/image/avatar'"
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:on-success="uploadSuccess"
|
||||
:headers="accessHeader()">
|
||||
<el-button size="small" round>修改头像</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div style="font-weight: bold">你好, {{ store.user.username }}</div>
|
||||
</div>
|
||||
<el-divider style="margin: 10px 0"/>
|
||||
<div style="font-size: 14px;color: grey;padding: 10px">
|
||||
{{ desc || '这个用户很懒,没有填写个人简介~' }}
|
||||
</div>
|
||||
</card>
|
||||
<card style="margin-top: 10px;font-size: 14px">
|
||||
<div>账号注册时间: {{ registerTime }}</div>
|
||||
<div style="color: grey">欢迎加入我们的学习论坛!</div>
|
||||
</card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-right">
|
||||
<div style="position: sticky;top: 20px">
|
||||
<card>
|
||||
<div style="text-align: center;padding: 5px 15px 0 15px">
|
||||
<el-avatar :size="70" src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
|
||||
<div style="font-weight: bold">你好, {{store.user.username}}</div>
|
||||
</div>
|
||||
<el-divider style="margin: 10px 0"/>
|
||||
<div style="font-size: 14px;color: grey;padding: 10px">
|
||||
{{ desc || '这个用户很懒,没有填写个人简介~'}}
|
||||
</div>
|
||||
</card>
|
||||
<card style="margin-top: 10px;font-size: 14px">
|
||||
<div>账号注册时间: {{registerTime}}</div>
|
||||
<div style="color: grey">欢迎加入我们的学习论坛!</div>
|
||||
</card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.settings-left {
|
||||
flex: 1;
|
||||
margin: 20px;
|
||||
flex: 1;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.settings-right {
|
||||
width: 300px;
|
||||
margin: 20px 30px 20px 0;
|
||||
width: 300px;
|
||||
margin: 20px 30px 20px 0;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user