diff --git a/package-lock.json b/package-lock.json
index 2c5f2c418..3a6d57525 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -988,6 +988,30 @@
}
}
},
+ "antd-img-crop": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npm.taobao.org/antd-img-crop/download/antd-img-crop-3.12.0.tgz",
+ "integrity": "sha1-6S95QUkhNSwffjv7uu/Cc9pQFtU=",
+ "requires": {
+ "@babel/runtime": "^7.11.2",
+ "react-easy-crop": "^3.1.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.11.2",
+ "resolved": "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.11.2.tgz?cache=0&sync_timestamp=1596637887062&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.11.2.tgz",
+ "integrity": "sha1-9UnBPHVMxAuHZEufqfCaapX+BzY=",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.13.7.tgz?cache=0&sync_timestamp=1595456367497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.7.tgz",
+ "integrity": "sha1-ysLazIoepnX+qrrriugziYrkb1U="
+ }
+ }
+ },
"anymatch": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
@@ -14809,6 +14833,21 @@
"prop-types": "^15.6.0"
}
},
+ "react-easy-crop": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npm.taobao.org/react-easy-crop/download/react-easy-crop-3.2.0.tgz",
+ "integrity": "sha1-HGaM4eTIV8CdryXdrzmKwkdfRTw=",
+ "requires": {
+ "tslib": "2.0.1"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npm.taobao.org/tslib/download/tslib-2.0.1.tgz?cache=0&sync_timestamp=1602286603545&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-2.0.1.tgz",
+ "integrity": "sha1-QQ6w0RPltjVkkO7HSWA3JbAhtD4="
+ }
+ }
+ },
"react-error-overlay": {
"version": "6.1.0-next.80",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0-next.80.tgz",
diff --git a/package.json b/package.json
index 0b17601d9..3d1b2407c 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"@novnc/novnc": "^1.1.0",
"actioncable": "^5.2.4-3",
"antd": "^3.26.15",
+ "antd-img-crop": "^3.9.0",
"array-flatten": "^2.1.2",
"autoprefixer": "7.1.6",
"axios": "^0.18.1",
diff --git a/public/index.html b/public/index.html
index 1969fa0e0..07c2e378a 100755
--- a/public/index.html
+++ b/public/index.html
@@ -2,22 +2,12 @@
-
-
-
-
+
+
+
+
-
diff --git a/src/App.js b/src/App.js
index 28ec0d5f5..b8d91742b 100644
--- a/src/App.js
+++ b/src/App.js
@@ -61,7 +61,15 @@ const Shixunnopage = Loadable({
loader: () => import('./modules/404/Shixunnopage'),
loading: Loading,
})
-
+// 新版论坛交流
+const Forums = Loadable({
+ loader: () => import("./forums/Index"),
+ loading: Loading,
+});
+const Account = Loadable({
+ loader: () => import("./user_info/Index"),
+ loading: Loading,
+});
//500页面
const http500 = Loadable({
loader: () => import('./modules/500/http500'),
@@ -231,6 +239,19 @@ class App extends Component {
}
}>
+ (
+
+ )}
+ >
+ {/* 问吧、论坛交流 */}
+ (
+
+ )}
+ >
{/*项目*/}
{
+ const [loading, setLoading] = useState(false);
+ const [url, setUrl] = useState(undefined);
+
+ useEffect(() => {
+ if (imageUrl) {
+ setUrl(imageUrl);
+ }
+ }, [imageUrl])
+
+ function getBase64(img, callback) {
+ const reader = new FileReader();
+ reader.addEventListener('load', () => callback(reader.result));
+ reader.readAsDataURL(img);
+ }
+
+ function handleChange(info) {
+ if (info.file.status === 'uploading') {
+ setLoading(true);
+ return;
+ }
+ if (info.file.status === 'done') {
+ let filelist = info.fileList && info.fileList.length>0 && info.fileList[info.fileList.length-1];
+ if(filelist && filelist.response && filelist.response.status === -1){
+ setLoading(false)
+ setUrl(null)
+ message.error(filelist.response.message)
+ }else{
+ getBase64(info.file.originFileObj, imageUrl =>{
+ setLoading(false),
+ getImageUrl(filelist && filelist.response),
+ setUrl(imageUrl)
+ });
+ }
+ }
+ };
+ function beforeUpload(file) {
+ const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
+ if (!isJpgOrPng) {
+ message.error('只能上传图片!');
+ }
+ const isLt2M = file.size / 1024 / 1024 < 2;
+ if (!isLt2M) {
+ message.error('图片大小必须小于2MB!');
+ }
+ return isJpgOrPng && isLt2M;
+ }
+ const uploadButton = (
+
+ {loading ? : }
+
+ );
+ return (
+
+ {url ?
: uploadButton}
+
+ )
+})
\ No newline at end of file
diff --git a/src/forums/Component/Announcement.jsx b/src/forums/Component/Announcement.jsx
new file mode 100644
index 000000000..6ecce1125
--- /dev/null
+++ b/src/forums/Component/Announcement.jsx
@@ -0,0 +1,130 @@
+import React, { useState, useEffect } from "react";
+import { WhiteBack } from "../css/layout";
+import Title from "./Title";
+import { Modal, Button, Input , notification, message } from "antd";
+import Nodata from "../Nodata";
+import axios from 'axios';
+
+const TextArea = Input.TextArea;
+export default (({ content , operation ,plateId }) => {
+ const [word, setWord] = useState(undefined);
+ const [editWord, setEditWord] = useState(undefined);
+ const [show, setShow] = useState(false);
+ const [visible, setVisible] = useState(false);
+ const [AnnModalType, setAnnModalType] = useState(1); //默认为1:查看,否则为编辑
+
+ let child = document.getElementById("annWords") && document.getElementById("annWords").offsetHeight;
+ useEffect(() => {
+ if (content) {
+ setWord(content.notice);
+ }
+ }, [content]);
+
+ useEffect(() => {
+ changeShow();
+ }, [word, child , content]);
+
+ // 保存公告
+ function saveAnn() {
+ setWord(editWord);
+ setVisible(false);
+ // 调用保存接口
+ if(editWord){
+ const url = `/v1/forum_sections/${plateId}/edit_notice.json`;
+ axios.post(url,{
+ content:editWord
+ }).then(result=>{
+ if(result && result.data){
+ notification.open({message:"提示",description:result.data.message});
+ changeShow();
+ }
+ }).catch(error=>{
+ console.log(error);
+ })
+ }
+ }
+
+ function changeShow(){
+ let p = document.getElementById("annContent")&& document.getElementById("annContent").offsetHeight;
+ let c = document.getElementById("annWords") &&document.getElementById("annWords").offsetHeight;
+ if (c > p) {
+ setShow(true);
+ }else{
+ setShow(false);
+ }
+ }
+ // 取消编辑公告
+ function cancelAnn() {
+ setEditWord(word);
+ setVisible(false);
+ }
+
+ // 查看(1)或者编辑(2)
+ function changeAnnModalType(type) {
+ setAnnModalType(type);
+ setVisible(true);
+ setEditWord(word);
+ }
+
+ function changeText(e) {
+ setEditWord(e.target.value);
+ }
+ return (
+
+ setVisible(false)}
+ footer={
+ AnnModalType === 1 ? false :
+
+
+
+ }
+ >
+ {AnnModalType === 1 ? (
+ {word}
+ ) : (
+
+ )}
+
+
+ 公告
+ {
+ operation ?
+ changeAnnModalType(2)}>
+
+ :""
+ }
+
+
+
+ );
+});
diff --git a/src/forums/Component/AuthorCard.jsx b/src/forums/Component/AuthorCard.jsx
new file mode 100644
index 000000000..bd151fe31
--- /dev/null
+++ b/src/forums/Component/AuthorCard.jsx
@@ -0,0 +1,51 @@
+import React , { useState, useEffect } from 'react';
+import { WhiteBack , Greenline , UDStructure , Grid } from '../css/layout';
+import UserInfo from './UserInfo';
+import { getUrl } from "educoder";
+import StarUser from "../../user_info/User/StarUser";
+export default (({user})=>{
+ useEffect(() => {
+ if(user){
+ setFansCount(user.watchers_count)
+ }
+ },[]);
+ const [fansCount, setFansCount] = useState(0)
+ const fans_count = (count) => {
+ let new_fans_count = fansCount + count
+ setFansCount(new_fans_count)
+ }
+
+ return(
+
+
+ {user && user.description ? user.description : "这家伙太懒了,还未填写个人描述!"}
+ {user && !user.is_current_user && (
+
+ )}
+
+
+ {user && user.memos_count}
+ 文章数
+
+
+ {user && user.replies_count}
+ 评论数
+
+
+ {fansCount}
+ 关注者
+
+
+
+ )
+})
\ No newline at end of file
diff --git a/src/forums/Component/AuthorItem.jsx b/src/forums/Component/AuthorItem.jsx
new file mode 100644
index 000000000..ad0f3fa63
--- /dev/null
+++ b/src/forums/Component/AuthorItem.jsx
@@ -0,0 +1,23 @@
+import React, { useState } from "react";
+import { WhiteBack } from "../css/layout";
+import Title from './Title';
+import UserInfo from "./UserInfo";
+import { getUrl } from 'educoder';
+export default (({ author }) => {
+ return (
+
+ 版块活跃作者
+
+ {
+ author && author.length > 0 ?
+ author.map((item,key)=>{
+ return(
+
+ )
+ })
+ :""
+ }
+
+
+ );
+});
diff --git a/src/forums/Component/BestItem.jsx b/src/forums/Component/BestItem.jsx
new file mode 100644
index 000000000..74e6c8e03
--- /dev/null
+++ b/src/forums/Component/BestItem.jsx
@@ -0,0 +1,27 @@
+import React,{useEffect,useState} from 'react';
+import {Link} from 'react-router-dom';
+import Nodata from '../Nodata';
+
+export default (({ memos , selectKey })=>{
+ const [list, setList] = useState(undefined);
+ const [ keys , setKeys ] = useState(undefined);
+
+ useEffect(() => {
+ if (memos) {
+ setList(memos);
+ setKeys(selectKey[0]);
+ }
+ }, [memos , selectKey]);
+ return(
+
+ {
+ list && list.length>0 ? list.map((item,key)=>{
+ return(
+ - {item.subject}
+ )
+ }):
+
+ }
+
+ )
+})
\ No newline at end of file
diff --git a/src/forums/Component/BestModalItem.jsx b/src/forums/Component/BestModalItem.jsx
new file mode 100644
index 000000000..8f01295af
--- /dev/null
+++ b/src/forums/Component/BestModalItem.jsx
@@ -0,0 +1,54 @@
+import React, { useState } from "react";
+import { WhiteBack, AlignCenter } from "../css/layout";
+import { Link } from 'react-router-dom';
+import Title from "./Title";
+import ring from "../image/radius.png";
+import styled from "styled-components";
+
+const Img = styled.img`
+ {
+ width: 40px;
+ height: 40px;
+ margin-right: 14px;
+ border-radius: 50%;
+ }
+`;
+const Prag = styled.p`
+ {
+ color: #999;
+ font-size: 14px;
+ line-height: 18px;
+ margin: 4px 0 !important;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ word-break: break-all;
+ }
+`;
+
+export default ({ recommand }) => {
+ return (
+
+ 精选板块
+
+ {recommand && recommand.length > 0
+ ? recommand.map((item, key) => {
+ return (
+
+
+
+
{item.title}
+
{item.description || "暂无描述~"}
+ {/*
{item.description}
*/}
+
{item.watchers_count}人收藏{item.memos_count}个话题
+
+
+ );
+ })
+ : ""}
+
+
+ );
+};
diff --git a/src/forums/Component/DoubleItem.jsx b/src/forums/Component/DoubleItem.jsx
new file mode 100644
index 000000000..c7a8df9e8
--- /dev/null
+++ b/src/forums/Component/DoubleItem.jsx
@@ -0,0 +1,66 @@
+import React, { useState, useEffect } from "react";
+import logo from "../image/radius.png";
+import { FlexAJ, P } from "../css/layout";
+import { Link } from "react-router-dom";
+import styled from "styled-components";
+
+const Img = styled.img`
+ {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ margin-right: 25px;
+ }
+`;
+
+const Prag = styled.p`
+ {
+ color: #333;
+ font-size: 14px;
+ line-height: 18px;
+ margin-bottom: 10px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ word-break: break-all;
+ height:18px;
+ }
+`;
+
+export default (props) => {
+ const list = props.plate;
+
+ return (
+
+ {list && list.length > 0
+ ? list.map((item, key) => {
+ return (
+
+

+
+
+
{item.title}
+
帖子数:{item.memos_count}
+
+
{item.description || 暂无描述~}
+
+ 版主:{item.user_name}
+
+
+
+ );
+ })
+ : ""}
+
+ );
+};
diff --git a/src/forums/Component/ItemDropDown.jsx b/src/forums/Component/ItemDropDown.jsx
new file mode 100644
index 000000000..8d5406ea9
--- /dev/null
+++ b/src/forums/Component/ItemDropDown.jsx
@@ -0,0 +1,114 @@
+import React from "react";
+import { Menu , Dropdown , notification } from "antd";
+import {Link} from 'react-router-dom';
+import axios from 'axios';
+
+// permission = {
+// admin://管理员
+// banned_permission://管理员、且有置顶、推荐权限
+// is_currentUser: true, #是否为当前用户,判断是否显示编辑/删除,并隐藏其他的
+// is_fine: true, #是否精华帖子
+// sticky: true, #是否置顶
+// memo_watched: true, #是否收藏
+// is_deleted:true#是否已经申请删除
+// }
+export default ({ id , permission , calbackFunc , confirm }) => {
+ // 置顶、取消置顶
+ function changeSticky(s){
+ let sticky = s ? 0 : 1;//1为置顶,0为取消置顶
+ const url = `/v1/memos/${id}/set-top-or-down.json`;
+ axios.get(url,{
+ params:{
+ sticky
+ }
+ }).then(result=>{
+ if(result){
+ notification.open({message:"提示",description:result.data.message});
+ calbackFunc && calbackFunc();
+ }
+ })
+ }
+ // 推荐、取消推荐
+ function changeFine(f){
+ let is_fine = f ? 0 : 1;//1表示加精,0表示取消加精
+ const url = `/v1/memos/${id}/is_fine.json`;
+ axios.post(url,{
+ is_fine
+ }).then(result=>{
+ if(result){
+ notification.open({message:"提示",description:result.data.message});
+ calbackFunc && calbackFunc();
+ }
+ })
+ }
+
+ // 关注、取消关注
+ function changeMemoWatched(m){
+ let is_watch = m ? 0 : 1;//1为添加关注,0为取消关注
+ const url = `/v1/memos/${id}/watch_memo.json`;
+ axios.post(url,{
+ is_watch
+ }).then(result=>{
+ if(result){
+ notification.open({message:"提示",description:result.data.message});
+ calbackFunc && calbackFunc();
+ }
+ })
+ }
+ // 管理员直接删除帖子
+ function deleteForum(){
+ confirm && confirm({
+ content: '确认删除帖子?',
+ onOk:()=>{
+ const url = `/v1/memos/${id}/destroy.json`;
+ axios.post(url).then(result=>{
+ if(result){
+ notification.open({message:"提示",description:result.data.message});
+ calbackFunc && calbackFunc();
+ window.location.href="/forums"
+ }
+ })
+ }
+ })
+ }
+
+ // 发布者申请删除、撤销申请删除
+ function sendDeleteForum(d){
+ let is_apply = d ? 0 : 1;//1为申请删除,0为撤销申请删除
+ confirm && confirm({
+ content: '确认申请删帖?',
+ onOk:()=>{
+ const url = `/v1/memos/${id}/confirm_delete.json`;
+ axios.post(url,{
+ is_apply
+ }).then(result=>{
+ if(result){
+ notification.open({message:"提示",description:result.data.message});
+ calbackFunc && calbackFunc();
+ }
+ })
+ }
+ })
+ }
+
+ const menu=(
+ permission &&
+
+ )
+ return (
+
+
+
+ );
+};
diff --git a/src/forums/Component/ItemType.jsx b/src/forums/Component/ItemType.jsx
new file mode 100644
index 000000000..d52014bc6
--- /dev/null
+++ b/src/forums/Component/ItemType.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+
+export function Type(value){
+ if(value === "交流"){
+ return (【交流】)
+ }else if(value === "求助"){
+ return (【求助】)
+ }
+}
+
+export function Tag(value){
+ if(value === "置顶"){
+ return (置顶)
+ }else if(value === "精华"){
+ return (精华)
+ }else if(value === "原创"){
+ return (原创)
+ }
+}
\ No newline at end of file
diff --git a/src/forums/Component/ListItem.jsx b/src/forums/Component/ListItem.jsx
new file mode 100644
index 000000000..ca4007328
--- /dev/null
+++ b/src/forums/Component/ListItem.jsx
@@ -0,0 +1,89 @@
+import React, { useState, useEffect } from "react";
+import { FlexAJ, AlignCenter, LeftLine } from "../css/layout";
+import { Link } from "react-router-dom";
+import { getUrl } from 'educoder';
+import { Type, Tag } from "./ItemType";
+import User from "./User";
+import Drop from "./ItemDropDown";
+import Nodata from '../Nodata';
+
+export default ({ memos , current_user , calbackFunc , confirm }) => {
+ const [list, setList] = useState(undefined);
+
+ useEffect(() => {
+ if (memos) {
+ setList(memos);
+ }
+ }, [memos]);
+
+ return (
+
+ {list && list.length > 0 ? (
+ list.map((item, key) => {
+ let permission = {
+ index:key,
+ admin:current_user && current_user.admin,
+ banned_permission:item.banned_permission,
+ login:current_user && current_user.login,
+ is_currentUser:current_user && (item.user_login === current_user.login),
+ is_fine:item.is_fine,
+ sticky:item.sticky,
+ memo_watched:item.memo_watched,
+ user_banned_permission:item.banned_permission,
+ is_deleted:item.apply_destroy
+ }
+ return (
+ -
+
+
+ {Type(item.tag_name)}
+
+ {item.subject}
+
+ { item.sticky === true ? {Tag("置顶")} : "" }
+ { item.is_original === true ? {Tag("原创")} : "" }
+ { item.is_fine === true ? {Tag("精华")} : "" }
+
+
+ { item.apply_destroy ? 已申请删帖 : "" }
+ {
+ current_user && current_user.login ?
+
+ :""
+ }
+
+
+
+
+
+ {item.forum_section_title ? {item.forum_section_title} : "" }
+ {item.published_time ? {item.published_time} : "" }
+
+
+
+
+ {item.replies_count}
+
+
+
+ {item.praises_count}
+
+
+
+ {item.replies_count}
+
+
+
+
+ );
+ })
+ ) :
+
+ }
+
+ );
+};
diff --git a/src/forums/Component/ListSearch.jsx b/src/forums/Component/ListSearch.jsx
new file mode 100644
index 000000000..ee13f2690
--- /dev/null
+++ b/src/forums/Component/ListSearch.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { Input } from 'antd';
+import { Greenback } from '../css/layout';
+import { Link } from 'react-router-dom';
+
+const Search = Input.Search;
+export default (({ onSearch , current_user })=>{
+ return(
+
+
+ {
+ current_user && current_user.login ?
+
+ 写点什么
+
+ :
+
+ 写点什么
+
+ }
+
+ )
+})
\ No newline at end of file
diff --git a/src/forums/Component/Paginations.jsx b/src/forums/Component/Paginations.jsx
new file mode 100644
index 000000000..cc19dd437
--- /dev/null
+++ b/src/forums/Component/Paginations.jsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { Pagination } from "antd";
+
+export default (({page,total,changePage , pageSize}) => {
+ return (
+ total > pageSize ?
+ :""
+ );
+});
diff --git a/src/forums/Component/Prise.jsx b/src/forums/Component/Prise.jsx
new file mode 100644
index 000000000..394f01b46
--- /dev/null
+++ b/src/forums/Component/Prise.jsx
@@ -0,0 +1,45 @@
+import React, { useState, useEffect } from "react";
+import axios from "axios";
+import { message } from "antd";
+export default ({ isPrised, num, memo_id, container_type, current_login }) => {
+ const [flag, setFlag] = useState(undefined);
+ const [number, setNumber] = useState(undefined);
+
+ useEffect(() => {
+ setFlag(isPrised);
+ setNumber(num);
+ }, []);
+
+ function priseForums() {
+ if (current_login) {
+ axios
+ .post(`/v1/discusses/${memo_id}/plus.json`, {
+ container_type: container_type,
+ id: memo_id,
+ type: flag ? 0 : 1,
+ })
+ .then((result) => {
+ setNumber(result.data.praise_count);
+ setFlag(!flag);
+ message.success(flag ? "取消点赞" : "已点赞")
+ })
+ .catch((error) => {
+ message.error(error);
+ });
+ } else {
+ window.open("/login", "_blank");
+ }
+ }
+ return (
+
+
+
+
+ {number}
+
+ );
+};
diff --git a/src/forums/Component/Title.jsx b/src/forums/Component/Title.jsx
new file mode 100644
index 000000000..c31adbd14
--- /dev/null
+++ b/src/forums/Component/Title.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const Nav = styled.div`{
+ background-color:#fff;
+ padding:20px 30px;
+ border-bottom:1px solid #eee;
+ font-size:16px;
+ color:#333;
+ display:flex;
+ justify-content: space-between;
+ align-items:center;
+}`
+
+export default (({children,className})=>{
+ return(
+
+ )
+})
\ No newline at end of file
diff --git a/src/forums/Component/Top.jsx b/src/forums/Component/Top.jsx
new file mode 100644
index 000000000..203aa822c
--- /dev/null
+++ b/src/forums/Component/Top.jsx
@@ -0,0 +1,152 @@
+import React, { useEffect , useState } from "react";
+import { Button , notification } from "antd";
+import { WhiteBack, FlexStart, P, FlexAJ, AlignCenter } from "../css/layout";
+import Radius from "../image/radius.png";
+import styled from "styled-components";
+import axios from "axios";
+import {Link} from 'react-router-dom';
+
+const Img = styled.img`
+ {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ margin-right: 15px;
+ }
+`;
+const Prag = styled.p`
+ {
+ color: #333;
+ font-size: 14px;
+ line-height: 18px;
+ margin: 10px 0px;
+ text-align: justify;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ word-break: break-all;
+ max-height: 54px;
+ }
+`;
+const Spanleft = styled.span`
+ {
+ margin-right: 30px;
+ color: #888;
+ font-size: 12px;
+ }
+`;
+const Spanright = styled.span`
+ {
+ margin-left: 30px;
+ color: #888;
+ font-size: 12px;
+ & > label {
+ color: #333;
+ }
+ }
+`;
+export default ({ headData, title , operation , history }) => {
+ const [section, setSection] = useState(undefined);
+ const [sectionUser, setSectionUser] = useState(undefined);
+ const [forumModers, setForumModers] = useState(undefined);
+ const [ watched , setWacth ] = useState(headData && headData.watched);
+
+ useEffect(() => {
+ if (headData) {
+ setSection(headData.forum_section);
+ setSectionUser(headData.forum_section_user);
+ setForumModers(headData.forum_moders);
+ setWacth(headData.watched);
+ }
+ }, [headData]);
+
+ // 收藏、取消收藏
+ function saveForum(id){
+ if(id){
+ const url = `/v1/memos/forum_memos/${id}/is_watch.json`;
+ axios.post(url,{
+ is_watch:watched?0:1
+ }).then(result=>{
+ if(result && result.data && result.data.status!=-1){
+ setWacth(!watched);
+ notification.open({message:"提示",description:result.data.message});
+ }
+ }).catch(error=>{
+ console.log(error);
+ })
+ }
+ }
+
+ function toManage(id){
+ history.push(`/forums/manage/${section && section.id}`);
+ }
+
+ return (
+
+
+
+
+
+ {section && section.title}
+
+ {
+ operation ?
+
+ :""
+ }
+
+
+
+
+ {section && section.description ? (
+
{section.description}
+ ) : (
+ ""
+ )}
+
+
+ 版主:{sectionUser && sectionUser.username}
+ {forumModers && forumModers.length > 0 ? (
+
+ 管理员:
+ {forumModers.map((item, key) => {
+ return key < forumModers.length - 1
+ ? {item.username}、
+ : {item.username};
+ })}
+
+ ) : (
+ ""
+ )}
+
+
+
+ 版块主题:
+
+ {section && section.publish_today_coun ? (
+
+ 今日发帖:
+
+ ) : (
+ ""
+ )}
+ {section && section.replies_today_count ? (
+
+ 今日回帖:
+
+ ) : (
+ ""
+ )}
+
+
+
+
+
+ );
+};
diff --git a/src/forums/Component/UrlItem.jsx b/src/forums/Component/UrlItem.jsx
new file mode 100644
index 000000000..e20480a17
--- /dev/null
+++ b/src/forums/Component/UrlItem.jsx
@@ -0,0 +1,42 @@
+import React , { useState , useEffect } from "react";
+import { Link } from 'react-router-dom';
+
+export default ({user}) => {
+
+ return (
+
+ );
+};
diff --git a/src/forums/Component/User.jsx b/src/forums/Component/User.jsx
new file mode 100644
index 000000000..a83458265
--- /dev/null
+++ b/src/forums/Component/User.jsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { AlignCenter } from '../css/layout';
+import styled from 'styled-components';
+import { Link } from 'react-router-dom';
+
+const Img = styled.img`{
+ width:20px;
+ height:20px;
+ border-radius:50%;
+ margin-right:10px;
+}`
+const Span = styled.span`{
+ color:#999;
+}`
+export default (({name,url,login})=>{
+ return(
+
+
+
+ {name}
+
+
+ )
+})
\ No newline at end of file
diff --git a/src/forums/Component/UserInfo.jsx b/src/forums/Component/UserInfo.jsx
new file mode 100644
index 000000000..074640391
--- /dev/null
+++ b/src/forums/Component/UserInfo.jsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import styled from 'styled-components';
+import { Link } from 'react-router-dom';
+
+export default ({ url , name , column , login })=>{
+ const Img = styled.span`
+ display:flex;
+ ${column && "flex-direction: column;text-align:center;"}
+ align-items: center;
+ & img{
+ width:30px;
+ height:30px;
+ border-radius:50%;
+ }
+ ${!column && `
+ & span{
+ margin-left:8px;
+ }`
+ }
+ `;
+ return(
+
+
+
+ {name}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/forums/Forums.js b/src/forums/Forums.js
new file mode 100644
index 000000000..fb86d5cb8
--- /dev/null
+++ b/src/forums/Forums.js
@@ -0,0 +1,197 @@
+import React, { useEffect, useState } from "react";
+import { Pagination, Menu, Spin } from "antd";
+
+import {
+ Box,
+ Long,
+ Short,
+ Gap,
+ WhiteBack,
+ LeftLine,
+ AlignCenter,
+} from "./css/layout";
+import Title from "./Component/Title";
+import ListItem from "./Component/ListItem";
+import "./css/All.scss";
+import ListSearch from "./Component/ListSearch";
+import UrlItem from "./Component/UrlItem";
+import BestItem from "./Component/BestItem";
+import DoubleItem from "./Component/DoubleItem";
+import "./css/Theme.scss";
+import { Link } from "react-router-dom";
+import axios from "axios";
+
+const PAGESIZE = 10; // 首页帖子每页只展示5条
+function aa(props) {
+ const current_user = props.current_user;
+ const [data, setData] = useState(undefined); //接口所有数据
+ const [memos, setMemos] = useState([]); //帖子列表
+ const [sort, setSort] = useState("published_at"); //最新最热
+ const [listSpin, setListSpin] = useState(true);
+ const [memosCount, setMemosCount] = useState([]); //帖子数量
+
+ const [page, setPage] = useState(1); //分页
+ const [search, setSearch] = useState(undefined); //搜索内容
+ const [selectKey, setSelectKey] = useState(["hot"]);
+
+ const [hotMemos, setHotMemos] = useState([]); //热门推荐、版主推荐
+ const [forumSections, setForumSections] = useState([]); //二级模块
+
+ const [plate, setPlate] = useState(undefined); // 所有版块
+ useEffect(() => {
+ init();
+ }, [page, search, sort]);
+
+ async function init() {
+ setListSpin(true);
+ let url = `/v1/memos.json`;
+ axios
+ .get(url, {
+ params: { page, search, sort , limit:PAGESIZE },
+ })
+ .then((result) => {
+ if (result) {
+ setData(result.data);
+ setMemos(result.data.memos);
+ setListSpin(false);
+ setMemosCount(result.data.memos_count);
+ setHotMemos(result.data.hottest_memos);
+ setForumSections(result.data.forum_sections);
+ }
+ })
+ .catch((e) => {
+ console.log(e);
+ });
+ }
+
+ useEffect(() => {
+ async function init() {
+ let url = `/v1/forum_sections.json`;
+ axios
+ .get(url, {
+ params: { is_detail: true },
+ })
+ .then((result) => {
+ if (result) {
+ setPlate(result.data.forum_sections);
+ }
+ })
+ .catch((e) => {
+ console.log(e);
+ });
+ }
+ init();
+ }, []);
+ // 翻页
+ function changePage(page) {
+ setPage(page);
+ }
+ // 搜索
+ function onSearch(e) {
+ setSearch(e);
+ }
+
+ function changeMenu(e) {
+ setSelectKey(e.key);
+ if (e.key === "hot") {
+ setHotMemos(data.hottest_memos);
+ } else {
+ setHotMemos(data.recommend_memos);
+ }
+ }
+
+ function changeSort(value) {
+ setSort(value);
+ }
+ return (
+
+
+
+
+
+ 论坛首页
+
+ changeSort("published_at")}
+ className={
+ sort === "published_at" ? "green cPointer" : "cPointer"
+ }
+ >
+ 最新
+
+ changeSort("replies_count")}
+ className={
+ sort === "published_at" ? "cPointer" : "green cPointer"
+ }
+ style={{ fontSize: "16px" }}
+ >
+ 最热
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 热门话题
+
+
+
+ 版主推荐
+
+
+
+
+
+ {plate && plate.length > 0
+ ? plate.map((item, key) => {
+ return item.children_tags && item.children_tags.length > 0 ? (
+
+
+ {/* {item.name} */}
+
+
+ {item.name}
+
+
+
+
+
+ ) : (
+ ""
+ );
+ })
+ : ""}
+
+ );
+}
+export default aa;
diff --git a/src/forums/ForumsDetail.jsx b/src/forums/ForumsDetail.jsx
new file mode 100644
index 000000000..a8ee79da9
--- /dev/null
+++ b/src/forums/ForumsDetail.jsx
@@ -0,0 +1,248 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import { getUrl } from "educoder";
+import { Breadcrumb, Spin, Empty } from "antd";
+import { Link } from "react-router-dom";
+import {
+ WhiteBack,
+ Box,
+ Long,
+ Short,
+ Gap,
+ FlexAJ,
+ AlignCenter,
+ P,
+} from "./css/layout";
+import Title from "./Component/Title";
+import RenderHtml from "../components/render-html";
+import { Type, Tag } from "./Component/ItemType";
+import Drop from "./Component/ItemDropDown";
+import UserInfo from "./Component/UserInfo";
+import Prise from "./Component/Prise";
+import ListItem from "./Component/ListItem";
+import AuthorCard from "./Component/AuthorCard";
+import BestItem from "./Component/BestItem";
+import Original from "./image/original.png";
+import Comments from "./new_comments/comments";
+import Attachments from '../forge/Upload/attachment';
+function memo_show(props) {
+ const memo_id = props.match.params.id;
+ const current_user = props.current_user;
+
+ const [memo, getMemo] = useState(null);
+ const [memoUser, getMemoUser] = useState(null);
+ const [replies, setReplies] = useState([]);
+ const [memoImage, setMemoImage] = useState(null);
+ const [recent_memos, setRecentmemos] = useState([]);
+ const [bannedPermission, setPermission] = useState(undefined);
+ const [isBanned, setIsBanned] = useState(false);
+ const [page, setListPage] = useState(1);
+ const [limit, setLimitType] = useState(5);
+ const [isSpin, setSpinType] = useState(false);
+ const [dropPermission, setDropPermission] = useState({});
+ useEffect(() => {
+ init();
+ related_memos();
+ }, [memo_id]);
+
+ async function init() {
+ setSpinType(true);
+ let url = `/v1/memos/${memo_id}.json`;
+ axios
+ .get(url)
+ .then((result) => {
+ if (result) {
+ let per = {
+ admin:result.data && result.data.is_current_admin,
+ login: result.data && result.data.current_login,
+ banned_permission:result.data.banned_permission,
+ is_currentUser: result.data.author_info ? result.data.author_info.is_current_user : undefined,
+ is_fine: result.data.memo && result.data.memo.is_fine,
+ sticky: result.data.memo && result.data.memo.sticky,
+ memo_watched: result.data.memo && result.data.memo.memo_watched,
+ is_deleted:result.data.memo && result.data.memo.apply_destroy
+ }
+ setPermission(per);
+
+ getMemo(result.data.memo);
+ setMemoImage(result.data.memo_image_info); //封面信息
+ setRecentmemos(result.data.recent_memos);
+ setIsBanned(result.data.is_banned);
+ getMemoUser(result.data.author_info);
+ }
+ setSpinType(false);
+ })
+ .catch((e) => {
+ setSpinType(false);
+ console.log(e);
+ });
+ }
+
+ function related_memos() {
+ let url = `/v1/memos/${memo_id}/related_memos.json`;
+ axios
+ .get(url)
+ .then((result) => {
+ if (result) {
+ setReplies(result.data.memos);
+ }
+ setSpinType(false);
+ })
+ .catch((e) => {
+ setSpinType(false);
+ console.log(e);
+ });
+ }
+
+ return (
+
+
+ {memo && memoUser ? (
+
+
+
+ 论坛
+
+ {memo && memo.forum_tag && memo.forum_tag.id && (
+
+
+ {memo && memo.forum_tag.title}
+
+
+ )}
+
+ {memo ? memo.subject : "帖子详情"}
+
+
+
+
+
+
+
+
+ {memo && Type(`${memo.tag_name}`)}
+
+ {memo && memo.subject}
+
+ {memo && memo.is_fine && Tag("精华")}
+ {memo && memo.sticky && Tag("置顶")}
+
+
+ {current_user && current_user.login ? (
+
+ ):""}
+
+ {memo && memo.is_original && (
+
+ )}
+
+
+
+
+
+
+
+ {memo && memo.viewed_count}
+
+
+
+
+
+ {memo && memo.replies_count}
+
+
+
+ {memo && memo.published_time}
+
+ {memo && memo.apply_destroy && (
+ 已申请删帖
+ )}
+
+
+
+ {memoImage && (
+
+

+
+ )}
+
+
+ {memo.attachment_url && memo.attachment_url.length > 0 &&
+
}
+
+ {memo.reprint_link && (
+
转载自:{memo.reprint_link}
+ )}
+
+
+
+
+
+
+ {
+ replies && replies.length > 0 &&
+
+
+ 相关推荐
+
+
+
+ }
+
+
+
+
+
+
+ 作者最近文章
+
+ 更多
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+export default memo_show;
diff --git a/src/forums/Index.jsx b/src/forums/Index.jsx
new file mode 100644
index 000000000..fd087b0bf
--- /dev/null
+++ b/src/forums/Index.jsx
@@ -0,0 +1,58 @@
+import React, { Component } from "react";
+import { TPMIndexHOC } from "../modules/tpm/TPMIndexHOC";
+import { SnackbarHOC } from "educoder";
+import { CNotificationHOC } from "../modules/courses/common/CNotificationHOC";
+import { Route, Switch } from "react-router-dom";
+import Loading from "../Loading";
+import Loadable from "react-loadable";
+import "./css/Index.css";
+import "./css/All.scss";
+
+const Detail = Loadable({
+ loader: () => import("./ForumsDetail"),
+ loading: Loading,
+});
+const Theme = Loadable({
+ loader: () => import("./Theme"),
+ loading: Loading,
+});
+const Forums = Loadable({
+ loader: () => import("./Forums"),
+ loading: Loading,
+});
+const New = Loadable({
+ loader: () => import("./New"),
+ loading: Loading,
+});
+class Index extends Component {
+ render() {
+ return (
+
+
+ {/* 主题全部 */}
+ (
+
+ )}
+ >
+ {/* 详情 */}
+ }
+ >
+ }
+ >
+ }
+ >
+ }>
+
+
+ );
+ }
+}
+export default CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(Index)));
diff --git a/src/forums/New.jsx b/src/forums/New.jsx
new file mode 100644
index 000000000..743456295
--- /dev/null
+++ b/src/forums/New.jsx
@@ -0,0 +1,374 @@
+import React, { useCallback, useState, useEffect, forwardRef } from "react";
+import { Banner, WhiteBack, AlignCenter, Greenback } from "./css/layout";
+import { Form, Input, Select, Radio, Button, notification, Spin } from "antd";
+import MDEditor from "../modules/tpm/challengesnew/tpm-md-editor";
+import Uplaod from "../forge/Upload/Index";
+import UplaodCover from "../forge/Upload/Cover";
+import Attachments from '../forge/Upload/attachment';
+import "./css/All.scss";
+import axios from "axios";
+
+const TextArea = Input.TextArea;
+const Option = Select.Option;
+
+// forum_id: values.forum_id, //大主题版块的id, int类型 (必填)
+// attachment_id: ImgInfo.attachment_id, //上传的封面id
+// children_forum_id: values.children_forum_id, //小主题版块的id,int类型
+// attachments, //上传的附件id数组
+// memo: {
+// //帖子的相关内容
+// subject: values.subject, // 帖子的标题 (必填)
+// content, //帖子的内容 (必填)
+// tag_id: values.tag_id, //帖子的标签,1为交流,2为求助(必填)
+// is_original: values.is_original, //是否原创,默认为true,(必填)
+// reprint_link: values.reprint_link, //转载链接,仅当is_original为false时,出现
+// }
+function New(props, ref) {
+ const {
+ form: { getFieldDecorator, validateFields, setFieldsValue },
+ } = props;
+ const [parentPlate, setParentPlate] = useState(undefined); //父版块
+ const [childPlate, setChildPlate] = useState(undefined); //子版块
+ const [content, setContent] = useState(""); //内容
+ const [ImgInfo, setImgInfo] = useState(undefined); //封面图信息
+ const [attachments, setAttachments] = useState(undefined); //上传附件的id数组
+ const [spinning, setSpining] = useState(false);
+
+ const [ fileList ,setFileList ] = useState(undefined);
+
+ const [ originalType , setOriginalType ] = useState(2);
+
+ let forumId = props.match.params.forumId;
+
+ useEffect(() => {
+ setFieldsValue({
+ tag_id: 1,
+ is_original: 2,
+ });
+ getPlate();
+ }, []);
+
+ function getDetail(plate){
+ if(forumId){
+ const url = `/v1/memos/${forumId}/edit.json`;
+ axios.get(url).then(result=>{
+ if(result){
+ setContent(result.data.content);
+
+ let forum_id = result.data.forum_section && result.data.forum_section.forum_id;
+ let child = plate && plate.filter((item) => item.id === forum_id);
+ let memo_original = result.data.is_original ? 1 : 2
+ setChildPlate(child && child.length>0 && child[0].children_tags);
+ setOriginalType(memo_original)
+ setFieldsValue({
+ ...result.data,
+ forum_id,
+ tag_id: result.data.tag_id,
+ children_forum_id:result.data.forum_section_id,
+ is_original: memo_original
+ });
+ let attachments_url = result.data.attachments_url && result.data.attachments_url.map((item,key)=>{
+ return({
+ title:item.title,
+ ...item
+ })
+ });
+ let array = result.data.attachments_url && result.data.attachments_url.map((item,key)=>{
+ return item.id
+ })
+ setAttachments(array);
+ setFileList(attachments_url);
+ setImgInfo(result.data.memo_image_info);
+ }
+ }).catch(error=>{
+ console.log(error);
+ })
+ }
+ }
+
+ // 获取所有版块
+ function getPlate() {
+ setSpining(true);
+
+ const url = `/v1/forum_sections.json`;
+ axios.get(url)
+ .then((result) => {
+ if (result && result.data) {
+ setParentPlate(result.data.forum_sections);
+ setFieldsValue({
+ forum_id: 0,
+ });
+ if(forumId){
+ getDetail(result.data.forum_sections);
+ }
+ setSpining(false);
+
+ }
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ }
+ // 切换主题版块
+ function changeParent(e) {
+ let child = parentPlate.filter((item) => item.id === e);
+ if (child && child.length === 1) {
+ setChildPlate(child[0].children_tags);
+ setFieldsValue({
+ children_forum_id:
+ child[0].children_tags && child[0].children_tags.length > 0
+ ? child[0].children_tags[0].id
+ : "",
+ });
+ } else {
+ setChildPlate(undefined);
+ setFieldsValue({
+ children_forum_id: "",
+ });
+ }
+ }
+
+ const helper = useCallback(
+ (label, name, rules, widget, isRequired) => (
+
+ {label}
+
+ {getFieldDecorator(name, { rules, validateFirst: true })(widget)}
+
+
+ ),
+ []
+ );
+
+ // 修改内容
+ function onContentChange(e) {
+ setContent(e);
+ }
+
+ // 上传的图片
+ function UploadFunc(e) {
+ attachments && attachments.length>0 && attachments.map((item,key)=>{
+ return e.push(item.id)
+ });
+ setAttachments(e);
+ }
+
+ // 获取封面图片
+ function getImageUrl(imgId) {
+ setImgInfo(imgId);
+ }
+
+ // 返回
+ function returnNew() {
+ props.history.goBack();
+ }
+ // 提交
+ function sureNew() {
+ setSpining(true);
+ // console.log("dfasdfasdfad")
+ validateFields((error, values) => {
+ if (!error) {
+ let params = {
+ forum_id: values.forum_id,
+ attachment_id: ImgInfo && ImgInfo.attachment_id,
+ children_forum_id: values.children_forum_id,
+ attachments,
+ memo: {
+ subject: values.subject,
+ content,
+ tag_id: values.tag_id,
+ is_original: values.is_original,
+ reprint_link: values.reprint_link,
+ }
+ }
+ if(forumId){
+ // 编辑保存
+ const url = `/v1/memos/${forumId}/update.json`;
+ axios.post(url, params).then((result) => {
+ if (result && result.data) {
+ notification.open({
+ message: "提示",
+ description: result.data.message,
+ });
+ if(result.data.status === 1){
+ props.history.push(`/forums/${forumId}/detail`);
+ }
+ setSpining(false);
+ }
+ })
+ .catch((error) => {
+ setSpining(true);
+ console.log(error);
+ });
+ }else{
+ // 新建提交
+ const url = `/v1/memos/create.json`;
+ axios.post(url, params).then((result) => {
+ if (result && result.data) {
+ notification.open({
+ message: "提示",
+ description: result.data.message,
+ });
+
+ if(result.data.status === 1){
+ props.history.push(`/forums/${result.data.memo_id}/detail`);
+ }
+ setSpining(false);
+ }
+ })
+ .catch((error) => {
+ setSpining(false);
+ console.log(error);
+ });
+ }
+ }else{
+ console.log(error)
+ }
+ });
+ }
+
+ function showNotification(description){
+ notification.open({
+ message:"提示",
+ description
+ })
+ }
+
+ function changeOriginal(e){
+ setOriginalType(e.target.value);
+ }
+ // 编辑删除附件时获取删除后剩余的数组
+ function deleteFunc(id){
+ let team = attachments && attachments.length>0 && attachments.filter(item=>item!==id);
+ setAttachments(team);
+ }
+ return (
+
+
+ { forumId ? "编辑" : "新建"}
+
+
+
+ 主题板块
+
+
+ {helper(
+ "",
+ "forum_id",
+ [{ required: true, message: "请选择主题板块" }],
+
+ )}
+ {helper(
+ "",
+ "children_forum_id",
+ [],
+
+ )}
+
+
+ 上传封面:
+
+ (只支持JPG、PNG、JPEG大小不超过2M建议尺寸4:3)
+
+
+
+ {helper(
+ "帖子标签",
+ "tag_id",
+ [{ required: true, message: "请选择帖子标签" }],
+ ,
+ true
+ )}
+ {helper(
+ "是否原创",
+ "is_original",
+ [],
+
+ 是
+ 否
+ ,
+ true
+ )}
+ { originalType !==1? helper(
+ "转载自",
+ "reprint_link",
+ [],
+
+ ) :""}
+
+
+
+
+
+
+ { forumId ? "保存" : "提交"}
+
+
+
+ );
+}
+export default Form.create()(forwardRef(New));
diff --git a/src/forums/Nodata.js b/src/forums/Nodata.js
new file mode 100644
index 000000000..1e09608f8
--- /dev/null
+++ b/src/forums/Nodata.js
@@ -0,0 +1,18 @@
+import React , { Component } from 'react';
+
+import nodata from './image/nodata.png';
+
+class Nodata extends Component{
+ render(){
+ const { _html } = this.props;
+ return(
+
+
+

+
{_html}
+
+
+ )
+ }
+}
+export default Nodata;
\ No newline at end of file
diff --git a/src/forums/Theme.js b/src/forums/Theme.js
new file mode 100644
index 000000000..aa47a549e
--- /dev/null
+++ b/src/forums/Theme.js
@@ -0,0 +1,156 @@
+//主题页
+import React, { useState, useEffect } from "react";
+import { Breadcrumb, Menu, Spin } from "antd";
+import { Link } from "react-router-dom";
+import ListSearch from './Component/ListSearch';
+import { Box, Long, Short, Gap, WhiteBack, LeftLine, FlexAJ, AlignCenter } from './css/layout';
+import axios from 'axios';
+import Tops from './Component/Top';
+import ListItem from './Component/ListItem';
+import Paginations from './Component/Paginations';
+import ThemeRight from './ThemeRight';
+import Nodata from './Nodata';
+import './css/Theme.scss';
+
+function theme(props) {
+ const [ operation , setOperation ]= useState(undefined);
+ const [menuKey, setMenuKey] = useState("all");//tab
+ const [search, setSearch] = useState(undefined);//搜索内容
+ const [listSpin, setListSpin] = useState(true);
+ const [sort, setSort] = useState("published_at");//排序
+ const [pageSize, setPageSize] = useState(0);//每页条数
+ const [page, setPage] = useState(1);
+ const [total, setTotal] = useState(0);
+ const [memos, setMemos] = useState(undefined);//帖子列表
+ const [ breadCrumb , setBreadCrumb ] = useState(undefined);
+ const [ headData , setHeadData] = useState(undefined);// 头部信息
+
+ let plateMainId = props.match.params.plateMainId;
+ let current_user = props.current_user;
+
+ // 获取列表
+ useEffect(() => {
+ if (plateMainId) {
+ InitList();
+ }
+ }, [plateMainId, page, search, menuKey, sort])
+
+ async function InitList() {
+ setListSpin(true);
+ const url = `/v1/memos/forum_memos/${plateMainId}.json`;
+ axios.get(url, {
+ params: {
+ page, search, sort, select_type: menuKey
+ }
+ }).then(result => {
+ if (result) {
+ setMemos(result.data.memos);
+ setTotal(result.data.memos_count);
+ setListSpin(false);
+ setPageSize(result.data.limit);
+ }
+ }).catch(error => {
+ console.log(error);
+ })
+ }
+
+ useEffect(()=>{
+ async function getTopInfo(){
+ const url = `/v1/memos/forum_memos_head/${plateMainId}.json`;
+ axios.get(url).then(result=>{
+ if(result && result.data){
+ setBreadCrumb(result.data.bread_crumb.forum_tag);
+ setHeadData(result.data);
+ filterUsers(current_user,result.data.forum_moders,result.data.forum_section_user);
+ }
+ }).catch(error=>{
+ console.log(error);
+ })
+ }
+ if(plateMainId && current_user) getTopInfo();
+ },[plateMainId, current_user])
+
+ let pathname = props.location.pathname;
+ useEffect(() => {
+ if (pathname) {
+ if (pathname === "/forums/involvedTopics") {
+ setMenuKey("my_topics");
+ } else if (pathname === "/forums/topic") {
+ setMenuKey("my_memos");
+ } else if (pathname === "/forums/recommend") {
+ setMenuKey("is_fine");
+ } else {
+ setMenuKey("all");
+ }
+ }
+ }, [pathname])
+ // 判断当前用户是否是版主或者管理员
+ function filterUsers(currentUser,moders,users){
+ if(currentUser){
+ let admin = moders && moders.filter(item=>item.user_login === current_user.login);//管理员
+ let creater = users && users.user_login === current_user.login;//版主
+ setOperation((admin && admin.length>0)|| creater);
+ }
+ }
+ // 搜索
+ function onSearch(e) {
+ setSearch(e);
+ }
+ // 翻页
+ function changePage(page) {
+ setPage(page);
+ }
+
+ function changeMenu(e) {
+ setMenuKey(e.key);
+ }
+ return (
+
+
+ 论坛交流
+ { breadCrumb && breadCrumb.title ? {breadCrumb.title}:"" }
+ { breadCrumb && breadCrumb.children_bread_crumb ?{breadCrumb.children_bread_crumb && breadCrumb.children_bread_crumb.title}:""}
+
+
+
+
+
+
+
+
+ setSort("published_at")} className={sort === "published_at" ? "green cPointer" : "cPointer"}>最新
+ setSort("replies_count")} className={sort === "published_at" ? "cPointer" : "green cPointer"} style={{ fontSize: '14px' }}>最热
+
+
+
+ {
+ memos && memos.length > 0 ?
+
+
+
+ :
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+} export default theme
\ No newline at end of file
diff --git a/src/forums/ThemeRight.jsx b/src/forums/ThemeRight.jsx
new file mode 100644
index 000000000..4028baf75
--- /dev/null
+++ b/src/forums/ThemeRight.jsx
@@ -0,0 +1,52 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import AuthorItem from './Component/AuthorItem';
+import BestModalItem from './Component/BestModalItem';
+import Announcement from './Component/Announcement';
+
+export default (({ plateId , operation })=> {
+ const [content, setContent] = useState(undefined);//公告
+ const [ recommand , setRecommand ]= useState(undefined);//推荐版块
+ const [ author , setAuthor ]= useState(undefined);//推荐作者
+
+ useEffect(()=>{
+ async function Init(){
+ const url = `/v1/memos/forum_memos_right/${plateId}.json`;
+ axios.get(url).then(result=>{
+ if(result){
+ setContent({notice:result.data.notice,login:result.data.user_login,name:result.data.username});
+ setRecommand(result.data.recommend_forum_sections);
+ setAuthor(result.data.active_users);
+ }
+ }).catch(error=>{
+ console.log(error);
+ })
+ }
+ if(plateId){
+ Init();
+ }
+ },[plateId])
+
+ return (
+
+ {
+ content &&
+
+ }
+ {
+ author && author.length > 0 &&
+
+ }
+ {
+ recommand && recommand.length > 0 &&
+
+
+
+ }
+
+ );
+});
diff --git a/src/forums/css/All.scss b/src/forums/css/All.scss
new file mode 100644
index 000000000..4420a261e
--- /dev/null
+++ b/src/forums/css/All.scss
@@ -0,0 +1,285 @@
+// page:UrlItem
+.urllist {
+ & > a {
+ display: flex;
+ justify-content: space-between;
+ padding: 12px 0px;
+ color: #333;
+ align-items: center;
+ border-bottom: 1px solid #eee;
+ i {
+ font-size: 14px;
+ color: #ccc;
+ }
+ }
+ & > a:last-child {
+ border-bottom: none;
+ }
+}
+.memo-detail-ul{
+ .BestUl{height: auto !important;}
+}
+.BestUl {
+ padding: 15px 25px;
+ height: 288px;
+ li {
+ padding: 6px 0px 5px 13px;
+ position: relative;
+ &::before {
+ position: absolute;
+ width: 4px;
+ height: 4px;
+ content: "";
+ border-radius: 50%;
+ background-color: #21b350;
+ left: 0px;
+ top: 14px;
+ }
+ }
+}
+.doubleItems {
+ padding: 0px 30px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+
+ & > a {
+ padding: 20px 0px;
+ width: 50%;
+ box-sizing: border-box;
+ border-bottom: 1px solid #eee;
+ display: flex;
+ align-items: flex-start;
+ text-align: justify;
+ cursor: pointer;
+ &:nth-child(odd) {
+ padding-right: 30px;
+ }
+ &:nth-child(even) {
+ padding-left: 30px;
+ }
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+}
+.unlow {
+ border: none !important;
+}
+.authorUl {
+ display: grid;
+ grid-template-columns: repeat(3, 33.33%);
+ grid-template-rows: 65px;
+ padding: 20px 30px;
+ & > span >img{
+ width:40px;
+ height:40px;
+ }
+ & > span:nth-child(3n + 1),
+ & > span:first-child {
+ align-items: flex-start;
+ img{margin-left: 10px;}
+ }
+ & > span:nth-child(3n) {
+ align-items: flex-end;
+ img{margin-right: 10px;}
+ }
+}
+.BestModalUl {
+ padding: 10px 30px;
+ & > div {
+ padding: 10px 0px;
+ }
+}
+.annContent {
+ margin-bottom: 10px;
+ overflow: hidden;
+ height: 100px;
+ line-height: 20px;
+ position: relative;
+ .annWords {
+ margin-bottom: 0px;
+ word-break: break-all;
+ }
+}
+.annBtn {
+ position: absolute;
+ right: 27px;
+ top: 90px;
+ color: #888;
+ background-color: #fff;
+}
+.none_panels{
+ text-align: center;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding:20px 0px;
+ img{
+ margin-bottom: 15px;
+ width:60%;
+ }
+ .none_p_title{
+ font-size: 14px;
+ color: #999;
+ }
+}
+.formStyle{
+ padding:20px 30px!important;
+ .lables{
+ position: relative;
+ color:#333;
+ margin-bottom: 5px;
+ display: block;
+ font-size: 16px;
+ }
+ .lables.must::before{
+ content: "*";
+ color: #F73030;
+ font-size: 18px;
+ position: absolute;
+ left: -15px;
+ height: 100%;
+ }
+}
+.UploadStyle{
+ border:none!important;
+ text-align: left!important;
+ background-color: #fff!important;
+ height: 20px!important;
+ line-height: 20px!important;
+ .ant-upload{
+ padding:0px!important;
+ }
+}
+.ant-upload-list-item-card-actions .anticon.anticon-download{
+ display:none!important;
+}
+.inlineEnd{
+ display: flex;
+ align-items: center;
+ .ant-row.ant-form-item{
+ margin-bottom: 0px;
+ width:260px;
+ margin-right: 20px;
+ }
+}
+.headerInfo{
+ padding:20px 0px!important;
+ position: relative;
+ align-items:flex-start!important;
+ .headerInfoLeft{
+ display:flex;
+ flex:1;
+ align-items:flex-start;
+ width:0;
+ }
+ .originalTag{
+ position: absolute;
+ right: 50px;
+ top:20px;
+ z-index: 10;
+ }
+ .tag{
+ margin-top:3px;
+ }
+}
+.icon-wrap {
+ margin-left: 30px;
+ color: #ccc;
+ float: left;
+ line-height: 20px;
+ display: flex;
+ align-items: center;
+ .span-text{
+ margin-left: 3px;
+ }
+}
+.tag{
+ display: inline-block;
+ height: 18px;
+ line-height: 18px;
+ padding:0px 8px;
+ font-size: 12px;
+ color:#fff;
+ margin-left: 8px;
+ border-radius: 12px;
+ &.tagRed{
+ background-color: #FA4A4A;
+ }
+ &.tagBlue{
+ background-color: #50C7FF;
+ }
+ &.tagOrange{
+ background-color: #FA6400;
+ }
+}
+.greenLiftLine{
+ font-size: 16px;
+ color: #333;
+ height: 22px;
+ line-height: 22px;
+ margin-left: 14px;
+ position: relative;
+ &::before{
+ position: absolute;
+ left: -14px;
+ height: 100%;
+ width:6px;
+ content: "";
+ top:0px;
+ background-color: #21B350;
+ }
+}
+.priseBox{
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding:20px 0px;
+ span:first-child{
+ height: 70px;
+ width: 70px;
+ line-height: 70px;
+ border-radius: 50%;
+ background-color:#F3F7F5 ;
+ text-align: center;
+ cursor: pointer;
+ i{
+ font-size: 27px !important;
+ color: #21B350;
+ }
+ }
+ span:last-child{
+ margin-top: 10px;
+ color:#888888 ;
+ };
+}
+.authorCard{
+ padding:40px 35px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ img{
+ height: 60px!important;
+ width:60px!important;
+ margin-bottom: 10px;
+ }
+ img + span{
+ color: #333;
+ font-size: 16px;
+ }
+}
+.tip_tag{
+ font-size: 12px;
+ background:rgba(238,238,238,1);
+border-radius:10px;
+color: #888;
+margin-left: 8px;
+padding: 1px 8px;
+}
+.b-bottom-none{border-bottom: none !important;}
+.edu-text-center{text-align: center;}
+.pd510{padding: 5px 10px;}
+.fwt-600{font-weight: 600;}
\ No newline at end of file
diff --git a/src/forums/css/Index.css b/src/forums/css/Index.css
new file mode 100644
index 000000000..d62d9c7ab
--- /dev/null
+++ b/src/forums/css/Index.css
@@ -0,0 +1,84 @@
+
+ul,ol,dl{
+ margin:0px;
+}
+p{
+ margin-bottom: 8px;
+}
+.ml4{
+ margin-left: 4px;
+}
+.ml8{
+ margin-left: 8px;
+}
+.mt8{
+ margin-top: 8px;
+}
+.grey-3{
+ color:#333!important;
+}.grey-8{
+ color:#888!important;
+}.grey-9{
+ color:#999!important;
+}
+a.grey-9:hover{
+ color:#5091ff!important
+}
+.green{
+ color: #21B350!important;
+}
+.blue{
+ color: #5091FF!important;
+}
+.orange{
+ color: #FA6400!important;
+}
+.flex1{
+ flex:1;
+}
+.cPointer{
+ cursor: pointer;
+}
+.bigNoData .none_panels>div{
+ width:30%;
+}
+.markdown-body p{
+ white-space: pre-wrap;
+}
+/*超过隐藏*/
+.task-hide {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.task-hide2 {
+ overflow:-moz-hidden-unscrollable; white-space: nowrap; text-overflow:ellipsis;
+}
+.center{
+ text-align: center;
+}
+.greenbtn{
+ height:30px;
+ line-height:30px;
+ border-radius:2px;
+ background-color:#28BD6C;
+ color:#fff!important;
+ padding:0px 12px;
+ display:inline-block;
+ min-width:80px;
+ text-align:center;
+}
+.forumList{
+ padding:0px 30px;
+}
+.forumList > li{
+ border-bottom: 1px solid #eee;
+ padding:15px 0px;
+}
+.forumList > li:last-child{
+ border-bottom: none;
+}
+.pd200{padding: 200px 0;}
+.minH400{
+ min-height: 400px;
+}
diff --git a/src/forums/css/Theme.scss b/src/forums/css/Theme.scss
new file mode 100644
index 000000000..05c9a4ec3
--- /dev/null
+++ b/src/forums/css/Theme.scss
@@ -0,0 +1,25 @@
+
+.newMenu .ant-menu-item, .ant-menu-submenu-title{
+ margin: 2px 0px 2px 30px!important;
+ padding:0px!important;
+ font-size: 16px;
+}
+.newMenu.ant-menu-horizontal > .ant-menu-item:hover,
+.newMenu.ant-menu-horizontal > .ant-menu-item-selected,
+.newMenu.ant-menu-horizontal > .ant-menu-submenu:hover{
+ color: #21B350!important;
+ border-bottom:2px solid #fff!important;
+}
+.newMenu.ant-menu-horizontal > .ant-menu-item-selected{
+ color: #21B350!important;
+ position:relative;
+}
+.newMenu.ant-menu-horizontal > .ant-menu-item-selected:after{
+ position:absolute;
+ width:100%;
+ height:2px;
+ background:#28BD6C;
+ content:"";
+ left:0px;
+ bottom:-4px;
+}
\ No newline at end of file
diff --git a/src/forums/css/layout.jsx b/src/forums/css/layout.jsx
new file mode 100644
index 000000000..87c7dfc44
--- /dev/null
+++ b/src/forums/css/layout.jsx
@@ -0,0 +1,207 @@
+import styled from 'styled-components';
+
+export const Banner = styled.div`{
+ padding:20px 30px;
+ color:#333;
+ font-size:18px;
+ border-bottom:1px solid #eee;
+ background-color:#fff;
+ border-radius:5px 5px 0px 0px;
+}`
+export const AlignCenterBetween = styled.div`{
+ display:flex;
+ align-items: center;
+ padding: 14px 14px 14px 20px;
+ justify-content: space-between;
+ border-bottom:1px solid #eee;
+}`
+export const FlexAJ = styled.div`{
+ display:flex;
+ align-items: center;
+ justify-content: space-between;
+}`
+export const AlignCenter = styled.div`{
+ display:flex;
+ align-items: center;
+}`
+export const FlexStart = styled.div`{
+ display:flex;
+ align-items: flex-start;
+}`
+export const P = styled.p`{
+ color:#333;
+ font-size:18px;
+ height:24px;
+ line-height:24px;
+ margin-bottom:8px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}`
+// 左右结构
+export const Box = styled.div`{
+ display:flex;
+ align-item:flex-start;
+}`
+export const Long = styled.div`{
+ width:72%;
+ border-radius:5px;
+ margin-bottom:15px;
+}`
+export const Short = styled.div`{
+ width:28%;
+ border-radius:5px;
+ margin-bottom:15px;
+}`
+export const Gap = styled.div`{
+ padding-left:15px;
+ box-sizing:border-box;
+}`
+export const WhiteBack = styled.div`{
+ background-color:#fff;
+ border-radius:5px;
+}`
+export const Blueline = styled.a`{
+ height:30px;
+ line-height:28px;
+ border-radius:2px;
+ border:1px solid rgba(80,145,255,1);
+ color:rgba(80,145,255,1);
+ padding:0px 12px;
+ display:inline-block;
+}`
+export const Redline = styled.a`{
+ height:30px;
+ line-height:28px;
+ border-radius:2px;
+ border:1px solid #F73030;
+ color:#F73030;
+ padding:0px 12px;
+ display:inline-block;
+ min-width:80px;
+ text-align:center;
+}`
+export const Greenline = styled.a`{
+ height:30px;
+ line-height:28px;
+ border-radius:2px;
+ border:1px solid #28BD6C;
+ color:#28BD6C!important;
+ padding:0px 12px;
+ display:inline-block;
+ min-width:80px;
+ text-align:center;
+}`
+export const Greenback = styled.a`{
+ height:30px;
+ line-height:30px;
+ border-radius:2px;
+ background-color:#28BD6C;
+ color:#fff!important;
+ padding:0px 12px;
+ display:inline-block;
+ min-width:80px;
+ text-align:center;
+}`
+export const Blueback = styled.a`{
+ height:30px;
+ line-height:30px;
+ border-radius:2px;
+ background-color:rgba(80,145,255,1);
+ color:#fff;
+ padding:0px 12px;
+ display:inline-block;
+ min-width:80px;
+ text-align:center;
+}`
+export const Redback = styled.a`{
+ height:30px;
+ line-height:30px;
+ border-radius:2px;
+ background-color:#F73030;
+ color:#fff;
+ padding:0px 12px;
+ display:inline-block;
+ min-width:80px;
+ text-align:center;
+}`
+export const NumUl = styled.ul`{
+ padding-left: 20px;
+ & > li{
+ list-style-type: decimal;
+ color:#888;
+ height:24px;
+ line-height:24px;
+ }
+}`
+export const GreenUnder = styled.a`{
+ color:#28BD6C!important;
+ position:relative;
+ &:after{
+ position:absolute;
+ bottom:-2px;
+ left:0px;
+ width:100%;
+ height:1px;
+ content:'';
+ background:#28BD6C;
+ }
+}`
+export const Cancel = styled.a`{
+ height:32px;
+ line-height:32px;
+ border-radius:2px;
+ background-color:#BBBBBB;
+ color:#fff;
+ padding:0px 12px;
+ display:inline-block;
+ min-width:64px;
+ text-align:center;
+ letter-spacing: 4px;
+}`
+export const Content = styled.div`{
+ width:1200px;
+ margin:20px auto;
+ text-align:center;
+ display:flex;
+ align-Items:center;
+ background-color:#fff;
+ justify-content: center;
+}`
+export const LeftLine = styled.span`{
+ font-size: 12px;
+ color: #999;
+ position: relative;
+ margin-left: 20px;
+ height: 18px;
+ line-height: 18px;
+ &:before{
+ position: absolute;
+ left: -10px;
+ top: 50%;
+ margin-top:-6px;
+ height: 12px;
+ width: 1px;
+ content: "";
+ background: #ccc;
+ }
+}`
+export const Grid = styled.div`{
+ display:grid;
+ grid-template-columns: repeat(3, 33.33%);
+ grid-template-rows: 65px;
+ width: 100%;
+}`
+export const UDStructure = styled.span`{
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ span:first-child{
+ color:#333;
+ font-size:20px;
+ margin-bottom:6px;
+ }
+ span:last-child{
+ font-size:14px;
+ }
+}`
diff --git a/src/forums/image/nodata.png b/src/forums/image/nodata.png
new file mode 100644
index 000000000..ad4c76b97
Binary files /dev/null and b/src/forums/image/nodata.png differ
diff --git a/src/forums/image/original.png b/src/forums/image/original.png
new file mode 100755
index 000000000..1d9aed7c8
Binary files /dev/null and b/src/forums/image/original.png differ
diff --git a/src/forums/image/radius.png b/src/forums/image/radius.png
new file mode 100755
index 000000000..a4da037fd
Binary files /dev/null and b/src/forums/image/radius.png differ
diff --git a/src/forums/new_comments/Title.jsx b/src/forums/new_comments/Title.jsx
new file mode 100644
index 000000000..c31adbd14
--- /dev/null
+++ b/src/forums/new_comments/Title.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const Nav = styled.div`{
+ background-color:#fff;
+ padding:20px 30px;
+ border-bottom:1px solid #eee;
+ font-size:16px;
+ color:#333;
+ display:flex;
+ justify-content: space-between;
+ align-items:center;
+}`
+
+export default (({children,className})=>{
+ return(
+
+ )
+})
\ No newline at end of file
diff --git a/src/forums/new_comments/children_journals.js b/src/forums/new_comments/children_journals.js
new file mode 100644
index 000000000..68a1ad6fd
--- /dev/null
+++ b/src/forums/new_comments/children_journals.js
@@ -0,0 +1,26 @@
+import React, { useState, useEffect } from "react";
+import { List } from "antd";
+import CommentList from "./comment_list";
+function children_journals({ replies, user_image, current_login }) {
+
+ return (
+
+ (
+
+
+
+ )}
+ />
+
+ );
+}
+export default children_journals;
diff --git a/src/forums/new_comments/comment.scss b/src/forums/new_comments/comment.scss
new file mode 100644
index 000000000..a092c3fd9
--- /dev/null
+++ b/src/forums/new_comments/comment.scss
@@ -0,0 +1,59 @@
+.comments-lists{
+ padding: 10px 30px 15px 30px;
+ .ant-list-item{padding: 16px 20px !important;}
+ .new-comment-head{
+ background-color: #fafafa;
+ }
+ .user-image{
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ }
+ .add_reply_button{
+ background-color: #fff!important;
+ color: #bbb;
+ width: 100%;
+ text-align: left;
+ border: 1px solid #eee;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+}
+.grid-item{
+ display: grid;
+ grid-template-columns: max-content 1fr;
+ align-items: center;
+}
+.grid-item-3{
+ display: grid;
+ grid-template-columns: max-content max-content 1fr;
+ align-items: center;
+}
+.grid-item-left{
+ display: grid;
+ grid-template-columns: 1fr max-content;
+ align-items: center;
+}
+.ver-middle{vertical-align: middle;}
+.grid-item-top {
+ display: grid;
+ align-items: center;
+ grid-template-columns: max-content 1fr;
+ align-items: flex-start;
+}
+.pd20{padding: 20px;}
+.width100{width: 100%;}
+.color-grey-8{color: #888;}
+.children-memo-item{
+ // padding: 0 20px ;
+ background-color: #fafafa;
+ // margin-left: 40px;
+ margin-top: 10px;
+}
+.color-green{
+ color: #21B350;
+}
+.user-is-star{
+ background-color: #21B350 !important;
+ color: #fff !important;
+}
diff --git a/src/forums/new_comments/comment_list.js b/src/forums/new_comments/comment_list.js
new file mode 100644
index 000000000..b0354306b
--- /dev/null
+++ b/src/forums/new_comments/comment_list.js
@@ -0,0 +1,241 @@
+import React, { useState, useEffect } from "react";
+import RenderHtml from "../../components/render-html";
+import { Popconfirm, Button, message } from "antd";
+import { Link } from "react-router-dom";
+import NewComment from "./new_comment";
+import { getUrl } from "educoder";
+import axios from "axios";
+import ChildrenJournals from "./children_journals";
+function comment_list({
+ item,
+ user_image,
+ current_login,
+ is_children,
+ target_type,
+ commet_destroy,
+}) {
+ const [is_reply, setIsReply] = useState(false);
+ const [is_delete, setIsDelete] = useState(false);
+ const [deleteSpin, setDeleteSpin] = useState(false);
+ const [subComments, setSubComments] = useState([]);
+ const [subCommentsCount, setSubCommentsCount] = useState(0);
+ const [page, setChangePage] = useState(1);
+ const [is_praise, setIsPraise] = useState(false);
+ const [praisesCont, setPraisesCount] = useState(0);
+ const [isSpin, setIsSpin] = useState(false);
+ const [limit, setLimit] = useState(5);
+ useEffect(() => {
+ setSubComments(item.children);
+ if (item.children) {
+ setSubCommentsCount(item.children.length);
+ }else{
+ setSubCommentsCount(0);
+ }
+ setIsPraise(item.user_praise);
+ setPraisesCount(item.praise_count);
+ }, [item]);
+ const is_reply_click = (reply_boolean) => {
+ if (current_login) {
+ setIsReply(reply_boolean);
+ } else {
+ window.open("/login", "_blank");
+ }
+ };
+
+ const praise_reply = (id) => {
+ if (current_login) {
+ axios
+ .post(`/v1/discusses/${id}/plus.json`, {
+ container_type: "Memo",
+ id: id,
+ type: is_praise ? 0 : 1,
+ })
+ .then((result) => {
+ setPraisesCount(result.data.praise_count);
+ setIsPraise(!is_praise);
+ message.success(is_praise ? "取消点赞" : "已点赞")
+ })
+ .catch((error) => {
+ message.error(error);
+ });
+ } else {
+ window.open("/login", "_blank");
+ }
+ };
+
+ function deleteorder(id) {
+ setDeleteSpin(true);
+ let url = `/v1/memos/${id}/destroy.json`;
+ axios
+ .post(url)
+ .then((result) => {
+ if (result) {
+ if (result.data.status === 0) {
+ setIsDelete(true);
+ if (is_children) {
+ setSubCommentsCount(subCommentsCount - 1);
+ } else {
+ commet_destroy();
+ }
+ message.success(result.data.message);
+ } else {
+ message.error(result.data.message);
+ }
+ }
+ setDeleteSpin(false);
+ })
+ .catch((e) => {
+ setDeleteSpin(false);
+ message.error(e);
+ });
+ }
+ const create_new_children = (reply) => {
+ if (subComments && subComments.length > 0) {
+ setSubComments([reply, ...subComments]);
+ } else {
+ setSubComments([reply]);
+ }
+ setSubCommentsCount(subCommentsCount + 1);
+ setIsReply(false);
+ };
+
+ function get_more_reply(id) {
+ let new_page = page;
+ if (subCommentsCount < limit) {
+ setChangePage(1);
+ new_page = 1;
+ } else {
+ new_page = page + 1;
+ setChangePage(new_page);
+ }
+ setIsSpin(true);
+ let url = `/v1/${target_type}/${id}/more_reply.json`;
+ axios
+ .get(url, {
+ params: { page: new_page, limit },
+ })
+ .then((result) => {
+ if (result && result.data.memo_replies) {
+ setSubComments(subComments.concat(result.data.memo_replies));
+ }
+ setIsSpin(false);
+ })
+ .catch((e) => {
+ setIsSpin(false);
+ message.error(e);
+ });
+ }
+
+ return (
+
+ {is_delete ? (
+
已删除
+ ) : (
+
+
+
+
})
+
+
+ {item && item.username}
+
+
+
+ {item.content && (
+
+ )}
+
+
{item.time}
+
+ {item.admin || current_login === item.user_login ? (
+
deleteorder(item.id)}
+ >
+
+
+ ) : (
+ ""
+ )}
+ {!is_children && (
+
+ )}
+ {!is_children && (
+
+ )}
+
+
+ {is_reply && !is_children && (
+
+ )}
+ {subComments && subCommentsCount > 0 && (
+
+ )}
+ {item &&
+ subComments &&
+ item.replies_count > subCommentsCount &&
+ item.replies_count >= page * limit && (
+
+
+
+ )}
+
+
+ )}
+
+ );
+}
+export default comment_list;
diff --git a/src/forums/new_comments/comments.js b/src/forums/new_comments/comments.js
new file mode 100644
index 000000000..d4cf6caa2
--- /dev/null
+++ b/src/forums/new_comments/comments.js
@@ -0,0 +1,146 @@
+import React, { useState, useEffect } from "react";
+
+import axios from "axios";
+
+import { List, Pagination, message, Spin } from "antd";
+
+import Title from "./Title";
+import NewComment from "./new_comment";
+import CommentList from "./comment_list";
+import ClickReply from "./head_reply_button";
+import "./comment.scss";
+
+function comments({
+ target_id,
+ target_type,
+ current_user_image,
+ current_login,
+}) {
+ const [currentImage, setCurrentImage] = useState(null);
+ const [replies, setReplies] = useState([]);
+ const [replies_count, setRepliesCount] = useState(0);
+
+ const [isSpin, setIsSpin] = useState(false);
+ const [page, setListPage] = useState(1);
+ const [limit, setLimitType] = useState(10);
+ const [isClick, setIsClick] = useState(false);
+ useEffect(() => {
+ async function init() {
+ setIsSpin(true);
+ let url = `/v1/${target_type}/${target_id}/more_reply.json`;
+ axios
+ .get(url, {
+ params: { page, limit },
+ })
+ .then((result) => {
+ if (result && result.data.memo_replies) {
+ setReplies(result.data.memo_replies);
+ setRepliesCount(result.data.memos_count);
+ }
+ setIsSpin(false);
+ })
+ .catch((e) => {
+ setIsSpin(false);
+ message.error(e);
+ });
+ }
+ init();
+ }, [page, target_id]);
+
+ useEffect(() => {
+ if (current_user_image) {
+ setCurrentImage(current_user_image);
+ } else {
+ setCurrentImage("/images/avatars/User/boy.jpg");
+ }
+ }, []);
+ // 翻页
+ function changePage(page) {
+ setListPage(page);
+ }
+
+ const click_reply = (reply_boolean) => {
+ if(current_login){
+ setIsClick(reply_boolean);
+ }else{
+ window.open("/login", "_blank")
+ }
+ };
+
+ const create_new_reply = (reply) => {
+ let new_memos_count = replies_count + 1
+ setRepliesCount(new_memos_count)
+ setReplies([reply, ...replies]);
+ setIsClick(false);
+ };
+
+ const success_delete_reply = (reply) => {
+ let new_memos_count = replies_count - 1
+ setRepliesCount(new_memos_count)
+ };
+
+ return (
+
+
+
+ 评论
+ {replies_count > 0 && (
+ {replies_count}
+ )}
+
+
+
+
+
+ {isClick ? (
+
+ ) : (
+
+ )}
+
+ {replies && replies.length > 0 && (
+
(
+
+
+
+ )}
+ />
+ )}
+ {replies_count > limit && (
+
+ )}
+
+
+
+ );
+}
+
+export default comments;
diff --git a/src/forums/new_comments/head_reply_button.js b/src/forums/new_comments/head_reply_button.js
new file mode 100644
index 000000000..46b36d510
--- /dev/null
+++ b/src/forums/new_comments/head_reply_button.js
@@ -0,0 +1,19 @@
+import React from "react";
+import { Button } from "antd";
+import { getUrl } from "educoder";
+function reply_click({ user_image, click_button }) {
+ const click_reply_to = () =>{
+ click_button(true)
+ }
+ return (
+
+
})
+
+
+
+
+ );
+}
+export default reply_click;
diff --git a/src/forums/new_comments/new_comment.js b/src/forums/new_comments/new_comment.js
new file mode 100644
index 000000000..2021791b0
--- /dev/null
+++ b/src/forums/new_comments/new_comment.js
@@ -0,0 +1,67 @@
+import React, { useState, useEffect } from "react";
+import { getUrl } from "educoder";
+import { Button, message } from "antd";
+import MDEditor from "../../modules/tpm/challengesnew/tpm-md-editor";
+import axios from "axios";
+function new_comment({ memo_id, user_image, click_button, new_reply }) {
+ const [journal_spin, setJournalSpin] = useState(false);
+ const [content, setContent] = useState("");
+
+ function change_input(value) {
+ setContent(value);
+ }
+
+ function add_reply() {
+ setJournalSpin(true);
+ let url = `/v1/memos/${memo_id}/reply.json`;
+ axios
+ .post(url, {parent_id: memo_id, content: content})
+ .then((result) => {
+ if (result) {
+ if (result.data.status === 0) {
+ new_reply(result.data.reply)
+ } else {
+ message.error(result.data.message);
+ }
+ }
+ setJournalSpin(false);
+ })
+ .catch((e) => {
+ setJournalSpin(false);
+ message.error(e);
+ });
+ }
+ function click_reply_cancel() {
+ click_button(false);
+ }
+
+ return (
+
+
})
+
+
+ );
+}
+export default new_comment;
diff --git a/src/modules/tpm/TPMIndex.css b/src/modules/tpm/TPMIndex.css
index 8ee4561dd..094e41f0f 100644
--- a/src/modules/tpm/TPMIndex.css
+++ b/src/modules/tpm/TPMIndex.css
@@ -95,7 +95,7 @@ body>.-task-title {
}
.newContainer {
- background: #EAEBEC !important;
+ background: #f5f5f5;
}
.ant-modal-title {
diff --git a/src/user_info/Index.jsx b/src/user_info/Index.jsx
new file mode 100644
index 000000000..c4ffa2468
--- /dev/null
+++ b/src/user_info/Index.jsx
@@ -0,0 +1,84 @@
+import React from "react";
+import { TPMIndexHOC } from "../modules/tpm/TPMIndexHOC";
+import { SnackbarHOC } from "educoder";
+import { CNotificationHOC } from "../modules/courses/common/CNotificationHOC";
+import { Route, Switch } from "react-router-dom";
+import Loading from "../Loading";
+import Loadable from "react-loadable";
+import UserInfo from "./User/Account";
+import { MainContent, MinH400 } from "./css/projects";
+import '../forums/css/All.scss';
+import '../forums/css/Index.css';
+
+const Projects = Loadable({
+ loader: () => import("./User/Projects"),
+ loading: Loading,
+});
+
+const Memos = Loadable({
+ loader: () => import("./Memo/Memos"),
+ loading: Loading,
+});
+
+const Sections = Loadable({
+ loader: () => import("./Memo/Sections"),
+ loading: Loading,
+});
+
+const Blocks = Loadable({
+ loader: () => import("./User/BlockUsers"),
+ loading: Loading,
+});
+
+function Index(props) {
+ return (
+
+
+
+
+
+ }
+ >
+ }
+ >
+ }
+ >
+ }
+ >
+ }
+ >
+ }
+ >
+ }
+ >
+ }
+ >
+
+ }
+ >
+
+
+
+
+
+ );
+}
+export default CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(Index)));
diff --git a/src/user_info/Memo/Memos.js b/src/user_info/Memo/Memos.js
new file mode 100644
index 000000000..7e04731b8
--- /dev/null
+++ b/src/user_info/Memo/Memos.js
@@ -0,0 +1,101 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import { PagenationDiv } from "../css/projects";
+import { Spin, Empty, Pagination } from "antd";
+import ForumSections from "./SelectTitle";
+import { SelectSection } from "../css/layout";
+import ListItem from "../../forums/Component/ListItem";
+import RepliesMemos from "./Replies";
+
+function memos(props) {
+ const user_login = props.match.params.login;
+ const [is_current_uesr, setIsCurrentUser] = useState(false);
+ const [memos, getMemos] = useState([]);
+ const [memosSize, setMemosSize] = useState(0);
+ const [page, setListPage] = useState(1);
+ const [memo_type, setMemoType] = useState(null);
+ const [limit, setLimitType] = useState(15);
+ const [isSpin, setSpinType] = useState(false);
+ const [common_select, setCommonSelect] = useState(null);
+ useEffect(() => {
+ let memos_type = props.location.pathname.split("/")
+ setMemoType(memos_type.pop());
+ }, [props.location]);
+
+ useEffect(() => {
+ if(memo_type){
+ init();
+ }
+ }, [memo_type, common_select, page]);
+
+ async function init() {
+ setSpinType(true);
+
+ let url = `/v1/my_memos/${user_login}/memos.json`;
+ axios.get(url, {
+ params: {
+ ...common_select,
+ limit,
+ memo_type,
+ page,
+ },
+ }).then((result) => {
+ if (result) {
+ getMemos(result.data.memos);
+ setMemosSize(result.data.memos_count);
+ setIsCurrentUser(result.data.is_current_user);
+ }
+ setSpinType(false);
+ })
+ .catch((e) => {
+ setSpinType(false);
+ console.log(e);
+ });
+ }
+
+ function select_memos(target_params) {
+ setCommonSelect(target_params);
+ }
+ const handleChangePage = (page) => {
+ setListPage(page);
+ };
+
+ return (
+
+ {is_current_uesr && (
+
+ )}
+
+
+ {memos && memos.length > 0 ? (
+
+ {memo_type === "replies" ?
+ :
+ }
+
+ ) : (
+
+ )}
+
+
+ {memosSize > limit && (
+
+
+
+ )}
+
+ );
+}
+export default memos;
diff --git a/src/user_info/Memo/Replies.js b/src/user_info/Memo/Replies.js
new file mode 100644
index 000000000..1d6e093c9
--- /dev/null
+++ b/src/user_info/Memo/Replies.js
@@ -0,0 +1,37 @@
+import React from "react";
+import { FlexAJ, AlignCenter } from "../../forums/css/layout";
+import "./select_title.scss";
+import Drop from "../../forums/Component/ItemDropDown";
+import { Link } from "react-router-dom";
+function replies_memos({ memos }) {
+ return (
+
+ {memos.map((item, key) => {
+ return (
+ -
+
+
+ 评论文章:
+ {item.subject}
+
+
+
+
+
+ {item && item.new_reply && (
+
+
{item.new_reply.content}
+
+ {item.new_reply.published_time}
+
+
+ )}
+ 查看
+
+
+ );
+ })}
+
+ );
+}
+export default replies_memos;
diff --git a/src/user_info/Memo/ReplyItem.js b/src/user_info/Memo/ReplyItem.js
new file mode 100644
index 000000000..b72b067d1
--- /dev/null
+++ b/src/user_info/Memo/ReplyItem.js
@@ -0,0 +1,10 @@
+import React, { useEffect, useState } from "react";
+
+function reply_item({item}){
+ return (
+
+
+
+ )
+
+}
\ No newline at end of file
diff --git a/src/user_info/Memo/SectionItem.js b/src/user_info/Memo/SectionItem.js
new file mode 100644
index 000000000..315d4722e
--- /dev/null
+++ b/src/user_info/Memo/SectionItem.js
@@ -0,0 +1,62 @@
+import React, { useEffect, useState } from "react";
+import { Card, Col, Button, message } from "antd";
+import "../User/user.scss";
+import axios from "axios";
+function section_item({ forum_section, key }) {
+ const [isStar, setIsStar] = useState(true)
+ const [isLoading, setIsLoading] = useState(false)
+
+ function rendomColor() {
+ let r_a = Math.floor(Math.random() * 255);
+ let r_g = Math.floor(Math.random() * 255);
+ let r_b = Math.floor(Math.random() * 255);
+ let radom_color = "rgba(" + r_a + "," + r_g + "," + r_b + ",0.8)";
+ return radom_color;
+ }
+
+ function handleClick(){
+ setIsLoading(true);
+ let url = `/v1/memos/forum_memos/${forum_section.id}/is_watch.json`;
+ axios.post(url, {
+ is_watch: isStar ? 0 : 1
+ })
+ .then((result) => {
+ if(result.data.status === 0){
+ message.success(result.data.message);
+ setIsStar(!isStar)
+ }else{
+ message.error(result.data.message);
+ }
+ setIsLoading(false);
+ })
+ .catch((e) => {
+ setIsLoading(false);
+ console.log(e);
+ });
+ }
+ return (
+
+ handleClick()} loading={isLoading}>
+ {isStar ? "取消收藏" : "收藏"}
+ ,
+ ,
+ ]}
+ bodyStyle={{ background: rendomColor() }}
+ >
+
+
{forum_section.title}
+
+ {forum_section.memos_count}个话题
+
+
+
+
+ );
+}
+export default section_item;
diff --git a/src/user_info/Memo/Sections.js b/src/user_info/Memo/Sections.js
new file mode 100644
index 000000000..9a9b70e59
--- /dev/null
+++ b/src/user_info/Memo/Sections.js
@@ -0,0 +1,70 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import { Spin, Empty, Row, Pagination } from "antd";
+import { PagenationDiv } from "../css/projects";
+import SectionItem from "./SectionItem";
+function sections(props) {
+ const user_login = props.match.params.login;
+ const [isSpin, setSpinType] = useState(false);
+ const [forum_sections, getSections] = useState([]);
+ const [page, setListPage] = useState(1);
+ const [limit, setLimitType] = useState(32);
+ const [sectionsSize, setSectinsSize] = useState(0);
+ useEffect(() => {
+ async function init() {
+ setSpinType(true);
+ let url = `/v1/my_memos/${user_login}/my_interested.json`;
+ axios
+ .get(url, {
+ params: {
+ limit,
+ page,
+ },
+ })
+ .then((result) => {
+ if (result) {
+ getSections(result.data.forum_details);
+ setSectinsSize(result.data.count);
+ }
+ setSpinType(false);
+ })
+ .catch((e) => {
+ setSpinType(false);
+ console.log(e);
+ });
+ }
+ init();
+ }, [page]);
+
+ const handleChangePage = (page) => {
+ setListPage(page);
+ };
+
+ return (
+
+
+ {forum_sections && forum_sections.length > 0 ? (
+
+ {forum_sections.map((item, key) => {
+ return ;
+ })}
+
+ ) : (
+
+ )}
+
+ {sectionsSize > limit && (
+
+
+
+ )}
+
+ );
+}
+export default sections;
diff --git a/src/user_info/Memo/SelectTitle.js b/src/user_info/Memo/SelectTitle.js
new file mode 100644
index 000000000..35c8675ef
--- /dev/null
+++ b/src/user_info/Memo/SelectTitle.js
@@ -0,0 +1,119 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import { Cascader,DatePicker, Button } from "antd";
+import "./select_title.scss";
+import { SelectSection, GridItem2 } from "../css/layout";
+
+const SectionOptions = [
+ {
+ title: "待审查的话题",
+ id: "hidden",
+ },
+ {
+ title: "已发布的话题",
+ id: "show",
+ },
+];
+
+const InputSize = "large"
+function select_title({section_params, select_memos}) {
+ const [isLoading, setLoadingType] = useState(false);
+ const [forumSections, setSectionsType] = useState([]);
+ const [sectionParams, setSectionParams] = useState([]);
+ useEffect(() => {
+ async function init() {
+ setLoadingType(true);
+ let url = `/v1/forum_sections/select_sections.json`;
+ axios
+ .get(url)
+ .then((result) => {
+ if (result) {
+ setSectionsType(result.data);
+ }
+ setLoadingType(false);
+ })
+ .catch((e) => {
+ setLoadingType(false);
+ console.log(e);
+ });
+ }
+ init();
+ }, []);
+
+ function onChange(value, selectedOptions) {
+ let new_section_params = {...sectionParams, forum_section_id: value[value.length-1]}
+ setSectionParams(new_section_params)
+ select_memos(new_section_params)
+ }
+
+ function isHiddenChange(value, selectedOptions) {
+ let new_section_params = {...sectionParams, is_hidden: value[0]}
+ setSectionParams(new_section_params)
+ select_memos(new_section_params)
+ }
+
+ function selectBeginTime(date,dateString) {
+ setSectionParams({...sectionParams, start_time: dateString})
+ }
+
+ function selectEndTime(date,dateString) {
+ setSectionParams({...sectionParams, end_time: dateString})
+ }
+
+ function SectionSearch(){
+ select_memos(sectionParams)
+ }
+
+ function filter(inputValue, path) {
+ return path.some(
+ (option) =>
+ option.title.toLowerCase().indexOf(inputValue.toLowerCase()) > -1
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default select_title;
diff --git a/src/user_info/Memo/select_title.scss b/src/user_info/Memo/select_title.scss
new file mode 100644
index 000000000..720416d02
--- /dev/null
+++ b/src/user_info/Memo/select_title.scss
@@ -0,0 +1,27 @@
+.d-inline-block{display: inline-block;}
+.section-cascader{
+ width: 120px;
+
+}
+.ant-cascader-menu{
+ min-height: 20px;
+ max-height: 180px;
+ height: auto;
+}
+.forumList{
+ padding:0px 30px;
+}
+.forumList > li{
+ border-bottom: 1px solid #eee;
+ padding:15px 0px;
+}
+.forumList > li:last-child{
+ border-bottom: none;
+}
+.color-gray-8{color: #888;}
+.reply-link{
+ padding: 2px 16px;
+ color: #21B350 !important;
+ border: 1px solid #21B350;
+ border-radius: 20px;
+}
\ No newline at end of file
diff --git a/src/user_info/Modal/modal.scss b/src/user_info/Modal/modal.scss
new file mode 100644
index 000000000..7795594d8
--- /dev/null
+++ b/src/user_info/Modal/modal.scss
@@ -0,0 +1,18 @@
+.fs16{font-size: 16px;}
+.lineH2{line-height: 2;}
+
+.tips-modal-confirm{
+ .ant-modal-body{
+ width: 100%;
+ color: #333333;
+ height: 180px;
+ display: table-cell;
+ vertical-align: middle;
+ .modal-content-padding{
+ padding: 10px 25px;
+ }
+ }
+}
+.tips-modal {
+ .ant-modal-footer{border-top: none !important}
+}
\ No newline at end of file
diff --git a/src/user_info/Modal/tip_modal.js b/src/user_info/Modal/tip_modal.js
new file mode 100644
index 000000000..4629d8156
--- /dev/null
+++ b/src/user_info/Modal/tip_modal.js
@@ -0,0 +1,61 @@
+import React, { useEffect, useState } from "react";
+import { Modal, Button } from "antd";
+import "./modal.scss";
+function tip_modal({ data, okClick, cancelClick }) {
+ const [isShow, setShowModal] = useState(false);
+ useEffect(() => {
+ setShowModal(true);
+ });
+ const ok_click = () => {
+ setShowModal(false);
+ okClick(data.modal_type);
+ };
+ const cancel_click = () => {
+ setShowModal(false);
+ cancelClick(false);
+ };
+ return (
+
+ {data &&
+ (data.modal_type === "confirm" ? (
+
cancel_click()}
+ footer={[
+
+
+
,
+ ]}
+ >
+
+
+ ) : (
+
ok_click()}
+ onCancel={() => cancel_click()}
+ okText="确认"
+ cancelText="取消"
+ wrapClassName="tips-modal-confirm"
+ >
+
+ {data.head && (
+
+ {data.head}
+
+ )}
+ {data.content &&
{data.content}
}
+
+
+ ))}
+
+ );
+}
+export default tip_modal;
diff --git a/src/user_info/User/Account.js b/src/user_info/User/Account.js
new file mode 100644
index 000000000..4c163186c
--- /dev/null
+++ b/src/user_info/User/Account.js
@@ -0,0 +1,50 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import HeadUser from "./HeadUser";
+import { MainUser, HeadImage, HeadCon } from "../css/layout.jsx";
+import { Spin } from "antd";
+import UserMenu from "./Menu";
+import "../css/layout.scss"
+function account(props) {
+ const [UserInfo, setUserInfo] = useState(null); //用户的内容
+ const [isSpin, setSpin] = useState(false); //用户的内容
+ const user_login = props.match.params.login;
+ useEffect(() => {
+ async function init(login) {
+ setSpin(true);
+ let url = `/v1/users/${login}/user_info.json`;
+ axios
+ .get(url)
+ .then((result) => {
+ if (result) {
+ setUserInfo(result.data.user);
+ }
+ setSpin(false);
+ })
+ .catch((e) => {
+ setSpin(false);
+ console.log(e);
+ });
+ }
+ if (user_login) {
+ init(user_login);
+ }
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+export default account;
diff --git a/src/user_info/User/BlockItem.js b/src/user_info/User/BlockItem.js
new file mode 100644
index 000000000..18f418e6f
--- /dev/null
+++ b/src/user_info/User/BlockItem.js
@@ -0,0 +1,36 @@
+import React from "react";
+import { Card, Col, Avatar } from "antd";
+import { getUrl } from "educoder";
+import "./user.scss";
+
+function block_item({ item, key }) {
+
+ return (
+
+
+
+
+
+
+ {item.username}
+
+
+
+ {item.brief ? item.brief : "这家伙太懒了,还未填写个人描述!"}
+
+
+ {/*
+
+
*/}
+
+
+ );
+}
+export default block_item;
diff --git a/src/user_info/User/BlockUsers.js b/src/user_info/User/BlockUsers.js
new file mode 100644
index 000000000..3516e2a82
--- /dev/null
+++ b/src/user_info/User/BlockUsers.js
@@ -0,0 +1,72 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import { Spin, Empty, Row, Pagination } from "antd";
+import { PagenationDiv } from "../css/projects";
+import BlockItem from "./BlockItem";
+
+function block_users(props) {
+ const user_login = props.match.params.login;
+ const [isSpin, setSpinType] = useState(false);
+ const [users, setUsers] = useState([]);
+ const [page, setListPage] = useState(1);
+ const [limit, setLimitType] = useState(30);
+ const [usersSize, setSectinsSize] = useState(0);
+ useEffect(() => {
+ async function init() {
+ setSpinType(true);
+ let url = `/v1/users/${user_login}/block_user_lists.json`;
+ axios
+ .get(url, {
+ params: {
+ limit,
+ page,
+ },
+ })
+ .then((result) => {
+ if (result) {
+ setUsers(result.data.users);
+ setSectinsSize(result.data.users_size);
+ }
+ setSpinType(false);
+ })
+ .catch((e) => {
+ setSpinType(false);
+ console.log(e);
+ });
+ }
+ init();
+ }, [page]);
+
+
+ const handleChangePage = (page) => {
+ setListPage(page);
+ };
+
+ return (
+
+
+ {users && users.length > 0 ? (
+
+ {users.map((item, key) => {
+ return ;
+ })}
+
+ ) : (
+
+ )}
+
+ {usersSize > limit && (
+
+
+
+ )}
+
+ );
+}
+export default block_users;
diff --git a/src/user_info/User/Blocks.js b/src/user_info/User/Blocks.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/user_info/User/HeadUser.js b/src/user_info/User/HeadUser.js
new file mode 100644
index 000000000..8e6abb811
--- /dev/null
+++ b/src/user_info/User/HeadUser.js
@@ -0,0 +1,68 @@
+import React, { useState } from "react";
+import { Pt80, HeadTab, ShowName, DivInline } from "../css/layout.jsx";
+import UserIdentify from "./UserIdentify";
+import UserAvatar from "./UserAvatar";
+import UserBrief from "./UserBrief";
+import StarUser from "./StarUser";
+
+
+function head_user({ UserInfo }) {
+ const [fansCount, setFansCount] = useState(0)
+ const fans_count = (count) => {
+ let new_fans_count = UserInfo.fans_count + count
+ setFansCount(new_fans_count)
+ }
+ return (
+
+ );
+}
+export default head_user;
diff --git a/src/user_info/User/Menu.js b/src/user_info/User/Menu.js
new file mode 100644
index 000000000..1f7a01805
--- /dev/null
+++ b/src/user_info/User/Menu.js
@@ -0,0 +1,46 @@
+import React, { useEffect, useState } from "react";
+import { Menu } from "antd";
+import "./menu.scss";
+
+function user_menu({ is_current_user, login, props }) {
+ const [defaultMenu, setDefaultMenu] = useState("p_project");
+ const handleClick = (e) => {
+ setDefaultMenu(e.key);
+ props.history.push(`/accounts/${login}/${e.key}`);
+ };
+ useEffect(() => {
+ const location_path = props.location.pathname.split("/");
+ const current_path = location_path[location_path.length - 1];
+ if (location_path.length > 3 && current_path) {
+ setDefaultMenu(current_path);
+ } else {
+ setDefaultMenu("p_project");
+ }
+ }, [props.location]);
+ const show_name = is_current_user ? "我" : "TA";
+ return (
+
+
+
+ );
+}
+export default user_menu;
diff --git a/src/user_info/User/ProjectItem.js b/src/user_info/User/ProjectItem.js
new file mode 100644
index 000000000..cf01e8872
--- /dev/null
+++ b/src/user_info/User/ProjectItem.js
@@ -0,0 +1,53 @@
+import React from "react";
+import { Card, Col, Avatar, Tooltip } from "antd";
+import { getUrl } from "educoder";
+import "./user.scss";
+
+function project_item({ project, key }) {
+ const item_bottom = () => {
+ return (
+
+
+
+ {project.members_count}
+
+ {project.commits_count > 0 && (
+
+
+ {project.commits_count}
+
+ )}
+
+ );
+ };
+ return (
+
+
+ {project.is_public && (
+
+
+
+ 公开
+
+ )}
+
+
+
+
+
+
+
{project.school_name}
+
+
+
+ );
+}
+export default project_item;
diff --git a/src/user_info/User/Projects.js b/src/user_info/User/Projects.js
new file mode 100644
index 000000000..52d61f413
--- /dev/null
+++ b/src/user_info/User/Projects.js
@@ -0,0 +1,118 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import { Spin, Menu, Button, Empty, Row, Pagination } from "antd";
+import "./menu.scss";
+import "./user.scss";
+import ProjectItem from "./ProjectItem";
+import { PagenationDiv } from "../css/projects";
+
+function user_projects(props) {
+ const user_login = props.match.params.login;
+ const [is_current_uesr, setIsCurrentUser] = useState(false);
+ const [projects, getProjectsList] = useState([]);
+ const [projectsSize, setProjectsSize] = useState(0);
+ const [page, setListPage] = useState(1);
+ const [type, setListType] = useState("");
+ const [p, setSelectType] = useState("a");
+ const [order, setOrderType] = useState("desc");
+ const [limit, setLimitType] = useState(16);
+ const [isSpin, setSpinType] = useState(false);
+ useEffect(() => {
+ if (props.location.pathname.indexOf("l_projects") > -1) {
+ setListType("l_projects");
+ } else {
+ setListType("p_projects");
+ }
+ }, [props.location]);
+
+ useEffect(() => {
+ async function init() {
+ setSpinType(true);
+ let url = `/v1/users/${user_login}/user_projects.json`;
+ axios
+ .get(url, {
+ params: { page, type, p, order, limit },
+ })
+ .then((result) => {
+ if (result) {
+ getProjectsList(result.data.projects);
+ setProjectsSize(result.data.projects_size);
+ setIsCurrentUser(result.data.is_current_user);
+ }
+ setSpinType(false);
+ })
+ .catch((e) => {
+ setSpinType(false);
+ console.log(e);
+ });
+ }
+ if(type.length > 0){
+ init();
+ }
+
+ }, [page, type, p, order]);
+
+ const select_project = (e) => {
+ setSelectType(e.key);
+ };
+ const select_order = () => {
+ setOrderType(order === "desc" ? "asc" : "desc");
+ };
+
+ const handleChangePage = (page) => {
+ setListPage(page);
+ };
+
+ return (
+
+ {is_current_uesr && (
+
+
+
+ )}
+
+ 共{projectsSize}个项目
+
+
+
+ {projects && projects.length > 0 ? (
+
+ {projects.map((item, key) => {
+ return ;
+ })}
+
+ ) : (
+
+ )}
+
+ {projectsSize > limit && (
+
+
+
+ )}
+
+ );
+}
+export default user_projects;
diff --git a/src/user_info/User/StarUser.js b/src/user_info/User/StarUser.js
new file mode 100644
index 000000000..ddf3b6dea
--- /dev/null
+++ b/src/user_info/User/StarUser.js
@@ -0,0 +1,169 @@
+import React, { useEffect, useState } from "react";
+import { Button, message} from "antd";
+import axios from "axios";
+import { DivInline } from "../css/layout.jsx";
+import "./user.scss";
+import TipModal from "../Modal/tip_modal";
+
+function star_user({
+ is_watched,
+ login,
+ current_login,
+ user_id,
+ set_fans_count,
+ is_blocked,
+ is_blocked_by,
+ user_name,
+ show_block
+}) {
+ const [isStar, setStarStatus] = useState(false);
+ const [isBlocked, setBlockStatus] = useState(false);
+ const [showModal, setModalStatus] = useState(false);
+ const [isLoading, setLoadinStatus] = useState(false);
+ const [isBlockLoading, setBlockLoadingStatus] = useState(false);
+ const [modalData, setDataModal] = useState({});
+ useEffect(() => {
+ if (is_watched) {
+ setStarStatus(is_watched);
+ }
+ if (is_blocked) {
+ setBlockStatus(is_blocked);
+ }
+ },[is_blocked, is_watched]);
+ const to_star = (star_type) => {
+ if(current_login){
+ setLoadinStatus(true);
+ let url = `/v1/users/${login}/watch_user.json`;
+ if (is_blocked_by) {
+ setModalStatus(true)
+ setLoadinStatus(false);
+ setDataModal({
+ title: "提示",
+ modal_type: "confirm",
+ head: null,
+ content: "对方已将你加入黑名单,你不能关注对方。"
+ });
+ } else {
+ axios
+ .post(url, { watch: star_type, login: login })
+ .then((result) => {
+ if (result && result.data.status >= 0) {
+ message.success(result.data.message);
+ setStarStatus(!isStar);
+ if (star_type === "watch") {
+ set_fans_count(1);
+ } else {
+ set_fans_count(-1);
+ }
+ } else {
+ message.error(result.data.message);
+ }
+ setLoadinStatus(false);
+ })
+ .catch((e) => {
+ setLoadinStatus(false);
+ console.log(e);
+ });
+ }
+ }else{
+ window.open("/login", "_blank")
+ }
+
+ };
+ const to_block = (block_type) => {
+ setModalStatus(false);
+ setBlockLoadingStatus(true);
+ let url = `/v1/users/${login}/block_user.json`;
+ axios
+ .post(url, { block: block_type })
+ .then((result) => {
+ if (result && result.data.status >= 0) {
+ message.success(result.data.message);
+ setBlockStatus(!isBlocked);
+ if (block_type === "block" && isStar) {
+ //如果是已关注的状态,则取消关注状态
+ setStarStatus(false);
+ set_fans_count(-1);
+ }
+ } else {
+ message.error(result.data.message);
+ }
+ setBlockLoadingStatus(false);
+ })
+ .catch((e) => {
+ setBlockLoadingStatus(false);
+ console.log(e);
+ });
+ };
+ const confirm_block = () => {
+
+ if (isBlocked) {
+ setDataModal({
+ title: "移出黑名单",
+ modal_type: "cancel",
+ head: null,
+ content: "移出黑名单后,对方可以关注你,向你发私信,评论你的帖子。"
+ });
+ } else {
+ setDataModal({
+ title: "加入黑名单",
+ modal_type: "block",
+ head: `确定要将${user_name}加入黑名单?`,
+ content:
+ " 加入黑名单后,已有关注关系将被解除,对方不能再关注你,向你发私信,评论你的帖子等。但仍可以查看你的公开信息。"
+ });
+ }
+ setModalStatus(true);
+ };
+ const cancel_click = () => {
+ setModalStatus(false)
+ setBlockLoadingStatus(false)
+ }
+ return (
+
+
+
+
+ {show_block && current_login && }
+
+
+ {showModal && (
+
+ )}
+
+ );
+}
+export default star_user;
diff --git a/src/user_info/User/UserAvatar.js b/src/user_info/User/UserAvatar.js
new file mode 100644
index 000000000..7f9777b88
--- /dev/null
+++ b/src/user_info/User/UserAvatar.js
@@ -0,0 +1,106 @@
+import React, { useEffect, useState } from "react";
+import { Avatar, Upload, message } from "antd";
+import { getUrl } from "educoder";
+import "antd/dist/antd.css";
+import { HeadPhoto, HeadPhotoHover } from "../css/layout.jsx";
+import ImgCrop from "antd-img-crop";
+import axios from "axios";
+function user_avatar({ avatar, is_current_user, user_id, login }) {
+ const current_route = window.location.protocol + "//" + window.location.host;
+ const [isHover, setIsHover] = useState(false);
+ const [isClick, setIsClick] = useState(false);
+ const [userAvatar, setUserAvatar] = useState("");
+ const [isLoading, setLoadinStatus] = useState(false);
+ useEffect(() => {
+ setUserAvatar(avatar);
+ }, []);
+ const beforeUpload = (file) => {
+ const isJPG = file.type === "image/jpeg";
+ const isPNG = file.type === "image/png";
+ const isGIF = file.type === "image/gif";
+ if (!isJPG && !isPNG && !isGIF) {
+ message.error("只能上传图片文件!(jpg、png或gif)");
+ setIsHover(false);
+ setIsClick(false);
+ return false;
+ }
+ const isLt2M = file.size / 1024 / 1024 < 1;
+ if (!isLt2M) {
+ setIsHover(false);
+ setIsClick(false);
+ message.error("图片大小必须小于 2MB!");
+ return false;
+ }
+ return true;
+ };
+
+ function sync_upload_picture(file) {
+ setLoadinStatus(true);
+ let base64Url = "";
+ let reader = new FileReader();
+ reader.readAsDataURL(file.file);
+ reader.onloadend = function () {
+ base64Url = reader.result;
+ if (base64Url && base64Url.length > 0) {
+ handleUpload(base64Url)
+ } else {
+ setLoadinStatus(false);
+ message.error("上传失败,请稍后重试");
+ }
+ };
+ }
+
+ function handleUpload(base64Url) {
+ var formData = new FormData();
+ formData.append("img", base64Url);
+ formData.append("source_type", "User");
+ formData.append("source_id", user_id);
+ formData.append("is_direct", 0);
+ axios.post(`/upload_avatar.json`, formData)
+ .then((result) => {
+ if (result && result.data.status === 0) {
+ setUserAvatar(result.data.url)
+
+ } else {
+ message.error(result.data.message);
+ }
+ setIsHover(false);
+ setIsClick(false);
+ setLoadinStatus(false);
+ })
+ .catch((e) => {
+ setIsHover(false);
+ setIsClick(false);
+ setLoadinStatus(false);
+ console.log(e);
+ });
+ }
+
+ return (
+
+ setIsHover(true)}
+ onMouseLeave={() => setIsHover(isClick)}
+ >
+
+ {is_current_user && (isHover || isClick) && (
+ setIsClick(true)}>
+
+
+ {isLoading?"上传中..":"修改头像"}
+
+
+
+ )}
+
+
+ );
+}
+
+export default user_avatar;
diff --git a/src/user_info/User/UserBrief.js b/src/user_info/User/UserBrief.js
new file mode 100644
index 000000000..a33e7b05c
--- /dev/null
+++ b/src/user_info/User/UserBrief.js
@@ -0,0 +1,81 @@
+import React, {useState } from "react";
+import { Form, Input, Icon, Button, message } from "antd";
+import axios from "axios";
+const { Item } = Form;
+
+function user_brief({ brief, login, is_current_user }) {
+ const [editBrief, setEditBrief] = useState(null);
+ const [isEdit, setEditStatus] = useState(false);
+ const [isLoading, setLoadingStatus] = useState(false);
+ const changeInput = (e) => {
+ setEditBrief(e.target.value)
+ };
+ const edit_brief = () => {
+ setLoadingStatus(true);
+ let url = `/v1/users/${login}/edit_brief.json`;
+ axios
+ .post(url, { content: editBrief })
+ .then((result) => {
+ if (result && result.data.status >= 0) {
+ message.success(result.data.message);
+ setEditStatus(false);
+ } else {
+ message.error(result.data.message);
+ }
+ setLoadingStatus(false);
+ })
+ .catch((e) => {
+ setLoadingStatus(false);
+ console.log(e);
+ });
+ };
+ return (
+
+ {!isEdit && (
+
+ {editBrief
+ ? editBrief
+ : brief
+ ? brief
+ : "这家伙很懒,什么都没留下~"}
+ {is_current_user && (
+ setEditStatus(true)}>
+
+
+ )}
+
+ )}
+ {isEdit && (
+
+ )}
+
+ );
+}
+export default user_brief;
diff --git a/src/user_info/User/UserIdentify.js b/src/user_info/User/UserIdentify.js
new file mode 100644
index 000000000..b90f0d071
--- /dev/null
+++ b/src/user_info/User/UserIdentify.js
@@ -0,0 +1,109 @@
+import React from "react";
+import { Tooltip } from "antd";
+import { getImageUrl } from "educoder";
+import {
+ DivInline,
+ GridItem5,
+ IdentifyName,
+ IdentifyA,
+} from "../css/layout.jsx";
+function user_identify({ user_info }) {
+ const identityImage = getImageUrl(
+ "images/educoder/icon/auto-identityed.svg"
+ );
+ const identityIng = getImageUrl("images/educoder/icon/auto-identity.svg");
+ const autoPosted = getImageUrl("images/educoder/icon/auto-posted.svg");
+ const autoPost = getImageUrl("images/educoder/icon/auto-post.svg");
+ const autoPhone = getImageUrl("images/educoder/icon/auto-phone.svg");
+ const autoPhoned = getImageUrl("images/educoder/icon/auto-phoneed.svg");
+ const autoMailed = getImageUrl("images/educoder/icon/auto-emailed.svg");
+ const autoMail = getImageUrl("images/educoder/icon/auto-email.svg");
+
+ return (
+
+ );
+}
+
+export default user_identify;
diff --git a/src/user_info/User/menu.scss b/src/user_info/User/menu.scss
new file mode 100644
index 000000000..10472bc30
--- /dev/null
+++ b/src/user_info/User/menu.scss
@@ -0,0 +1,45 @@
+.user-menu, .user-submenu {
+ .ant-menu-horizontal {
+ border-bottom: none !important;
+ padding: 15px !important;
+ text-align: left !important;
+ }
+ .ant-menu-item{
+ border-bottom: none !important;
+ font-size: 16px !important;
+ margin: 0 10px !important;
+ }
+}
+.user-menu{
+ .ant-menu-item-selected{
+ color: #21B350 !important;
+ border: 1px solid #21B350 !important;
+ border-radius: 24px;
+ &::after{
+ content: none !important
+ }
+ }
+ .ant-menu-item:hover{
+ color: #21B350 !important;
+ &::after{
+ content: '';
+ position: absolute;
+ left: 30px;
+ bottom: 0;
+ right: auto;
+ height: 2px;
+ width: 15px;
+ background-color: #21B350;
+ }
+ }
+}
+.user-submenu{
+ margin-top: 15px;
+ .ant-menu-item-selected, .ant-menu-item:hover{
+ color: #21B350 !important;
+ }
+ .ant-menu-horizontal {
+ padding: 8px 15px !important;
+ }
+}
+
diff --git a/src/user_info/User/user.scss b/src/user_info/User/user.scss
new file mode 100644
index 000000000..ca4e47b01
--- /dev/null
+++ b/src/user_info/User/user.scss
@@ -0,0 +1,192 @@
+.pt80 {
+ padding-top: 80px;
+}
+.pr {
+ position: relative;
+}
+.width120 {
+ width: 120px;
+}
+.ant-btn-success {
+ color: #fff;
+ background-color: #21b350;
+ border-color: #21b350 !important;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
+ -webkit-box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
+ box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
+}
+.ant-btn-success:hover {
+ color: #fff;
+ background-color: #21b350;
+ border-color: #21b350;
+ opacity: 0.8;
+}
+.ant-btn-success.ant-btn-background-ghost {
+ color: #21b350 !important;
+}
+.ant-btn-success:focus {
+ color: #fff;
+ background-color: #21b350;
+ border-color: #21b350 !important;
+}
+.fs12 {
+ font-size: 12px;
+}
+
+.black-color {
+ color: rgba(51, 51, 51, 1);
+}
+.ant-btn-background-ghost.ant-btn-danger {
+ color: #fa4a4a;
+ border-color: #fa4a4a;
+}
+.ant-btn-danger {
+ border-color: #fa4a4a;
+ background-color: #fa4a4a;
+}
+.grid-item-left {
+ display: grid;
+ grid-template-columns: 1fr max-content;
+ align-items: center;
+}
+.user-project-size {
+ padding: 16px 25px;
+}
+.text-right {
+ text-align: right;
+}
+.color-grey {
+ color: #999 !important;
+}
+.color-black {
+ color: #333;
+}
+.color-green {
+ color: #21b351 !important;
+}
+.minH300 {
+ min-height: 300px;
+}
+.pd100 {
+ padding: 100px 20px;
+}
+.project-card-item {
+ margin-bottom: 20px;
+ position: relative;
+ .project-public-tip {
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+ .project-item-user {
+ text-align: center;
+ margin-top: 16px;
+ }
+ .ant-card-hoverable:hover {
+ bottom: 3px;
+ box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.3);
+ }
+ .project-user-school {
+ color: #999;
+ margin: 16px 0 10px 0;
+ font-size: 16px;
+ height: 32px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+ .project-user-bottom {
+ color: #666 !important;
+ }
+ .ant-card {
+ padding: 16px 0;
+ }
+ .ant-card-head {
+ border-bottom: none;
+ text-align: center;
+ padding: 0 20px;
+ }
+ .ant-card-head-wrapper {
+ padding: 16px 0 0 0;
+ }
+ .ant-card-head-title {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ height: 45px;
+ white-space: initial;
+ padding: 0;
+ a {
+ font-size: 16px;
+ color: #1a0b00;
+ }
+
+ a:hover {
+ color: #21b351;
+ }
+ }
+ .publicpart {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: 0;
+ height: 0;
+ border-left: 80px solid #21b351;
+ border-bottom: 80px solid transparent;
+ z-index: 1;
+ }
+ .smalltrangle {
+ display: block;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ border-left: 25px solid #fff;
+ border-bottom: 25px solid transparent;
+ z-index: 2;
+ }
+ .publicword {
+ transform: rotate(-45deg);
+ text-align: center;
+ color: #fff;
+ font-size: 14px;
+ display: block;
+ position: absolute;
+ width: 50px;
+ left: 0px;
+ z-index: 3;
+ top: 16px;
+ }
+ .ant-card-body {
+ padding: 0 24px;
+ }
+ .ant-card-actions {
+ background: #fff;
+ li {
+ margin: 16px 0 0 0;
+ }
+ }
+}
+.position-absolute {
+ position: absolute;
+}
+
+.forum-section-card{
+ .ant-card{border-radius: 8px;}
+ .intresting-forum-section{
+ text-align: center;
+ height: 166px;
+ overflow: hidden;
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+ justify-content: center;
+ }
+ .ant-card-body{margin-top: -16px; border-top-left-radius: 8px; border-top-right-radius: 8px;}
+}
+.block-item-user{
+ text-align: center;
+ padding: 50px 0 40px 0;
+ width: 100%;
+ display: inline-block;
+}
\ No newline at end of file
diff --git a/src/user_info/css/head_user.jsx b/src/user_info/css/head_user.jsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/user_info/css/layout.jsx b/src/user_info/css/layout.jsx
new file mode 100644
index 000000000..b09e5adcd
--- /dev/null
+++ b/src/user_info/css/layout.jsx
@@ -0,0 +1,149 @@
+import styled from 'styled-components';
+import imgs from '../images/userhead.jpg';
+
+export const NewUserMain = styled.div`{
+ margin: 0 auto;
+ min-width: 1200px;
+ padding-top: 60px;
+ padding-bottom: 117px;
+}`
+
+export const SelectSection = styled.div`{
+ padding: 15px;
+ margin-top: 15px;
+ background: #fff;
+ input, button{height: 36px; line-height: 36px;}
+ input::placeholder{font-size: 14px;}
+}`
+
+export const MainUser = styled.div`{
+ width: 100%;
+ background: #fff;
+ margin-bottom: 20px;
+ position: relative;
+}`
+
+export const HeadImage = styled.div`{
+ width: 100%;
+ height: 160px;
+ background-image: url('${imgs}');
+ position: absolute;
+ width: 100%;
+ left: 0px;
+ top: 0px;
+}`
+
+export const HeadCon = styled.div`{
+ position: absolute;
+ width: 100%;
+ left: 0px;
+ top: 0px;
+ min-height: 356px;
+}`
+
+export const IdentifyA = styled.a`{
+ width: 20px;
+ height: 20px;
+ line-height: 20px;
+ text-align: center;
+ border-radius: 50%;
+ background-color: #F4FAFF;
+ margin-right: 5px;
+}`
+
+export const RelativePos = styled.div`{
+ position: relative;
+}`
+
+export const DivInline = styled.div`{
+ display: inline-block;
+ width: auto;
+ line-height: 26px;
+ font-size: 16px;
+}`
+
+export const IdentifyName = styled.span`{
+ color: #686868;
+ font-size: 14px;
+ margin-right: 10px;
+}`
+
+export const GridItem5 = styled.div`{
+ display: grid;
+ grid-template-columns: max-content max-content max-content max-content max-content;
+ align-items: center;
+}`
+
+export const GridItem2 = styled.div`{
+ display: grid;
+ grid-template-columns: max-content 1fr;
+ align-items: center;
+}`
+export const Pt80 = styled.div`{
+ padding-top: 80px;
+}`
+
+export const ShowName = styled.span`{
+ display: block;
+ width: auto;
+ color: #05101A;
+ font-size: 24px;
+ height: 28px;
+ line-height: 28px;
+ margin-top: 5px;
+ clear: both;
+}`
+
+export const HeadPhoto = styled.div`{
+ margin-top: 14px;
+ text-align: center;
+ background: #FFFFff;
+ width: 115px;
+ height: 115px;
+ padding: 3px;
+ border-radius: 50%;
+ position: relative;
+ float: left;
+ margin-top: 19px;
+ box-sizing: border-box;
+}`
+
+export const HeadPhotoHover = styled.div`{
+ display: block;
+ cursor: pointer;
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ width: 109px;
+ height: 109px;
+ text-align: center;
+ line-height: 112px;
+ border-radius: 50%;
+ background-color: rgba(0,0,0,0.5);
+ color: #fff;
+ .upload-edit-word{
+ color: #fff !important;
+ }
+}`
+
+export const HeadTab = styled.div`{
+ width: 188px;
+ height: 60px;
+ text-align: center;
+ span {
+ color: #989898;
+ font-size: 14px;
+ };
+ a {
+ color: #fff;
+ font-size: 24px;
+ line-height: 2;
+ };
+ span, a{
+ {
+ display: block;
+ width: 100%;
+ text-align: center;
+ }
+ }
+}`
\ No newline at end of file
diff --git a/src/user_info/css/layout.scss b/src/user_info/css/layout.scss
new file mode 100644
index 000000000..1fd43739a
--- /dev/null
+++ b/src/user_info/css/layout.scss
@@ -0,0 +1,3 @@
+a:hover {
+ color: #21b351;
+}
\ No newline at end of file
diff --git a/src/user_info/css/projects.jsx b/src/user_info/css/projects.jsx
new file mode 100644
index 000000000..37797aa75
--- /dev/null
+++ b/src/user_info/css/projects.jsx
@@ -0,0 +1,14 @@
+import styled from 'styled-components';
+export const MainContent = styled.div`{
+ width: 1200px;
+ margin: 0 auto;
+ zoom: 1;
+}`
+export const PagenationDiv = styled.div`{
+ text-align: center;
+ padding: 20px;
+}`
+
+export const MinH400 = styled.div`{
+ min-height: 400px;
+}`
\ No newline at end of file
diff --git a/src/user_info/images/userhead.jpg b/src/user_info/images/userhead.jpg
new file mode 100644
index 000000000..0be793deb
Binary files /dev/null and b/src/user_info/images/userhead.jpg differ