Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
23c17f34a9 | |||
735024b7ce | |||
8609d8bee5 | |||
dc0dfbc037 | |||
a361be9609 | |||
132f14e850 | |||
6933748af3 |
@ -80,7 +80,7 @@
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>2.0.53</version>
|
||||
<version>2.0.25</version>
|
||||
</dependency>
|
||||
<!-- Jwt令牌生成校验框架 -->
|
||||
<dependency>
|
||||
|
@ -1,9 +1,7 @@
|
||||
package com.example.config;
|
||||
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.QueueBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@ -12,42 +10,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
*/
|
||||
@Configuration
|
||||
public class RabbitConfiguration {
|
||||
|
||||
@Bean
|
||||
public MessageConverter jsonMessageConverter() {
|
||||
return new Jackson2JsonMessageConverter();
|
||||
}
|
||||
|
||||
@Bean("errorQueue")
|
||||
public Queue dlQueue(){
|
||||
return QueueBuilder
|
||||
.durable("error")
|
||||
.ttl(24 * 60 * 60 * 1000)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean("errorExchange")
|
||||
public Exchange dlExchange(){
|
||||
return ExchangeBuilder.directExchange("dlx.direct").build();
|
||||
}
|
||||
|
||||
@Bean("dlBinding") //死信交换机和死信队列进绑定
|
||||
public Binding dlBinding(@Qualifier("errorExchange") Exchange exchange,
|
||||
@Qualifier("errorQueue") Queue queue){
|
||||
return BindingBuilder
|
||||
.bind(queue)
|
||||
.to(exchange)
|
||||
.with("error-message")
|
||||
.noargs();
|
||||
}
|
||||
|
||||
@Bean("mailQueue")
|
||||
public Queue queue(){
|
||||
return QueueBuilder
|
||||
.durable("mail")
|
||||
.deadLetterExchange("dlx.direct")
|
||||
.deadLetterRoutingKey("error-message")
|
||||
.ttl(3 * 60 * 1000)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
@ -105,8 +105,8 @@ public class SecurityConfiguration {
|
||||
} else if(exceptionOrAuthentication instanceof Authentication authentication){
|
||||
User user = (User) authentication.getPrincipal();
|
||||
Account account = service.findAccountByNameOrEmail(user.getUsername());
|
||||
if(account.isBanned()){
|
||||
writer.write(RestBean.forbidden("登录失败,此账户已被封禁,请俩系管理员").asJsonString());
|
||||
if(account.isBanned()) {
|
||||
writer.write(RestBean.forbidden("登录失败,此账户已被封禁").asJsonString());
|
||||
return;
|
||||
}
|
||||
String jwt = utils.createJwt(user, account.getUsername(), account.getId());
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.example.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -32,10 +32,10 @@ public class ForumController {
|
||||
TopicService topicService;
|
||||
|
||||
@Resource
|
||||
AccountService accountService;
|
||||
ControllerUtils utils;
|
||||
|
||||
@Resource
|
||||
ControllerUtils utils;
|
||||
AccountService accountService;
|
||||
|
||||
@GetMapping("/weather")
|
||||
public RestBean<WeatherVO> weather(double longitude, double latitude){
|
||||
@ -57,7 +57,7 @@ public class ForumController {
|
||||
public RestBean<Void> createTopic(@Valid @RequestBody TopicCreateVO vo,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int id) {
|
||||
Account account = accountService.findAccountById(id);
|
||||
if (account.isMute()) {
|
||||
if(account.isMute()) {
|
||||
return RestBean.forbidden("您已被禁言,无法创建新的主题");
|
||||
}
|
||||
return utils.messageHandle(() -> topicService.createTopic(id, vo));
|
||||
@ -104,7 +104,7 @@ public class ForumController {
|
||||
public RestBean<Void> addComment(@Valid @RequestBody AddCommentVO vo,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int id){
|
||||
Account account = accountService.findAccountById(id);
|
||||
if (account.isMute()) {
|
||||
if(account.isMute()) {
|
||||
return RestBean.forbidden("您已被禁言,无法创建新的回复");
|
||||
}
|
||||
return utils.messageHandle(() -> topicService.createComment(id, vo));
|
||||
|
@ -68,19 +68,23 @@ public class AccountAdminController {
|
||||
handleBanned(account, save);
|
||||
BeanUtils.copyProperties(save, account, "password", "registerTime");
|
||||
service.saveOrUpdate(account);
|
||||
AccountDetails details = detailsService.findAccountDetailsById(id);
|
||||
AccountDetails saveDetails = object.getJSONObject("detail").toJavaObject(AccountDetails.class);
|
||||
detailsService.saveOrUpdate(saveDetails);
|
||||
BeanUtils.copyProperties(saveDetails, details);
|
||||
detailsService.saveOrUpdate(details);
|
||||
AccountPrivacy privacy = privacyService.accountPrivacy(id);
|
||||
AccountPrivacy savePrivacy = object.getJSONObject("privacy").toJavaObject(AccountPrivacy.class);
|
||||
BeanUtils.copyProperties(savePrivacy, privacy);
|
||||
privacyService.saveOrUpdate(savePrivacy);
|
||||
return RestBean.success();
|
||||
}
|
||||
|
||||
private void handleBanned(Account old, Account current) {
|
||||
String key = Const.BANNED_BLOCK + old.getId();
|
||||
if(old.isBanned() && !current.isBanned()) {
|
||||
template.delete(key);
|
||||
} else if(!old.isBanned() && current.isBanned()) {
|
||||
if(!old.isBanned() && current.isBanned()) {
|
||||
template.opsForValue().set(key, "true", expire, TimeUnit.HOURS);
|
||||
} else if(old.isBanned() && !current.isBanned()) {
|
||||
template.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
package com.example.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class QueueMessage {
|
||||
private String messageType;
|
||||
private final Map<String, Object> data = new HashMap<>();
|
||||
|
||||
public static QueueMessage create(String messageType) {
|
||||
QueueMessage queueMessage = new QueueMessage();
|
||||
queueMessage.messageType = messageType;
|
||||
return queueMessage;
|
||||
}
|
||||
|
||||
public QueueMessage put(String key, Object value) {
|
||||
data.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String key) {
|
||||
return (T) data.get(key);
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.example.entity.BaseData;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@ -14,6 +15,7 @@ import java.util.Date;
|
||||
*/
|
||||
@Data
|
||||
@TableName("db_account")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Account implements BaseData {
|
||||
@TableId(type = IdType.AUTO)
|
||||
|
@ -1,30 +0,0 @@
|
||||
package com.example.entity.dto;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName("db_verify_email")
|
||||
public class VerifyEmail {
|
||||
@TableId(type = IdType.AUTO)
|
||||
Integer id;
|
||||
String email;
|
||||
String type;
|
||||
String code;
|
||||
Date time;
|
||||
boolean success;
|
||||
|
||||
public static VerifyEmail success() {
|
||||
return new VerifyEmail().setSuccess(true);
|
||||
}
|
||||
|
||||
public static VerifyEmail failure() {
|
||||
return new VerifyEmail().setSuccess(false);
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
JwtUtils utils;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate template;
|
||||
StringRedisTemplate template;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
|
@ -1,37 +0,0 @@
|
||||
package com.example.listener;
|
||||
|
||||
import com.example.entity.QueueMessage;
|
||||
import com.example.entity.dto.VerifyEmail;
|
||||
import com.example.mapper.VerifyEmailMapper;
|
||||
import com.example.utils.Const;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RabbitListener(queues = Const.MQ_ERROR)
|
||||
public class ErrorQueueListener {
|
||||
|
||||
@Resource
|
||||
VerifyEmailMapper mapper;
|
||||
|
||||
@RabbitHandler
|
||||
public void saveErrorToDatabase(QueueMessage message) {
|
||||
log.error("出现一条错误的队列消息: {}", message);
|
||||
switch (message.getMessageType()) {
|
||||
case "email" -> {
|
||||
VerifyEmail error = VerifyEmail.failure()
|
||||
.setCode(message.get("code").toString())
|
||||
.setType(message.get("type"))
|
||||
.setEmail(message.get("email"))
|
||||
.setTime(new Date());
|
||||
mapper.insert(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,6 @@
|
||||
package com.example.listener;
|
||||
|
||||
import com.example.entity.QueueMessage;
|
||||
import com.example.entity.dto.VerifyEmail;
|
||||
import com.example.mapper.VerifyEmailMapper;
|
||||
import com.example.utils.Const;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -13,34 +8,30 @@ import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用于处理邮件发送的消息队列监听器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RabbitListener(queues = Const.MQ_MAIL, concurrency = "10")
|
||||
@RabbitListener(queues = "mail")
|
||||
public class MailQueueListener {
|
||||
|
||||
@Resource
|
||||
JavaMailSender sender;
|
||||
|
||||
@Resource
|
||||
VerifyEmailMapper emailMapper;
|
||||
|
||||
@Value("${spring.mail.username}")
|
||||
String username;
|
||||
|
||||
/**
|
||||
* 处理邮件发送
|
||||
* @param message 邮件信息
|
||||
* @param data 邮件信息
|
||||
*/
|
||||
@RabbitHandler
|
||||
public void sendMailMessage(QueueMessage message) {
|
||||
String email = message.get("email"), type = message.get("type");
|
||||
Integer code = message.get("code");
|
||||
SimpleMailMessage mailMessage = switch (type) {
|
||||
public void sendMailMessage(Map<String, Object> data) {
|
||||
String email = data.get("email").toString();
|
||||
Integer code = (Integer) data.get("code");
|
||||
SimpleMailMessage message = switch (data.get("type").toString()) {
|
||||
case "register" ->
|
||||
createMessage("欢迎注册我们的网站",
|
||||
"您的邮件注册验证码为: "+code+",有效时间3分钟,为了保障您的账户安全,请勿向他人泄露验证码信息。",
|
||||
@ -55,15 +46,8 @@ public class MailQueueListener {
|
||||
email);
|
||||
default -> null;
|
||||
};
|
||||
if(mailMessage == null) return;
|
||||
log.info("正在向 {} 发送 {} 类型的电子邮件...", email, type);
|
||||
sender.send(mailMessage);
|
||||
VerifyEmail record = VerifyEmail.success()
|
||||
.setCode(message.get("code").toString())
|
||||
.setType(message.get("type"))
|
||||
.setEmail(message.get("email"))
|
||||
.setTime(new Date());
|
||||
emailMapper.insert(record);
|
||||
if(message == null) return;
|
||||
sender.send(message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,9 +0,0 @@
|
||||
package com.example.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.entity.dto.VerifyEmail;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface VerifyEmailMapper extends BaseMapper<VerifyEmail> {
|
||||
}
|
@ -2,7 +2,6 @@ package com.example.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.example.entity.QueueMessage;
|
||||
import com.example.entity.dto.Account;
|
||||
import com.example.entity.dto.AccountDetails;
|
||||
import com.example.entity.dto.AccountPrivacy;
|
||||
@ -24,6 +23,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -86,9 +86,8 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
return "请求频繁,请稍后再试";
|
||||
Random random = new Random();
|
||||
int code = random.nextInt(899999) + 100000;
|
||||
QueueMessage message = QueueMessage.create("email");
|
||||
message.put("type",type).put("email", email).put("code", code);
|
||||
rabbitTemplate.convertAndSend(Const.MQ_MAIL, message);
|
||||
Map<String, Object> data = Map.of("type",type,"email", email, "code", code);
|
||||
rabbitTemplate.convertAndSend(Const.MQ_MAIL, data);
|
||||
stringRedisTemplate.opsForValue()
|
||||
.set(Const.VERIFY_EMAIL_DATA + email, String.valueOf(code), 3, TimeUnit.MINUTES);
|
||||
return null;
|
||||
|
@ -21,7 +21,6 @@ public final class Const {
|
||||
public final static String ATTR_USER_ID = "userId";
|
||||
//消息队列
|
||||
public final static String MQ_MAIL = "mail";
|
||||
public final static String MQ_ERROR = "error";
|
||||
//用户角色
|
||||
public final static String ROLE_DEFAULT = "user";
|
||||
public final static String ROLE_ADMIN = "admin";
|
||||
|
@ -13,12 +13,6 @@ spring:
|
||||
username: admin
|
||||
password: admin
|
||||
virtual-host: /
|
||||
listener:
|
||||
simple:
|
||||
retry:
|
||||
enabled: true
|
||||
max-attempts: 3
|
||||
initial-interval: 1000ms
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/study
|
||||
username: root
|
||||
|
@ -3,7 +3,7 @@ import { useDark, useToggle } from '@vueuse/core'
|
||||
import {onMounted, provide, ref} from "vue";
|
||||
import {isUnauthorized} from "@/net";
|
||||
import {apiUserInfo} from "@/net/api/user";
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import zhCn from "element-plus/es/locale/lang/zh-cn";
|
||||
|
||||
useDark({
|
||||
selector: 'html',
|
||||
@ -12,13 +12,13 @@ useDark({
|
||||
valueLight: 'light'
|
||||
})
|
||||
|
||||
const loading = ref()
|
||||
provide('userLoading', loading)
|
||||
|
||||
useDark({
|
||||
onChanged(dark) { useToggle(dark) }
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
provide('userLoading', loading)
|
||||
|
||||
onMounted(() => {
|
||||
if(!isUnauthorized()) {
|
||||
apiUserInfo(loading)
|
||||
@ -27,11 +27,11 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-config-provider :locale="zhCn">
|
||||
<div class="wrapper">
|
||||
<router-view/>
|
||||
</div>
|
||||
</el-config-provider>
|
||||
<el-config-provider :locale="zhCn">
|
||||
<div class="wrapper">
|
||||
<router-view/>
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import {get} from "@/net";
|
||||
import {ref} from "vue";
|
||||
import LightCard from "@/components/LightCard.vue";
|
||||
import router from "@/router";
|
||||
import TopicTag from "@/components/TopicTag.vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {apiForumCollect, apiForumCollectDelete} from "@/net/api/forum";
|
||||
|
||||
defineProps({
|
||||
show: Boolean
|
||||
@ -15,11 +15,11 @@ const emit = defineEmits(['close'])
|
||||
const list = ref([])
|
||||
|
||||
function init() {
|
||||
get('/api/forum/collects', data => list.value = data)
|
||||
apiForumCollect(data => list.value = data)
|
||||
}
|
||||
|
||||
function deleteCollect(index, tid) {
|
||||
get(`/api/forum/interact?tid=${tid}&type=collect&state=false`, () => {
|
||||
apiForumCollectDelete(tid, () => {
|
||||
ElMessage.success('已取消收藏!')
|
||||
list.value.splice(index, 1)
|
||||
})
|
||||
|
@ -2,8 +2,8 @@
|
||||
import {Delta, QuillEditor} from "@vueup/vue-quill";
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
import {ref} from "vue";
|
||||
import {post} from "@/net";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {apiForumCommentSubmit} from "@/net/api/forum";
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
@ -22,7 +22,7 @@ function submitComment() {
|
||||
ElMessage.warning('评论字数已经超出最大限制,请缩减评论内容!')
|
||||
return
|
||||
}
|
||||
post('/api/forum/add-comment', {
|
||||
apiForumCommentSubmit({
|
||||
tid: props.tid,
|
||||
quote: props.quote ? props.quote.id : -1,
|
||||
content: JSON.stringify(content.value)
|
||||
|
@ -6,10 +6,11 @@ import ImageResize from "quill-image-resize-vue";
|
||||
import { ImageExtend, QuillWatch } from "quill-image-super-solution-module";
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
import axios from "axios";
|
||||
import {accessHeader, post} from "@/net";
|
||||
import {accessHeader} from "@/net";
|
||||
import {ElMessage} from "element-plus";
|
||||
import ColorDot from "@/components/ColorDot.vue";
|
||||
import {useStore} from "@/store";
|
||||
import {apiForumTopicCreate} from "@/net/api/forum";
|
||||
|
||||
const store = useStore()
|
||||
|
||||
@ -33,7 +34,7 @@ const props = defineProps({
|
||||
},
|
||||
submit: {
|
||||
default: (editor, success) => {
|
||||
post('/api/forum/create-topic', {
|
||||
apiForumTopicCreate({
|
||||
type: editor.type.id,
|
||||
title: editor.title,
|
||||
content: editor.text
|
||||
|
@ -1,120 +0,0 @@
|
||||
<script setup>
|
||||
import {EditPen} from "@element-plus/icons-vue";
|
||||
import {reactive} from "vue";
|
||||
import {apiUserDetailTotal, apiUserSave} from "@/net/api/user";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const editor = reactive({
|
||||
id: 0,
|
||||
display: false,
|
||||
temp: {},
|
||||
loading: false,
|
||||
})
|
||||
|
||||
function loadUserEditor(user) {
|
||||
editor.id = user.id
|
||||
editor.display = true
|
||||
editor.loading = true
|
||||
apiUserDetailTotal(editor.id, data => {
|
||||
editor.temp = { ...data, ...user }
|
||||
editor.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ loadUserEditor })
|
||||
|
||||
function saveUserSettings() {
|
||||
editor.display = false
|
||||
apiUserSave(editor.temp, () => {
|
||||
const user = userTable.data.find(user => user.id === editor.id)
|
||||
Object.assign(user, editor.temp)
|
||||
ElMessage.success('数据保存成功')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer v-model="editor.display" size="380" :close-on-click-modal="false">
|
||||
<template #header>
|
||||
<div>
|
||||
<div style="font-weight: bold">
|
||||
<el-icon><EditPen/></el-icon>
|
||||
编辑用户信息
|
||||
</div>
|
||||
<div style="font-size: 13px">编辑完成后请点击下方保存按钮</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="editor.loading" element-loading-text="数据加载中,请稍后..." style="height: 100%">
|
||||
<el-form label-position="top" v-if="!editor.loading">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="editor.temp.username"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="电子邮件">
|
||||
<el-input v-model="editor.temp.email"/>
|
||||
</el-form-item>
|
||||
<div style="display: flex;font-size: 14px">
|
||||
<div>
|
||||
<span style="margin-right: 10px">禁言</span>
|
||||
<el-switch v-model="editor.temp.mute"/>
|
||||
</div>
|
||||
<el-divider direction="vertical" style="height: 30px;margin: 0 20px"/>
|
||||
<div>
|
||||
<span style="margin-right: 10px">账号封禁</span>
|
||||
<el-switch v-model="editor.temp.banned"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px;color: #606266;font-size: 14px">
|
||||
注册时间: {{ new Date(editor.temp.registerTime).toLocaleString() }}
|
||||
</div>
|
||||
<el-divider direction="horizontal"/>
|
||||
<el-form-item label="性别">
|
||||
<el-radio-group v-model="editor.temp.detail.gender">
|
||||
<el-radio :label="0">男</el-radio>
|
||||
<el-radio :label="1">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="editor.temp.detail.phone"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="QQ账号">
|
||||
<el-input v-model="editor.temp.detail.qq"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="微信账号">
|
||||
<el-input v-model="editor.temp.detail.wx"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="个人简介">
|
||||
<el-input type="textarea" :rows="4" v-model="editor.temp.detail.desc"/>
|
||||
</el-form-item>
|
||||
<el-divider direction="horizontal"/>
|
||||
<div style="padding-bottom: 20px">
|
||||
<div style="margin-bottom: 10px;">隐私设置</div>
|
||||
<el-checkbox v-model="editor.temp.privacy.phone">
|
||||
公开展示用户的手机号
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="editor.temp.privacy.email">
|
||||
公开展示用户的电子邮件地址
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="editor.temp.privacy.wx">
|
||||
公开展示用户的微信号
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="editor.temp.privacy.qq">
|
||||
公开展示用户的QQ号
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="editor.temp.privacy.gender">
|
||||
公开展示用户的性别
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div style="text-align: center">
|
||||
<el-button type="success" @click="saveUserSettings">保存</el-button>
|
||||
<el-button type="info" @click="editor.display = false">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,15 +1,16 @@
|
||||
<script setup>
|
||||
import router from "@/router";
|
||||
import {Back, Message, Operation, Right} from "@element-plus/icons-vue";
|
||||
import {useStore} from "@/store";
|
||||
import {logout} from "@/net";
|
||||
|
||||
defineProps({
|
||||
admin: Boolean
|
||||
})
|
||||
import {isRoleAdmin, logout} from "@/net";
|
||||
import router from "@/router";
|
||||
import {computed} from "vue";
|
||||
import {useRoute} from "vue-router";
|
||||
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
const isAdminPage = computed(() => route.fullPath.startsWith("/admin"))
|
||||
|
||||
function userLogout() {
|
||||
logout(() => router.push("/"))
|
||||
}
|
||||
@ -17,10 +18,10 @@ function userLogout() {
|
||||
|
||||
<template>
|
||||
<div class="user-info">
|
||||
<template v-if="store.isAdmin">
|
||||
<template v-if="isRoleAdmin()">
|
||||
<el-button type="primary" size="small"
|
||||
@click="router.push('/index')"
|
||||
v-if="admin">
|
||||
v-if="isAdminPage">
|
||||
回到用户端
|
||||
<el-icon style="margin-left: 5px">
|
||||
<Right/>
|
||||
@ -35,7 +36,7 @@ function userLogout() {
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
<template/>
|
||||
<slot/>
|
||||
<div class="profile">
|
||||
<div>{{ store.user.username }}</div>
|
||||
<div>{{ store.user.email }}</div>
|
||||
@ -68,10 +69,16 @@ function userLogout() {
|
||||
|
||||
<style scoped>
|
||||
.user-info {
|
||||
width: 320px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
.el-avatar:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile {
|
||||
text-align: right;
|
||||
|
||||
|
48
my-project-frontend/src/net/api/forum.js
Normal file
48
my-project-frontend/src/net/api/forum.js
Normal file
@ -0,0 +1,48 @@
|
||||
import {get, post} from "@/net";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
export const apiForumTypes = (success) =>
|
||||
get('/api/forum/types', success)
|
||||
|
||||
export const apiForumTopic = (tid, success) =>
|
||||
get(`api/forum/topic?tid=${tid}`, success)
|
||||
|
||||
export const apiForumInteract = (tid, type, topic, message) => {
|
||||
get(`/api/forum/interact?tid=${tid}&type=${type}&state=${!topic[type]}`, () => {
|
||||
topic[type] = !topic[type]
|
||||
if(topic[type])
|
||||
ElMessage.success(`${message}成功!`)
|
||||
else
|
||||
ElMessage.success(`已取消${message}!`)
|
||||
})
|
||||
}
|
||||
|
||||
export const apiForumUpdateTopic = (data, success) =>
|
||||
post('/api/forum/update-topic', data, success)
|
||||
|
||||
export const apiForumComments = (tid, page, success) =>
|
||||
get(`/api/forum/comments?tid=${tid}&page=${page}`, success)
|
||||
|
||||
export const apiForumCommentDelete = (id, success) =>
|
||||
get(`/api/forum/delete-comment?id=${id}`, success)
|
||||
|
||||
export const apiForumCommentSubmit = (data, success) =>
|
||||
post('/api/forum/add-comment', data, success)
|
||||
|
||||
export const apiForumTopicCreate = (data, success) =>
|
||||
post('/api/forum/create-topic', data, success)
|
||||
|
||||
export const apiForumTopTopics = (success) =>
|
||||
get('/api/forum/top-topic', success)
|
||||
|
||||
export const apiForumTopicList = (page, type, success) =>
|
||||
get(`/api/forum/list-topic?page=${page}&type=${type}`, success)
|
||||
|
||||
export const apiForumWeather = (longitude, latitude, success) =>
|
||||
get(`/api/forum/weather?longitude=${longitude}&latitude=${latitude}`, success)
|
||||
|
||||
export const apiForumCollect = (success) =>
|
||||
get('/api/forum/collects', success)
|
||||
|
||||
export const apiForumCollectDelete = (tid, success) =>
|
||||
get(`/api/forum/interact?tid=${tid}&type=collect&state=false`, success)
|
@ -1,40 +1,88 @@
|
||||
import {get, post} from "@/net";
|
||||
import {useStore} from "@/store";
|
||||
import {get, post} from "@/net";
|
||||
import {ElMessage} from "element-plus";
|
||||
import router from "@/router";
|
||||
|
||||
export const apiUserInfo = (loadingRef) => {
|
||||
if(loadingRef) loadingRef.value = true
|
||||
loadingRef.value = true
|
||||
const store = useStore();
|
||||
get('/api/user/info', (data) => {
|
||||
const store = useStore();
|
||||
store.user = data
|
||||
if(loadingRef) loadingRef.value = false
|
||||
loadingRef.value = false
|
||||
})
|
||||
}
|
||||
|
||||
export const apiUserChangePassword = (form, success) =>
|
||||
post('/api/user/change-password', form, success)
|
||||
export const apiAuthRegister = (data) => {
|
||||
post('/api/auth/register', data, () => {
|
||||
ElMessage.success('注册成功,欢迎加入我们')
|
||||
router.push("/")
|
||||
})
|
||||
}
|
||||
|
||||
export const apiAuthAskCode = (email, coldTime, type = 'register') => {
|
||||
coldTime.value = 60
|
||||
get(`/api/auth/ask-code?email=${email}&type=${type}`, () => {
|
||||
ElMessage.success(`验证码已发送到邮箱: ${email},请注意查收`)
|
||||
const handle = setInterval(() => {
|
||||
coldTime.value--
|
||||
if(coldTime.value === 0) {
|
||||
clearInterval(handle)
|
||||
}
|
||||
}, 1000)
|
||||
}, (message) => {
|
||||
ElMessage.warning(message)
|
||||
coldTime.value = 0
|
||||
})
|
||||
}
|
||||
|
||||
export const apiAuthRestConfirm = (data, activeRef) =>
|
||||
post('/api/auth/reset-confirm', data, () => activeRef.value++)
|
||||
|
||||
export const apiAuthResetPassword = (data) => {
|
||||
post('/api/auth/reset-password', data, () => {
|
||||
ElMessage.success('密码重置成功,请重新登录')
|
||||
router.push('/')
|
||||
})
|
||||
}
|
||||
|
||||
export const apiUserPrivacy = (success) =>
|
||||
get('/api/user/privacy', success)
|
||||
|
||||
export const apiUserPrivacySave = (data, loadingRef, success) => {
|
||||
export const apiUserPrivacySave = (data, loadingRef) => {
|
||||
loadingRef.value = true
|
||||
post('/api/user/save-privacy', data, () => {
|
||||
ElMessage.success('隐私设置修改成功!')
|
||||
loadingRef.value = false
|
||||
success()
|
||||
})
|
||||
}
|
||||
|
||||
export const apiUserDetailSave = (form, success, failure) =>
|
||||
post('/api/user/save-details', form, success, failure)
|
||||
export const apiUserChangePassword = (data, success) =>
|
||||
post('/api/user/change-password', data, success)
|
||||
|
||||
export const apiUserDetail = (success) =>
|
||||
get('/api/user/details', success)
|
||||
|
||||
export const apiUserDetailSave = (form, success, failure) =>
|
||||
post('/api/user/save-details', form, success, failure)
|
||||
|
||||
export const apiUserModifyEmail = (form, success) =>
|
||||
post('/api/user/modify-email', form, success)
|
||||
|
||||
export const apiNotificationList = (success) =>
|
||||
get('/api/notification/list', success)
|
||||
|
||||
export const apiNotificationDeleteAll = (success) =>
|
||||
get(`/api/notification/delete-all`, success)
|
||||
|
||||
export const apiNotificationDelete = (id, success) =>
|
||||
get(`/api/notification/delete?id=${id}`, success)
|
||||
|
||||
export const apiUserList = (page, size, success) =>
|
||||
get(`/api/admin/user/list?page=${page}&size=${size}`, success)
|
||||
get(`api/admin/user/list?page=${page}&size=${size}`, success)
|
||||
|
||||
export const apiUserDetailTotal = (id, success) =>
|
||||
get(`api/admin/user/detail?id=${id}`, success)
|
||||
|
||||
export const apiUserSave = (data, success) =>
|
||||
post('/api/admin/user/save', data, success)
|
||||
|
||||
|
@ -113,8 +113,8 @@ function isUnauthorized() {
|
||||
return !takeAccessToken()
|
||||
}
|
||||
|
||||
function isAdminRole() {
|
||||
function isRoleAdmin() {
|
||||
return takeAccessToken()?.role === 'admin'
|
||||
}
|
||||
|
||||
export { post, get, login, logout, isUnauthorized, isAdminRole, accessHeader }
|
||||
export { post, get, login, logout, isUnauthorized, isRoleAdmin, accessHeader }
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import {isAdminRole, isUnauthorized} from "@/net";
|
||||
import {useStore} from "@/store";
|
||||
import {isRoleAdmin, isUnauthorized} from "@/net";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -71,10 +70,6 @@ const router = createRouter({
|
||||
path: 'forum',
|
||||
name: 'admin-forum',
|
||||
component: () => import('@/views/admin/ForumAdmin.vue')
|
||||
}, {
|
||||
path: 'email',
|
||||
name: 'admin-email',
|
||||
component: () => import('@/views/admin/EmailAdmin.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -82,12 +77,12 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const unauthorized = isUnauthorized(), admin = isAdminRole()
|
||||
const unauthorized = isUnauthorized(), admin = isRoleAdmin()
|
||||
if(to.name.startsWith('welcome') && !unauthorized) {
|
||||
next('/index')
|
||||
} else if(to.fullPath.startsWith('/admin') && !admin) {
|
||||
next('/index')
|
||||
} else if(to.fullPath.startsWith('/index') && unauthorized) {
|
||||
}else if(to.fullPath.startsWith('/index') && unauthorized) {
|
||||
next('/')
|
||||
} else {
|
||||
next()
|
||||
|
@ -17,9 +17,6 @@ export const useStore = defineStore('general', {
|
||||
}
|
||||
}
|
||||
}, getters: {
|
||||
isAdmin() {
|
||||
return this.user.role === 'admin'
|
||||
},
|
||||
avatarUrl() {
|
||||
if(this.user.avatar)
|
||||
return `${axios.defaults.baseURL}/images${this.user.avatar}`
|
||||
|
@ -1,66 +1,65 @@
|
||||
<script setup>
|
||||
|
||||
import {
|
||||
Bell,
|
||||
ChatDotSquare, Collection,
|
||||
DataLine,
|
||||
Document,
|
||||
Files,
|
||||
Location, Message,
|
||||
Location,
|
||||
Monitor, Notification, Position, School,
|
||||
Umbrella,
|
||||
User
|
||||
} from "@element-plus/icons-vue";
|
||||
import UserInfo from "@/components/UserInfo.vue";
|
||||
import {inject, onMounted, ref} from "vue";
|
||||
import {useRoute} from "vue-router";
|
||||
import router from "@/router";
|
||||
import {useRoute} from "vue-router";
|
||||
|
||||
const adminMenu = [
|
||||
{
|
||||
title: '校园论坛管理', icon: Location, sub: [
|
||||
{title: '用户管理', icon: User, index: '/admin/user' },
|
||||
{title: '邮件发信管理', icon: Message, index: '/admin/email' },
|
||||
{title: '帖子广场管理', icon: ChatDotSquare, index: '/admin/forum' },
|
||||
{title: '失物招领管理', icon: Bell},
|
||||
{title: '校园活动管理', icon: Notification},
|
||||
{title: '表白墙管理', icon: Umbrella},
|
||||
{title: '合作机构管理', icon: School}
|
||||
{ title: '用户管理', icon: User, index: '/admin/user' },
|
||||
{ title: '帖子广场管理', icon: ChatDotSquare, index: '/admin/forum' },
|
||||
{ title: '失物招领管理', icon: Bell },
|
||||
{ title: '校园活动管理', icon: Notification },
|
||||
{ title: '表白墙管理', icon: Umbrella },
|
||||
{ title: '合作机构管理', icon: School }
|
||||
]
|
||||
}, {
|
||||
title: '探索与发现管理', icon: Position, sub: [
|
||||
{title: '成绩查询管理', icon: Document},
|
||||
{title: '班级课程表管理', icon: Files},
|
||||
{title: '教务通知管理', icon: Monitor},
|
||||
{title: '在线图书馆管理', icon: Collection},
|
||||
{title: '预约教室管理', icon: DataLine}
|
||||
{ title: '成绩管理', icon: Document },
|
||||
{ title: '课程表管理', icon: Files },
|
||||
{ title: '教务通知管理', icon: Monitor },
|
||||
{ title: '在线图书馆管理', icon: Collection },
|
||||
{ title: '预约教室管理', icon: DataLine }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const route = useRoute()
|
||||
const loading = inject('userLoading')
|
||||
|
||||
const pageTabs = ref([])
|
||||
|
||||
function handleTabsClick({ props }) {
|
||||
function handleTabClick({ props }) {
|
||||
router.push(props.name)
|
||||
}
|
||||
|
||||
function handleTabRemove(name) {
|
||||
function handleTabClose(name) {
|
||||
const index = pageTabs.value.findIndex(tab => tab.name === name)
|
||||
const isCurrent = name === route.fullPath
|
||||
pageTabs.value.splice(index, 1)
|
||||
if(pageTabs.value.length > 0) {
|
||||
//若标签列表中仍有未删除的,且关闭的是当前正在浏览的页面,则自动调整到前一个,没有就后一个
|
||||
//删除后,标签列表中还有剩余的Tab且关闭的是当前的,则自动进行切换,默认切换到上一个,如果没有上一个,则切换到下一个
|
||||
if(isCurrent) {
|
||||
router.push(pageTabs.value[Math.max(0, index - 1)].name) //优化后算法
|
||||
router.push(pageTabs.value[Math.max(0, index - 1)].name)
|
||||
}
|
||||
} else { //一个都没有了那就回到欢迎页
|
||||
} else {
|
||||
router.push('/admin')
|
||||
}
|
||||
}
|
||||
|
||||
function openAdminTab(menu) {
|
||||
function addAdminTab(menu) {
|
||||
if(!menu.index) return
|
||||
if(pageTabs.value.findIndex(tab => tab.name === menu.index) < 0) {
|
||||
pageTabs.value.push({
|
||||
@ -75,7 +74,7 @@ onMounted(() => {
|
||||
.flatMap(menu => menu.sub)
|
||||
.find(sub => sub.index === route.fullPath)
|
||||
if(initPage) {
|
||||
openAdminTab(initPage)
|
||||
addAdminTab(initPage)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -84,15 +83,15 @@ onMounted(() => {
|
||||
<div class="admin-content" v-loading="loading" element-loading-text="正在进入,请稍后...">
|
||||
<el-container style="height: 100%">
|
||||
<el-aside width="230px" class="admin-content-aside">
|
||||
<div style="text-align: center;padding: 15px 0 10px;height: 32px">
|
||||
<div class="logo-box">
|
||||
<el-image class="logo" src="https://element-plus.org/images/element-plus-logo.svg"/>
|
||||
</div>
|
||||
<el-scrollbar style="height: calc(100% - 60px)">
|
||||
<el-scrollbar style="height: calc(100vh - 57px)">
|
||||
<el-menu
|
||||
router
|
||||
:default-active="$route.path"
|
||||
:default-openeds="['1', '2', '3']"
|
||||
style="height: calc(100% - 60px);border-right: none">
|
||||
:default-openeds="['1', '2']"
|
||||
style="min-height: calc(100vh - 57px);border: none">
|
||||
<el-sub-menu :index="(index + 1).toString()"
|
||||
v-for="(menu, index) in adminMenu">
|
||||
<template #title>
|
||||
@ -102,17 +101,13 @@ onMounted(() => {
|
||||
<span><b>{{ menu.title }}</b></span>
|
||||
</template>
|
||||
<el-menu-item :index="subMenu.index"
|
||||
@click="openAdminTab(subMenu)"
|
||||
@click="addAdminTab(subMenu)"
|
||||
v-for="subMenu in menu.sub">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="subMenu.icon"/>
|
||||
</el-icon>
|
||||
{{ subMenu.title }}
|
||||
<el-tag style="margin-left: 10px" size="small"
|
||||
:type="subMenu.tag.type"
|
||||
v-if="subMenu.tag">{{ subMenu.tag.name }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
@ -122,21 +117,18 @@ onMounted(() => {
|
||||
<el-container>
|
||||
<el-header class="admin-content-header">
|
||||
<div style="flex: 1">
|
||||
<el-tabs :model-value="route.fullPath"
|
||||
type="card"
|
||||
<el-tabs type="card"
|
||||
:model-value="route.fullPath"
|
||||
closable
|
||||
@tab-remove="handleTabRemove"
|
||||
@tab-click="handleTabsClick">
|
||||
<el-tab-pane
|
||||
v-for="item in pageTabs"
|
||||
:key="item.name"
|
||||
:label="item.title"
|
||||
:name="item.name">
|
||||
{{ item.content }}
|
||||
</el-tab-pane>
|
||||
@tab-remove="handleTabClose"
|
||||
@tab-click="handleTabClick">
|
||||
<el-tab-pane v-for="tab in pageTabs"
|
||||
:label="tab.title"
|
||||
:name="tab.name"
|
||||
:key="tab.name"/>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<user-info admin/>
|
||||
<user-info/>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<router-view v-slot="{ Component }">
|
||||
@ -150,43 +142,49 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
<style scoped>
|
||||
.admin-content {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.admin-content-aside {
|
||||
border-right: solid 1px var(--el-border-color);
|
||||
.admin-content-aside {
|
||||
border-right: solid 1px var(--el-border-color);
|
||||
|
||||
.logo {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
.logo-box {
|
||||
text-align: center;
|
||||
padding: 15px 0 10px;
|
||||
height: 32px;
|
||||
|
||||
.admin-content-header {
|
||||
border-bottom: solid 1px var(--el-border-color);
|
||||
height: 55px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
:deep(.el-tabs__header) {
|
||||
height: 32px;
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
.logo {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav) {
|
||||
gap: 10px;
|
||||
border: none;
|
||||
}
|
||||
.admin-content-header {
|
||||
border-bottom: solid 1px var(--el-border-color);
|
||||
height: 55px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 32px;
|
||||
padding: 0 15px !important;
|
||||
border-radius: 6px;
|
||||
border: solid 1px var(--el-border-color) !important;
|
||||
:deep(.el-tabs__header) {
|
||||
height: 32px;
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav) {
|
||||
gap: 10px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 32px;
|
||||
padding: 0 15px;
|
||||
border-radius: 6px;
|
||||
border: solid 1px var(--el-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<script setup>
|
||||
import {get} from '@/net'
|
||||
import {inject, reactive, ref} from "vue";
|
||||
import {
|
||||
Bell,
|
||||
@ -13,29 +12,31 @@ import {
|
||||
} from "@element-plus/icons-vue";
|
||||
import LightCard from "@/components/LightCard.vue";
|
||||
import UserInfo from "@/components/UserInfo.vue";
|
||||
import {apiNotificationDelete, apiNotificationDeleteAll, apiNotificationList} from "@/net/api/user";
|
||||
|
||||
const userMenu = [
|
||||
{ title: '校园论坛', icon: Location, sub: [
|
||||
{ title: '帖子广场', index: '/index', icon: ChatDotSquare },
|
||||
{ title: '失物招领', icon: Bell },
|
||||
{ title: '校园活动', icon: Notification },
|
||||
{ title: '表白墙', icon: Umbrella },
|
||||
{ title: '海文考研', icon: School, tag: { name: '合作机构', type: '' } }
|
||||
]
|
||||
}, {
|
||||
title: '探索与发现', icon: Position, sub: [
|
||||
{ title: '成绩查询', icon: Document },
|
||||
{ title: '班级课程表', icon: Files },
|
||||
{ title: '教务通知', icon: Monitor },
|
||||
{ title: '在线图书馆', icon: Collection },
|
||||
{ title: '预约教室', icon: DataLine }
|
||||
]
|
||||
}, {
|
||||
title: '个人设置', icon: Operation, sub: [
|
||||
{ title: '个人信息设置', icon: User, index: '/index/user-setting' },
|
||||
{ title: '账号安全设置', icon: Lock, index: '/index/privacy-setting' }
|
||||
]
|
||||
}
|
||||
{
|
||||
title: '校园论坛', icon: Location, sub: [
|
||||
{ title: '帖子广场', icon: ChatDotSquare, index: '/index' },
|
||||
{ title: '失物招领', icon: Bell },
|
||||
{ title: '校园活动', icon: Notification },
|
||||
{ title: '表白墙', icon: Umbrella },
|
||||
{ title: '海文考研', icon: School }
|
||||
]
|
||||
}, {
|
||||
title: '探索与发现', icon: Position, sub: [
|
||||
{ title: '成绩查询', icon: Document },
|
||||
{ title: '班级课程表', icon: Files },
|
||||
{ title: '教务通知', icon: Monitor },
|
||||
{ title: '在线图书馆', icon: Collection },
|
||||
{ title: '预约教室', icon: DataLine }
|
||||
]
|
||||
}, {
|
||||
title: '个人设置', icon: Operation, sub: [
|
||||
{ title: '个人信息设置', icon: User, index: '/index/user-setting' },
|
||||
{ title: '账号安全设置', icon: Lock, index: '/index/privacy-setting' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const loading = inject('userLoading')
|
||||
@ -47,18 +48,18 @@ const searchInput = reactive({
|
||||
const notification = ref([])
|
||||
|
||||
const loadNotification =
|
||||
() => get('/api/notification/list', data => notification.value = data)
|
||||
() => apiNotificationList(data => notification.value = data)
|
||||
loadNotification()
|
||||
|
||||
function confirmNotification(id, url) {
|
||||
get(`/api/notification/delete?id=${id}`, () => {
|
||||
apiNotificationDelete(id, () => {
|
||||
loadNotification()
|
||||
window.open(url)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteAllNotification() {
|
||||
get(`/api/notification/delete-all`, loadNotification)
|
||||
apiNotificationDeleteAll(loadNotification)
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -66,7 +67,9 @@ function deleteAllNotification() {
|
||||
<div class="main-content" v-loading="loading" element-loading-text="正在进入,请稍后...">
|
||||
<el-container style="height: 100%" v-if="!loading">
|
||||
<el-header class="main-content-header">
|
||||
<el-image class="logo" src="https://element-plus.org/images/element-plus-logo.svg"/>
|
||||
<div style="width: 320px;height: 32px">
|
||||
<el-image class="logo" src="https://element-plus.org/images/element-plus-logo.svg"/>
|
||||
</div>
|
||||
<div style="flex: 1;padding: 0 20px;text-align: center">
|
||||
<el-input v-model="searchInput.text" style="width: 100%;max-width: 500px"
|
||||
placeholder="搜索论坛相关内容...">
|
||||
@ -124,26 +127,23 @@ function deleteAllNotification() {
|
||||
:default-active="$route.path"
|
||||
:default-openeds="['1', '2', '3']"
|
||||
style="min-height: calc(100vh - 55px)">
|
||||
<el-sub-menu :index="(index + 1).toString()"
|
||||
v-for="(menu, index) in userMenu">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="menu.icon"/>
|
||||
</el-icon>
|
||||
<span><b>{{ menu.title }}</b></span>
|
||||
</template>
|
||||
<el-menu-item :index="subMenu.index" v-for="subMenu in menu.sub">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="subMenu.icon"/>
|
||||
</el-icon>
|
||||
{{ subMenu.title }}
|
||||
<el-tag style="margin-left: 10px" size="small"
|
||||
:type="subMenu.tag.type"
|
||||
v-if="subMenu.tag">{{ subMenu.tag.name }}</el-tag>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu :index="(index + 1).toString()"
|
||||
v-for="(menu, index) in userMenu">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="menu.icon"/>
|
||||
</el-icon>
|
||||
<span><b>{{ menu.title }}</b></span>
|
||||
</template>
|
||||
<el-menu-item :index="subMenu.index" v-for="subMenu in menu.sub">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="subMenu.icon"/>
|
||||
</el-icon>
|
||||
{{ subMenu.title }}
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
@ -205,17 +205,9 @@ function deleteAllNotification() {
|
||||
|
||||
.logo {
|
||||
height: 32px;
|
||||
width: 340px;
|
||||
text-align: left;
|
||||
|
||||
:deep(.el-image__inner) {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
gap: 20px;
|
||||
width: 340px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
@ -226,6 +218,7 @@ function deleteAllNotification() {
|
||||
|
||||
.profile {
|
||||
text-align: right;
|
||||
margin-right: 20px;
|
||||
|
||||
:first-child {
|
||||
font-size: 18px;
|
||||
|
@ -1,32 +0,0 @@
|
||||
<script setup>
|
||||
import {Message} from "@element-plus/icons-vue";
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="email-admin">
|
||||
<div class="title">
|
||||
<el-icon><Message/></el-icon>
|
||||
邮件发信列表
|
||||
</div>
|
||||
<div class="desc">在这里查看所有发送的电子邮件列表,失败邮件可以选择重新发送</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.email-admin {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: #bababa;
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,7 +4,7 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
我是帖子管理
|
||||
我是论坛管理
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,12 +1,19 @@
|
||||
<script setup>
|
||||
import {EditPen, User} from "@element-plus/icons-vue";
|
||||
import {apiUserList} from "@/net/api/user";
|
||||
import {reactive, ref, watchEffect} from "vue";
|
||||
import {apiUserDetailTotal, apiUserList, apiUserSave} from "@/net/api/user";
|
||||
import {reactive, watchEffect} from "vue";
|
||||
import {useStore} from "@/store";
|
||||
import UserEditor from "@/components/UserEditor.vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const editor = reactive({
|
||||
id: 0,
|
||||
display: false,
|
||||
temp: {},
|
||||
loading: false
|
||||
})
|
||||
|
||||
const userTable = reactive({
|
||||
page: 1,
|
||||
size: 10,
|
||||
@ -14,8 +21,6 @@ const userTable = reactive({
|
||||
data: []
|
||||
})
|
||||
|
||||
const editorRef = ref()
|
||||
|
||||
function userStatus(user) {
|
||||
if(user.mute && user.banned)
|
||||
return '禁言中、封禁中'
|
||||
@ -27,6 +32,25 @@ function userStatus(user) {
|
||||
return '正常'
|
||||
}
|
||||
|
||||
function openUserEditor(user) {
|
||||
editor.id = user.id
|
||||
editor.display = true
|
||||
editor.loading = true
|
||||
apiUserDetailTotal(editor.id, data => {
|
||||
editor.temp = { ...data, ...user }
|
||||
editor.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
function saveUserDetail() {
|
||||
editor.display = false
|
||||
apiUserSave(editor.temp, () => {
|
||||
const user = userTable.data.find(user => user.id === editor.id)
|
||||
Object.assign(user, editor.temp)
|
||||
ElMessage.success('数据保存成功')
|
||||
})
|
||||
}
|
||||
|
||||
watchEffect(() => apiUserList(userTable.page, userTable.size, data => {
|
||||
userTable.total = data.total
|
||||
userTable.data = data.list
|
||||
@ -36,12 +60,14 @@ watchEffect(() => apiUserList(userTable.page, userTable.size, data => {
|
||||
<template>
|
||||
<div class="user-admin">
|
||||
<div class="title">
|
||||
<el-icon><User/></el-icon>
|
||||
论坛用户列表
|
||||
<el-icon><User/></el-icon>
|
||||
论坛用户列表
|
||||
</div>
|
||||
<div class="desc">在这里管理论坛的所有用户,包括账号信息、封禁和禁言</div>
|
||||
<el-table :data="userTable.data" style="width: 100%;flex: 1">
|
||||
<el-table-column prop="id" label="编号" width="80" />
|
||||
<div class="desc">
|
||||
在这里管理论坛的所有用户,包括账号信息、封禁和禁言处理。
|
||||
</div>
|
||||
<el-table :data="userTable.data" height="320">
|
||||
<el-table-column prop="id" label="编号" width="80"/>
|
||||
<el-table-column label="用户名" width="180">
|
||||
<template #default="{ row }">
|
||||
<div class="table-username">
|
||||
@ -56,42 +82,75 @@ watchEffect(() => apiUserList(userTable.page, userTable.size, data => {
|
||||
<el-tag v-else>普通用户</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="email" label="电子邮件" />
|
||||
<el-table-column prop="email" label="电子邮件"/>
|
||||
<el-table-column label="注册时间">
|
||||
<template #default="{ row }">
|
||||
{{ new Date(row.registerTime).toLocaleString() }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<el-table-column label="状态" align="center">
|
||||
<template #default="{ row }">
|
||||
<div>{{ userStatus(row) }}</div>
|
||||
{{ userStatus(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" :icon="EditPen"
|
||||
@click="editorRef.loadUserEditor(row)"
|
||||
:disabled="store.user.id === row.id">编辑</el-button>
|
||||
<el-button type="primary" size="small" :icon="EditPen"
|
||||
@click="openUserEditor(row)"
|
||||
:disabled="row.role === 'admin'">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top: 20px;display: flex;justify-content: right">
|
||||
<el-pagination style="width: fit-content"
|
||||
:total="userTable.total"
|
||||
<div class="pagination">
|
||||
<el-pagination :total="userTable.total"
|
||||
v-model:current-page="userTable.page"
|
||||
v-model:page-size="userTable.size"
|
||||
layout="total, sizes, prev, pager, next, jumper"/>
|
||||
</div>
|
||||
<user-editor ref="editorRef"/>
|
||||
<el-drawer v-model="editor.display">
|
||||
<template #header>
|
||||
<div>
|
||||
<div style="font-weight: bold">
|
||||
<el-icon><EditPen/></el-icon> 编辑用户信息
|
||||
</div>
|
||||
<div style="font-size: 13px">编辑完成后请点击下方保存按钮</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="editor.temp.username"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="电子邮件">
|
||||
<el-input v-model="editor.temp.email"/>
|
||||
</el-form-item>
|
||||
<div style="display: flex;font-size: 14px;gap: 20px">
|
||||
<div>
|
||||
<span style="margin-right: 10px">禁言</span>
|
||||
<el-switch v-model="editor.temp.mute"/>
|
||||
</div>
|
||||
<el-divider style="height: 30px" direction="vertical"/>
|
||||
<div>
|
||||
<span style="margin-right: 10px">账号封禁</span>
|
||||
<el-switch v-model="editor.temp.banned"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 10px;color: #606266;font-size: 14px">
|
||||
注册时间: {{ new Date(editor.temp.registerTime).toLocaleString() }}
|
||||
</div>
|
||||
<el-divider/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div style="text-align: center">
|
||||
<el-button type="success" @click="saveUserDetail">保存</el-button>
|
||||
<el-button type="info" @click="editor.display = false">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-admin {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -109,6 +168,12 @@ watchEffect(() => apiUserList(userTable.page, userTable.size, data => {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
:deep(.el-drawer__header) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
我是管理端欢迎页
|
||||
我是欢迎页
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import {get} from "@/net";
|
||||
import {useStore} from "@/store";
|
||||
import {apiForumTypes} from "@/net/api/forum";
|
||||
|
||||
const store = useStore()
|
||||
|
||||
get('/api/forum/types', data => {
|
||||
apiForumTypes(data => {
|
||||
const array = []
|
||||
array.push({name: '全部', id: 0, color: 'linear-gradient(45deg, white, red, orange, gold, green, blue)'})
|
||||
data.forEach(d => array.push(d))
|
||||
|
@ -1,8 +1,6 @@
|
||||
<script setup>
|
||||
import {useRoute} from "vue-router";
|
||||
import {get, post} from "@/net";
|
||||
import axios from "axios";
|
||||
import {computed, reactive, ref} from "vue";
|
||||
import {reactive, ref} from "vue";
|
||||
import {ArrowLeft, ChatSquare, CircleCheck, Delete, EditPen, Female, Male, Plus, Star} from "@element-plus/icons-vue";
|
||||
import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html';
|
||||
import Card from "@/components/Card.vue";
|
||||
@ -13,6 +11,13 @@ import {ElMessage} from "element-plus";
|
||||
import {useStore} from "@/store";
|
||||
import TopicEditor from "@/components/TopicEditor.vue";
|
||||
import TopicCommentEditor from "@/components/TopicCommentEditor.vue";
|
||||
import {
|
||||
apiForumCommentDelete,
|
||||
apiForumComments,
|
||||
apiForumInteract,
|
||||
apiForumTopic,
|
||||
apiForumUpdateTopic
|
||||
} from "@/net/api/forum";
|
||||
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
@ -33,7 +38,7 @@ const comment = reactive({
|
||||
quote: null
|
||||
})
|
||||
|
||||
const init = () => get(`api/forum/topic?tid=${tid}`, data => {
|
||||
const init = () => apiForumTopic(tid, data => {
|
||||
topic.data = data
|
||||
topic.like = data.interact.like
|
||||
topic.collect = data.interact.collect
|
||||
@ -48,17 +53,11 @@ function convertToHtml(content) {
|
||||
}
|
||||
|
||||
function interact(type, message) {
|
||||
get(`/api/forum/interact?tid=${tid}&type=${type}&state=${!topic[type]}`, () => {
|
||||
topic[type] = !topic[type]
|
||||
if(topic[type])
|
||||
ElMessage.success(`${message}成功!`)
|
||||
else
|
||||
ElMessage.success(`已取消${message}!`)
|
||||
})
|
||||
apiForumInteract(tid, type, topic, message)
|
||||
}
|
||||
|
||||
function updateTopic(editor) {
|
||||
post('/api/forum/update-topic', {
|
||||
apiForumUpdateTopic({
|
||||
id: tid,
|
||||
type: editor.type.id,
|
||||
title: editor.title,
|
||||
@ -73,7 +72,7 @@ function updateTopic(editor) {
|
||||
function loadComments(page) {
|
||||
topic.comments = null
|
||||
topic.page = page
|
||||
get(`/api/forum/comments?tid=${tid}&page=${page - 1}`, data => topic.comments = data)
|
||||
apiForumComments(tid, page - 1, data => topic.comments = data)
|
||||
}
|
||||
|
||||
function onCommentAdd() {
|
||||
@ -82,7 +81,7 @@ function onCommentAdd() {
|
||||
}
|
||||
|
||||
function deleteComment(id) {
|
||||
get(`/api/forum/delete-comment?id=${id}`, () => {
|
||||
apiForumCommentDelete(id, () => {
|
||||
ElMessage.success('删除评论成功!')
|
||||
loadComments(topic.page)
|
||||
})
|
||||
|
@ -13,8 +13,7 @@ import {
|
||||
Microphone, CircleCheck, Star, FolderOpened, ArrowRightBold
|
||||
} from "@element-plus/icons-vue";
|
||||
import Weather from "@/components/Weather.vue";
|
||||
import {computed, reactive, ref, watch} from "vue";
|
||||
import {get} from "@/net";
|
||||
import {computed, onMounted, reactive, ref, watch} from "vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import TopicEditor from "@/components/TopicEditor.vue";
|
||||
import {useStore} from "@/store";
|
||||
@ -22,6 +21,7 @@ import ColorDot from "@/components/ColorDot.vue";
|
||||
import router from "@/router";
|
||||
import TopicTag from "@/components/TopicTag.vue";
|
||||
import TopicCollectList from "@/components/TopicCollectList.vue";
|
||||
import {apiForumTopicList, apiForumTopTopics, apiForumWeather} from "@/net/api/forum";
|
||||
|
||||
const store = useStore()
|
||||
|
||||
@ -47,10 +47,10 @@ const today = computed(() => {
|
||||
const date = new Date()
|
||||
return `${date.getFullYear()} 年 ${date.getMonth() + 1} 月 ${date.getDate()} 日`
|
||||
})
|
||||
get('/api/forum/top-topic', data => topics.top = data)
|
||||
|
||||
function updateList(){
|
||||
if(topics.end) return
|
||||
get(`/api/forum/list-topic?page=${topics.page}&type=${topics.type}`, data => {
|
||||
apiForumTopicList(topics.page, topics.type, data => {
|
||||
if(data) {
|
||||
data.forEach(d => topics.list.push(d))
|
||||
topics.page++
|
||||
@ -75,14 +75,14 @@ function resetList() {
|
||||
navigator.geolocation.getCurrentPosition(position => {
|
||||
const longitude = position.coords.longitude
|
||||
const latitude = position.coords.latitude
|
||||
get(`/api/forum/weather?longitude=${longitude}&latitude=${latitude}`, data => {
|
||||
apiForumWeather(longitude, latitude, data => {
|
||||
Object.assign(weather, data)
|
||||
weather.success = true
|
||||
})
|
||||
}, error => {
|
||||
console.info(error)
|
||||
ElMessage.warning('位置信息获取超时,请检测网络设置')
|
||||
get(`/api/forum/weather?longitude=116.40529&latitude=39.90499`, data => {
|
||||
apiForumWeather(116.40529, 39.90499, data => {
|
||||
Object.assign(weather, data)
|
||||
weather.success = true
|
||||
})
|
||||
@ -90,6 +90,10 @@ navigator.geolocation.getCurrentPosition(position => {
|
||||
timeout: 3000,
|
||||
enableHighAccuracy: true
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
apiForumTopTopics(data => topics.top = data)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import Card from "@/components/Card.vue";
|
||||
import {Setting, Switch, Lock} from "@element-plus/icons-vue";
|
||||
import {reactive, ref} from "vue";
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {apiUserChangePassword, apiUserPrivacy, apiUserPrivacySave} from "@/net/api/user";
|
||||
|
||||
@ -35,7 +35,7 @@ const rules = {
|
||||
}
|
||||
const formRef = ref()
|
||||
const valid = ref(false)
|
||||
const onValidate = (_, isValid) => valid.value = isValid
|
||||
const onValidate = (prop, isValid) => valid.value = isValid
|
||||
|
||||
function resetPassword(){
|
||||
formRef.value.validate(valid => {
|
||||
@ -57,15 +57,16 @@ const privacy = reactive({
|
||||
gender: false
|
||||
})
|
||||
|
||||
apiUserPrivacy(data => {
|
||||
Object.assign(privacy, data)
|
||||
saving.value = false
|
||||
})
|
||||
|
||||
function savePrivacy(type, status){
|
||||
apiUserPrivacySave({ type, status }, saving,
|
||||
() => ElMessage.success('隐私设置修改成功!'))
|
||||
apiUserPrivacySave({ type, status }, saving)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
apiUserPrivacy(data => {
|
||||
Object.assign(privacy, data)
|
||||
saving.value = false
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -3,11 +3,11 @@
|
||||
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 {accessHeader, get, post} from "@/net";
|
||||
import {computed, onMounted, reactive, ref} from "vue";
|
||||
import {accessHeader} from "@/net";
|
||||
import {ElMessage} from "element-plus";
|
||||
import axios from "axios";
|
||||
import {apiUserDetail, apiUserDetailSave} from "@/net/api/user";
|
||||
import {apiAuthAskCode, apiUserDetail, apiUserDetailSave, apiUserModifyEmail} from "@/net/api/user";
|
||||
|
||||
const store = useStore()
|
||||
|
||||
@ -28,7 +28,7 @@ const emailForm = reactive({
|
||||
email: '',
|
||||
code: ''
|
||||
})
|
||||
const validateUsername = (rule, value, callback) => {
|
||||
const validateUsername = (_, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请输入用户名'))
|
||||
} else if (!/^[a-zA-Z0-9\u4e00-\u9fa5]+$/.test(value)) {
|
||||
@ -61,7 +61,7 @@ function saveDetails() {
|
||||
store.user.usernamew = baseForm.username
|
||||
desc.value = baseForm.desc
|
||||
loading.base = false
|
||||
}, (message) => {
|
||||
}, message => {
|
||||
ElMessage.warning(message)
|
||||
loading.base = false
|
||||
})
|
||||
@ -69,14 +69,6 @@ function saveDetails() {
|
||||
})
|
||||
}
|
||||
|
||||
apiUserDetail(data => {
|
||||
baseForm.username = store.user.username
|
||||
Object.assign(baseForm, data)
|
||||
baseForm.desc = desc.value = data.desc
|
||||
emailForm.email = store.user.email
|
||||
loading.form = false
|
||||
})
|
||||
|
||||
const coldTime = ref(0)
|
||||
const isEmailValid = ref(true)
|
||||
const onValidate = (prop, isValid) => {
|
||||
@ -87,19 +79,7 @@ const onValidate = (prop, isValid) => {
|
||||
function sendEmailCode() {
|
||||
emailFormRef.value.validate(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) {
|
||||
clearInterval(handle)
|
||||
}
|
||||
}, 1000)
|
||||
}, (message) => {
|
||||
ElMessage.warning(message)
|
||||
coldTime.value = 0
|
||||
})
|
||||
apiAuthAskCode(emailForm.email, coldTime, 'modify')
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -107,7 +87,7 @@ function sendEmailCode() {
|
||||
function modifyEmail() {
|
||||
emailFormRef.value.validate(isValid => {
|
||||
if (isValid) {
|
||||
post('/api/user/modify-email', emailForm, () => {
|
||||
apiUserModifyEmail(emailForm, () => {
|
||||
ElMessage.success('邮件修改成功')
|
||||
store.user.email = emailForm.email
|
||||
emailForm.code = ''
|
||||
@ -131,6 +111,16 @@ function uploadSuccess(response){
|
||||
ElMessage.success('头像上传成功')
|
||||
store.user.avatar = response.data
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
apiUserDetail(data => {
|
||||
baseForm.username = store.user.username
|
||||
baseForm.desc = desc.value = data.desc
|
||||
Object.assign(baseForm, data)
|
||||
emailForm.email = store.user.email
|
||||
loading.form = false
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -80,9 +80,7 @@
|
||||
<script setup>
|
||||
import {reactive, ref} from "vue";
|
||||
import {EditPen, Lock, Message} from "@element-plus/icons-vue";
|
||||
import {get, post} from "@/net";
|
||||
import {ElMessage} from "element-plus";
|
||||
import router from "@/router";
|
||||
import {apiAuthAskCode, apiAuthResetPassword, apiAuthRestConfirm} from "@/net/api/user";
|
||||
|
||||
const active = ref(0)
|
||||
|
||||
@ -129,29 +127,15 @@ const onValidate = (prop, isValid) => {
|
||||
isEmailValid.value = isValid
|
||||
}
|
||||
|
||||
const validateEmail = () => {
|
||||
coldTime.value = 60
|
||||
get(`/api/auth/ask-code?email=${form.email}&type=reset`, () => {
|
||||
ElMessage.success(`验证码已发送到邮箱: ${form.email},请注意查收`)
|
||||
const handle = setInterval(() => {
|
||||
coldTime.value--
|
||||
if(coldTime.value === 0) {
|
||||
clearInterval(handle)
|
||||
}
|
||||
}, 1000)
|
||||
}, (message) => {
|
||||
ElMessage.warning(message)
|
||||
coldTime.value = 0
|
||||
})
|
||||
}
|
||||
const validateEmail = () => apiAuthAskCode(form.email, coldTime, 'reset')
|
||||
|
||||
const confirmReset = () => {
|
||||
formRef.value.validate((isValid) => {
|
||||
if(isValid) {
|
||||
post('/api/auth/reset-confirm', {
|
||||
apiAuthRestConfirm({
|
||||
email: form.email,
|
||||
code: form.code
|
||||
}, () => active.value++)
|
||||
}, active)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -159,13 +143,10 @@ const confirmReset = () => {
|
||||
const doReset = () => {
|
||||
formRef.value.validate((isValid) => {
|
||||
if(isValid) {
|
||||
post('/api/auth/reset-password', {
|
||||
apiAuthResetPassword({
|
||||
email: form.email,
|
||||
code: form.code,
|
||||
password: form.password
|
||||
}, () => {
|
||||
ElMessage.success('密码重置成功,请重新登录')
|
||||
router.push('/')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -62,8 +62,6 @@ const form = reactive({
|
||||
remember: false
|
||||
})
|
||||
|
||||
const loading = inject('userLoading')
|
||||
|
||||
const rules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名' }
|
||||
@ -73,6 +71,8 @@ const rules = {
|
||||
]
|
||||
}
|
||||
|
||||
const loading = inject('userLoading')
|
||||
|
||||
function userLogin() {
|
||||
formRef.value.validate((isValid) => {
|
||||
if(isValid) {
|
||||
|
@ -68,7 +68,7 @@ import {EditPen, Lock, Message, User} from "@element-plus/icons-vue";
|
||||
import router from "@/router";
|
||||
import {reactive, ref} from "vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {get, post} from "@/net";
|
||||
import {apiAuthAskCode, apiAuthRegister} from "@/net/api/user";
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
@ -131,14 +131,11 @@ const onValidate = (prop, isValid) => {
|
||||
const register = () => {
|
||||
formRef.value.validate((isValid) => {
|
||||
if(isValid) {
|
||||
post('/api/auth/register', {
|
||||
apiAuthRegister({
|
||||
username: form.username,
|
||||
password: form.password,
|
||||
email: form.email,
|
||||
code: form.code
|
||||
}, () => {
|
||||
ElMessage.success('注册成功,欢迎加入我们')
|
||||
router.push("/")
|
||||
})
|
||||
} else {
|
||||
ElMessage.warning('请完整填写注册表单内容!')
|
||||
@ -146,21 +143,7 @@ const register = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const validateEmail = () => {
|
||||
coldTime.value = 60
|
||||
get(`/api/auth/ask-code?email=${form.email}&type=register`, () => {
|
||||
ElMessage.success(`验证码已发送到邮箱: ${form.email},请注意查收`)
|
||||
const handle = setInterval(() => {
|
||||
coldTime.value--
|
||||
if(coldTime.value === 0) {
|
||||
clearInterval(handle)
|
||||
}
|
||||
}, 1000)
|
||||
}, undefined, (message) => {
|
||||
ElMessage.warning(message)
|
||||
coldTime.value = 0
|
||||
})
|
||||
}
|
||||
const validateEmail = () => apiAuthAskCode(form.email, coldTime)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
Loading…
x
Reference in New Issue
Block a user