This commit is contained in:
caishi 2020-10-12 17:48:42 +08:00
parent 4f13421872
commit 6d8834403a
73 changed files with 5242 additions and 16 deletions

39
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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">

View File

@ -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"}

70
src/forge/Upload/Cover.js Normal file
View File

@ -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>
)
})

View File

@ -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="填写公告1600字"
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>
);
});

View File

@ -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>
)
})

View File

@ -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>
);
});

View File

@ -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>
)
})

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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;//10
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;//10
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;//10
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;//10
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>
);
};

View File

@ -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>)
}
}

View File

@ -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>
);
};

View File

@ -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>
)
})

View File

@ -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>:""
);
});

View File

@ -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>
);
};

View File

@ -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>
)
})

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
)
})

View File

@ -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>
)
}

197
src/forums/Forums.js Normal file
View File

@ -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;

248
src/forums/ForumsDetail.jsx Normal file
View File

@ -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;

58
src/forums/Index.jsx Normal file
View File

@ -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)));

374
src/forums/New.jsx Normal file
View File

@ -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, //idint
// attachments, //id
// memo: {
// //
// subject: values.subject, //
// content, //
// tag_id: values.tag_id, //12
// is_original: values.is_original, //true
// reprint_link: values.reprint_link, //is_originalfalse
// }
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">
(只支持JPGPNGJPEG大小不超过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));

18
src/forums/Nodata.js Normal file
View File

@ -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;

156
src/forums/Theme.js Normal file
View File

@ -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

52
src/forums/ThemeRight.jsx Normal file
View File

@ -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>
);
});

285
src/forums/css/All.scss Normal file
View File

@ -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;}

84
src/forums/css/Index.css Normal file
View File

@ -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;
}

25
src/forums/css/Theme.scss Normal file
View File

@ -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;
}

207
src/forums/css/layout.jsx Normal file
View File

@ -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;
}
}`

BIN
src/forums/image/nodata.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
src/forums/image/original.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
src/forums/image/radius.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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>
)
})

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -95,7 +95,7 @@ body>.-task-title {
}
.newContainer {
background: #EAEBEC !important;
background: #f5f5f5;
}
.ant-modal-title {

84
src/user_info/Index.jsx Normal file
View File

@ -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)));

101
src/user_info/Memo/Memos.js Normal file
View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,10 @@
import React, { useEffect, useState } from "react";
function reply_item({item}){
return (
<div>
</div>
)
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}

View File

View File

@ -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;
}
}
}`

View File

@ -0,0 +1,3 @@
a:hover {
color: #21b351;
}

View File

@ -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