优化论坛

This commit is contained in:
曙光 2024-10-02 00:04:04 +08:00
parent c62ce6cb39
commit 488a01f4ee
6 changed files with 427 additions and 407 deletions

View File

@ -20,7 +20,7 @@ export type OpenAPIConfig = {
};
export const OpenAPI: OpenAPIConfig = {
BASE: 'http://oj.shuguangwl.com:8101',
BASE: 'http://localhost:8101',
VERSION: '1.0',
WITH_CREDENTIALS: true,
CREDENTIALS: 'include',

View File

@ -1,5 +1,5 @@
<template>
<div style=" background-color: var(--background-color);color: var(--text-color);">
<div style="background-color: var(--background-color); color: var(--text-color);">
<a-row id="globalHeader" align="center" :wrap="false">
<a-col flex="auto">
<a-menu
@ -14,63 +14,56 @@
>
<div class="title-bar">
<img class="logo-m" src="../assets/logo.png" style="height: 35px" />
<div class="title" style='font-family: "楷体";color:black;font-size: 24px;'>Code Master</div>
<div class="title" style='font-family: "楷体"; color: black; font-size: 24px;'>Code Master</div>
</div>
</a-menu-item>
<a-menu-item v-for="item in visRouters" :key="item.path">
<component :is="item.meta?.icon"> </component>{{ item.name }}
<component :is="item.meta?.icon"></component>{{ item.name }}
</a-menu-item>
</a-menu>
</a-col>
<a-col flex="100px">
<a-space size="large" direction="vertical">
<a-space size="large">
<a-dropdown>
<IconSun v-if="mode==1" @click="day()" style=" font-size: 20px;" />
<IconMoon v-if="mode==0" @click="drak()" style=" font-size: 20px;color:aliceblue"/>
<!-- 切换主题的图标 -->
<IconSun v-if="mode == 1" @click="day()" style="font-size: 20px;" />
<IconMoon v-if="mode == 0" @click="drak()" style="font-size: 20px; color: aliceblue" />
<template #content>
<a-space size="large" direction="vertical" fill>
<a-row @click="day()">
<a-button type="text"><IconSun @click="day()" style=" font-size: 20px;" />白天模式</a-button>
<a-button type="text"><IconSun style="font-size: 20px;" />白天模式</a-button>
</a-row>
</a-space>
<a-space size="large" direction="vertical" fill>
<a-row @click="drak()">
<a-button type="text"><IconMoon style=" font-size: 20px;"/>黑夜模式</a-button>
<a-button type="text"><IconMoon style="font-size: 20px;" />黑夜模式</a-button>
</a-row>
</a-space>
</template>
</a-dropdown>
</a-space>
</a-space>
</a-col>
<a-col flex="100px">
<template v-if="loginState">
<template v-if="loginState">
<a-space size="large" direction="vertical">
<a-space size="large">
<a-dropdown
@select="handleSelect">
<!-- 用户头像预留 -->
<a-avatar
:size="48">
<img src="https://img.pqblog.com/i/2024/07/13/215137.jpg" alt="">
<!-- <p>已登录</p> -->
<a-dropdown @select="handleSelect">
<!-- 用户头像 -->
<a-avatar :size="48">
<img :src="store.state.user?.loginUser.userAvatar" alt="" />
</a-avatar>
<template #content>
<a-doption @click="gotoCenter()">
<a-button type="text">
<icon-user style=" font-size: 20px;" />
<icon-user style="font-size: 20px;" />
个人中心
</a-button>
</a-doption>
<a-doption @click="logout()">
<a-button type="text">
<icon-import style=" font-size: 20px;" />
<icon-import style="font-size: 20px;" />
退出登录
</a-button>
</a-doption>
@ -78,101 +71,95 @@
</a-dropdown>
</a-space>
</a-space>
</template>
<template v-else>
</template>
<template v-else>
<a-space size="large" direction="vertical">
<a-space size="large">
<a-button type="primary" @click="login()">登陆</a-button>
</a-space>
</a-space>
</template>
</template>
</a-col>
</a-row>
</div>
</template>
<script setup lang="ts">
import { routes } from "@/router/routes";
import { ref, computed } from "vue";
import { useRouter,useRoute } from "vue-router";
import { useStore } from "vuex";
import { Message } from '@arco-design/web-vue';
import accessCheck from "@/access/access_check";
import ACCESS_ENMU from "@/access/ACCESS_ENUM";
import { UserControllerService } from "../../generated";
import { routes } from "@/router/routes"; //
import { ref, computed } from "vue"; // Vue 3 API
import { useRouter, useRoute } from "vue-router"; // Vue Router
import { useStore } from "vuex"; // Vuex
import { Message } from '@arco-design/web-vue'; //
import accessCheck from "@/access/access_check"; //
import ACCESS_ENMU from "@/access/ACCESS_ENUM"; //
import { UserControllerService } from "../../generated"; //
const router = useRouter();
const route = useRoute().matched[0].children;
const store = useStore();
const store = useStore(); // Vuex
const router = useRouter(); //
const route = useRoute().matched[0].children; //
let selectedKeys = ref(["/"]);
const Loginuser = store.state.user?.loginUser;
const loginState=computed(()=>{
let selectedKeys = ref(["/"]); //
const loginState = computed(() => {
return store.state.user?.loginUser && store.state.user.loginUser.userRole && store.state.user.loginUser.userRole !== 'notlogin';
})
console.log(Loginuser);
router.afterEach((to, from, failure) => {
selectedKeys.value = [to.path];
});
let mode=ref(1)
const drak=()=>{
mode.value=0
document.body.setAttribute('arco-theme', 'dark')
document.body.classList.add('dark-theme'); //
}); //
const visRouters = computed(() => {
return routes[1]?.children.filter((item) => {
//
if (item.meta?.hidden) return false;
if (!accessCheck(store.state.user?.loginUser, item.meta?.access as string)) return false;
return true;
});
}); //
// //
// document.body.removeAttribute('arco-theme');
}
const day=()=>{
mode.value=1
// //
document.body.classList.remove('dark-theme'); //
document.body.removeAttribute('arco-theme');
}
const logout=async()=>{
console.log("正在退出")
console.log(store.state.user.loginUser)
let res=await UserControllerService.userLogoutUsingPost()
if(res.code===0){
Message.success("退出成功")
console.log(store.state.user.loginUser)
// 1: 0:
const mode = ref(1);
//
const drak = () => {
mode.value = 0;
document.body.setAttribute('arco-theme', 'dark');
document.body.classList.add('dark-theme'); //
};
//
const day = () => {
mode.value = 1;
document.body.classList.remove('dark-theme'); //
document.body.removeAttribute('arco-theme');
};
// 退
const logout = async () => {
console.log("正在退出");
let res = await UserControllerService.userLogoutUsingPost();
if (res.code === 0) {
Message.success("退出成功");
store.commit('user/updateUser', {}); //
}
}
//
const visRouters = computed(() =>{
return routes[1]?.children.filter((item) => {
//
if (item.meta?.hidden) {
return false;
}
//
if (!accessCheck(store.state.user?.loginUser, item.meta?.access as string)) {
return false;
}
return true;
})
});
console.log(visRouters)
//
const username = computed(
() => store.state.user?.loginUser?.userName ?? "123"
);
const login=()=>{
router.push({
path:'/user/login'
})
}
//
const doMenuClick = (key: string) => {
router.push({
path: key,
});
};
//
const username = computed(() => store.state.user?.loginUser?.userName ?? "123");
//
const doMenuClick = (key: string) => {
router.push({ path: key });
};
//
const gotoCenter=()=>{
router.push('/user/center')
}
const gotoCenter = () => {
router.push('/user/center');
};
//
router.afterEach((to) => {
selectedKeys.value = [to.path];
});
//
const login = () => {
router.push({ path: '/user/login' });
};
</script>
<style>
@ -180,13 +167,7 @@ const gotoCenter=()=>{
display: flex;
align-items: center;
}
.user-info {
.title-bar {
display: flex;
flex-direction: row;
justify-content: end;
}
.title-bar{
display:flex;
}
</style>

View File

@ -1,21 +1,18 @@
<template>
<div class="info" style="align-items: center;">
<div class="avatar" style="display: flex;justify-content: center;align-items: center;width: 50%;">
<a-avatar :size="100" id="userAvatar" @click="onClcik" :style="{ backgroundColor: '#0A65CC' }">
<img v-if="form.userAvatar" class="avatar-image" alt="avatar" :src="form.userAvatar" />
<div class="avatar" style="display: flex; justify-content: center; flex-direction: column; align-items: center; width: 50%;">
<a-avatar :size="100" id="userAvatar" @click="triggerFileInput">
<img v-if="form.userAvatar" alt="avatar" :src="form.userAvatar" class="avatar-image"/>
<IconUser v-else />
<template #trigger-icon>
<IconEdit/>
<IconEdit />
</template>
</a-avatar>
<!-- 隐藏的文件输入框 -->
<input type="file" @change="getfile" ref="fileInput" style="display: none;" />
</div>
<a-space
direction="vertical"
size="large"
:style="{ width: '600px' }"
>
<a-space direction="vertical" size="large" :style="{ width: '600px' }">
<a-form :model="form" layout="vertical">
<a-form-item field="userName" label="昵称">
<a-input v-model="form.userName" placeholder="昵称(必填项)" />
@ -35,13 +32,12 @@
show-word-limit
/>
</a-form-item>
<a-button type="primary" class="save" @click="saveInfo"
>保存</a-button
>
<a-button type="primary" class="save" @click="saveInfo">保存</a-button>
</a-form>
</a-space>
</div>
</template>
<script setup>
import { IconUser, IconEdit } from "@arco-design/web-vue/es/icon";
import { useStore } from "vuex";
@ -49,13 +45,14 @@ import { computed, ref } from "vue";
import { Message } from "@arco-design/web-vue";
import { UserControllerService } from "../../../generated";
import { useRouter } from "vue-router";
import axios from 'axios';
const store = useStore();
const router = useRouter();
//
const fileInput=ref(null)
//
const loginUser = computed(() => store.state.user.loginUser);
//console.log(loginUser);
const selectedFile = ref(null);
const form = ref({
userName: loginUser.value.userName,
userProfile: loginUser.value.userProfile,
@ -63,39 +60,74 @@ const form = ref({
email: loginUser.value.email,
phone: loginUser.value.phone
});
//
// const file = ref();
// const onChange = (_, currentFile) => {
// file.value = {
// ...currentFile,
// url: URL.createObjectURL(currentFile.file),
// };
// };
// const onProgress = (currentFile) => {
// file.value = currentFile;
// };
//
const saveInfo = async () => {
//console.log(form.value);
const uploadSuccess = await uploadFile();
if (!uploadSuccess) return; //
const res = await UserControllerService.updateMyUserUsingPost(form.value);
if (res.code === 0) {
Message.success("修改个人信息成功");
await store.dispatch("user/getLoginUser");
router.push({
path: "/about",
replace: true,
});
} else {
Message.error("修改个人信息失败" + res.message);
Message.error("修改个人信息失败: " + res.message);
}
};
const onClcik = () => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
let result = 'https://api.multiavatar.com/';
for (let i = 0; i < 16; i++) {
result += characters.charAt(Math.floor(Math.random() * 52));
//
const triggerFileInput = () => {
console.log(fileInput.value)
fileInput.value.click()
// const fileInput = document.querySelector('input[type="file"]');
// fileInput.click(); //
};
//
const getfile = async (event) => {
const files = event.target.files;
selectedFile.value = files[0]; //
if (files.length > 0) {
console.log('选择的文件:', selectedFile.value);
await uploadFile(); //
} else {
console.log('没有选择文件');
}
};
//
const uploadFile = async () => {
if (!selectedFile.value) {
alert('请先选择一个文件');
return false; //
}
const formData = new FormData();
formData.append('image', selectedFile.value); // FormData
formData.append('token', 123); // token FormData token
try {
const response = await axios.post('http://222.186.56.232:40061/api/index.php', formData, {
headers: {
'Content-Type': 'multipart/form-data', //
},
});
form.value.userAvatar = response.data.url; // URL
console.log('文件上传成功:', response.data.url);
return true; //
} catch (error) {
console.error('文件上传失败:', error.response ? error.response.data : error.message);
return false; //
}
alert(result)
form.userAvatar= result
};
</script>
<style scoped>
.avatar-image {
width: 100%;
border-radius: 50%;
}
#userAvatar {
position: relative;
}
</style>

View File

@ -1,34 +0,0 @@
// // src/axios.js
// import axios from 'axios';
// import { inject } from 'vue';
// const loading = inject('loading');
// const instance = axios.create({
// baseURL: 'http://127.0.0.1:8101',
// timeout: 10000,
// });
// instance.interceptors.request.use(
// (config) => {
// loading.value = true;
// return config;
// },
// (error) => {
// loading.value = false;
// return Promise.reject(error);
// }
// );
// instance.interceptors.response.use(
// (response) => {
// loading.value = false;
// return response;
// },
// (error) => {
// loading.value = false;
// return Promise.reject(error);
// }
// );
// export default instance;

View File

@ -1,7 +1,7 @@
<template>
<div class="forum-publish">
<h1>发布新帖子</h1>
<a-form :model="form" layout="vertical" @submit="handleSubmit">
<a-form :model="form" layout="vertical" >
<a-form-item
label="标题"
field="title"
@ -22,7 +22,7 @@
/>
</a-form-item>
<a-button type="primary" html-type="submit">发布</a-button>
<a-button type="primary" html-type="submit" @click="handleSubmit">发布</a-button>
</a-form>
<a-message v-if="message" :type="messageType" :content="message" />
@ -31,11 +31,13 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { PostControllerService } from "../../../generated";
import { Message } from '@arco-design/web-vue';
const form = ref({
title: '',
content: ''
content: '',
tags:["组队"]
});
const message = ref('');
@ -54,15 +56,11 @@ const handleSubmit = async (event: Event) => {
// API
try {
// API
await new Promise((resolve) => setTimeout(resolve, 1000));
let res=await PostControllerService.addPostUsingPost(form.value)
//
message.value = '帖子发布成功!';
messageType.value = 'success';
//
form.value.title = '';
form.value.content = '';
if(res.code==0){
alert("发布成功")
}
} catch (error) {
message.value = '发布帖子失败,请稍后重试';
messageType.value = 'error';

View File

@ -1,24 +1,23 @@
<template>
<div class="forum-home">
<!-- 顶部导航栏 -->
<a-layout>
<a-layout-content>
<div class="content">
<a-row gutter={24}>
<div class="forum-container">
<!-- 侧边栏 -->
<a-col span="6">
<a-card title="分类" class="sidebar">
<a-menu mode="inline" theme="light">
<a-menu-item key="category1">分类 1</a-menu-item>
<a-menu-item key="category2">分类 2</a-menu-item>
<a-menu-item key="category3">分类 3</a-menu-item>
</a-menu>
</a-card>
</a-col>
<aside class="sidebar" style="color: var(--text-color);">
<h3 class="sidebar-title" style="color: var(--text-color);">分类</h3>
<ul class="category-list" style="color: var(--text-color);">
<li
v-for="category in categories"
:key="category.id"
@click="selectCategory(category)"
:class="{ active: selectedCategory && selectedCategory.id === category.id }"
>
{{ category.name }}
</li>
</ul>
</aside>
<!-- 主内容区域 -->
<main class="main-content">
<a-list
class="list-demo-action-layout"
:bordered="false"
@ -26,136 +25,180 @@
:pagination-props="paginationProps"
>
<template #item="{ item }">
<a-list-item class="list-demo-item" action-layout="vertical">
<a-list-item class="list-demo-item" action-layout="vertical" style="display: flex;flex-direction: row;justify-content: center;align-items: center;">
<template #actions>
<span><icon-heart />83</span>
<span><icon-star />{{ item.index }}</span>
<span><icon-message />Reply</span>
<span><icon-heart />{{ item.thumbNum }}</span>
<span><icon-star />{{ item.favourNum }}</span>
<span><icon-message />收藏</span>
</template>
<template #extra>
<div className="image-area">
<img alt="arco-design" :src="item.imageSrc" />
<template #content>
<div class="content-area">
你好你好你好你好你好你好你好你好你好你好你好你好你好你好
</div>
</template>
<a-list-item-meta
:title="item.title"
:description="item.description"
>
<template #extra>
<div class="image-area">
<img alt="arco-design" :src="item.imageSrc || `https://api.miaomc.cn/image/get?${Math.random()}`" />
</div>
</template>
<a-list-item-meta :title="item.title" :description="item.content">
<template #avatar>
<a-avatar shape="square">
<img alt="avatar" :src="item.avatar" />
<img alt="avatar" :src="item.user.userAvatar" />
</a-avatar>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-row>
</div>
</a-layout-content>
</a-layout>
</main>
</div>
</template>
<script lang="ts" setup>
import { ref,reactive } from 'vue';
import { useRouter } from 'vue-router';
const names = ['Socrates', 'Balzac', 'Plato'];
const avatarSrc = [
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/e278888093bef8910e829486fb45dd69.png~tplv-uwbnlip3yd-webp.webp',
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/9eeb1800d9b78349b24682c3518ac4a3.png~tplv-uwbnlip3yd-webp.webp',
];
const imageSrc = [
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/29c1f9d7d17c503c5d7bf4e538cb7c4f.png~tplv-uwbnlip3yd-webp.webp',
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/04d7bc31dd67dcdf380bc3f6aa07599f.png~tplv-uwbnlip3yd-webp.webp',
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/1f61854a849a076318ed527c8fca1bbf.png~tplv-uwbnlip3yd-webp.webp',
];
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { PostControllerService } from '../../../generated'; //
const dataSource = new Array(15).fill(null).map((_, index) => {
return {
index: index,
avatar: avatarSrc[index % avatarSrc.length],
title: names[index % names.length],
description:
'Beijing ByteDance Technology Co., Ltd. is an enterprise located in China. ByteDance has products such as TikTok, Toutiao, volcano video and Douyin (the Chinese version of TikTok).',
imageSrc: imageSrc[index % imageSrc.length],
};
});
const paginationProps=reactive({
//
const article = ref([]); //
//
const categories = ref([
{ id: 1, name: "所有文章" },
{ id: 2, name: "前端开发" },
{ id: 3, name: "后端开发" },
{ id: 4, name: "全栈开发" },
]); //
const selectedCategory = ref(null); //
//
const paginationProps = reactive({
defaultPageSize: 3,
total: dataSource.length
})
total: 0,
current: 1,
onChange: (page) => fetchData(page), //
});
const getRandomImg=()=>{return }
//
const fetchData = async (page = 1) => {
try {
const res = await PostControllerService.listPostVoByPageUsingPost({
current: page,
pageSize: paginationProps.defaultPageSize,
categoryId: selectedCategory.value ? selectedCategory.value.id : null, //
});
if (res.code === 0) {
//
article.value = res.data.records.map((item) => ({
...item,
imageSrc: item.imageSrc || `https://api.miaomc.cn/image/get?${Math.random()}`,
}));
paginationProps.total = res.data.total; //
}
} catch (error) {
console.error('数据获取失败:', error);
}
};
//
onMounted(() => {
fetchData();
});
//
const selectCategory = (category) => {
selectedCategory.value = category;
fetchData(); //
};
// dataSource
const dataSource = article;
</script>
<style scoped>
.forum-home {
/* background-color: #f0f2f5; 背景颜色 */
}
.list-demo-action-layout {
width: 60%;
border-radius: 2px;
overflow: hidden;
}
.list-demo-action-layout .image-area {
width: 183px;
height: 119px;
border-radius: 2px;
overflow: hidden;
}
.list-demo-action-layout .list-demo-item {
padding: 20px 0;
border-bottom: 1px solid var(--color-fill-3);
}
.list-demo-action-layout .image-area img {
width: 100%;
}
.list-demo-action-layout .arco-list-item-action .arco-icon {
margin: 0 4px;
}
.header {
background-color: var(--background-color);
color: var(--text-color);
padding: 0 20px;
/* 布局样式 */
.forum-container {
display: flex;
align-items: center;
}
.logo {
font-size: 24px;
font-weight: bold;
margin-right: 20px;
}
.nav-menu {
flex-grow: 1;
}
.content {
padding: 80px;
padding: 20px;
gap: 20px;
color:--text-color;
}
/* 侧边栏样式 */
.sidebar {
background-color: var(--background-color);
color: var(--text-color);
flex: 0 0 200px;
background-color: --background-color;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border: 0.5px solid #fff;
}
.post-list {
background-color: var(--background-color);
color: var(--text-color);
border-radius: 8px;
.sidebar-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.footer {
text-align: center;
padding: 10px;
background-color: var(--background-color);
color: var(--text-color);
.category-list {
list-style: none;
padding: 0;
margin: 0;
}
.category-list li {
padding: 8px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s;
}
.category-list li:hover {
background-color: #d8d8d8;
}
.category-list li.active {
background-color: #1890ff;
color: white;
}
/* 主内容区域样式 */
.main-content {
flex: 1;
}
.list-demo-action-layout {
background-color: --background-color;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.list-demo-item {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.image-area {
display: flex;
justify-content: center;
align-items: center;
width: 150px;
height: 150px;
overflow: hidden;
border-radius: 8px;
border: 1px solid #e5e5e5;
margin-left: 10px;
}
.image-area img {
max-width: 100%;
max-height: 100%;
}
</style>