forked from Gitlink/forgeplus-react
This commit is contained in:
parent
4f13421872
commit
6d8834403a
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -2,22 +2,12 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name=”Keywords” Content=”trustie,trustieforge,forge,确实让创建更美好,协同开发平台″>
|
||||
<meta name=”Keywords” Content=”TrustieOpenSourceProject″>
|
||||
<meta name=”Keywords” Content=”issue,bug,tracker,软件工程,课程实践″>
|
||||
<meta name=”Description” Content=”持续构建协同、共享、可信的软件创建生态开源创作与软件生产相结合,支持大规模群体开展软件协同创新活动”>
|
||||
<meta name=”Keywords” Content="trustie,trustieforge,forge,确实让创建更美好,协同开发平台">
|
||||
<meta name=”Keywords” Content="TrustieOpenSourceProject">
|
||||
<meta name=”Keywords” Content="issue,bug,tracker,软件工程,课程实践">
|
||||
<meta name=”Description” Content="持续构建协同、共享、可信的软件创建生态开源创作与软件生产相结合,支持大规模群体开展软件协同创新活动">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<!-- <script type="text/javascript">
|
||||
window.__isR = true;
|
||||
if (
|
||||
(navigator.userAgent.indexOf('MSIE 9') != -1
|
||||
|| navigator.userAgent.indexOf('MSIE 10') != -1)
|
||||
&&
|
||||
location.pathname.indexOf("/compatibility") == -1) {
|
||||
location.href = '/compatibility.html'
|
||||
}
|
||||
</script> -->
|
||||
<link rel=" stylesheet" type="text/css" href="%PUBLIC_URL%css/iconfont.css">
|
||||
<link rel=" stylesheet" type="text/css" href="%PUBLIC_URL%css/edu-purge.css">
|
||||
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/editormd.min.css">
|
||||
|
|
23
src/App.js
23
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 {
|
|||
}
|
||||
}>
|
||||
</Route>
|
||||
<Route
|
||||
path="/accounts/:login"
|
||||
render={(props) => (
|
||||
<Account {...this.props} {...this.state} {...props} />
|
||||
)}
|
||||
></Route>
|
||||
{/* 问吧、论坛交流 */}
|
||||
<Route
|
||||
path="/forums"
|
||||
render={(props) => (
|
||||
<Forums {...this.props} {...this.state} {...props} />
|
||||
)}
|
||||
></Route>
|
||||
{/*项目*/}
|
||||
<Route
|
||||
path={"/projects"}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Upload, Spin, message } from 'antd';
|
||||
import { getUploadActionUrl } from 'educoder';
|
||||
|
||||
export default (({ imageUrl, getImageUrl }) => {
|
||||
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 = (
|
||||
<div>
|
||||
{loading ? <Spin size={"small"} /> : <i className="iconfont icon-tianjiadaohang font-20" />}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<Upload
|
||||
name="file"
|
||||
listType="picture-card"
|
||||
className="avatar-uploader"
|
||||
showUploadList={false}
|
||||
action={getUploadActionUrl()}
|
||||
beforeUpload={beforeUpload}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{url ? <img src={url} alt="avatar" style={{ width: '100%' }} /> : uploadButton}
|
||||
</Upload>
|
||||
)
|
||||
})
|
|
@ -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 (
|
||||
<WhiteBack>
|
||||
<Modal
|
||||
visible={visible}
|
||||
title="公告"
|
||||
closable={true}
|
||||
onCancel={()=>setVisible(false)}
|
||||
footer={
|
||||
AnnModalType === 1 ? false : <div>
|
||||
<Button onClick={cancelAnn}>取消</Button>
|
||||
<Button onClick={saveAnn} type={"primary"}>
|
||||
发布
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{AnnModalType === 1 ? (
|
||||
<p style={{ maxHeight: "100px", overflowY: "auto" }}>{word}</p>
|
||||
) : (
|
||||
<TextArea
|
||||
placeholder="填写公告,1~600字"
|
||||
value={editWord}
|
||||
rows={5}
|
||||
onChange={changeText}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
<Title>
|
||||
<span>公告</span>
|
||||
{
|
||||
operation ?
|
||||
<a onClick={() => changeAnnModalType(2)}>
|
||||
<i className="iconfont icon-bianji3 grey-9"></i>
|
||||
</a>:""
|
||||
}
|
||||
</Title>
|
||||
<div style={{ padding: "10px 30px" }} className="pr">
|
||||
{word ? (
|
||||
<React.Fragment>
|
||||
<div id="annContent" className="annContent">
|
||||
<p id="annWords" className="annWords">
|
||||
{word}
|
||||
</p>
|
||||
</div>
|
||||
<span className="grey-8">版主:{content.name}</span>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Nodata _html={"暂无公告"} />
|
||||
)}
|
||||
{
|
||||
word && show === true ?
|
||||
<a className="annBtn" onClick={() => changeAnnModalType(1)}>
|
||||
…<span className="green ml4">查看</span>
|
||||
</a> : ""
|
||||
}
|
||||
</div>
|
||||
</WhiteBack>
|
||||
);
|
||||
});
|
|
@ -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(
|
||||
<WhiteBack className="authorCard">
|
||||
<UserInfo url={getUrl(user && user.image_url)} name={user && user.username} login={user && user.login} column/>
|
||||
<p style={{width:"100%"}} className="task-hide grey-8 mt10 mb15 edu-text-center">{user && user.description ? user.description : "这家伙太懒了,还未填写个人描述!"}</p>
|
||||
{user && !user.is_current_user && (
|
||||
<StarUser
|
||||
current_login={user.current_login}
|
||||
login={user.login}
|
||||
user_id={user.user_id}
|
||||
is_watched={user.watched}
|
||||
is_blocked={user.is_blocked}
|
||||
user_name={user.username}
|
||||
is_blocked_by={user.is_blocked_by}
|
||||
set_fans_count={fans_count}
|
||||
show_block={false}
|
||||
></StarUser>
|
||||
)}
|
||||
<Grid className="mt20">
|
||||
<UDStructure>
|
||||
<span>{user && user.memos_count}</span>
|
||||
<span>文章数</span>
|
||||
</UDStructure>
|
||||
<UDStructure>
|
||||
<span>{user && user.replies_count}</span>
|
||||
<span>评论数</span>
|
||||
</UDStructure>
|
||||
<UDStructure>
|
||||
<span>{fansCount}</span>
|
||||
<span>关注者</span>
|
||||
</UDStructure>
|
||||
</Grid>
|
||||
</WhiteBack>
|
||||
)
|
||||
})
|
|
@ -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 (
|
||||
<WhiteBack>
|
||||
<Title>版块活跃作者</Title>
|
||||
<ul className="authorUl">
|
||||
{
|
||||
author && author.length > 0 ?
|
||||
author.map((item,key)=>{
|
||||
return(
|
||||
<UserInfo login={item.login} url={getUrl(`/images/${item.image_url}`)} name={item.username} column/>
|
||||
)
|
||||
})
|
||||
:""
|
||||
}
|
||||
</ul>
|
||||
</WhiteBack>
|
||||
);
|
||||
});
|
|
@ -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(
|
||||
<ul className="BestUl">
|
||||
{
|
||||
list && list.length>0 ? list.map((item,key)=>{
|
||||
return(
|
||||
<li className="task-hide"><Link to={`/forums/${item.id}/detail`}>{item.subject}</Link></li>
|
||||
)
|
||||
}):
|
||||
<Nodata _html={`暂无${keys === "hot" ? "话题":"推荐"}~`} />
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
})
|
|
@ -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 (
|
||||
<WhiteBack>
|
||||
<Title>精选板块</Title>
|
||||
<ul className="BestModalUl">
|
||||
{recommand && recommand.length > 0
|
||||
? recommand.map((item, key) => {
|
||||
return (
|
||||
<AlignCenter>
|
||||
<Img src={item.picture || ring} />
|
||||
<div className="flex1" style={{ width: "0" }}>
|
||||
<Link to={`/forums/theme/${item.id}`} className="grey-3">{item.title}</Link>
|
||||
<Prag>{item.description || "暂无描述~"}</Prag>
|
||||
{/* <p className="task-hide">{item.description}</p> */}
|
||||
<span className="font-12 grey-9">{item.watchers_count}人收藏<span className="ml15">{item.memos_count}个话题</span></span>
|
||||
</div>
|
||||
</AlignCenter>
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</ul>
|
||||
</WhiteBack>
|
||||
);
|
||||
};
|
|
@ -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 (
|
||||
<div className="doubleItems">
|
||||
{list && list.length > 0
|
||||
? list.map((item, key) => {
|
||||
return (
|
||||
<Link
|
||||
to={`/forums/theme/${item.id}`}
|
||||
style={{
|
||||
borderBottom:
|
||||
key === list.length - 2 && list.length % 2 === 0
|
||||
? "none"
|
||||
: "",
|
||||
}}
|
||||
>
|
||||
<Img src={item && item.picture ? item.picture : logo} />
|
||||
<div className="flex1"style={{width:"0"}}>
|
||||
<div style={{display:"flex"}}>
|
||||
<P style={{maxWidth:"370px"}}>{item.title}</P>
|
||||
<span className="ml15" style={{fontSize:"14px",color:"#999"}}>帖子数:{item.memos_count}</span>
|
||||
</div>
|
||||
<Prag>{item.description || <span className="grey-9">暂无描述~</span>}</Prag>
|
||||
<FlexAJ style={{ fontSize: "12px" }}>
|
||||
<span>版主:{item.user_name}</span>
|
||||
</FlexAJ>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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 &&
|
||||
<Menu style={{minWidth:"100px",textAlign:'center'}}>
|
||||
{ permission.banned_permission && <Menu.Item onClick={()=>changeSticky(permission.sticky)}>{permission.sticky ? "取消置顶":"置顶"}</Menu.Item>}
|
||||
{ permission.banned_permission && <Menu.Item onClick={()=>changeFine(permission.is_fine)}>{permission.is_fine ? "取消推荐":"推荐"}</Menu.Item>}
|
||||
{ permission.login && <Menu.Item onClick={()=>changeMemoWatched(permission.memo_watched)}>{permission.memo_watched ? "取消收藏":"收藏"}</Menu.Item> }
|
||||
{ (permission.admin || permission.is_currentUser) && <Menu.Item><Link to={`/forums/${id}/edit`}>编辑</Link></Menu.Item>}
|
||||
{ permission.admin ?
|
||||
<Menu.Item onClick={()=>deleteForum()}>删除</Menu.Item>
|
||||
:
|
||||
permission.is_currentUser ?
|
||||
<Menu.Item onClick={()=>sendDeleteForum(permission.is_deleted)}>{permission.is_deleted?"撤销申请":"申请删帖"}</Menu.Item>:""
|
||||
}
|
||||
</Menu>
|
||||
)
|
||||
return (
|
||||
<Dropdown overlay={menu} align={"center"} placement={"bottomCenter"}>
|
||||
<i className="iconfont icon-gengduo1"></i>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
|
||||
export function Type(value){
|
||||
if(value === "交流"){
|
||||
return (<span className="blue">【交流】</span>)
|
||||
}else if(value === "求助"){
|
||||
return (<span className="orange">【求助】</span>)
|
||||
}
|
||||
}
|
||||
|
||||
export function Tag(value){
|
||||
if(value === "置顶"){
|
||||
return (<span className="tag tagRed">置顶</span>)
|
||||
}else if(value === "精华"){
|
||||
return (<span className="tag tagBlue">精华</span>)
|
||||
}else if(value === "原创"){
|
||||
return (<span className="tag tagOrange">原创</span>)
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<ul className="forumList">
|
||||
{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 (
|
||||
<li>
|
||||
<FlexAJ>
|
||||
<AlignCenter style={{ marginLeft: "-8px" }}>
|
||||
<span>{Type(item.tag_name)}</span>
|
||||
<a
|
||||
className="grey-3 task-hide"
|
||||
style={{ maxWidth: "700px" }}
|
||||
href={`/forums/${item.id}/detail`}
|
||||
>
|
||||
{item.subject}
|
||||
</a>
|
||||
{ item.sticky === true ? <span className="ml8">{Tag("置顶")}</span> : "" }
|
||||
{ item.is_original === true ? <span className="ml8">{Tag("原创")}</span> : "" }
|
||||
{ item.is_fine === true ? <span className="ml8">{Tag("精华")}</span> : "" }
|
||||
</AlignCenter>
|
||||
<AlignCenter>
|
||||
{ item.apply_destroy ? <span className="orange font-12 mr10">已申请删帖</span> : "" }
|
||||
{
|
||||
current_user && current_user.login ?
|
||||
<Drop permission={permission} id={item.id} calbackFunc={calbackFunc} confirm={confirm}/>
|
||||
:""
|
||||
}
|
||||
</AlignCenter>
|
||||
</FlexAJ>
|
||||
<FlexAJ className="mt8">
|
||||
<AlignCenter>
|
||||
<User login={item.user_login} name={item.username} url={getUrl("/images/"+item.image_url)}></User>
|
||||
{item.forum_section_title ? <Link to={`/forums/theme/${item.forum_section_id}`}><LeftLine>{item.forum_section_title}</LeftLine></Link> : "" }
|
||||
{item.published_time ? <LeftLine>{item.published_time}</LeftLine> : "" }
|
||||
</AlignCenter>
|
||||
<span>
|
||||
<span class="icon-wrap">
|
||||
<i class="iconfont icon-zhengyan font-18"></i>
|
||||
<span class="span-text">{item.replies_count}</span>
|
||||
</span>
|
||||
<span class="icon-wrap">
|
||||
<i class="iconfont icon-dianzan2 font-14"></i>
|
||||
<span class="span-text">{item.praises_count}</span>
|
||||
</span>
|
||||
<span class="icon-wrap">
|
||||
<i class="iconfont icon-pinglun1 font-14"></i>
|
||||
<span class="span-text">{item.replies_count}</span>
|
||||
</span>
|
||||
</span>
|
||||
</FlexAJ>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
) :
|
||||
<AlignCenter style={{height:"400px"}} className="bigNoData"><Nodata _html="暂无数据" /></AlignCenter>
|
||||
}
|
||||
</ul>
|
||||
);
|
||||
};
|
|
@ -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(
|
||||
<React.Fragment>
|
||||
<Search onSearch={onSearch} placeholder="搜索" allowClear/>
|
||||
{
|
||||
current_user && current_user.login ?
|
||||
<Link className="greenbtn" to={`/forums/new`} style={{width:"100%",marginTop:"20px"}}>
|
||||
<i className="iconfont icon-bianjishijuan3x" style={{marginRight:"5px"}}></i>写点什么
|
||||
</Link>
|
||||
:
|
||||
<a className="greenbtn" to={"/login"} style={{width:"100%",marginTop:"20px"}}>
|
||||
<i className="iconfont icon-bianjishijuan3x" style={{marginRight:"5px"}}></i>写点什么
|
||||
</a>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import { Pagination } from "antd";
|
||||
|
||||
export default (({page,total,changePage , pageSize}) => {
|
||||
return (
|
||||
total > pageSize ?
|
||||
<div className="center" style={{ padding: "25px 0px" }}>
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
current={page}
|
||||
onChange={changePage}
|
||||
total={total}
|
||||
pageSize={pageSize}
|
||||
/>
|
||||
</div>:""
|
||||
);
|
||||
});
|
|
@ -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 (
|
||||
<div className="priseBox">
|
||||
<span onClick={priseForums}>
|
||||
<i
|
||||
className={
|
||||
flag ? "iconfont icon-dianzan" : "iconfont icon-dianzan-xian"
|
||||
}
|
||||
></i>
|
||||
</span>
|
||||
<span>{number}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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(
|
||||
<Nav className={className}>{children}</Nav>
|
||||
)
|
||||
})
|
|
@ -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 (
|
||||
<WhiteBack style={{ marginBottom: "15px", padding: "20px 30px" }}>
|
||||
<FlexStart>
|
||||
<Img src={section && section.picture ? section.picture : Radius} />
|
||||
<div className="flex1">
|
||||
<FlexAJ>
|
||||
<P style={{ marginBottom: "0px" }}>{section && section.title}</P>
|
||||
<AlignCenter>
|
||||
{
|
||||
operation ?
|
||||
<Button onClick={()=>toManage(section && section.id)}>
|
||||
<i className="iconfont icon-shezhi2"></i>板块管理
|
||||
</Button>
|
||||
:""
|
||||
}
|
||||
|
||||
<Button onClick={()=>saveForum(section && section.id)} style={{ marginLeft: "30px" }}>
|
||||
<i className={watched?"iconfont icon-pingfen-xian":"iconfont icon-pingfen-xian"}></i>{watched?"取消收藏":"收藏"}
|
||||
</Button>
|
||||
</AlignCenter>
|
||||
</FlexAJ>
|
||||
{section && section.description ? (
|
||||
<Prag>{section.description}</Prag>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<FlexAJ className="mt10">
|
||||
<span>
|
||||
<Spanleft>版主:<Link className="grey-9" to={`/accounts/${sectionUser && sectionUser.user_login}`}>{sectionUser && sectionUser.username}</Link></Spanleft>
|
||||
{forumModers && forumModers.length > 0 ? (
|
||||
<Spanleft>
|
||||
管理员:
|
||||
{forumModers.map((item, key) => {
|
||||
return key < forumModers.length - 1
|
||||
? <span><Link className="grey-9" to={`/accounts/${item.user_login}`}>{item.username}</Link>、</span>
|
||||
: <Link className="grey-9" to={`/accounts/${item.user_login}`}>{item.username}</Link>;
|
||||
})}
|
||||
</Spanleft>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
<Spanright>
|
||||
版块主题:<label>{section && section.memos_count}</label>
|
||||
</Spanright>
|
||||
{section && section.publish_today_coun ? (
|
||||
<Spanright>
|
||||
今日发帖:<label>{section.publish_today_count}</label>
|
||||
</Spanright>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{section && section.replies_today_count ? (
|
||||
<Spanright>
|
||||
今日回帖:<label>{section.replies_today_count}</label>
|
||||
</Spanright>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</span>
|
||||
</FlexAJ>
|
||||
</div>
|
||||
</FlexStart>
|
||||
</WhiteBack>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
import React , { useState , useEffect } from "react";
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default ({user}) => {
|
||||
|
||||
return (
|
||||
<ul className="urllist">
|
||||
{
|
||||
user && user.login ?
|
||||
<React.Fragment>
|
||||
<Link to={`/accounts/${user.login}/memos`}>
|
||||
<span>我的帖子</span>
|
||||
<i className="iconfont icon-youjiantou"></i>
|
||||
</Link>
|
||||
<Link to={`/accounts/${user && user.login}/stars`}>
|
||||
<span>我的收藏</span>
|
||||
<i className="iconfont icon-youjiantou"></i>
|
||||
</Link>
|
||||
<Link to={`/accounts/${user && user.login}/interesting`}>
|
||||
<span>我感兴趣的论坛</span>
|
||||
<i className="iconfont icon-youjiantou"></i>
|
||||
</Link>
|
||||
</React.Fragment>
|
||||
:
|
||||
<React.Fragment>
|
||||
<a href={"/login"}>
|
||||
<span>我的帖子</span>
|
||||
<i className="iconfont icon-youjiantou"></i>
|
||||
</a>
|
||||
<a href={`/login`}>
|
||||
<span>我的收藏</span>
|
||||
<i className="iconfont icon-youjiantou"></i>
|
||||
</a>
|
||||
<a href={`/login`}>
|
||||
<span>我感兴趣的论坛</span>
|
||||
<i className="iconfont icon-youjiantou"></i>
|
||||
</a>
|
||||
</React.Fragment>
|
||||
}
|
||||
</ul>
|
||||
);
|
||||
};
|
|
@ -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(
|
||||
<Link to={`/accounts/${login}`}>
|
||||
<AlignCenter>
|
||||
<Img src={url}/>
|
||||
<Span>{name}</Span>
|
||||
</AlignCenter>
|
||||
</Link>
|
||||
)
|
||||
})
|
|
@ -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(
|
||||
<Link to={`/accounts/${login}`}>
|
||||
<Img>
|
||||
<img src={url} alt=""/>
|
||||
<span className="task-hide" style={{maxWidth:"84px",textAlign:"center"}}>{name}</span>
|
||||
</Img>
|
||||
</Link>
|
||||
)
|
||||
}
|
|
@ -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 (
|
||||
<div className="clearfix educontent pt20">
|
||||
<Box>
|
||||
<Long>
|
||||
<WhiteBack>
|
||||
<Title>
|
||||
<span>论坛首页</span>
|
||||
<AlignCenter>
|
||||
<span
|
||||
onClick={() => changeSort("published_at")}
|
||||
className={
|
||||
sort === "published_at" ? "green cPointer" : "cPointer"
|
||||
}
|
||||
>
|
||||
最新
|
||||
</span>
|
||||
<LeftLine
|
||||
onClick={() => changeSort("replies_count")}
|
||||
className={
|
||||
sort === "published_at" ? "cPointer" : "green cPointer"
|
||||
}
|
||||
style={{ fontSize: "16px" }}
|
||||
>
|
||||
最热
|
||||
</LeftLine>
|
||||
</AlignCenter>
|
||||
</Title>
|
||||
<Spin spinning={listSpin}>
|
||||
<div style={{ minHeight: "868px" }}>
|
||||
<ListItem
|
||||
memos={memos}
|
||||
current_user={current_user}
|
||||
calbackFunc={init}
|
||||
confirm={props.confirm}
|
||||
/>
|
||||
</div>
|
||||
</Spin>
|
||||
<div className="center" style={{ padding: "25px 0px" }}>
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
current={page}
|
||||
onChange={changePage}
|
||||
total={memosCount}
|
||||
pageSize={PAGESIZE}
|
||||
/>
|
||||
</div>
|
||||
</WhiteBack>
|
||||
</Long>
|
||||
<Short>
|
||||
<Gap>
|
||||
<WhiteBack style={{ marginBottom: "15px" }}>
|
||||
<div style={{ padding: "20px" }}>
|
||||
<ListSearch onSearch={onSearch} current_user={current_user} />
|
||||
</div>
|
||||
<div style={{ padding: "0px 20px", borderTop: "1px solid #eee" }}>
|
||||
<UrlItem user={current_user} />
|
||||
</div>
|
||||
</WhiteBack>
|
||||
<WhiteBack style={{ marginBottom: "15px" }}>
|
||||
<Title>热门话题</Title>
|
||||
<BestItem memos={data && data.hottest_memos} selectKey={selectKey} />
|
||||
</WhiteBack>
|
||||
<WhiteBack>
|
||||
<Title>版主推荐</Title>
|
||||
<BestItem memos={data && data.recommend_memos} selectKey={selectKey} />
|
||||
</WhiteBack>
|
||||
</Gap>
|
||||
</Short>
|
||||
</Box>
|
||||
{plate && plate.length > 0
|
||||
? plate.map((item, key) => {
|
||||
return item.children_tags && item.children_tags.length > 0 ? (
|
||||
<WhiteBack style={{ marginBottom: "15px" }}>
|
||||
|
||||
{/* <Title className="fwt-600">{item.name}</Title> */}
|
||||
<Title className="fwt-600">
|
||||
<Link to={`/forums/theme/${item.id}`}>
|
||||
{item.name}
|
||||
</Link>
|
||||
</Title>
|
||||
|
||||
<DoubleItem plate={item.children_tags} {...props} />
|
||||
</WhiteBack>
|
||||
) : (
|
||||
""
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default aa;
|
|
@ -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 (
|
||||
<div className="clearfix educontent pt20 minH400">
|
||||
<Spin spinning={isSpin}>
|
||||
{memo && memoUser ? (
|
||||
<div>
|
||||
<Breadcrumb separator=">" style={{ marginBottom: "10px" }}>
|
||||
<Breadcrumb.Item>
|
||||
<Link to={`/forums`}>论坛</Link>
|
||||
</Breadcrumb.Item>
|
||||
{memo && memo.forum_tag && memo.forum_tag.id && (
|
||||
<Breadcrumb.Item>
|
||||
<Link to={`/forums/theme/${memo && memo.forum_tag.id}`}>
|
||||
{memo && memo.forum_tag.title}
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
)}
|
||||
<Breadcrumb.Item>
|
||||
{memo ? memo.subject : "帖子详情"}
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<Box>
|
||||
<Long>
|
||||
<WhiteBack>
|
||||
<div style={{ padding: "0px 30px" }}>
|
||||
<Title className="headerInfo">
|
||||
<div className="headerInfoLeft">
|
||||
{memo && Type(`${memo.tag_name}`)}
|
||||
<p className="font-18 grey-3" style={{ marginBottom: "0px",maxWidth:"514px"}}>
|
||||
{memo && memo.subject}
|
||||
</p>
|
||||
{memo && memo.is_fine && Tag("精华")}
|
||||
{memo && memo.sticky && Tag("置顶")}
|
||||
</div>
|
||||
<div style={{width:"130px",textAlign:"right"}}>
|
||||
{current_user && current_user.login ? (
|
||||
<Drop permission={bannedPermission} id={memo_id} calbackFunc={init} confirm={props.confirm}/>
|
||||
):""}
|
||||
</div>
|
||||
{memo && memo.is_original && (
|
||||
<img
|
||||
src={Original}
|
||||
className="originalTag"
|
||||
width="80px"
|
||||
/>
|
||||
)}
|
||||
</Title>
|
||||
<AlignCenter className="font-12 pt15 pb15">
|
||||
<UserInfo
|
||||
url={getUrl(memoUser.image_url)}
|
||||
name={memoUser.username}
|
||||
login={memoUser.login}
|
||||
/>
|
||||
|
||||
<span className="icon-wrap ml20">
|
||||
<i className="iconfont icon-zhengyan font-18"></i>
|
||||
<span className="span-text">
|
||||
{memo && memo.viewed_count}
|
||||
</span>
|
||||
</span>
|
||||
<span className="icon-wrap ml20">
|
||||
<i className="iconfont icon-pinglun1 font-14"></i>
|
||||
<span className="span-text">
|
||||
{memo && memo.replies_count}
|
||||
</span>
|
||||
</span>
|
||||
<span className="grey-8 ml20">
|
||||
{memo && memo.published_time}
|
||||
</span>
|
||||
{memo && memo.apply_destroy && (
|
||||
<span className="orange ml20">已申请删帖</span>
|
||||
)}
|
||||
</AlignCenter>
|
||||
|
||||
<div className="bor-bottom-greyE pb20 mb15">
|
||||
{memoImage && (
|
||||
<div className="pb20">
|
||||
<img src={memoImage.url} style={{width: "100%"}}></img>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<RenderHtml
|
||||
className="tipsContent"
|
||||
value={memo.content}
|
||||
/>
|
||||
{memo.attachment_url && memo.attachment_url.length > 0 &&
|
||||
<Attachments
|
||||
attachments={memo.attachment_url}
|
||||
canDelete={false}
|
||||
/>}
|
||||
</div>
|
||||
{memo.reprint_link && (
|
||||
<p className="font-12 grey-8">转载自:{memo.reprint_link}</p>
|
||||
)}
|
||||
<Prise
|
||||
isPrised={memo.user_praise}
|
||||
num={memo.praises_count}
|
||||
memo_id={memo.id}
|
||||
container_type="Memo"
|
||||
current_login={memoUser && memoUser.current_login}
|
||||
/>
|
||||
</div>
|
||||
</WhiteBack>
|
||||
<WhiteBack className="mt15">
|
||||
<Comments
|
||||
target_id={memo && memo.id}
|
||||
target_type="memos"
|
||||
current_user_image={memoUser && memoUser.current_image_url}
|
||||
current_login={memoUser && memoUser.current_login}
|
||||
/>
|
||||
</WhiteBack>
|
||||
{
|
||||
replies && replies.length > 0 &&
|
||||
<WhiteBack className="mt15">
|
||||
<Title>
|
||||
<span className="greenLiftLine">相关推荐</span>
|
||||
</Title>
|
||||
<ListItem memos={replies} calbackFunc={related_memos} current_user={current_user} confirm = {props.confirm}/>
|
||||
</WhiteBack>
|
||||
}
|
||||
</Long>
|
||||
<Short>
|
||||
<Gap>
|
||||
<AuthorCard user={memoUser} />
|
||||
<WhiteBack className="mt15">
|
||||
<Title>
|
||||
<span>作者最近文章</span>
|
||||
<a
|
||||
className="font-12 grey-9"
|
||||
href={`/accounts/${memoUser && memoUser.login}/memos`}
|
||||
>
|
||||
更多
|
||||
<i className="font-12 iconfont icon-youjiantou ml5"></i>
|
||||
</a>
|
||||
</Title>
|
||||
<div className="memo-detail-ul">
|
||||
<BestItem memos={recent_memos} selectKey={["recommend"]} />
|
||||
</div>
|
||||
|
||||
</WhiteBack>
|
||||
</Gap>
|
||||
</Short>
|
||||
</Box>
|
||||
</div>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} className="pd200"></Empty>
|
||||
)}
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo_show;
|
|
@ -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 (
|
||||
<div className="newMain">
|
||||
<Switch {...this.props}>
|
||||
{/* 主题全部 */}
|
||||
<Route
|
||||
path="/forums/theme/:plateMainId"
|
||||
render={(props) => (
|
||||
<Theme {...this.props} {...props} />
|
||||
)}
|
||||
></Route>
|
||||
{/* 详情 */}
|
||||
<Route
|
||||
path="/forums/:id/detail"
|
||||
render={(props) => <Detail {...this.props} {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/forums/:forumId/edit"
|
||||
render={(props) => <New {...this.props} {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/forums/new"
|
||||
render={(props) => <New {...this.props} {...props} />}
|
||||
></Route>
|
||||
<Route path="/forums" render={(props) => <Forums {...this.props} {...props}/>}></Route>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(Index)));
|
|
@ -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) => (
|
||||
<React.Fragment>
|
||||
<span className={isRequired ? "lables must" : "lables"}>{label}</span>
|
||||
<Form.Item>
|
||||
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
|
||||
</Form.Item>
|
||||
</React.Fragment>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// 修改内容
|
||||
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 (
|
||||
<div className="educontent pt20 pb30">
|
||||
<WhiteBack>
|
||||
<Banner>{ forumId ? "编辑" : "新建"}</Banner>
|
||||
<Spin spinning={spinning}>
|
||||
<Form className="formStyle">
|
||||
{helper(
|
||||
"标题",
|
||||
"subject",
|
||||
[{ required: true, message: "请输入标题" }],
|
||||
<Input placeholder="请输入标题" maxLength={50} />,
|
||||
true
|
||||
)}
|
||||
{helper(
|
||||
"内容",
|
||||
"content",
|
||||
[{ required: true, message: "请输入内容" }],
|
||||
<MDEditor
|
||||
placeholder={"请输入描述信息"}
|
||||
watch={true}
|
||||
height={250}
|
||||
mdID={"oj-description"}
|
||||
initValue={content}
|
||||
onChange={onContentChange}
|
||||
></MDEditor>,
|
||||
true
|
||||
)}
|
||||
<div className="mb20">
|
||||
<Uplaod
|
||||
className="UploadStyle"
|
||||
isComplete={true}
|
||||
load={UploadFunc}
|
||||
content={
|
||||
<div>
|
||||
<span className="green">上传附件</span>
|
||||
<span className="grey-9">(单个文件50M以内)</span>
|
||||
</div>
|
||||
}
|
||||
size={100}
|
||||
></Uplaod>
|
||||
{forumId && fileList && fileList.length > 0 ? (
|
||||
<Attachments
|
||||
attachments={fileList}
|
||||
showNotification={showNotification}
|
||||
canDelete={true}
|
||||
deleteFunc={deleteFunc}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
<p>
|
||||
<span className="lables must">主题板块</span>
|
||||
</p>
|
||||
<div className="inlineEnd">
|
||||
{helper(
|
||||
"",
|
||||
"forum_id",
|
||||
[{ required: true, message: "请选择主题板块" }],
|
||||
<Select onChange={changeParent}>
|
||||
<Option value={0}>请选择主题板块</Option>
|
||||
{parentPlate &&
|
||||
parentPlate.length > 0 &&
|
||||
parentPlate.map((item, key) => {
|
||||
return <Option value={item.id}>{item.name}</Option>;
|
||||
})}
|
||||
</Select>
|
||||
)}
|
||||
{helper(
|
||||
"",
|
||||
"children_forum_id",
|
||||
[],
|
||||
<Select>
|
||||
{childPlate &&
|
||||
childPlate.length > 0 &&
|
||||
childPlate.map((item, key) => {
|
||||
return <Option value={item.id}>{item.title}</Option>;
|
||||
})}
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
<AlignCenter className="clearfix mt20 mb10">
|
||||
<span className="lables fl">上传封面:</span>
|
||||
<span className="grey-9">
|
||||
(只支持JPG、PNG、JPEG大小不超过2M建议尺寸4:3)
|
||||
</span>
|
||||
</AlignCenter>
|
||||
<UplaodCover
|
||||
imageUrl={ImgInfo && ImgInfo.url}
|
||||
getImageUrl={getImageUrl}
|
||||
/>
|
||||
{helper(
|
||||
"帖子标签",
|
||||
"tag_id",
|
||||
[{ required: true, message: "请选择帖子标签" }],
|
||||
<Select style={{ width: "260px" }}>
|
||||
<Option value={1}>交流</Option>
|
||||
<Option value={2}>求助</Option>
|
||||
</Select>,
|
||||
true
|
||||
)}
|
||||
{helper(
|
||||
"是否原创",
|
||||
"is_original",
|
||||
[],
|
||||
<Radio.Group onChange={changeOriginal}>
|
||||
<Radio value={1}>是</Radio>
|
||||
<Radio value={2}>否</Radio>
|
||||
</Radio.Group>,
|
||||
true
|
||||
)}
|
||||
{ originalType !==1? helper(
|
||||
"转载自",
|
||||
"reprint_link",
|
||||
[],
|
||||
<Input style={{ width: "260px" }} placeholder="转载链接"/>
|
||||
) :""}
|
||||
</Form>
|
||||
</Spin>
|
||||
</WhiteBack>
|
||||
<div className="mt20">
|
||||
<Button onClick={returnNew}>返回</Button>
|
||||
<Greenback onClick={sureNew} className="ml20">
|
||||
{ forumId ? "保存" : "提交"}
|
||||
</Greenback>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Form.create()(forwardRef(New));
|
|
@ -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(
|
||||
<div className="none_panels">
|
||||
<div>
|
||||
<img src={nodata} alt="" />
|
||||
<div className="none_p_title">{_html}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default Nodata;
|
|
@ -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 (
|
||||
<div className="clearfix educontent pt20" >
|
||||
<Breadcrumb separator=">" style={{ marginBottom: "10px" }}>
|
||||
<Breadcrumb.Item><Link to={`/forums`}>论坛交流</Link></Breadcrumb.Item>
|
||||
{ breadCrumb && breadCrumb.title ? <Breadcrumb.Item><Link to={`/forums/theme/${breadCrumb.id}`}>{breadCrumb.title}</Link></Breadcrumb.Item>:"" }
|
||||
{ breadCrumb && breadCrumb.children_bread_crumb ?<Breadcrumb.Item>{breadCrumb.children_bread_crumb && breadCrumb.children_bread_crumb.title}</Breadcrumb.Item>:""}
|
||||
</Breadcrumb>
|
||||
<Tops operation={operation} plateMainId={plateMainId} headData = {headData} title={breadCrumb && breadCrumb.title} history={props.history}/>
|
||||
<Box>
|
||||
<Long>
|
||||
<WhiteBack>
|
||||
<FlexAJ style={{ borderBottom: "1px solid #eee" }}>
|
||||
<Menu className="unlow newMenu" selectedKeys={[menuKey]} onClick={changeMenu} mode="horizontal">
|
||||
<Menu.Item key="all">全部</Menu.Item>
|
||||
<Menu.Item key="is_fine">推荐精华</Menu.Item>
|
||||
<Menu.Item key="my_memos">我的话题</Menu.Item>
|
||||
<Menu.Item key="my_topics">我参与的话题</Menu.Item>
|
||||
</Menu>
|
||||
<AlignCenter style={{ marginRight: "30px" }}>
|
||||
<span onClick={() => setSort("published_at")} className={sort === "published_at" ? "green cPointer" : "cPointer"}>最新</span>
|
||||
<LeftLine onClick={() => setSort("replies_count")} className={sort === "published_at" ? "cPointer" : "green cPointer"} style={{ fontSize: '14px' }}>最热</LeftLine>
|
||||
</AlignCenter>
|
||||
</FlexAJ>
|
||||
<Spin spinning={listSpin}>
|
||||
{
|
||||
memos && memos.length > 0 ?
|
||||
<div style={{ minHeight: "400px" }}>
|
||||
<ListItem memos={memos} current_user={current_user} calbackFunc={InitList} confirm = {props.confirm}/>
|
||||
</div>
|
||||
:
|
||||
<AlignCenter style={{height:"400px"}} className="bigNoData">
|
||||
<Nodata _html={"暂无帖子~"} />
|
||||
</AlignCenter>
|
||||
}
|
||||
</Spin>
|
||||
<Paginations page={page} total={total} pageSize={pageSize} changePage={changePage} />
|
||||
</WhiteBack>
|
||||
</Long>
|
||||
<Short>
|
||||
<Gap>
|
||||
<WhiteBack style={{ padding: "20px" }}>
|
||||
<ListSearch onSearch={onSearch} current_user={current_user}/>
|
||||
</WhiteBack>
|
||||
<ThemeRight operation={operation} plateId={plateMainId}/>
|
||||
</Gap>
|
||||
</Short>
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
} export default theme
|
|
@ -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 (
|
||||
<React.Fragment>
|
||||
{
|
||||
content &&
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<Announcement plateId={plateId} content={content} operation={operation}/>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
author && author.length > 0 &&
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<AuthorItem author={author}/>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
recommand && recommand.length > 0 &&
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<BestModalItem recommand={recommand}/>
|
||||
</div>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
|
@ -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;}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}`
|
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -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(
|
||||
<Nav className={className}>{children}</Nav>
|
||||
)
|
||||
})
|
|
@ -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 (
|
||||
<div className="children-memo-item">
|
||||
<List
|
||||
size="large"
|
||||
header=""
|
||||
dataSource={replies}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<CommentList
|
||||
item={item}
|
||||
user_image={user_image}
|
||||
current_login={current_login}
|
||||
is_children={true}
|
||||
></CommentList>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default children_journals;
|
|
@ -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;
|
||||
}
|
|
@ -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 (
|
||||
<div className="width100">
|
||||
{is_delete ? (
|
||||
<div className="pd20 edu-text-center">已删除</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="pb5">
|
||||
<Link
|
||||
to={`/accounts/${item && item.user_login}`}
|
||||
className="show-user-link"
|
||||
>
|
||||
<img className="user-image" src={getUrl(item.image_url)} />
|
||||
</Link>
|
||||
<Link
|
||||
to={`/accounts/${item && item.user_login}`}
|
||||
className="show-user-link color-black ml10 fwb"
|
||||
>
|
||||
{item && item.username}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="ml40">
|
||||
{item.content && (
|
||||
<RenderHtml className="tipsContent" value={item.content} />
|
||||
)}
|
||||
<div className="grid-item-left mt5">
|
||||
<span className="color-grey-8">{item.time}</span>
|
||||
<div className="text-right grid-item-3">
|
||||
{item.admin || current_login === item.user_login ? (
|
||||
<Popconfirm
|
||||
placement="bottom"
|
||||
title={"确定要删除当前评论吗?"}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
onConfirm={() => deleteorder(item.id)}
|
||||
>
|
||||
<Button type="link" loading={deleteSpin}>
|
||||
<i className="iconfont icon-shanchu font-14 color-grey-8 mr5 ver-middle"></i>
|
||||
<span className="font-14 color-grey-8 ver-middle">删除</span>
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{!is_children && (
|
||||
<Button
|
||||
type="link"
|
||||
className="ml-10"
|
||||
onClick={() => {
|
||||
praise_reply(item.id);
|
||||
}}
|
||||
>
|
||||
{is_praise ? (
|
||||
<i class="iconfont icon-dianzan color-green font-14 mr5 ver-middle"></i>
|
||||
) : (
|
||||
<i class="iconfont icon-dianzan-xian font-14 color-grey-8 mr5 ver-middle"></i>
|
||||
)}
|
||||
<span className="font-14 color-grey-8 ver-middle">
|
||||
{praisesCont}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
{!is_children && (
|
||||
<Button
|
||||
type="link"
|
||||
className="ml-10"
|
||||
onClick={() => {
|
||||
is_reply_click(true);
|
||||
}}
|
||||
>
|
||||
<i className="iconfont icon-pinglun1 font-14 color-grey-8 mr5 ver-middle"></i>
|
||||
<span className="font-14 color-grey-8 ver-middle">
|
||||
{subCommentsCount}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{is_reply && !is_children && (
|
||||
<NewComment
|
||||
memo_id={item.id}
|
||||
user_image={user_image}
|
||||
click_button={is_reply_click}
|
||||
new_reply={create_new_children}
|
||||
></NewComment>
|
||||
)}
|
||||
{subComments && subCommentsCount > 0 && (
|
||||
<ChildrenJournals
|
||||
replies={subComments}
|
||||
user_image={user_image}
|
||||
current_login={current_login}
|
||||
></ChildrenJournals>
|
||||
)}
|
||||
{item &&
|
||||
subComments &&
|
||||
item.replies_count > subCommentsCount &&
|
||||
item.replies_count >= page * limit && (
|
||||
<div className="mt10 edu-text-center">
|
||||
<Button
|
||||
loading={isSpin}
|
||||
type="success"
|
||||
onClick={() => get_more_reply(item.id)}
|
||||
>
|
||||
查看更多回复
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default comment_list;
|
|
@ -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 (
|
||||
<div>
|
||||
<Title className="b-bottom-none">
|
||||
<span className="greenLiftLine">
|
||||
评论
|
||||
{replies_count > 0 && (
|
||||
<span className="tip_tag">{replies_count}</span>
|
||||
)}
|
||||
</span>
|
||||
</Title>
|
||||
|
||||
<Spin spinning={isSpin}>
|
||||
<div className="comments-lists">
|
||||
{isClick ? (
|
||||
<NewComment
|
||||
click_button={click_reply}
|
||||
memo_id={target_id}
|
||||
user_image={currentImage}
|
||||
new_reply={create_new_reply}
|
||||
></NewComment>
|
||||
) : (
|
||||
<ClickReply
|
||||
user_image={currentImage}
|
||||
click_button={click_reply}
|
||||
></ClickReply>
|
||||
)}
|
||||
|
||||
{replies && replies.length > 0 && (
|
||||
<List
|
||||
size="large"
|
||||
loading={isSpin}
|
||||
header=""
|
||||
dataSource={replies}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<CommentList
|
||||
item={item}
|
||||
user_image={currentImage}
|
||||
current_login={current_login}
|
||||
is_children={false}
|
||||
target_type={target_type}
|
||||
commet_destroy={success_delete_reply}
|
||||
></CommentList>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{replies_count > limit && (
|
||||
<div className="edu-text-center pd20">
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
current={page}
|
||||
onChange={changePage}
|
||||
total={replies_count}
|
||||
pageSize={limit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default comments;
|
|
@ -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 (
|
||||
<div className="new-comment-head grid-item pd20">
|
||||
<img src={getUrl(user_image)} className="user-image"></img>
|
||||
<span className="reply-comment-input mr20">
|
||||
<Button className="add_reply_button ml10" onClick={click_reply_to}>
|
||||
<span>添加评论...</span>
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default reply_click;
|
|
@ -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 (
|
||||
<div className="grid-item-top pt20">
|
||||
<img src={getUrl(user_image)} className="user-image mr10"></img>
|
||||
<div>
|
||||
<MDEditor
|
||||
placeholder={"添加评论..."}
|
||||
height={200}
|
||||
mdID={
|
||||
memo_id
|
||||
? "orderdetail-add-descriptions" + memo_id
|
||||
: "orderdetail-add-descriptions"
|
||||
}
|
||||
onChange={change_input}
|
||||
></MDEditor>
|
||||
<p className="clearfix mt20">
|
||||
<Button
|
||||
type="success"
|
||||
onClick={add_reply}
|
||||
loading={journal_spin}
|
||||
className="mr15"
|
||||
>
|
||||
评论
|
||||
</Button>
|
||||
<Button onClick={click_reply_cancel}>取消</Button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default new_comment;
|
|
@ -95,7 +95,7 @@ body>.-task-title {
|
|||
}
|
||||
|
||||
.newContainer {
|
||||
background: #EAEBEC !important;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.ant-modal-title {
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
<UserInfo {...props}></UserInfo>
|
||||
<MainContent>
|
||||
<MinH400>
|
||||
<Switch {...props}>
|
||||
<Route
|
||||
path="/accounts/:login/blocks"
|
||||
render={() => <Blocks {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/accounts/:login/interesting"
|
||||
render={() => <Sections {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/accounts/:login/replies"
|
||||
render={() => <Memos {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/accounts/:login/memos"
|
||||
render={() => <Memos {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/accounts/:login/histories"
|
||||
render={() => <Memos {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/accounts/:login/stars"
|
||||
render={() => <Memos {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/accounts/:login/p_projects"
|
||||
render={() => <Projects {...props} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/accounts/:login/l_projects"
|
||||
render={() => <Projects {...props} />}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/accounts/:login"
|
||||
render={() => <Projects {...props} />}
|
||||
></Route>
|
||||
</Switch>
|
||||
</MinH400>
|
||||
|
||||
</MainContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(Index)));
|
|
@ -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 (
|
||||
<div>
|
||||
{is_current_uesr && (
|
||||
<div>
|
||||
<ForumSections select_memos={select_memos}></ForumSections>
|
||||
<div className="color-grey font-12 mt15">共{memosSize}个帖子</div>
|
||||
</div>
|
||||
)}
|
||||
<SelectSection>
|
||||
<Spin spinning={isSpin}>
|
||||
{memos && memos.length > 0 ? (
|
||||
<div>
|
||||
{memo_type === "replies" ?
|
||||
<RepliesMemos memos={memos}></RepliesMemos> :
|
||||
<ListItem memos={memos} calbackFunc={init} confirm={props.confirm} current_user={props.current_user}></ListItem>}
|
||||
</div>
|
||||
) : (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
className="pd100"
|
||||
></Empty>
|
||||
)}
|
||||
</Spin>
|
||||
</SelectSection>
|
||||
{memosSize > limit && (
|
||||
<PagenationDiv>
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
current={page}
|
||||
onChange={handleChangePage}
|
||||
total={memosSize}
|
||||
pageSize={limit}
|
||||
/>
|
||||
</PagenationDiv>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default memos;
|
|
@ -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 (
|
||||
<ul className="forumList">
|
||||
{memos.map((item, key) => {
|
||||
return (
|
||||
<li key={key}>
|
||||
<FlexAJ>
|
||||
<AlignCenter>
|
||||
<span className="color-gray-8">评论文章:</span>
|
||||
<Link to={`/forums/${item.id}/detail`} className="grey-3 task-hide" style={{ maxWidth: "700px" }}>{item.subject}</Link>
|
||||
</AlignCenter>
|
||||
<Drop />
|
||||
</FlexAJ>
|
||||
|
||||
<FlexAJ className="mt8">
|
||||
{item && item.new_reply && (
|
||||
<div>
|
||||
<div>{item.new_reply.content}</div>
|
||||
<div className="mt8 color-gray-8 font-12">
|
||||
{item.new_reply.published_time}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Link to={`/forums/${item.id}/detail`} className="reply-link">查看</Link>
|
||||
</FlexAJ>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
export default replies_memos;
|
|
@ -0,0 +1,10 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
|
||||
function reply_item({item}){
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
|
@ -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 (
|
||||
<Col span={6} className="project-card-item forum-section-card" key={key}>
|
||||
<Card
|
||||
bordered={false}
|
||||
hoverable={true}
|
||||
actions={[
|
||||
<Button type="link" size="small" onClick={()=>handleClick()} loading={isLoading}>
|
||||
<span className="color-grey" >{isStar ? "取消收藏" : "收藏"}</span>
|
||||
</Button>,
|
||||
<Button type="link" size="small" href={`/forums/theme/${forum_section.id}`}>
|
||||
<span className="color-green" >查看</span>
|
||||
</Button>,
|
||||
]}
|
||||
bodyStyle={{ background: rendomColor() }}
|
||||
>
|
||||
<div className="intresting-forum-section">
|
||||
<div className="font-20 color-white">{forum_section.title}</div>
|
||||
<div className="mt10 color-white">
|
||||
{forum_section.memos_count}个话题
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
export default section_item;
|
|
@ -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 (
|
||||
<div className="mt15">
|
||||
<Spin spinning={isSpin}>
|
||||
{forum_sections && forum_sections.length > 0 ? (
|
||||
<Row gutter={20}>
|
||||
{forum_sections.map((item, key) => {
|
||||
return <SectionItem forum_section={item} key={key}></SectionItem>;
|
||||
})}
|
||||
</Row>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} className="pd100"></Empty>
|
||||
)}
|
||||
</Spin>
|
||||
{sectionsSize > limit && (
|
||||
<PagenationDiv>
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
current={page}
|
||||
onChange={handleChangePage}
|
||||
total={sectionsSize}
|
||||
pageSize={limit}
|
||||
/>
|
||||
</PagenationDiv>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default sections;
|
|
@ -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 (
|
||||
<div>
|
||||
<SelectSection>
|
||||
<GridItem2>
|
||||
<div>
|
||||
<span className="mr15 d-inline-block section-cascader">
|
||||
<Cascader
|
||||
fieldNames={{
|
||||
label: "title",
|
||||
value: "id",
|
||||
children: "childrens",
|
||||
}}
|
||||
changeOnSelect
|
||||
options={forumSections}
|
||||
onChange={onChange}
|
||||
placeholder="选择版块"
|
||||
showSearch={{ filter }}
|
||||
size={InputSize}
|
||||
/>
|
||||
</span>
|
||||
<span className="d-inline-block section-cascader">
|
||||
<Cascader
|
||||
fieldNames={{ label: "title", value: "id" }}
|
||||
options={SectionOptions}
|
||||
onChange={isHiddenChange}
|
||||
placeholder="选择话题"
|
||||
size={InputSize}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="mr15">
|
||||
<DatePicker onChange={selectBeginTime} placeholder="开始日期" size={InputSize} />
|
||||
</span>
|
||||
<span className="mr15">
|
||||
<DatePicker onChange={selectEndTime} placeholder="结束日期" size={InputSize}/>
|
||||
</span>
|
||||
<Button type="success" className="success-button" onClick={SectionSearch}>搜索</Button>
|
||||
</div>
|
||||
</GridItem2>
|
||||
</SelectSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default select_title;
|
|
@ -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;
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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 (
|
||||
<div>
|
||||
{data &&
|
||||
(data.modal_type === "confirm" ? (
|
||||
<Modal
|
||||
title={data.title}
|
||||
visible={isShow}
|
||||
wrapClassName="tips-modal tips-modal-confirm"
|
||||
onCancel={() => cancel_click()}
|
||||
footer={[
|
||||
<div className="edu-txt-center lineH2 mb10">
|
||||
<Button type="primary" onClick={() => cancel_click()}>
|
||||
我知道了
|
||||
</Button>
|
||||
</div>,
|
||||
]}
|
||||
>
|
||||
<div className="modal-content-padding">
|
||||
<div className="edu-txt-center lineH2">{data.content}</div>
|
||||
</div>
|
||||
</Modal>
|
||||
) : (
|
||||
<Modal
|
||||
title={data.title}
|
||||
visible={isShow}
|
||||
onOk={() => ok_click()}
|
||||
onCancel={() => cancel_click()}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
wrapClassName="tips-modal-confirm"
|
||||
>
|
||||
<div className="modal-content-padding">
|
||||
{data.head && (
|
||||
<div className="fs16 edu-txt-center lineH2 mb10">
|
||||
{data.head}
|
||||
</div>
|
||||
)}
|
||||
{data.content && <div className="lineH2">{data.content}</div>}
|
||||
</div>
|
||||
</Modal>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default tip_modal;
|
|
@ -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 (
|
||||
<Spin spinning={isSpin}>
|
||||
<MainUser
|
||||
style={{
|
||||
height: UserInfo && UserInfo.is_current_user ? "336px" : "400px",
|
||||
}}
|
||||
>
|
||||
<HeadImage></HeadImage>
|
||||
<HeadCon>
|
||||
<HeadUser UserInfo={UserInfo}></HeadUser>
|
||||
</HeadCon>
|
||||
</MainUser>
|
||||
<UserMenu is_current_user={UserInfo && UserInfo.is_current_user} login={UserInfo && UserInfo.login} props={props}></UserMenu>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
export default account;
|
|
@ -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 (
|
||||
<Col
|
||||
span={8}
|
||||
className="project-card-item forum-section-card"
|
||||
key={key}
|
||||
>
|
||||
<Card bordered={false} hoverable={true}>
|
||||
<a href={`/accounts/${item.login}`} className="block-item-user">
|
||||
<Avatar
|
||||
size={64}
|
||||
src={getUrl(item.image_url)}
|
||||
></Avatar>
|
||||
<div className="mt15">
|
||||
<span className="font-16 color-black">
|
||||
{item.username}
|
||||
</span>
|
||||
</div>
|
||||
<div className="color-grey font-14 mt15">
|
||||
{item.brief ? item.brief : "这家伙太懒了,还未填写个人描述!"}
|
||||
</div>
|
||||
</a>
|
||||
{/* <div className="block-item-user">
|
||||
|
||||
</div> */}
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
export default block_item;
|
|
@ -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 (
|
||||
<div className="mt15">
|
||||
<Spin spinning={isSpin}>
|
||||
{users && users.length > 0 ? (
|
||||
<Row gutter={20}>
|
||||
{users.map((item, key) => {
|
||||
return <BlockItem item={item} key={key}></BlockItem>;
|
||||
})}
|
||||
</Row>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} className="pd100"></Empty>
|
||||
)}
|
||||
</Spin>
|
||||
{usersSize > limit && (
|
||||
<PagenationDiv>
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
current={page}
|
||||
onChange={handleChangePage}
|
||||
total={usersSize}
|
||||
pageSize={limit}
|
||||
/>
|
||||
</PagenationDiv>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default block_users;
|
|
@ -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 (
|
||||
<div className="pr">
|
||||
<Pt80 className="educontent clearfix edu-txt-center">
|
||||
<DivInline>
|
||||
<HeadTab className="fl">
|
||||
<span>粉丝</span>
|
||||
<a href={`/users/${UserInfo && UserInfo.login}/user_fanslist`}>
|
||||
{fansCount <= 0 ? UserInfo && UserInfo.fans_count : fansCount}
|
||||
</a>
|
||||
</HeadTab>
|
||||
{UserInfo && (
|
||||
<UserAvatar
|
||||
avatar={UserInfo.image_url}
|
||||
user_id={UserInfo.user_id}
|
||||
is_current_user={UserInfo.is_current_user}
|
||||
login={UserInfo.login}
|
||||
></UserAvatar>
|
||||
)}
|
||||
<HeadTab className="fr">
|
||||
<span>关注</span>
|
||||
<a href={`/users/${UserInfo && UserInfo.login}/user_watchlist`}>
|
||||
{UserInfo && UserInfo.stars_count}
|
||||
</a>
|
||||
</HeadTab>
|
||||
<ShowName>{UserInfo && UserInfo.username}</ShowName>
|
||||
</DivInline>
|
||||
</Pt80>
|
||||
{UserInfo && <UserIdentify user_info={UserInfo}></UserIdentify>}
|
||||
<div className="mt15 educontent clearfix edu-txt-center">
|
||||
{UserInfo && (
|
||||
<UserBrief
|
||||
brief={UserInfo.brief}
|
||||
login={UserInfo.login}
|
||||
is_current_user={UserInfo.is_current_user}
|
||||
></UserBrief>
|
||||
)}
|
||||
{UserInfo && !UserInfo.is_current_user && (
|
||||
<StarUser
|
||||
current_login={UserInfo.current_login}
|
||||
login={UserInfo.login}
|
||||
user_id={UserInfo.user_id}
|
||||
is_watched={UserInfo.is_watched}
|
||||
is_blocked={UserInfo.is_blocked}
|
||||
user_name={UserInfo.username}
|
||||
is_blocked_by={UserInfo.is_blocked_by}
|
||||
set_fans_count={fans_count}
|
||||
show_block={true}
|
||||
></StarUser>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default head_user;
|
|
@ -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 (
|
||||
<div className="mt15 educontent clearfix edu-txt-center user-menu">
|
||||
<Menu
|
||||
onClick={(e) => handleClick(e)}
|
||||
selectedKeys={defaultMenu}
|
||||
mode="horizontal"
|
||||
>
|
||||
<Menu.Item key="p_projects">{show_name}管理的</Menu.Item>
|
||||
<Menu.Item key="l_projects">{show_name}参与的</Menu.Item>
|
||||
<Menu.Item key="memos">{show_name}的帖子</Menu.Item>
|
||||
<Menu.Item key="replies">{show_name}的回帖</Menu.Item>
|
||||
{is_current_user && (
|
||||
<Menu.Item key="histories">{show_name}的足迹</Menu.Item>
|
||||
)}
|
||||
{is_current_user && (
|
||||
<Menu.Item key="stars">{show_name}的收藏</Menu.Item>
|
||||
)}
|
||||
{is_current_user && (
|
||||
<Menu.Item key="interesting">{show_name}感兴趣的论坛</Menu.Item>
|
||||
)}
|
||||
{is_current_user && <Menu.Item key="blocks">黑名单</Menu.Item>}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default user_menu;
|
|
@ -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 (
|
||||
<span className="project-user-bottom">
|
||||
<Tooltip title="成员数">
|
||||
<i class="iconfont icon-chengyuan mr3"></i>
|
||||
{project.members_count}
|
||||
</Tooltip>
|
||||
{project.commits_count > 0 && (
|
||||
<Tooltip title="版本库" className="ml10">
|
||||
<i class="iconfont icon-banbenku mr3"></i>
|
||||
{project.commits_count}
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Col span={6} className="project-card-item" key={key}>
|
||||
<Card bordered={false} hoverable={true} actions={[item_bottom()]}>
|
||||
{project.is_public && (
|
||||
<span className="project-public-tip">
|
||||
<span className="publicpart"></span>
|
||||
<span className="smalltrangle"></span>
|
||||
<span className="publicword">公开</span>
|
||||
</span>
|
||||
)}
|
||||
<div class="ant-card-head">
|
||||
<div class="ant-card-head-wrapper">
|
||||
<div class="ant-card-head-title">
|
||||
<a href={`/projects/${project.login}/${project.identifier}`}>{project.name}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="project-item-user">
|
||||
<a href={`/accounts/${project.login}`}>
|
||||
<Avatar size={64} src={getUrl(project.image_url)}></Avatar>
|
||||
</a>
|
||||
<div className="mt15">
|
||||
<a href={`/accounts/${project.login}`}>{project.username}</a>
|
||||
</div>
|
||||
<div className="project-user-school">{project.school_name}</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
export default project_item;
|
|
@ -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 (
|
||||
<div className="minH300">
|
||||
{is_current_uesr && (
|
||||
<div className="user-submenu">
|
||||
<Menu
|
||||
onClick={(e) => select_project(e)}
|
||||
selectedKeys={p}
|
||||
mode="horizontal"
|
||||
>
|
||||
<Menu.Item key="a">全部</Menu.Item>
|
||||
<Menu.Item key="1">公开</Menu.Item>
|
||||
<Menu.Item key="0">私有</Menu.Item>
|
||||
</Menu>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid-item-left user-project-size">
|
||||
<span className="color-grey font-12">共{projectsSize}个项目</span>
|
||||
<Button
|
||||
type="link"
|
||||
className="text-right"
|
||||
onClick={() => select_order()}
|
||||
>
|
||||
<span className="color-grey font-12">
|
||||
{order === "desc" ? "最近更新" : "最远更新"}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Spin spinning={isSpin}>
|
||||
{projects && projects.length > 0 ? (
|
||||
<Row gutter={20}>
|
||||
{projects.map((item, key) => {
|
||||
return <ProjectItem project={item} key={key}></ProjectItem>;
|
||||
})}
|
||||
</Row>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} className="pd100"></Empty>
|
||||
)}
|
||||
</Spin>
|
||||
{projectsSize > limit && (
|
||||
<PagenationDiv>
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
current={page}
|
||||
onChange={handleChangePage}
|
||||
total={projectsSize}
|
||||
pageSize={limit}
|
||||
/>
|
||||
</PagenationDiv>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default user_projects;
|
|
@ -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 (
|
||||
<DivInline>
|
||||
<div>
|
||||
<Button
|
||||
icon="heart"
|
||||
type={isBlocked ? "default" : "success"}
|
||||
ghost={!isStar}
|
||||
className={isStar ? "user-is-star" : ""}
|
||||
disabled={isBlocked}
|
||||
onClick={() => to_star(isStar ? "cancel" : "watch")}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isStar ? "已关注" : "关注"}
|
||||
</Button>
|
||||
<Button
|
||||
icon="mail"
|
||||
type={isBlocked ? "default" : "success"}
|
||||
className="ml15"
|
||||
ghost={true}
|
||||
disabled={isBlocked}
|
||||
href={`/users/${current_login}/message_detail?user_id=${user_id}`}
|
||||
target="_bank"
|
||||
>
|
||||
私信
|
||||
</Button>
|
||||
{show_block && current_login && <Button
|
||||
icon="stop"
|
||||
type="danger"
|
||||
className="ml15"
|
||||
ghost={!isBlocked}
|
||||
onClick={() => confirm_block()}
|
||||
loading={isBlockLoading}
|
||||
>
|
||||
{isBlocked ? "移出黑名单" : "加入黑名单"}
|
||||
</Button>}
|
||||
|
||||
</div>
|
||||
{showModal && (
|
||||
<TipModal
|
||||
data={modalData}
|
||||
okClick={to_block}
|
||||
cancelClick={cancel_click}
|
||||
></TipModal>
|
||||
)}
|
||||
</DivInline>
|
||||
);
|
||||
}
|
||||
export default star_user;
|
|
@ -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 (
|
||||
<HeadPhoto>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
onMouseEnter={() => setIsHover(true)}
|
||||
onMouseLeave={() => setIsHover(isClick)}
|
||||
>
|
||||
<Avatar size={109} src={getUrl(userAvatar)} />
|
||||
{is_current_user && (isHover || isClick) && (
|
||||
<HeadPhotoHover onClick={() => setIsClick(true)}>
|
||||
<ImgCrop rotate>
|
||||
<Upload
|
||||
action={current_route + "/upload_avatar.json"}
|
||||
showUploadList={false}
|
||||
customRequest={sync_upload_picture}
|
||||
beforeUpload={beforeUpload}
|
||||
>
|
||||
<span className="upload-edit-word">{isLoading?"上传中..":"修改头像"}</span>
|
||||
</Upload>
|
||||
</ImgCrop>
|
||||
</HeadPhotoHover>
|
||||
)}
|
||||
</a>
|
||||
</HeadPhoto>
|
||||
);
|
||||
}
|
||||
|
||||
export default user_avatar;
|
|
@ -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 (
|
||||
<div className="mb25">
|
||||
{!isEdit && (
|
||||
<div>
|
||||
{editBrief
|
||||
? editBrief
|
||||
: brief
|
||||
? brief
|
||||
: "这家伙很懒,什么都没留下~"}
|
||||
{is_current_user && (
|
||||
<span className="ml15" onClick={() => setEditStatus(true)}>
|
||||
<Icon type="edit"></Icon>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isEdit && (
|
||||
<Form layout="inline">
|
||||
<Item>
|
||||
<Input
|
||||
placeholder="个人简介"
|
||||
defaultValue={brief}
|
||||
onChange={(e) => changeInput(e)}
|
||||
maxLength={20}
|
||||
/>
|
||||
</Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
size="small"
|
||||
loading={isLoading}
|
||||
className="mt8"
|
||||
onClick={() => edit_brief()}
|
||||
>
|
||||
提交
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
onClick={() => setEditStatus(false)}
|
||||
className="ml15 mt8"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default user_brief;
|
|
@ -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 (
|
||||
<div className="educontent clearfix edu-txt-center mt10">
|
||||
<DivInline>
|
||||
<GridItem5>
|
||||
{user_info.identify && user_info.identify !== "学生" && (
|
||||
<IdentifyName>{user_info.identify}</IdentifyName>
|
||||
)}
|
||||
<Tooltip
|
||||
title={
|
||||
user_info.authentication
|
||||
? "已实名认证"
|
||||
: user_info.identify_status
|
||||
? "实名认证中"
|
||||
: "未实名认证"
|
||||
}
|
||||
>
|
||||
<IdentifyA
|
||||
href={
|
||||
user_info.is_current_user
|
||||
? "/account/authentication"
|
||||
: "javascript:void(0)"
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={user_info.authentication ? identityImage : identityIng}
|
||||
></img>
|
||||
</IdentifyA>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
user_info.professional
|
||||
? "已职业认证"
|
||||
: user_info.profession_status
|
||||
? "职业认证中"
|
||||
: "未职业认证"
|
||||
}
|
||||
>
|
||||
<IdentifyA
|
||||
href={
|
||||
user_info.is_current_user
|
||||
? "/account/professional_certification"
|
||||
: "javascript:void(0)"
|
||||
}
|
||||
>
|
||||
<img src={user_info.professional ? autoPosted : autoPost}></img>
|
||||
</IdentifyA>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
user_info.bind_phone
|
||||
? "已绑定手机"
|
||||
: "未绑定手机"
|
||||
}
|
||||
>
|
||||
<IdentifyA
|
||||
href={
|
||||
user_info.is_current_user
|
||||
? "/my/account"
|
||||
: "javascript:void(0)"
|
||||
}
|
||||
>
|
||||
<img src={user_info.bind_phone ? autoPhoned : autoPhone}></img>
|
||||
</IdentifyA>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
user_info.bind_email
|
||||
? "已绑定邮箱"
|
||||
: "未绑定邮箱"
|
||||
}
|
||||
>
|
||||
<IdentifyA
|
||||
href={
|
||||
user_info.is_current_user
|
||||
? "/my/account"
|
||||
: "javascript:void(0)"
|
||||
}
|
||||
>
|
||||
<img src={user_info.bind_email ? autoMailed : autoMail}></img>
|
||||
</IdentifyA>
|
||||
</Tooltip>
|
||||
</GridItem5>
|
||||
</DivInline>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default user_identify;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}`
|
|
@ -0,0 +1,3 @@
|
|||
a:hover {
|
||||
color: #21b351;
|
||||
}
|
|
@ -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;
|
||||
}`
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Loading…
Reference in New Issue