Merge pull request '更新代码' (#587) from gitlink_server into pre_gitlink_server

This commit is contained in:
durian 2023-10-07 14:42:18 +08:00
commit a3e0fdbff0
50 changed files with 3571 additions and 982 deletions

1872
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,8 @@
"dependencies": {
"@monaco-editor/react": "^2.3.0",
"@novnc/novnc": "^1.1.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-react": "^1.0.6",
"actioncable": "^5.2.4-3",
"ahooks": "^2.10.14",
"antd": "^3.26.15",

View File

@ -7,7 +7,7 @@ export {
getImageUrl as getImageUrl,getImageUrlAbsolute as getImageUrlAbsolute,getImage as getImage, getmyUrl as getmyUrl, getRandomNumber as getRandomNumber, getUrl as getUrl, getZoneUrl as getZoneUrl, publicSearchs as publicSearchs, getRandomcode as getRandomcode, getUrlmys as getUrlmys, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
, getUploadActionUrl as getUploadActionUrl, getUploadActionUrltwo as getUploadActionUrltwo, getUploadActionUrlthree as getUploadActionUrlthree, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
, getTaskUrlById as getTaskUrlById, TEST_HOST, htmlEncode as htmlEncode, getupload_git_file as getupload_git_file, getcdnImageUrl as getcdnImageUrl,
turnbar,returnbar,setSeoMeta as setSeoMeta , IsPC as IsPC
turnbar,returnbar,setSeoMeta as setSeoMeta , IsPC as IsPC, addMeta as addMeta
} from './UrlTool';
export { setmiyah as setmiyah } from './Component';

View File

@ -0,0 +1,125 @@
import React, { useState, useEffect } from 'react';
import { Editor, Toolbar } from '@wangeditor/editor-for-react';
import '@wangeditor/editor/dist/css/style.css';
import cookie from 'react-cookies';
import { message, Spin } from 'antd';
import axios from 'axios';
import './index.scss';
//
function MyEditor(props) {
const {value, setValue, uploadUrl, videoUploadUrl} = props;
// editor
const [editor, setEditor] = useState(null);
const [loading, setLoading] = useState(false);
// ajax html
useEffect(() => {
if(value && setValue){
setValue(value);
}
}, []);
//
const toolbarConfig = {};
//
toolbarConfig.excludeKeys = [
'todo', "fullScreen"
]
//
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF:{
uploadImage:{
server: uploadUrl,
headers: {
Authorization: cookie.load('autologin_trustie')
},
withCredentials: true,
fieldName: "file",
maxFileSize: 20 * 1024 * 1024,
allowedFileTypes: ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml", "image/bmp"],
customInsert(res, insertFn){
const {url} = res;
insertFn(url, "图片", url);
},
onBeforeUpload(file){
const {type, name} = Object.values(file)[0];
const typeRange = ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml", "image/bmp"];
const suffix = name && name.split(".")[1];
const isType = typeRange.indexOf(type) !== -1 && suffix !== "tiff";
if(!isType){
message.error("只能上传jpg、jpeg、webp、png、svg、gif、bmp格式的图片!")
return false;
}
return file;
},
onError(file, err) {
message.error(`${file.name} 上传出错, ${err}` )
},
},
uploadVideo:{
//
async customUpload(file, insertFn){
if (!file.type.includes("video")) {
return message.error("请上传视频格式的文件!");
} else if (file.size / 1024 / 1024 > 200) {
return message.error("请上传200M以内的文件");
}
const formData = new FormData();
formData.append("file", file);
setLoading(true);
axios.post(videoUploadUrl || uploadUrl, formData, {headers: {Authorization: cookie.load('autologin_trustie')}}).then(res=>{
setLoading(false);
if(res && res.data && res.data.code === 200){
insertFn(res.data.url)
}else{
message.error(res && res.data && res.data.msg)
}
})
}
}
}
};
// editor
useEffect(() => {
return () => {
if (editor === null) return;
editor.destroy();
setEditor(null);
};
}, [editor]);
const editorBoxClass = {
border: '1px solid #ccc',
zIndex: 100,
'video': {
maxWidth: '100%'
}
}
return (
<div style={editorBoxClass}>
<Spin spinning={loading} tip="上传中,请耐心等待">
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode="default"
style={{ borderBottom: '1px solid #ccc' }}
/>
<Editor
defaultConfig={editorConfig}
value={value}
onCreated={setEditor}
onChange={(editor) => setValue && setValue(editor.getHtml())}
mode="default"
style={{ height: '500px', overflowY: 'hidden' }}
/>
</Spin>
</div>
);
}
export default MyEditor;

View File

@ -0,0 +1,5 @@
.w-e-modal{
input, button, select, optgroup, textarea{
line-height: 1.9;
}
}

View File

@ -364,12 +364,13 @@ class NewHeader extends Component {
onMouseOut={ () => { this.setState( {showSubMenu: false }) } }
>
<a onClick={ () => { this.setState( {showSubMenu: true }) } }>特色专区</a>
<ul className={ `drop-down ${ showSubMenu ? 'drop-down-show' : '' }` } style={{ height : showSubMenu ? `${ zoneList.length * 46}px` : 0 }}>
<ul className={ `drop-down ${ showSubMenu ? 'drop-down-show' : '' }` } style={{ height : showSubMenu ? `${ current_user && current_user.login ? ((zoneList.length+1) * 46) : (zoneList.length * 46)}px` : 0 }}>
{
zoneList.map((e, k) => {
return <li className="drop-down-item" key={ k }><a href={ e.link } className="task-hide" title={ e.name } target="_blank">{ e.name }</a></li>
})
}
{current_user && current_user.login && <li className="drop-down-item"><Link to="/zone/apply" target="_blank">+申请创建</Link></li> }
</ul>
</li>
}

View File

@ -0,0 +1,58 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { tempEnum } from '../tempInfo';
import Crown from '../img/crown.png';
import Nodata from '../../Nodata';
import nodata from '../img/nodata.png';
function Member(props) {
const { vipLists, temp } = props;
return(
<div className="vip_list">
{
vipLists.map((i,k)=>{
return(
<div className="vip_list_card">
<div className="card_title">
<img src={Crown} alt="" width="37px" style={{marginBottom:5,marginRight:5}}/>
<div>
<div>
{i.typeName}
</div>
{ temp === tempEnum.zone1 && <div className="linear_gradient">{i.typeName}</div> }
</div>
</div>
<p className="card_desc">{i.typeIntroduction}</p>
{
i.zoneMemberList && i.zoneMemberList.length > 0 ?
<ul className="card_ul">
{
i.zoneMemberList.map((j,key)=>{
return(
<li className="card_u_li">
<div className="card_u_wrap">
<Link to={`/${j.login}`} ><img src={j.imageUrl} alt="" /></Link>
<div className="card_u_info">
<div className="card_u_up"><span className="card_name task-hide">{j.name}</span>{j.memberLevel && <span className="card_tag">{j.memberLevel}</span> }</div>
<p className="card_u_down task-hide-2">{j.introduction}</p>
</div>
</div>
</li>
)
})
}
</ul>
:
(
temp === tempEnum.zone1 ? <Nodata _html="暂无数据" img={ nodata }/> : <Nodata _html="暂无数据" />
)
}
</div>
)
})
}
</div>
)
}
export default Member;

View File

@ -0,0 +1,179 @@
import React, { useEffect } from 'react';
import { Form , Input , Row , Col, Button } from 'antd';
import '../index.scss';
import { postCreateZone } from '../api';
import axios from 'axios';
const { TextArea } = Input;
const phonereg = /^([1][3456789])\d{9}$/
function Apply(props){
const { form , current_user } = props;
const { getFieldDecorator , validateFields } = form;
useEffect(()=>{
document.title = '申请创建专区';
},[])
useEffect(()=>{
if(!(current_user && current_user.login)){
props.history.push("/403");
}
},[current_user])
function submitFunc(){
validateFields((error,values)=>{
if(!error){
const zoneApplication = {...values};
postCreateZone(zoneApplication).then(response=>{
if(response && response.data && response.data.code===200){
props.showNotification("提交成功!");
setTimeout(() => {
window.location.href="/";
}, 70);
}
}).catch(error=>{})
}
})
}
function checkPhone(rule, value, callback){
if(!value){
callback();
}
if(!phonereg.test(value)){
callback("请输入正确的手机号码");
}
callback();
}
function checkId(rule,value,callback){
value ? axios.post(`/accounts/check_keywords`, {
text:value
}).then(response => {
if(response && response.data.data===false){
callback("与系统标识重复,请更改标识");
}
callback();
}):callback();
}
return(
<div className="applyBox">
<div className="applyContent">
<p className="apply-t">申请创建专区</p>
<div className="apply-tips">
<h5>申请说明</h5>
<p>1如果您想要创建自己的专区请填写下方页面中的信息来提交专区申请</p>
<p>2管理员在收到您的专区申请后将会通过您提供的联系方式与您进行沟通请您留意查收信息</p>
<p>3如果您的专区建设情况出色希望在官方专区列表中展示请发送邮件至官方邮箱 gitlink@ccf.org.cn并在邮件中注明推荐专区+您的专区链接经官方审核后您的专区将会被推荐展示</p>
</div>
<Form className="applyForm">
<Form.Item
label="专区名称"
>
{getFieldDecorator("name",{
rules:[{required:true,message:"请输入专区名称"}]
})(
<Input placeholder="请输入专区名称" maxLength={50} style={{height:"36px"}}/>
)}
</Form.Item>
<Form.Item
label="专区介绍"
>
{getFieldDecorator("introduction",{
rules:[{required:true,message:"请输入专区介绍"}]
})(
<TextArea placeholder="请输入专区介绍内容" maxLength={200} rows={5}/>
)}
</Form.Item>
<Form.Item
label="专区用途"
>
{getFieldDecorator("purpose",{
rules:[]
})(
<TextArea placeholder="请输入专区用途内容" maxLength={200} rows={5}/>
)}
</Form.Item>
<Row type="flex" justify="space-between">
<Col span={11}>
<Form.Item
label="专区标识"
>
{getFieldDecorator("key",{
rules:[
{ required: true,message:"请输入专区域名标识" },
{ pattern: /^[a-zA-Z][a-zA-Z0-9_-]{0,18}[a-zA-Z]$/, message: "长度2-20只能包含数字、字母、下划线、中划线必须以字母开头结尾"},
{ validator:checkId }
],
validateTrigger:"onBlur",
validateFirst: true,
})(
<Input placeholder="请输入专区域名标识" maxLength={20} style={{height:"36px"}}/>
)}
</Form.Item>
</Col>
<Col span={11}>
<Form.Item
label="联系人"
>
{getFieldDecorator("contactPerson",{
rules:[{required:true,message:"请输入联系人"}]
})(
<Input placeholder="请输入联系人" maxLength={50} style={{height:"36px"}}/>
)}
</Form.Item>
</Col>
</Row>
<Row type="flex" justify="space-between">
<Col span={11}>
<Form.Item
label="联系人电话"
>
{getFieldDecorator("contactPhone",{
rules:[
{required:true,message:"请输入联系人电话"},
{
validator: (rule, value, callback) => { checkPhone(rule, value, callback) }
}
],
validateTrigger:"onBlur",
validateFirst: true,
})(
<Input placeholder="请输入联系人电话" maxLength={50} style={{height:"36px"}}/>
)}
</Form.Item>
</Col>
<Col span={11}>
<Form.Item
label="联系人微信号"
>
{getFieldDecorator("contactWechat",{
rules:[]
})(
<Input placeholder="请输入联系人微信号" maxLength={50} style={{height:"36px"}}/>
)}
</Form.Item>
</Col>
</Row>
<Form.Item
label="希望平台提供的服务"
>
{getFieldDecorator("servicesProvided",{
rules:[]
})(
<TextArea placeholder="请输入您希望平台提供的服务" maxLength={200} rows={5}/>
)}
</Form.Item>
<div className="mt20">
<Button style={{height:"36px",width:"100px"}} type="primary" onClick={submitFunc}>提交</Button>
<Button style={{marginLeft:"30px",height:"36px",width:"100px"}} onClick={()=>{window.location.href="/"}}>取消</Button>
</div>
</Form>
</div>
</div>
)
}
export default Form.create({ name: 'ApplyNew' })(Apply);

View File

@ -4,10 +4,12 @@ import { httpUrl } from '../fetch';
import { Link } from 'react-router-dom';
import shijian from '../img/shijian.png';
import xuexi from '../img/xuexiguanli.png';
import { getHomePageList, gethomePageDocList, getAllList, getNewsAllList } from '../api';
import { getHomePageList, gethomePageDocList, getAllList, getNewsAllList, getVIPLists } from '../api';
import axios from 'axios';
import Partner from '../Component/partner';
import MemberList from '../Component/memberList';
import '../indexZonebyCCF.scss';
import '../indexZone1.scss';
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import Slider from 'react-slick';
@ -51,11 +53,19 @@ function HeaderPageCCF(props) {
}, [id,cateId])
function getPersonList() {
getHomePageList(id).then(result => {
if (result) {
setPersonList(result.data.rows);
}
}).catch(error => { })
//
// getHomePageList(id).then(result => {
// if (result) {
// setPersonList(result.data.rows);
// }
// }).catch(error => { })
//
getVIPLists(id, {isHomepage: 1}).then(response=>{
if(response){
const list = response.data.rows && response.data.rows.filter(item=>item.zoneMemberList);
setPersonList(list);
}
})
}
function getNewsList() {
@ -102,7 +112,7 @@ function HeaderPageCCF(props) {
}).catch(error => { })
}
return (
<div className="zone_box">
<div className="zone_box pb100">
{
//
data && data.cmsShow === 1 && newsList && newsList.length >= 0 &&
@ -117,7 +127,7 @@ function HeaderPageCCF(props) {
return (
<div className="regform" key={k}>
<div className="newsBannerBox">
<img src={i.headImg || img1} alt="" width="785px" style={{objectFit: "cover"}} onClick={()=>{window.location.href=`/zone/${deptId}/newdetail/${i.id}`}} />
<img src={i.headImg || img1} alt="" className='newsImg' onClick={()=>{window.location.href=`/zone/${deptId}/newdetail/${i.id}`}} />
</div>
</div>
)
@ -234,23 +244,30 @@ function HeaderPageCCF(props) {
{
//
data && data.memberShow === 1 && personList && personList.length > 0 &&
<div className="zone_contributor">
<p className="in_title">{data.homepageMemberTitle}</p>
{
<ul className="boxmain zone_c_lists">
<div className={`zone_contributor boxmain ${temp}_VIP_box`}>
<p className="in_title mb50">{data.homepageMemberTitle}</p>
{/* <MemberList vipLists={personList} temp={temp}/> */}
{personList && personList.map(item=>{
return <div>
<p className='font-18'>{item.typeName}</p>
<ul className="boxmain zone_c_lists mt20">
{
personList.map((i, k) => {
item.zoneMemberList.map((i, k) => {
return (
<li key={k}>
<Link to={`/${i.login}`}><img src={i.imageUrl} alt="" /></Link>
<span>{i.name}</span>
<div className='mb10'>
<span>{i.name}</span>
{i.memberLevel && <span className="memberLevel_tag ml10">{i.memberLevel}</span> }
</div>
<p className="task-hide-2" style={{ display: i.introduction ? "" : "flex" }}>{i.introduction || "暂无~"}</p>
</li>
)
})
}
</ul>
}
</div>
})}
</div>
}

View File

@ -1,7 +1,7 @@
import React , { useState , useEffect } from 'react';
import { Menu , Spin , Skeleton } from 'antd';
import { Menu , Spin , Skeleton, Button, Dropdown } from 'antd';
import Hot from '../Component/hot';
import { Box , LongWidth } from '../../Component/layout';
import { Box , FlexAJ, LongWidth } from '../../Component/layout';
import { Link } from 'react-router-dom';
import Lists from './newsList';
import guideArrow from '../img/guideArrow.png';
@ -24,7 +24,18 @@ function Main(props){
const [ isSpin , setIsSpin ] = useState(true);
const [ newCateId , setNewCateId ] = useState(undefined);
const { deptId , cateId } = props.match.params;
const { id, temp } = props;
const { id, temp, role, history } = props;
const menu = (
<Menu>
<Menu.Item>
<a onClick={()=>{history.push(`/zone/${deptId}/news/add?type=richEditor`)}}>富文本编辑器</a>
</Menu.Item>
<Menu.Item>
<a onClick={()=>{history.push(`/zone/${deptId}/news/add?type=mdEditor`)}}>MD编辑器</a>
</Menu.Item>
</Menu>
);
useEffect(()=>{
@ -34,7 +45,7 @@ function Main(props){
getMainList();
} else {
getCateList();
}
}
getHotList();
}
},[id])
@ -109,7 +120,17 @@ function Main(props){
<div>
<div className="main_content">
<div className="boxmain">
<p className="in_title">{ tempConfig[temp].mainTitle }</p>
<div className='titleButBox'>
<p className="in_title">{tempConfig[temp].mainTitle}</p>
{role && role.role !== "None" && <div className='createButs mt10'>
{role.role === "Member" && <Button type="primary" ghost className='mr20'>
<Link to={`/zone/${deptId}/news/self`}>我的文章</Link>
</Button>}
{role.role === "Member" ? <Dropdown overlay={menu}>
<Button icon='plus' type="primary" ghost>新建</Button>
</Dropdown> : <Button type="primary" ghost onClick={()=>{window.open(role.docManageUrl)}}>管理资讯</Button>}
</div>}
</div>
{
mainList && mainList.length>0 &&
(

View File

@ -0,0 +1,185 @@
import React,{ useState , useEffect } from 'react';
import { Breadcrumb, Button, Form, Input, message, Select } from 'antd';
import { getZoneUrl } from 'educoder';
import MdEditor from '../../../modules/tpm/challengesnew/tpm-md-editor';
import RichEditor from '../../Component/RichEditor';
import UploadImage from '../../Team/Component/UploadImage';
import { tempConfig } from '../tempInfo';
import '../index.scss';
import { Base64 } from 'js-base64';
import { addNewsByDirId, getDirListById, getNewsDetailByAu, updateDoc } from '../api';
import { httpUrl } from '../fetch';
function NewsCreate(props){
const {history, id, role, current_user} = props;
const temp = props.temp;
const { getFieldDecorator, validateFields , setFieldsValue, setFields } = props.form;
const { deptId, id: newsId } = props.match.params;
const pathname = props.location.pathname;
const [type, setType] = useState(new URLSearchParams(props.location.search.substring(1)).get('type'));
const [value, setValue] = useState(undefined);
const [imageUrl, setImageUrl] = useState(undefined)
const [dirList, setDirList] = useState(undefined);
const [loading, setLoading] = useState(false);
const [isEdit, setIsEdit] = useState(false);
const layout = {
labelCol: { span: 2 },
wrapperCol: { span: 14 },
};
useEffect(()=>{
if((role && role.role === "None") || (current_user && !current_user.login)){
history.push(`/zone/${deptId}`);
}
}, [role])
useEffect(()=>{
id && getDircList();
},[id])
useEffect(()=>{
if(pathname){
if(pathname.endsWith("/edit")){
setIsEdit(true);
getNewsDetail();
}else{
setIsEdit(false);
}
}
},[pathname])
function getDircList(){
getDirListById(id).then(res=>{
if(res){
setDirList(res.data.rows);
}
})
}
function getNewsDetail() {
getNewsDetailByAu(newsId).then(res=>{
if(res && res.data){
const {name, summary, cmsDir, content, headImg, editorType} = res.data.data;
setType(editorType === "rich_editor" ? 'richEditor' : 'mdEditor')
setFieldsValue({
name,
summary,
dirId: cmsDir && cmsDir.id,
content: Base64.decode(content),
headImg
});
setImageUrl(headImg);
setValue(Base64.decode(content));
}
})
}
//
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
const hasContent = type === "richEditor" && fieldsValue.content === "<p><br></p>"
if(err || hasContent){
hasContent && setFields({content: {value:fieldsValue.content,errors:[new Error('请输入内容!')]}});
return;
}
setLoading(true);
const params = {
...fieldsValue,
editorType: type === "richEditor" ? "rich_editor" : "markdown_editor",
headImg: imageUrl,
content: Base64.encode(fieldsValue.content)
}
if(isEdit){
delete params.editorType;
return updateDoc(newsId, params).then(res=>{
const {data:{code, msg}} = res;
setLoading(false);
if(code === 200){
message.success("提交成功");
history.push(`/zone/${deptId}/newdetail/${newsId}`);
}else{
message.error(msg || '提交失败,请联系管理员!')
}
}).catch(e=>{
setLoading(false);
})
}
addNewsByDirId(fieldsValue.dirId, params).then(res=>{
const {data:{code, msg, data}} = res;
setLoading(false);
if(code === 200){
message.success("提交成功");
history.push(`/zone/${deptId}/newdetail/${data}`);
}else{
message.error(msg || '提交失败,请联系管理员!')
}
}).catch(e=>{
setLoading(false);
})
})
}
return(
<div className='boxmain pt30 newsCreateBox pb100'>
<Breadcrumb separator=">">
<Breadcrumb.Item href={`/zone/${deptId}/news`}>{tempConfig[temp].mainTitle}</Breadcrumb.Item>
<Breadcrumb.Item>{isEdit ? '编辑' : '新建'}文章</Breadcrumb.Item>
</Breadcrumb>
<div className='formBox'>
<Form {...layout} onSubmit={submit}>
<Form.Item label="标题">
{getFieldDecorator("name", {
rules:[{ required:true,message:"请输入文章标题!" },
{type: 'string', max: 200, message: "最大长度200"},
{pattern: /^[^<>[\]/\\]+$/, message: "不能输入特殊字符:< > [ ] / \\"}]
})(
<Input placeholder="请输入文章标题" maxLength={ 200 } width="220px"/>
)}
</Form.Item>
<Form.Item label="概览">
{getFieldDecorator("summary", {
rules:[{ required:true,message:"请输入概览!" },
{type: 'string', max: 200, message: "最大长度200"}]
})(
<Input.TextArea placeholder="请输入概览" maxLength={ 200 } width="220px"/>
)}
</Form.Item>
<Form.Item label="栏目">
{getFieldDecorator("dirId", {
rules:[{ required:true,message:"请选择文章所属栏目!" }]
})(
<Select placeholder="请选择文章所属栏目">
{dirList && dirList.map(item=>{
return <Select.Option key={item.id} value={item.id}>{item.name}</Select.Option>
})}
</Select>
)}
</Form.Item>
<Form.Item label="内容" wrapperCol={{span: 21}}>
{getFieldDecorator("content",{
rules:[{required:true,message:"请输入内容!"}],
})(
type === "richEditor" ? <RichEditor uploadUrl={`${httpUrl}/file/common/upload`} videoUploadUrl={`${httpUrl}/file/common/upload?type=cms`} setValue={(value)=>{setFieldsValue({"content": value})}}/> : <MdEditor
placeholder={"请输入详细介绍"}
height={500}
className='editNewsMd'
initValue={value}
onChange={(value)=>{setFieldsValue({"content": value})}}
imageExpand={false}
></MdEditor>
)}
</Form.Item>
<Form.Item label="首图" extra="建议图片比例宽为32上传jpg、jpeg、webp、png、svg、gif、bmp格式的图片">
<UploadImage maxSize={ 5 } getImageUrl={ (url) => setImageUrl(url) } url={ imageUrl ? imageUrl : undefined} action={ getZoneUrl(`/api/cms/common/upload`) } />
</Form.Item>
<Form.Item wrapperCol={{ offset: 2 }}>
<Button className='mr20 themeBut' onClick={()=>{history.go(-1)}}>取消</Button>
<Button type="primary" htmlType="submit" loading={loading} className='themeBut'>提交</Button>
</Form.Item>
</Form>
</div>
</div>
)
}
export default Form.create()(NewsCreate);

View File

@ -1,5 +1,5 @@
import React,{ useState , useEffect } from 'react';
import { Breadcrumb , Divider, Spin } from 'antd';
import { Badge, Breadcrumb , Divider, Spin } from 'antd';
import liulan from '../img/liulan.png';
import RenderHtml from '../../../components/render-html';
import { Base64 } from 'js-base64';
@ -7,7 +7,7 @@ import { getNewsDetail } from '../api';
import { IsPC } from 'educoder';
import { tempConfig } from '../tempInfo';
import Acce from '../Component/mobile/accessory';
import { addMeta, setSeoMeta } from 'educoder';
function NewsDetail(props){
@ -44,6 +44,23 @@ function NewsDetail(props){
}
},[id])
useEffect(()=>{
//
if(detail){
const { name, cmsDir, summary} = detail;
document.title = `${ name }/${ cmsDir.name }`;
addMeta('Keywords', `${ name },${ cmsDir.name },${ summary }`);
}
return componentWillUnmount
}, [detail])
function componentWillUnmount() {
if (zonedetail) {
document.title= zonedetail.name;
setSeoMeta(`${zonedetail.name},`, zonedetail.name, zonedetail.subTitle, `/zone/${deptId}`)
}
}
function getDetails(){
setIsSpin(true)
getNewsDetail(id).then(result=>{
@ -90,7 +107,9 @@ function NewsDetail(props){
<ul className="i_ul_value">
{ cmsDir && <li><a href={ detail.publishUserUrl } ><img src={ detail.publishUserImage } alt="" className="headimg" /> { detail.publishUserNickname }</a></li> }
{ cmsDir && <li>{ detail.isFirstPublish ? '创建于' : '更新于' }{detail.publishTime}</li> }
{ detail.visits && <li><img src={liulan} alt="" className="mr5" />{detail.visits}</li> }
{ !!detail.visits && <li><img src={liulan} alt="" className="mr5" />{detail.visits}</li> }
{ detail.auditStatus === '2' && <li><Badge color={temp === "zone" ? "#466aff" : "#089f7f"} text="待审核" className='pass'/></li>}
{ detail.auditStatus === '0' && <li><Badge color="#eb1212" text="未通过" className='failed'/></li>}
</ul>
</div>
{ !IsPC() && <Divider dashed={true} /> }

View File

@ -0,0 +1,197 @@
import React,{ useState, useEffect } from 'react';
import { Breadcrumb , Spin , Pagination , Modal, Button } from 'antd';
import { Link } from 'react-router-dom';
import { getNewsMyList , getSourceMyList , delNewsMyList , delSourceMyList } from '../api';
import moment from 'moment';
import nodata from '../img/nodata.png';
import { tempEnum } from '../tempInfo';
import emptydata from '../../Images/nodata.png';
import '../index.scss';
function SelfList(props){
const [ status , setStatus ] = useState(1);
const [ isSpin , setIsSpin ] = useState(true);
const [ source , setSource ] = useState(undefined);
const [ list , setList ] = useState(undefined);
const [ page , setPage ] = useState(1);
const [ total , setTotal ] = useState(0);
const [ visible ,setVisible ] = useState(false);
const [ deleId , setDeleId] = useState(undefined);
const pathname = props.location.pathname;
const { deptId} = props.match.params;
const { id , temp , data } = props;
const limit = 15;
useEffect(()=>{
if(id){
setIsSpin(true);
getFunc(source);
}
},[id,source,page])
useEffect(()=>{
if(page===1){
setIsSpin(true);
getFunc(source);
}else{
setPage(1);
}
},[status])
function getFunc(s){
if(s===true){
getSource();
}
if(s===false){
getNew();
}
}
useEffect(()=>{
if(pathname){
if(pathname.indexOf("/source/self")>-1){
setSource(true);
}else{
setSource(false);
}
}
},[pathname])
function getSource(){
getSourceMyList({
auditStatus:status,
zoneId:id,
pageSize:limit,
pageNum:page
}).then(result=>{
let rows = result.data && result.data.rows;
if(rows && rows.length>0){
setList(rows);
}else{
setList([]);
}
setTotal(result.data.total);
setIsSpin(false);
})
}
function getNew(){
getNewsMyList(id,{
auditStatus:status===0 ? 2 : status === 2 ? 0 : 1,
pageSize:limit,
pageNum:page
}).then(result=>{
let rows = result.data && result.data.rows;
if(rows && rows.length>0){
setList(rows);
}else{
setList([]);
}
setTotal(result.data.total);
setIsSpin(false);
})
}
function cancelFunc(){
setVisible(false);
setDeleId(undefined);
}
function sureFunc(){
if(source){
delSourceMyList(deleId).then(response=>{
setVisible(false);
props.showNotification("删除成功!");
getSource();
})
}else{
delNewsMyList(deleId).then(response=>{
setVisible(false);
if(status ===1){
props.showNotification("提交成功!");
}else{
props.showNotification("删除成功!");
getNew();
}
})
}
}
return(
<div className="selfBoxContent">
<Modal
visible={visible}
width="456px"
title={`申请删除${source?"资源":"文章"}`}
onCancel={cancelFunc}
footer={null}
className="delSModal"
>
<div className="delBoxCon">
<i className="iconfont icon-shanchu_tc_icon font-22 mr15" style={{color:"#ca0002"}}></i>
<div>
<p className="font-15 mb15 mt5" style={{color:"#202d40"}}>确定删除该{source?"资源":"文章"}?</p>
{(source || (!source && status === 1 && (data && data.docNeedAudit===1))) && <p style={{color:"#5f6872"}}>{source?"删除后所有资源文件将被清除,请谨慎操作":"此操作将提交删除申请,管理员同意申请后该文章将被删除"}</p> }
</div>
</div>
<div className="delBoxBtn">
<Button onClick={cancelFunc} style={{borderColor:"#d9d9d9",color:"rgba(0, 0, 0, 0.65)"}}>取消</Button>
<Button type="primary" onClick={sureFunc} style={{backgroundColor:"#fff"}}>确认</Button>
</div>
</Modal>
<Breadcrumb separator=">" style={{paddingTop:"20px"}}>
<Breadcrumb.Item><Link className="primaryColor" to={pathname.replace("/self","")}>{source ? "资源发布": temp === tempEnum.zone1 ? "新闻资讯":"领域资讯"}</Link></Breadcrumb.Item>
<Breadcrumb.Item>我的{source ? "资源":"文章"}</Breadcrumb.Item>
</Breadcrumb>
<ul className="selfMenu">
<li onClick={()=>setStatus(1)} className={status===1?"active":""}>已发布</li>
<li onClick={()=>setStatus(0)} className={status===0?"active":""}>待审核</li>
<li onClick={()=>setStatus(2)} className={status===2?"active":""}>未通过</li>
</ul>
<Spin spinning={isSpin}>
<div style={{backgroundColor:"#fff",borderRadius:"4px"}}>
<ul className="selfListPanel">
{
list && list.length === 0 &&
<div style={{display:"flex",justifyContent:"center",alignItems:"center",flexDirection:"column",height:500}}>
<img src={ temp === tempEnum.zone1 ? nodata : emptydata } alt="" width="300"/>
<p className="font-15 color-grey-6 mt20">暂无数据~</p>
</div>
}
{
list && list.length > 0 && list.map((i,k)=>{
return(
<li>
<div>
<Link to={`/zone/${deptId}/${source?"source":"newdetail"}/${i.id}`} className="s-name task-hide">{i.name}</Link>
<div className="s-info">
<span>
<span>{i.domainName || i.dirName}</span>
<span className="ml10 mr15">|</span>
<span><i className="iconfont icon-a-31shijian mr5 font-15"></i>{moment(i.createTime).format("YYYY/MM/DD")}</span>
</span>
<span className="s-info-right">
<a href={`/zone/${deptId}/${source ? 'source' : 'news'}/${i.id}/edit`} style={{color:"#466aff"}}><i className="iconfont icon-a-bianji12 mr5 font-14"></i>编辑</a>
<a style={{color:"#d81017",marginLeft:"25px"}}onClick={()=>{setVisible(true);setDeleId(i.id)}}><i className="iconfont icon-fuzhi-shanchu mr5 font-14"></i>删除</a>
</span>
</div>
</div>
</li>
)
})
}
</ul>
{
total > limit &&
<div style={{padding:"20px",textAlign:"right"}}>
<Pagination showQuickJumper current={page} total={total} pageSize={limit} onChange={(p)=>{setPage(p);window.scrollTo(0,450)}}/>
</div>
}
</div>
</Spin>
</div>
)
}
export default SelfList;

View File

@ -1,5 +1,5 @@
import React , { useEffect , useState } from 'react';
import { Input , Pagination, Spin, Tag } from 'antd';
import { Button, Input , Pagination, Spin, Tag } from 'antd';
import SourceIcon from '../img/sourceIcon.png';
import { Link } from 'react-router-dom';
import axios from 'axios';
@ -23,8 +23,7 @@ function Source(props){
const [ search , setSearch ] = useState(undefined);
const [ isSpin , setIsSpin ] = useState(true);
const limit = 20;
const { id , temp } = props;
const { id , temp, history, role } = props;
useEffect(()=>{
id && getLists();
@ -70,12 +69,20 @@ function Source(props){
setPage(1);
}
function addSource(){
if(role && role.role === "Manager"){
window.open(role.resourceManageUrl);
}else{
history.push(`/zone/${deptId}/source/add`)
}
}
return(
<div className={`${temp}_source_box`}>
<div className="source_h">
<p className="in_title">资源发布</p>
<div style={{marginTop:"30px",textAlign:"center"}}>
<Search
<div style={{marginTop:"30px",textAlign:"center"}} className='titleButBox'>
<Search
placeholder="请输入资源名称"
onSearch={onSourceSearch}
style={{width:'645px',borderColor:"#fff",boxShadow:"0px 0px 10px rgba(61, 85, 183, 0.1)"}}
@ -84,6 +91,12 @@ function Source(props){
allowClear
enterButton={temp === "zone" ? <span><i className="iconfont icon-sousuo5 font-15"/>搜索</span> :undefined}
/>
{role && role.role !== "None" && <div className='createButs'>
{role.role === "Member" && <Button size='large' type="primary" ghost className='mr20'>
<Link to={`/zone/${deptId}/source/self`}>我的资源</Link>
</Button>}
<Button size='large' icon={role.role === "Member" && 'plus'} type="primary" ghost onClick={addSource}>{role.role === "Member" ? '新建' : '管理资源'}</Button>
</div>}
</div>
</div>
<div className="sources">

View File

@ -0,0 +1,241 @@
import React,{ useState , useEffect } from 'react';
import { Breadcrumb, Button, Form, Icon, Input, message, Select, Upload } from 'antd';
import { getZoneUrl } from 'educoder';
import '../index.scss';
import { Link } from 'react-router-dom';
import { getTypeByFileId, getSourceZoneList, addResource, getSourceDetailByAu, updateMyResource } from '../api';
function SourceCreate(props){
const {history, id, role, current_user} = props;
const { getFieldDecorator, validateFields , setFieldsValue, setFields } = props.form;
const { deptId, sourceid} = props.match.params;
const pathname = props.location.pathname;
const [ zoneList , setZoneList ] = useState(undefined);
const [files, setFiles] = useState([]);
const [loadingBut, setLoadingBut] = useState(false);
const [isEdit, setIsEdit] = useState(false);
const layout = {
labelCol: { span: 2 },
wrapperCol: { span: 14 },
};
useEffect(()=>{
if((role && role.role === "None") || (current_user && !current_user.login)){
history.push(`/zone/${deptId}`);
}
}, [role])
useEffect(()=>{
id && getZoneList();
},[id])
useEffect(()=>{
if(pathname){
if(pathname.endsWith("/edit")){
setIsEdit(true);
getSourceDetail();
}else{
setIsEdit(false);
}
}
},[pathname])
function getSourceDetail() {
getSourceDetailByAu(sourceid).then(res=>{
if(res && res.data){
const {name, domainId, summary, fileList} = res.data.data;
const fileMap = fileList.map(item=>{
const {fileId, fileSizeInfo, fileOriginName, zoneResourceType: {id, name}} = item;
return {
uid: fileId,
status: "done",
response: {fileId: fileId, fileSize: fileSizeInfo},
name: fileOriginName,
zoneResourceType:{id, name}
}
})
setFiles(fileMap);
setFieldsValue({
name,
domainId,
summary,
fileList: fileMap
});
}
})
}
function getZoneList(){
getSourceZoneList(id).then(response=>{
if(response && response.data){
setZoneList(response.data.rows);
}
}).catch(error=>{})
}
// /
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err || !files.length){
!files.length && setFields({fileList: {value:undefined,errors:[new Error('请上传资源附件!')]}});
return;
}
const fileIds = files.map(item=>{return item.response.fileId}).toString();
const params = {
...fieldsValue,
zoneId: id,
fileIds
}
delete params.fileList;
if(isEdit){
delete params.zoneId;
params.id= parseInt(sourceid, 10);
updateMyResource(params).then((res)=>{
const {data:{code, msg}} = res;
if(code === 200){
message.success("提交成功");
history.push(`/zone/${deptId}/source/${sourceid}`);
}else{
message.error(msg || '提交失败,请联系管理员!')
}
})
return;
}
addResource(params).then((res)=>{
const {data:{code, msg, data}} = res;
if(code === 200){
message.success("提交成功");
history.push(`/zone/${deptId}/source/${data.id}`);
}else{
message.error(msg || '提交失败,请联系管理员!')
}
})
})
}
function beforeUpload(file){
if (file.size > 200 * 1024 * 1024) {
message.error('单个文件限制200MB以内');
return false;
}
if(files){
const sameNameFIles = files.filter(item=>item.name === file.name);
if(sameNameFIles.length >= 1){
message.error("请不要重复上传同名文件!");
return false;
}
}
return true;
}
async function onChange({file, fileList: fileListByUpload}){
if(file && (file.status === "done" || file.status === 'uploading' || file.status === 'removed')){
setLoadingBut(true);
const fileList = [...files];
const index = fileList.findIndex(item=>item.uid === file.uid);
if(index !== -1){
fileList[index] = file;
}else{
fileList.push(file);
}
setFiles(fileList);
if(!file.response) return
if(file.response.code !== 200){
message.error(file.response.msg || '上传失败,请联系管理员!')
fileList.pop()
setFiles([...fileList]);
setLoadingBut(false);
}else{
//
await getTypeByFileId(id, file.response.fileId).then((res)=>{
const {data:{code, data: data1, msg}} = res;
if(code !== 200){
return message.error(msg);
}
fileList[fileList.length-1].zoneResourceType = {
id: data1.id,
name: data1.name
}
})
setFiles([...fileList]);
setLoadingBut(false);
}
}
}
function deleteFile(uid){
const fileList = files.filter(item=>item.uid !== uid);
setFiles(fileList);
}
return(
<div className='boxmain pt30 newsCreateBox pb100 sourceCreateBox'>
<Breadcrumb separator=">">
<Breadcrumb.Item><Link className="primaryColor" to={`/zone/${deptId}/source`}>资源列表</Link></Breadcrumb.Item>
<Breadcrumb.Item>{isEdit ? '编辑' : '新建'}资源</Breadcrumb.Item>
</Breadcrumb>
<div className='formBox'>
<Form {...layout} onSubmit={submit}>
<Form.Item label="资源名称">
{getFieldDecorator("name", {
rules:[{ required:true,message:"请输入资源名称!" },
{type: 'string', max: 200, message: "最大长度200"}]
})(
<Input placeholder="请输入资源名称" maxLength={ 200 } width="220px"/>
)}
</Form.Item>
<Form.Item label="资源领域">
{getFieldDecorator("domainId", {
rules:[{ required:true,message:"请选择资源领域!" }]
})(
<Select placeholder="请选择资源领域">
{zoneList && zoneList.map(item =>{
return <Select.Option key={item.id} value={item.id}>{item.name}</Select.Option>
})}
</Select>
)}
</Form.Item>
<Form.Item label="资源简介">
{getFieldDecorator("summary", {
rules:[{ required:true,message:"请输入资源简介!" },
{type: 'string', max: 300, message: "最大长度300"}]
})(
<Input.TextArea placeholder="请输入资源简介" maxLength={ 200 } width="220px"/>
)}
</Form.Item>
<Form.Item label="资源附件" extra={ <span >可上传多个资源文件单个文件限制200MB以内</span> }>
{getFieldDecorator("fileList", {
rules:[{ required:true,message:"请上传资源附件!" }]
})(
<Upload action={getZoneUrl(`/api/file/common/upload?type=resource`)} showUploadList={false} beforeUpload={beforeUpload} onChange={onChange} withCredentials={true} headers={{Authorization: "3e33a5b0a35824f93666b1084488f0bd5f010025"}}>
<Button className='hoverThemeColorBut'>
<Icon type="upload" />单击上传
</Button>
</Upload>
)}
</Form.Item>
{/* 附件展示 */}
<div className="sourceFilesBox">
{files && files.map((item, index)=>{
const {uid, status, response, name, zoneResourceType} = item;
return <div key={index} className="sourseFileItem mb15">
{status === "uploading" ? <Icon type="loading" className='mr10'/> : <Icon type="file" className='mr10'/>}
<span className="fileName task-hide">{name}</span>
<span className='mr20 ml20' style={{width: '100px'}}>{response && response.fileSize}</span>
<span className='mr50'>{zoneResourceType && zoneResourceType.name}</span>
<Icon type="delete" onClick={()=>{deleteFile(uid)}}/>
</div>
})}
</div>
<Form.Item wrapperCol={{ offset: 2 }}>
<Button className='mr20 themeBut' onClick={()=>{history.go(-1)}}>取消</Button>
<Button type="primary" htmlType="submit" loading={loadingBut} className='themeBut'>提交</Button>
</Form.Item>
</Form>
</div>
</div>
)
}
export default Form.create()(SourceCreate);

View File

@ -1,19 +1,19 @@
import React , { useEffect , useState } from 'react';
import { Breadcrumb } from 'antd';
import { Badge, Breadcrumb } from 'antd';
import SourceFile from '../img/sourceFile.png';
import RenderHtml from '../../../components/render-html';
import { getSourceDetail } from '../api';
import { Link } from 'react-router-dom';
import axios from 'axios';
import { Base64 } from 'js-base64';
import { httpUrl } from '../fetch';
import { AlignCenter, FlexAJ } from '../../Component/layout';
import SourceIcon from '../img/sourceIcon.png';
import { addMeta, setSeoMeta } from 'educoder';
function SourceDetail(props){
const { deptId , sourceid } = props.match.params;
const [ detail , setDetail ] = useState(undefined);
const zonedetail = props.data;
const {temp} = props;
useEffect(()=>{
if(sourceid){
@ -21,6 +21,23 @@ function SourceDetail(props){
}
},[sourceid])
useEffect(()=>{
//
if(detail){
const { name, domainName, summary} = detail;
document.title = `${ name }/${ domainName }`;
addMeta('Keywords', `${ name },${ domainName },${ summary }`);
}
return componentWillUnmount
}, [detail])
function componentWillUnmount() {
if (zonedetail) {
document.title= zonedetail.name;
setSeoMeta(`${zonedetail.name},`, zonedetail.name, zonedetail.subTitle, `/zone/${deptId}`)
}
}
function getDetails(){
getSourceDetail(sourceid).then(response=>{
if(response){
@ -50,7 +67,9 @@ function SourceDetail(props){
</FlexAJ>
{detail.createUser && detail.createUser.avatar && <Link to={`/${detail.createUser.userName}`}><img alt='' src={detail.createUser.avatar} className='publicUserAvatar mr10'/></Link>}
{detail.createUser && <Link className='mr20' to={`/${detail.createUser.userName}`}>{detail.createUser.nickName}</Link>}
<span>发布于{detail.updateTime || detail.createTime}</span>
<span className='mr20'>发布于{detail.updateTime || detail.createTime}</span>
{detail.auditStatus === '0' && <Badge color={temp === "zone" ? "#466aff" : "#089f7f"} text="待审核" className='pass'/> }
{detail.auditStatus === '2' && <Badge color="#eb1212" text="未通过" className='failed'/> }
</div>
<div className='pb20'>{detail.summary}</div>
{

View File

@ -7,6 +7,7 @@ import { tempConfig, tempEnum } from '../tempInfo'
import MemberApply from '../Component/memberApply';
import { getVIPLists, getAuditStatus, applyJoin } from '../api';
import nodata from '../img/nodata.png';
import MemberList from '../Component/memberList';
import '../indexZone1.scss';
@ -43,7 +44,6 @@ function ZoneVIP(props){
function getMemberStatus() {
getAuditStatus(id).then(res => {
console.log(res)
if (res && res.data && res.data.code === 200) {
setMemberStatus(res.data.data)
} else {
@ -83,58 +83,9 @@ function ZoneVIP(props){
<Spin spinning={isSpin}>
<div style={{minHeight:400}}>
{
vipLists && vipLists.length> 0 &&
<div className="vip_list">
{
vipLists.map((i,k)=>{
return(
<div className="vip_list_card">
<div className="card_title">
<img src={Crown} alt="" width="37px" style={{marginBottom:5,marginRight:5}}/>
<div>
<div>
{i.typeName}
</div>
{ temp === tempEnum.zone1 && <div className="linear_gradient">{i.typeName}</div> }
</div>
</div>
<p className="card_desc">{i.typeIntroduction}</p>
{
i.zoneMemberList && i.zoneMemberList.length > 0 ?
<ul className="card_ul">
{
i.zoneMemberList.map((j,key)=>{
return(
<li className="card_u_li">
<div className="card_u_wrap">
<Link to={`/${j.login}`} ><img src={j.imageUrl} alt="" /></Link>
<div className="card_u_info">
<div className="card_u_up"><span className="card_name task-hide">{j.name}</span>{j.memberLevel && <span className="card_tag">{j.memberLevel}</span> }</div>
<p className="card_u_down task-hide-2">{j.introduction}</p>
</div>
</div>
</li>
)
})
}
</ul>
:
(
temp === tempEnum.zone1 ? <Nodata _html="暂无数据" img={ nodata }/> : <Nodata _html="暂无数据" />
)
}
</div>
)
})
}
</div>
}
{
vipLists && vipLists.length === 0 &&
<div style={{marginTop:30,backgroundColor:"#fff",padding:"20px"}}>
vipLists && (vipLists.length > 0 ? <MemberList vipLists={vipLists} temp={temp}/> : <div style={{marginTop:30,backgroundColor:"#fff",padding:"20px"}}>
<Nodata _html={"暂无数据"}/>
</div>
</div>)
}
</div>
</Spin>

View File

@ -17,6 +17,13 @@ export function getCheckZoneRole(id) {
});
}
export function getCurrentRole(id) {
return fetch({
url: `/zone/zoneFront/${id}/checkCurrentRole`,
method: 'get',
});
}
/********首页接口******/
export function getHomePageList(id) {
return fetch({
@ -60,6 +67,19 @@ export function getNewsDetail(id){
method: 'get',
})
}
export function getNewsMyList(id,params){
return fetch({
url:`/cms/doc/zone/${id}/myDocList`,
method:"get",
params
})
}
export function delNewsMyList(id){
return fetch({
url:`/cms/doc/${id}`,
method:"delete"
})
}
/**开源项目 */
export function getProjectsLists(id,params){
@ -76,10 +96,11 @@ export function getProjectsTypeLists(id){
})
}
/**专区会员 */
export function getVIPLists(id){
export function getVIPLists(id, params){
return fetch({
url:`/zone/open/${id}/member/overviewList`,
method: 'get',
params
})
}
@ -101,6 +122,13 @@ export function applyJoin(id,data) {
}
// 资源模块
export function addResource(data) {
return fetch({
url: `/zone/zoneFront/addResource`,
method: 'post',
data: data
});
}
export function getSourceTypeList(id){
return fetch({
url:`/zone/open/${id}/resourceType/list`,
@ -125,4 +153,103 @@ export function getSourceDetail(id){
url:`/zone/open/resource/detail/${id}`,
method: 'get'
})
}
export function getSourceMyList(params){
return fetch({
url:`/zone/zoneFront/myResourceList`,
method:"get",
params
})
}
export function delSourceMyList(id){
return fetch({
url:`/zone/zoneFront/removeResource/${id}`,
method:"delete"
})
}
// 创建专区
export function postCreateZone(data) {
return fetch({
url: `/zone/application`,
method: 'post',
data: data
})
}
// 根据文件id获取专区下资源类别
export function getTypeByFileId(zoneId, fileId){
return fetch({
url:`/zone/resourceType/zone/${zoneId}/getResourceTypeByFileId/${fileId}`,
method: 'get'
})
}
// 新增领域
export function addSourceType(data) {
return fetch({
url: `/zone/resourceType`,
method: 'post',
data: data
});
}
// 修改当前文件对应的资源类别
export function updateResourceTypeByFileId(data) {
return fetch({
url: '/zone/resourceType/updateResourceTypeByFileId',
method: 'PUT',
data: data
});
}
// 获取文章领域列表
export function getDirListById(id){
return fetch({
url:`/cms/doc/open/zone/${id}/dirList`,
method: 'get'
})
}
// 新增文章
export function addNewsByDirId(dirId, data) {
return fetch({
url: `/cms/doc/dir/${dirId}`,
method: 'post',
data: data
});
}
// 获取文章详细信息(需要用户权限)
export function getNewsDetailByAu(id){
return fetch({
url:`/cms/doc/${id}`,
method: 'get',
})
}
// 修改文章详细信息(需要用户权限)
export function updateDoc(id, data) {
return fetch({
url: `/cms/doc/${id}`,
method: 'PUT',
data: data
});
}
// 获取资源详细信息(需要用户权限)
export function getSourceDetailByAu(id){
return fetch({
url:`/zone/zoneFront/myResource/${id}`,
method: 'get',
})
}
// 修改资源详细信息(需要用户权限)
export function updateMyResource(data) {
return fetch({
url: `/zone/zoneFront/editResource`,
method: 'PUT',
data: data
});
}

View File

@ -8,7 +8,7 @@ import { CNotificationHOC } from "../../modules/courses/common/CNotificationHOC"
import { TPMIndexHOC } from "../../modules/tpm/TPMIndexHOC";
import Loadable from "react-loadable";
import Loading from "../../Loading";
import { getMainInfos , getCheckZoneRole } from './api';
import { getMainInfos , getCheckZoneRole, getCurrentRole } from './api';
import PublicBanner from "./Component/publicBanner";
import './index.scss';
import { IsPC } from 'educoder';
@ -16,19 +16,33 @@ import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { tempEnum } from "./tempInfo";
import { ImageLayerOfCommentHOC } from "../../modules/page/layers/ImageLayerOfCommentHOC";
import { setSeoMeta } from 'educoder';
const SourceDetail = Loadable({
loader: () => import("./Pages/sourceDetail"),
loading: Loading,
});
const SourceCreate = Loadable({
loader: () => import("./Pages/sourceCreate"),
loading: Loading,
});
const VIP = Loadable({
loader: () => import("./Pages/zoneVIP"),
loading: Loading,
});
const Apply = Loadable({
loader: () => import("./Pages/apply"),
loading: Loading,
});
const Source = Loadable({
loader: () => import("./Pages/source"),
loading: Loading,
});
const SourceSelfList = Loadable({
loader: () => import("./Pages/selfList"),
loading: Loading,
});
const HeaderPage = Loadable({
loader: () => import("./Pages/headerPage"),
loading: Loading,
@ -56,6 +70,10 @@ const NewsList = Loadable({
loader: () => import("./Pages/newsList"),
loading: Loading,
});
const NewsCreate = Loadable({
loader: () => import("./Pages/newsCreate"),
loading: Loading,
});
const Main = Loadable({
loader: () => import("./Pages/main"),
@ -73,11 +91,13 @@ function Index(props){
const { deptId } = props.match.params;
const [ id , setId ] = useState(undefined);
const [ temp , setTemp ] = useState(tempEnum.zone);
const [ role , setRole ] = useState(undefined);
const { pathname } = props.history.location;
const sourcedetail = pathname.indexOf(`/zone/${deptId}/newdetail/`)>-1 && IsPC();
const {current_user} = props;
useEffect(()=>{
if(deptId){
if(deptId && deptId!=="apply"){
getDetail();
}
},[deptId])
@ -107,7 +127,9 @@ function Index(props){
setId(data.id);
if(data.id){
getAdminUrl(data.id);
getRole(data.id)
}
setSeoMeta(`${data.name},`, data.name, data.subTitle, `/zone/${deptId}`)
}
}).catch(console.error())
}
@ -121,15 +143,41 @@ function Index(props){
}
}).catch(error=>{})
}
console.log(deptId)
function getRole(id){
if(current_user && current_user.login){
getCurrentRole(id).then(res=>{
setRole(res.data.data);
}).catch(error=>{})
}
}
return(
<div className="information_main">
{ !sourcedetail && (id ? <PublicBanner {...props} data={data} temp={ temp } adminUrl={adminUrl}/> : <div style={{ width: '100%', height: '450px' }}></div>)}
{ (!sourcedetail && deptId!=="apply") && (id ? <PublicBanner {...props} data={data} temp={ temp } adminUrl={adminUrl}/> : <div style={{ width: '100%', height: '450px' }}></div>)}
<Switch>
<Route
path="/zone/:deptId/news/self"
render={(p) => (
<SourceSelfList {...props} {...p} id={id} temp={ temp } data={data}/>
)}
></Route>
<Route
path="/zone/:deptId/news/add"
render={(p) => (
<NewsCreate {...props} {...p} id={id} temp={ temp } data={data} role={role}/>
)}
></Route>
<Route
path="/zone/:deptId/news/:id/edit"
render={(p) => (
<NewsCreate {...props} {...p} id={id} temp={ temp } data={data} role={role}/>
)}
></Route>
<Route
path="/zone/:deptId/news/:cateId"
render={(p) => (
<Main {...props} {...p} id={id} temp={ temp }/>
<Main {...props} {...p} id={id} temp={ temp } role={role}/>
)}
></Route>
<Route
@ -138,16 +186,34 @@ function Index(props){
<NewsDetail {...props} {...p} id={id} temp={ temp } data={data}/>
)}
></Route>
<Route
path="/zone/:deptId/source/self"
render={(p) => (
<SourceSelfList {...props} {...p} id={id} temp={ temp } data={data}/>
)}
></Route>
<Route
path="/zone/:deptId/source/add"
render={(p) => (
<SourceCreate {...props} {...p} id={id} temp={ temp } data={data} role={role}/>
)}
></Route>
<Route
path="/zone/:deptId/source/:sourceid/edit"
render={(p) => (
<SourceCreate {...props} {...p} id={id} temp={ temp } role={role}/>
)}
></Route>
<Route
path="/zone/:deptId/source/:sourceid"
render={(p) => (
<SourceDetail {...props} {...p} id={id} temp={ temp }/>
<SourceDetail {...props} {...p} id={id} temp={ temp } data={data}/>
)}
></Route>
<Route
path="/zone/:deptId/source"
render={(p) => (
<Source {...props} {...p} id={id} temp={ temp }/>
<Source {...props} {...p} id={id} temp={ temp } role={role}/>
)}
></Route>
<Route
@ -165,7 +231,7 @@ function Index(props){
<Route
path="/zone/:deptId/news"
render={(p) => (
<Main {...props} {...p} id={id} temp={ temp }/>
<Main {...props} {...p} id={id} temp={ temp } role={role}/>
)}
></Route>
{data && data.helperShow === 1 && <Route
@ -174,6 +240,12 @@ function Index(props){
<Help {...props} {...p} id={id} temp={ temp } data={data} adminUrl={adminUrl}/>
)}
></Route>}
<Route
path="/zone/apply"
render={(p) => (
<Apply {...props} {...p} id={id} temp={ temp }/>
)}
></Route>
<Route
path="/zone/:deptId"
render={(p) => (

View File

@ -15,6 +15,26 @@
}
}
// 公共样式
.themeBut{
border-color: var(--primary-color);
color: var(--primary-color);
&:hover, &:active, &:focus{
border-color: var(--light-color);
color: var(--light-color);
}
&.ant-btn-primary{
color: white;
background-color: var(--primary-color);
}
}
.hoverThemeColorBut{
&:hover, &:active, &:focus{
border-color: var(--light-color);
color: var(--light-color);
}
}
.information_main{
background-color:#f3f5f8;
min-height: 100vh;
@ -554,7 +574,7 @@
align-items: center;
height: 20px;
overflow: visible;
color: #466aff;
color: var(--primary-color);
}
.headimg {
width: 26px;
@ -1521,6 +1541,211 @@
}
}
}
// 新增文章/资源
.titleButBox{
width: 1200px;
margin: 0 auto;
position: relative;
.createButs{
position: absolute;
right: 0;
top: 0;
button{
border-color: var(--primary-color);
color: var(--primary-color);
&:hover, &:active, &:focus{
border-color: var(--light-color);
color: var(--light-color);
}
}
}
}
// 新增文章
.newsCreateBox{
.formBox{
background-color: #fff;
border-radius: 4px;
padding: 30px 35px 50px;
}
}
.editNewsMd .editormd-dialog-header{
padding: 5px 20px;
}
.editNewsMd .number-input{
width: 60px !important;
}
// 新增资源
.sourceCreateBox{
.sourceFilesBox{
margin-left: 93px;
}
.sourseFileItem{
display: flex;
align-items: center;
}
.fileName{
max-width: 400px;
display: inline-block;
}
}
.primaryColor{
color: var(--primary-color)!important;
}
}
.failed .ant-badge-status-text{
color: #eb1212;
}
.pass .ant-badge-status-text{
color: var(--primary-color);
}
// --------------新建专区
.applyBox{
background-color: #fff;
min-height: 100vh;
.applyContent{
width: 1200px;
margin:0px auto;
padding:7px 93.5px 50px;
.apply-t{
padding:18px 0px;
border-bottom: 1px solid #e0e6f5;
font-weight:700;
color:#151d40;
font-size:18px;
margin-bottom: 30px!important;
}
.apply-tips{
background:rgba(237, 242, 252, 0.72);
border:2px solid rgba(70, 106, 255, 0.1);
padding:20px;
font-size:15px;
margin-bottom: 20px;
p{
color: #424650;
margin-top: 14px;
line-height: 24px;
}
h5{
color:#000000;
font-size:15px;
}
}
}
.applyForm{
.ant-form-explain{
position: absolute;
}
}
}
.selfBoxContent{
width: 1200px;
margin:0px auto;
padding-bottom: 40px;
border-radius: 4px;
.selfMenu{
width: 540px;
margin:20px auto 25px;
background-color: #fff;
display: flex;
border-radius: 4px;
box-shadow:0px 0px 10px rgba(61, 85, 183, 0.1);
li{
flex: 1;
border-right: 1px solid rgba(141, 149, 172, 0.17);
font-size:17px;
text-align:center;
color:#3d485d;
height: 50px;
line-height: 50px;
cursor: pointer;
&:first-child{
border-radius: 4px 0px 0px 4px;
}
&:last-child{
border-right: none;
border-radius: 0px 4px 4px 0px;
}
&.active{
background-color: var(--primary-color);
color: #fff;
}
}
}
.selfListPanel{
padding:17px 0px;
background-color: #fff;
margin-top: 26px;
min-height: 400px;
li{
padding:0px 25px;
&:hover{
background-color: #f3f5f8;
.s-info-right{
display: block!important;
}
}
&>div{
border-bottom: 1px dashed rgba(170, 175, 190, 0.15);
padding:15px 0px;
.s-name{
position: relative;
color:#1f2329;
font-size:16px;
padding-left: 16px;
height: 26px;
line-height: 26px;
display: block;
&::before{
position: absolute;
left: 0px;
height: 8px;
width: 8px;
background-color: var(--primary-color);
border-radius: 50%;
top:8px;
content: "";
}
&:hover{
color: var(--primary-color);
}
}
.s-info{
display: flex;
justify-content: space-between;
width: 100%;
margin-top: 14px;
height: 20px;
color:#4c5876;
align-items: center;
.s-info-right{
display: none;
transition: 0.3s;
}
}
}
}
}
}
.delSModal{
.ant-modal-title{
text-align: left;
}
.delBoxCon{
width:334px;
display:flex;
margin:0px auto 20px;
align-items:flex-start;
}
.delBoxBtn{
text-align: center;
.ant-btn{
width: 95px;
margin:0px 20px;
&.ant-btn-primary{
color: var(--primary-color);
border-color: var(--primary-color);
}
}
}
}

View File

@ -20,19 +20,17 @@
div {
height: 100%;
.regform {
img {
position: relative;
z-index: 1;
}
}
.newsBannerBox{
height: 595px;
display: flex;
align-items: center;
align-content: center;
}
.newsImg{
width: 785px;
height: 590px;
position: relative;
z-index: 1;
}
}
}
}
@ -82,7 +80,7 @@
text-align: left;
left: 50%;
margin-left: -115px;
bottom: 20%;
bottom: 12%;
position: absolute;
display: flex !important;
z-index: 2;
@ -158,7 +156,7 @@
li {
background-color: #fff;
width: 385px;
padding: 20px !important;
padding: 22px 20px 19px;
border-radius: 4px;
margin-bottom: 25px !important;
position: relative;
@ -260,4 +258,12 @@
font-size: 32px;
text-align: center;
margin-top: 40px;
}
.memberLevel_tag{
padding: 0px 5px;
line-height: 25px;
display: inline-block;
border: 1px solid var(--primary-color);
color: var(--primary-color);
}

View File

@ -53,6 +53,12 @@ function AllMenus({owner,projectsId,chooseFunc,update,defaultNames,defaultIds,op
// Ids\namesundefined
setIds({author_id:undefined,issue_tag_ids:undefined,issue_priorities_id:undefined,milestone_id:undefined,sort_by:undefined,status_id:undefined,assigner_id:undefined});
setNames({author_name:undefined,issue_tag_name:undefined,issue_priorities_name:undefined,milestone_name:undefined,sortby_name:undefined,status_name:undefined,assigner_name:undefined});
},
setChooseTag: (info) => {
let copy = {...ids,issue_tag_ids:[info.id].join(",")};
let copyname = { ...names,issue_tag_name:info.name};
setIds(copy);setNames(copyname);
chooseFunc(copy,copyname);
}
}))
//

View File

@ -7,7 +7,7 @@ import { Tooltip } from 'antd';
// issue
function Datas({checkbox ,item , projectsId,owner}){
function Datas({checkbox ,item , projectsId,owner,chooseTagFunc}){
function statusTag(name){
switch (name) {
@ -40,7 +40,7 @@ function Datas({checkbox ,item , projectsId,owner}){
item.tags && item.tags.length>0?
item.tags.map((i,k)=>{
return(
<span style={{backgroundColor: `${i.color}`}} className="ml8 tagscolor task-hide" title={i.name}>{i.name}</span>
<span onClick={()=>chooseTagFunc(i)} style={{backgroundColor: `${i.color}`,cursor:"pointer"}} className="ml8 tagscolor task-hide" title={i.name}>{i.name}</span>
)
})
:""

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -340,6 +340,10 @@ function List(props){
setBegin(value[0] || '');
setEnd(value[1] || '');
}
function chooseTagFunc(taginfo){
menuRef.current && menuRef.current.setChooseTag(taginfo);
}
return(
<div>
<DelBox visible={visible} onCancel={()=>setVisible(false)} onSuccess={onSuccess}/>
@ -439,6 +443,7 @@ function List(props){
item={item}
owner={owner}
projectsId={projectsId}
chooseTagFunc={chooseTagFunc}
/>
)
})

View File

@ -501,22 +501,30 @@ class Detail extends Component {
// 同步镜像
synchronismMirror = () => {
const { platform } = this.state;
if (!platform) return;
const { projectsId, owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/sync_mirror.json`;
axios.post(url).then(result => {
if (result && result.data && result.data.status === 0) {
this.setState({
secondSync: true
})
this.canvasChannel(true);
} else {
this.props.showNotification("镜像同步失败!");
}
}).catch(error => {
console.log(error);
})
const { isManager } = this.state;
const { current_user , showLoginDialog } = this.props;
if(!(current_user && current_user.login)){
showLoginDialog();
return
}
if((current_user && current_user.admin) || isManager){
const { platform } = this.state;
if (!platform) return;
const { projectsId, owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/sync_mirror.json`;
axios.post(url).then(result => {
if (result && result.data && result.data.status === 0) {
this.setState({
secondSync: true
})
this.canvasChannel(true);
} else {
this.props.showNotification("镜像同步失败!");
}
}).catch(error => {
console.log(error);
})
}
}
textFunc = (forked_from_project_id, fork_info) => {
@ -580,7 +588,7 @@ class Detail extends Component {
firstSync ? "" :
<span className="df">
{
((current_user && current_user.admin) || isManager) && (projectDetail && projectDetail.type && projectDetail.type === 2) ?
(projectDetail && projectDetail.type && projectDetail.type === 2) ?
<a className="synchronism ml30" onClick={this.synchronismMirror}>同步镜像</a> : ""
}
<span className="detail_tag_btn">

View File

@ -6,7 +6,7 @@ import Datas from '../Issues/Component/datas';
import axios from 'axios';
import { FlexAJ } from '../Component/layout';
import CheckProfile from '../Component/ProfileModal/Profile';
import emp from '../../forge/Issues/Img/emp.png';
import millestones from '../../forge/Issues/Img/millestones-big.png';
import './milepost.scss';
import './order.scss';
import '../Issues/index.scss';
@ -222,7 +222,12 @@ function MilepostDetail(props){
</div>
</div>
{
total === 0 && <div className="milestonesNoDate"><img src={emp} alt=""/></div>
total === 0 &&
<div className="listempty">
<img src={millestones} style={{width:"68px"}} alt="" />
<p className="font-22 mt5 mb10">欢迎使用里程碑</p>
<p className="font-15">里程碑用于集中归类管理项目的疑修及进度在使用之前您可以先<CheckProfile {...props} checklogin sureFunc={()=>{props.history.push(`/${owner}/${projectsId}/issues/${mileId}/new`)}} className="color-blue">创建一个疑修</CheckProfile></p>
</div>
}
{
total > 0 &&

View File

@ -70,24 +70,28 @@ const Mysite = Loadable({
loader: () => import("./website/mySiteList"),
loading: Loading,
});
const CreateSite = Loadable({
loader: () => import("./website/createSite"),
loading: Loading,
});
function Index(props){
const { current_user,mygetHelmetapi , checkIfLogin } = props;
const { pathname } = props.location;
const notice_url = mygetHelmetapi && mygetHelmetapi.common && mygetHelmetapi.common.notice;
const {id_card_verify, website_permission} = current_user;
const [ avatarVisible , setAvatarVisible ] = useState(false);
const [active, setActive] = useState(1);
console.log('active', active);
useEffect(()=>{
if(checkIfLogin() === false){
props.history.push('/login');
props.history.push(`/login?go_page=${pathname}`);
}
},[])
useEffect(()=>{
console.log('pathname', pathname, ["/settings/profile"].indexOf(pathname));
const pattern = /^\/settings\/installbot\/\d+$/;
if(["/settings/profile"].indexOf(pathname) !== -1){
setActive(1);
}else if(["/settings/emails", "/settings/phone", "/settings/password", "/settings/cancel"].indexOf(pathname) !== -1){
@ -100,11 +104,11 @@ function Index(props){
setActive(5);
}else if(["/settings/CLA"].indexOf(pathname) !== -1){
setActive(6);
}else if(["/settings/mybot"].indexOf(pathname) !== -1){
}else if(["/settings/mybot", "/settings/mybot/new"].indexOf(pathname) !== -1){
setActive(7);
}else if(["/settings/installbot"].indexOf(pathname) !== -1){
}else if(["/settings/installbot"].indexOf(pathname) !== -1 || pattern.test(pathname)){
setActive(8);
}else if(["/settings/mysite"].indexOf(pathname) !== -1){
}else if(["/settings/mysite", "/settings/mysite/create"].indexOf(pathname) !== -1){
setActive(9);
}
}, [pathname])
@ -154,10 +158,10 @@ function Index(props){
<li className={active === 7 ?"active":""}><Link to={`/settings/mybot`}><i className="iconfont icon-kaifabot mr5 font-18"></i><span className="text-shodow-bold">我的Bot</span></Link></li>
<li className={active === 8 ?"active":""}><Link to={`/settings/installbot`}><i className="iconfont icon-BOTpeizhi mr5 font-17"></i><span className="text-shodow-bold">Bot安装</span></Link></li>
</ul>
{/* <ul className="securityUl">
<ul className="securityUl">
<li>个人建站</li>
<li className={active === 9 ?"active":""}><Link to={`/settings/mysite`}><i className="iconfont icon-liebiaoicon mr5 font-15"></i><span className="text-shodow-bold">我的站点</span></Link></li>
</ul> */}
</ul>
</div>
<LongWidth>
<Gap>
@ -262,6 +266,13 @@ function Index(props){
<Disposition {...props} {...p}/>
)}
></Route>
{/* 创建站点 */}
{id_card_verify && website_permission && <Route
path="/settings/mysite/create"
render={(p) => (
<CreateSite {...props} {...p}/>
)}
></Route>}
{/* 个人建站 */}
<Route
path="/settings/mysite"

View File

@ -111,4 +111,9 @@
border-color: rgba(230, 126, 34, 1);
color: rgba(230, 126, 34, 1);
}
&.error{
background-color: #fcbabe47;
border-color: #f60011;
color: #f60011;
}
}

View File

@ -0,0 +1,184 @@
import React, { useState, useEffect, Fragment } from "react";
import { Button, message, Form, Input, Select } from "antd";
import "./index.scss";
import "../../users/Material/Index.scss";
import axios from "axios";
import { AlignCenter, AlignTop } from "../../Component/layout";
import { Link } from "react-router-dom";
import { getImageUrl } from "../../../common/UrlTool";
const { TextArea } = Input;
function CreateSite(props) {
const { current_user, history, form, mygetHelmetapi:{site_page_deploy_domain} } = props;
const { getFieldDecorator, validateFieldsAndScroll, setFieldsValue } = form;
const [tag, setTag] = useState(undefined);
const [language, setLanguage] = useState(undefined);
const [themes, setThemes] = useState([]);
const [theme, setTheme] = useState(undefined);
useEffect(() => {
document.title = "创建站点";
}, []);
useEffect(()=>{
axios.get(`/site_pages/themes.json`, {params: {language_frame: language}}).then(res=>{
if(res && res.status === 200){
setThemes(res.data.themes);
setTheme(res.data.themes[0])
}
})
}, [language])
useEffect(()=>{
if(theme && theme.clone_url && theme && theme.clone_url.indexOf("/") > -1){
let arr = theme && theme.clone_url.split("/");
let first = arr[arr.length-1];
if(first.indexOf(".") > -1){
let second = first.split('.')[0];
if(!second)return;
setFieldsValue({
repository_name:second,
name:second
})
}else{
setFieldsValue({
repository_name:first
})
}
}
}, [theme])
function submit(e) {
e.preventDefault();
validateFieldsAndScroll((err, values) => {
if (!err) {
axios.post(`/projects/page_migrate.json`,{
...values,
clone_addr: theme && theme.clone_url,
theme: theme && theme.name,
user_id: current_user.user_id
}).then(res=>{
if(res && res.status === 200){
message.success("新建成功");
history.push(`/settings/mysite`);
}
})
}
});
}
return (
<Fragment>
<div className="mySites_head mb30"><Link to={`/settings/mysite`} className="font-16">我的站点 / </Link><span>新建站点</span></div>
<Form
form={form}
name="register"
className="createSiteForm"
scrollToFirstError
layout="horizontal"
labelCol={{ span: 3 }}
wrapperCol={{ span: 16 }}
onSubmit={submit}
>
<div className="formTitle font-16 mb20 ml20">站点配置</div>
<Form.Item name="站点名称" label="站点名称">
{getFieldDecorator("site_name", {
rules: [{ required: true, message: "请输入站点名称" },
{type: 'string', max: 50, min: 1, message: "长度1-50"}],
})(<Input placeholder="请输入站点名称" />)}
</Form.Item>
<Form.Item
name="站点标识"
label="站点标识"
className="Create-Form-biaoshi"
>
{getFieldDecorator("identifier", {
rules: [{ required: true, message: "请输入站点标识" },
{pattern: /^[a-zA-Z0-9]{2,100}$/, message: '长度2-100只能包含数字和字母'}],
})(
<Input
onChange={(e) => {
setTag(e.target.value);
}}
placeholder="请输入站点标识"
/>
)}
<span style={{wordBreak: 'break-all'}}>
http://{current_user.login}.{site_page_deploy_domain}/{tag}
</span>
</Form.Item>
<Form.Item name="建站工具" label="建站工具">
{getFieldDecorator("language_frame", {initialValue: 0})(
<Select style={{ width: 100 }} onChange={(value)=>{setLanguage(value)}}>
<Select.Option value={0}>hugo</Select.Option>
<Select.Option value={1}>jekyll</Select.Option>
<Select.Option value={2}>hexo</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item name="主题选择" label="主题选择" wrapperCol={{span: 20}}>
<AlignTop style={{flexWrap: 'wrap'}}>
{themes && themes.map(item=>{
const {image, name, clone_url} = item;
return <div className="mr20 themeBox" onClick={()=>{setTheme(item)}}>
<i className={`iconfont icon-wancheng ${theme && theme.clone_url === clone_url ? 'active' : ''}`}></i>
<img src={getImageUrl(image)} alt="" width="125px" height="85px"/>
<p className="task-hide" style={{maxWidth: '120px'}}>{name}</p>
</div>
})}
</AlignTop>
</Form.Item>
<div className="formTitle font-16 mb20 ml20">项目配置</div>
<div className="font-15 mb20 ml40">我们会为您生成一个项目来管理您的个人主页请配置您的项目信息</div>
<AlignCenter style={{marginLeft: '58px'}}>
<Form.Item name="拥有者" label="拥有者" style={{width: '260px'}} labelCol={{span: 6}}>
<Input
placeholder={current_user.username}
disabled
/>
</Form.Item>
{/* <span className="mb20 font-18">/</span> 0.87 */}
<Form.Item name="项目标识" label="项目标识" style={{flex: '0.83'}} labelCol={{span: 4}}>
{getFieldDecorator("repository_name", {
rules: [{ required: true, message: "请输入项目标识" },
{pattern: /^[a-zA-Z0-9][a-zA-Z0-9_.-]{2,100}[a-zA-Z0-9]$/, message: "长度2-100只能包含数字、字母、下划线、中划线、英文句号必须以数字和字母开头不能以下划线/中划线/英文句号开头和结尾"}],
})(
<Input placeholder="请输入项目标识"/>
)}
</Form.Item>
</AlignCenter>
<Form.Item
name="项目名称"
label="项目名称"
>
{getFieldDecorator("name", {
rules: [{ required: true, message: "请输入项目名称" },
{type: 'string', max: 50, min: 1, message: "长度1-50"}],
})(
<Input placeholder="请输入项目名称"/>
)}
</Form.Item>
<Form.Item name="项目简介" label="项目简介">
{getFieldDecorator("description", {
rules: [{type: 'string', max: 200, message: "长度200"}]
})(
<TextArea rows={4} />
)}
</Form.Item>
<Form.Item style={{marginLeft: '123px'}} className="mt40">
<Button type="primary" htmlType="submit" className="mr20">
创建站点
</Button>
<Button
onClick={() => {
history.push("/settings/mysite");
}}
>
取消
</Button>
</Form.Item>
</Form>
</Fragment>
);
}
export default Form.create()(CreateSite);

View File

@ -1,8 +1,33 @@
.default_head{
.mySites_head{
height: 65px;
line-height: 64px;
border-bottom: 1px solid rgba(224, 230, 245, 1);
color:#151d40;
font-size:17px;
padding-bottom:20px;
}
.createSiteForm{
.themeBox{
text-align: center;
position: relative;
cursor: pointer;
}
.icon-wancheng{
position: absolute;
right: 4px;
top: -7px;
color: #e5e3e0;
&.active{
color: #ffa13a;
}
}
}
.siteBox{
padding: 15px 0;
border-bottom: 1px dashed #e0e6f5;
.siteName{
color: #05101a;
&:active, &:hover{
color: $primary-color;
}
}
}

View File

@ -1,16 +1,88 @@
import React, { useState, useEffect, Fragment } from "react";
import { Button, Icon, message, Modal } from "antd";
import { Button, Pagination, Spin } from "antd";
import './index.scss';
import '../../users/Material/Index.scss';
import '../bot/exploit/index.scss'
import axios from "axios";
import Nodata from "../../Nodata";
import { FlexAJ } from "../../Component/layout";
function MySiteList(props){
const {current_user} = props;
const {current_user, history} = props;
const {id_card_verify, website_permission} = current_user;
const [total, setTotal] = useState(0);
const [pages, setPages] = useState([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
useEffect(()=>{
document.title='我的站点';
window.scrollTo(0,0);
}, [])
return <Fragment>
<div className="default_head">我的站点</div>
</Fragment>
useEffect(()=>{
if(id_card_verify){
setLoading(true);
//
axios.get(`/site_pages.json`, {params: {limit: 15, page: page}}).then(res=>{
if(res && res.status === 200){
const {total_count, pages} = res.data;
setPages(pages);
setTotal(total_count);
}
setLoading(false);
})
}
}, [id_card_verify, page])
return <Spin spinning={loading}>
<FlexAJ className="mySites_head">
<span>我的站点</span>
{id_card_verify && website_permission && <Button type="primary" onClick={()=>{history.push(`/settings/mysite/create`)}}>新建站点</Button>}
</FlexAJ>
{/* 站点权限被管理员关闭 */}
{id_card_verify && !website_permission && <div className="tipsBox font-15 mt20">您的站点权限被锁定请联系平台管理员</div>}
{/* 未通过实名认证 */}
{!id_card_verify && <Fragment>
<div className="tipsBox font-15 mt30">您尚未通过实名认证无法使用此服务如需使用请先进行实名认证</div>
<Button type="primary" className="mt20" onClick={()=>{history.push(`/settings/verification`)}}>前往验证</Button>
</Fragment>}
{/* 通过实名认证 */}
{id_card_verify && <Fragment>
{!loading && total === 0 ? <div className="mt30">
<div style={{marginBottom: '130px'}}>个人建站是一个免费的静态网页托管服务您可以使用 个人建站服务托管个人主页项目站点等静态网页</div>
<Nodata _html="暂无数据"/>
</div> : <div className="mt10">
{pages && pages.map(item=>{
const {owner, repo, url, state, state_description, build_state} = item
return <FlexAJ>
<div className="siteBox">
<i className="iconfont icon-cangkuyuyanicon font-16 mr5 color-grey-6"></i>
{state && build_state ? <a className={`font-16 siteName`} onClick={()=>{window.open(url)}}>{item.site_name}</a> : <span className={`font-16`}>{item.site_name}</span>}
<span className={`statusBot ml20 font-12 ${state ? build_state!== null ? build_state ? "public" : "error" : "private" : "error"}`}>{state ? build_state!== null ? build_state ? "已部署" : "部署失败" : "未部署" : '被关闭'}</span>
<div className="color-grey-6 mt8">
<span className="">主题</span> {item.theme}
<span className="ml5 mr5 color-grey-ccc">|</span>
建站工具{item.language_frame}
</div>
{!state && <div className="color-grey-6 mt5">关闭原因{state_description}</div>}
</div>
<Button className="themeCorBorBut" onClick={()=>{history.push(`/${owner}/${repo}/service/pages`)}}>去仓库</Button>
</FlexAJ>
})}
<div className="mt50" style={{textAlign: 'center'}}>
<Pagination
simple
current = {page}
pageSize={15}
onChange={(page)=>{setPage(page)}}
total = {total}
hideOnSinglePage
></Pagination>
</div>
</div>}
</Fragment>}
</Spin>
}
export default MySiteList;

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
import "./index.scss";
import Loadable from 'react-loadable';
@ -18,7 +18,22 @@ const Reposyncer = Loadable({
loader: () => import('./reposyncer'),
loading: Loading,
})
//
const Pages = Loadable({
loader: () => import('./pages'),
loading: Loading,
})
function ServerIndex(props){
const { projectDetail } = props;
const { owner , projectsId } = props.match.params;
useEffect(()=>{
// tab 3397
if(projectDetail && projectDetail.permission === ""){
props.history.push(`/${owner}/${projectsId}`);
}
}, [projectDetail])
return(
<div className="panels">
<Switch {...props}>
@ -32,6 +47,12 @@ function ServerIndex(props){
() => (<Data {...props}/>)
}
></Route>
{/* 个人建站服务 */}
{projectDetail && projectDetail.author.type === "User" && <Route path="/:owner/:projectsId/service/pages"
render={
() => (<Pages {...props}/>)
}
></Route>}
<Route path="/:owner/:projectsId/service"
render={
() => (<List {...props}/>)

View File

@ -7,7 +7,7 @@ function Main(props){
const { owner , projectsId } = props.match.params;
const [ has_trace_user , setHas_trace_user ] = useState(false);
const { current_user , resetUserInfo, projectDetail, showNotification } = props;
const { current_user , resetUserInfo, projectDetail, showNotification, isDeveloper, isManager, isReporter } = props;
useEffect(()=>{
//
@ -64,6 +64,16 @@ function Main(props){
<Link to={`/${owner}/${projectsId}/service/reposyncer`} className="btnhover">查看详情</Link>
</span>
</li> */}
{projectDetail && projectDetail.author.type === "User" && <li>
<span className="servername">
<img src={require('./img/logo.png')} alt=""/>
<a onClick={openDetail}>个人建站服务</a>
</span>
<p className="task-hide-2 serverdesc">支持HugoJekyllHexo静态网站服务</p>
<span className="serverbtn">
<Link to={`/${owner}/${projectsId}/service/pages`} className="btnhover">查看详情</Link>
</span>
</li>}
</ul>
</div>
)

View File

@ -0,0 +1,209 @@
import React, { Fragment, useEffect, useState } from 'react';
import { Spin, message, Button, Form, Input, Select, Descriptions, Alert } from 'antd';
import axios from 'axios';
import SelectBranch from '../../../forge/Branch/Select';
import './index.scss';
function Pages(props) {
const {current_user, history, form, projectDetail, defaultBranch, isManager} = props;
const { owner , projectsId } = props.match.params;
const {id_card_verify} = current_user;
const { getFieldDecorator, validateFieldsAndScroll } = form;
const [reload, setReload] = useState(undefined);
const [loading, setLoading] = useState(false);
const [pageInfo, setPageInfo] = useState(undefined);
const [tag, setTag] = useState(undefined);
const [branch, setBranch ] = useState(null);
const [building, setBuilding] = useState(false);
const [isBuild, setIsBuild] = useState(false);
const [result, setResult] = useState(undefined);
useEffect(()=>{
//
if(projectDetail){
const { author, name} = projectDetail;
document.title = `个人建站-${author.name}/${name}`;
}
}, [projectDetail])
useEffect(()=>{
if(id_card_verify){
setLoading(true);
//
axios.get(`/site_pages/x.json`, {params:{owner, repo: projectsId}}).then(res=>{
if(res && res.status === 200){
setPageInfo(res.data.data);
if(res.data.data){
const {last_build_info} = res.data.data;
last_build_info && last_build_info.length && setResult(last_build_info);
}
}
setLoading(false);
})
}
}, [id_card_verify, reload])
//
function submit(e) {
e.preventDefault();
validateFieldsAndScroll((err, values) => {
if (!err) {
axios.post(`/site_pages.json`,{
...values,
owner,
repo: projectsId
}).then(res=>{
if(res && res.status === 200){
setReload(Math.random());
message.success("创建成功");
}
})
}
});
}
//
function startBuild(){
setBuilding(true);
axios.post(`/site_pages/${pageInfo.id}/build.json`,{
owner,
repo: projectsId,
branch: branch || defaultBranch
}).then(res=>{
if(res && res.data && res.data.status === 0){
setResult(res.data.data);
}else{
message.error((res && res.data && res.data.message) || "部署失败,请稍后重试");
}
setReload(Math.random());
setIsBuild(false);
setBuilding(false);
})
}
return (
<div className='mb100'>
<div className="servertitle">
<span className="systitle">个人建站服务{!pageInfo && !loading && id_card_verify && isManager && ' - 新建站点'}</span>
</div>
{/* 未通过实名认证 */}
{!id_card_verify && <Fragment>
<div className="tipsBox font-15 mt30">您尚未通过实名认证无法使用此服务如需使用请先进行实名认证</div>
<Button type="primary" className="mt20" onClick={()=>{history.push(`/settings/verification`)}}>前往验证</Button>
</Fragment>}
{id_card_verify && isManager && <Spin spinning={loading}>
{/* 未开通 */}
{!pageInfo ? <Form
form={form}
name="register"
className="mt30"
scrollToFirstError
layout="horizontal"
labelCol={{ span: 2 }}
wrapperCol={{ span: 14 }}
onSubmit={submit}
>
<Form.Item name="站点名称" label="站点名称">
{getFieldDecorator("site_name", {
rules: [{ required: true, message: "请输入站点名称" },
{type: 'string', max: 50, min: 1, message: "长度1-50"}],
})(<Input placeholder="请输入站点名称" />)}
</Form.Item>
<Form.Item
name="站点标识"
label="站点标识"
className="Create-Form-biaoshi"
>
{getFieldDecorator("identifier", {
rules: [{ required: true, message: "请输入站点标识" },
{pattern: /^[a-zA-Z0-9]{2,100}$/, message: '长度2-100只能包含数字和字母'}],
})(
<Input
onChange={(e) => {
setTag(e.target.value);
}}
placeholder="请输入站点标识"
/>
)}
<span style={{wordBreak: 'break-all'}}>
http://{current_user.login}.kingchan.cn/{tag}
</span>
</Form.Item>
<Form.Item name="建站工具" label="建站工具">
{getFieldDecorator("language_frame", {initialValue: 0})(
<Select style={{ width: 100 }}>
<Select.Option value={0}>hugo</Select.Option>
<Select.Option value={1}>jekyll</Select.Option>
<Select.Option value={2}>hexo</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item style={{marginLeft: '100px'}} className="mt40">
<Button type="primary" htmlType="submit" className="mr20">
创建站点
</Button>
<Button
onClick={() => {
history.push(`/${owner}/${projectsId}/service`);
}}
>
取消
</Button>
</Form.Item>
</Form> : pageInfo.state ? <div className='mt30'>
{/* 已开通 */}
<Descriptions title="站点信息">
<Descriptions.Item label="站点名称">
<span onClick={()=>{pageInfo.last_build_at && window.open(pageInfo.url)}} className= {pageInfo.last_build_at && 'theme-btn'}>{pageInfo.site_name}</span>
<span className={`pageStateBox ml20 font-12 ${pageInfo.build_state ? "public" : "private"}`}>{pageInfo.build_state === null ? '未部署' : pageInfo.build_state ? "已部署" : "部署失败"}</span>
</Descriptions.Item>
<Descriptions.Item label="网站地址" span={2}><span onClick={()=>{pageInfo.last_build_at && window.open(pageInfo.url)}} className= {pageInfo.last_build_at && 'theme-btn'}>{pageInfo.url}</span></Descriptions.Item>
<Descriptions.Item label="建站工具">{pageInfo.language_frame}</Descriptions.Item>
<Descriptions.Item label="建站时间">{pageInfo.created_at}</Descriptions.Item>
{pageInfo.last_build_at && <Descriptions.Item label="上次部署时间">{pageInfo.last_build_at}</Descriptions.Item>}
</Descriptions>
{!isBuild && <Button type='primary' style={{width: '135px', height: '34px'}} className='mt20' onClick={()=>{setIsBuild(true)}}>去部署</Button>}
{isBuild && <div>
<div className='mt20 ant-descriptions-title'>部署</div>
<div style={{display: 'inline-flex'}}>
<span>部署分支</span>
<SelectBranch
repo_id={projectDetail && projectDetail.repo_id}
projectsId={projectsId}
branch={branch || defaultBranch}
changeBranch={(params)=>{setBranch(params)}}
owner={owner}
history={props.history}
tagflag={false}
branchList={projectDetail && projectDetail.branches && projectDetail.branches.list}
></SelectBranch>
<span className='color-grey-6 ml30'>选择您要部署的分支</span>
</div>
<div className='mt20'>
<Button type='primary' loading={building} onClick={startBuild}>确定</Button>
<Button type='primary' ghost onClick={()=>{setIsBuild(false)}} className='ml30'>取消</Button>
</div>
</div>}
</div> : <div className='mt30'>
{/* 站点权限被关闭 */}
{pageInfo && <Alert type="error" message={`您的个人站点 ${pageInfo.site_name} 已被关闭,关闭原因为:${pageInfo.state_description}`}></Alert>}
</div>}
{result && <div className='mt30'>
<div className='mt20 ant-descriptions-title'>部署结果</div>
<div className='buildResult'>{result.map(item=> <p>{item}</p>)}</div>
</div>}
</Spin>}
{!isManager && <div className='mt30'>
{/* 非管理员权限 */}
{pageInfo && pageInfo.state && <Descriptions title="站点信息">
<Descriptions.Item label="站点名称"><span onClick={()=>{pageInfo.last_build_at && window.open(pageInfo.url)}} className= {pageInfo.last_build_at && 'theme-btn'}>{pageInfo.site_name}</span></Descriptions.Item>
<Descriptions.Item label="网站地址" span={2}><span onClick={()=>{pageInfo.last_build_at && window.open(pageInfo.url)}} className= {pageInfo.last_build_at && 'theme-btn'}>{pageInfo.url}</span></Descriptions.Item>
<Descriptions.Item label="建站工具">{pageInfo.language_frame}</Descriptions.Item>
<Descriptions.Item label="建站时间">{pageInfo.created_at}</Descriptions.Item>
{pageInfo.last_build_at && <Descriptions.Item label="上次部署时间">{pageInfo.last_build_at}</Descriptions.Item>}
</Descriptions>}
</div>}
</div>
)
}
export default Form.create()(Pages);

View File

@ -0,0 +1,27 @@
.buildResult{
padding: 10px 20px;
background-color: rgb(20, 20, 20);
color: white;
}
.theme-btn{
color: $primary-color;
cursor: pointer;
}
.pageStateBox{
border: 1px solid $primary-color;
border-radius: 5px;
color: $primary-color;
padding: 4px 6px 4px;
background-color: rgba(70, 106, 255, 0.09);
line-height: 12px;
&.public{
background-color: rgba(58, 213, 115, 0.08);
border-color: #25c589;
color: #25c589;
}
&.private{
background-color: rgba(230, 126, 34, 0.08);
border-color: rgba(230, 126, 34, 1);
color: rgba(230, 126, 34, 1);
}
}

View File

@ -7,6 +7,7 @@ const TokenKey = 'autologin_trustie';
function UploadImage({ getImage , url, getImageId, maxSize = 2, action = getUploadActionUrl(), getImageUrl }){
const [ imageUrl , setImageUrl ] = useState(undefined);
const [loading, setLoading] = useState(false);
useEffect(()=>{
setImageUrl(url);
},[url])
@ -29,11 +30,13 @@ function UploadImage({ getImage , url, getImageId, maxSize = 2, action = getUplo
if (!isLt2M) {
message.error(`上传的图片不能超过${maxSize}MB!`);
}
isJpgOrPng && isLt2M && setLoading(true);
return isJpgOrPng && isLt2M;
}
//
function handleChange(info){
if(info && info.file && info.file.status === "done"){
setLoading(false);
getImageId && getImageId(info.file.response.id);
getImageUrl && getImageUrl(info.file.response.url)
getBase64(info.file.originFileObj, imageUrl =>
@ -58,7 +61,7 @@ function UploadImage({ getImage , url, getImageId, maxSize = 2, action = getUplo
<img src={imageUrl} alt="avatar" style={{ width: '100%' }} />
:
<div>
<Icon type={'plus'} />
<Icon type={loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">点击上传</div>
</div>
}

View File

@ -7,7 +7,7 @@ import Item from './ListItem';
import Right from './RightBox';
import NoData from '../Nodata';
import CheckProfile from '../Component/ProfileModal/Profile';
import { Menu , Pagination , Dropdown , Spin , Tabs , Radio } from 'antd';
import { Menu , Pagination , Dropdown , Spin , Tooltip , Radio } from 'antd';
import ConcentrateProject from '../users/GeneralView/ConcentrateProject';
import { getImageUrl } from 'educoder';
import RenderHtml from "../../components/render-html";
@ -117,7 +117,11 @@ function List(props){
<Link to={`/${OIdentifier}/setting`} className="color-blue ml10 font-14 df"><i className="iconfont icon-shezhi2"></i>设置</Link>
:""}
</p>
<div className="orz-desc task-hide-2">{organizeDetail && organizeDetail.description}</div>
<div className="orz-desc task-hide-2">
<Tooltip title={organizeDetail && organizeDetail.description} placement={"bottomLeft"}>
<span style={{display:"block"}}>{organizeDetail && organizeDetail.description}</span>
</Tooltip>
</div>
</div>
</div>
</div>

View File

@ -331,19 +331,20 @@ form{
border: 1px solid #DDDDDD !important;
background-color: #fff !important;
}
.ant-btn.ant-btn-background-ghost{
border-color: #D0D0D0;
}
.ant-btn.ant-btn-background-ghost.ant-btn-primary:hover{
background-color:$primary-color!important;
border-color: $primary-color;
color: #fff;
}
.ant-btn.ant-btn-background-ghost.ant-btn-danger:hover{
background-color: #DF0002!important;
border-color: #DF0002;
color: #fff;
}
// ghost模式按钮如有影响单独修改
// .ant-btn.ant-btn-background-ghost{
// border-color: #D0D0D0;
// }
// .ant-btn.ant-btn-background-ghost.ant-btn-primary:hover{
// background-color:$primary-color!important;
// border-color: $primary-color;
// color: #fff;
// }
// .ant-btn.ant-btn-background-ghost.ant-btn-danger:hover{
// background-color: #DF0002!important;
// border-color: #DF0002;
// color: #fff;
// }
.newPopUl{
li{
height: 30px;

View File

@ -35,7 +35,7 @@ function Index(props){
break;
case '/settings/verification':
setType('4');
documentTitle = "身份认证";
documentTitle = "实名认证";
break;
default:
setType('2');
@ -55,7 +55,7 @@ function Index(props){
<Menu.Item key="3" className="font-16" onClick={()=>{props.history.push('/settings/phone')}}>手机号管理</Menu.Item>
<Menu.Item key="0" className="font-16" onClick={()=>{props.history.push('/settings/emails')}}>邮箱管理</Menu.Item>
<Menu.Item key="1" className="font-16" onClick={()=>{props.history.push('/settings/password')}}>密码管理</Menu.Item>
{/* <Menu.Item key="4" className="font-16" onClick={()=>{props.history.push('/settings/verification')}}>身份认证</Menu.Item> */}
<Menu.Item key="4" className="font-16" onClick={()=>{props.history.push('/settings/verification')}}>实名认证</Menu.Item>
<Menu.Item key="2" className="font-16" onClick={()=>{props.history.push('/settings/cancel')}}>账号注销</Menu.Item>
</Menu>}
</div>

View File

@ -9,7 +9,7 @@ import { getImageUrl } from '../../../common/UrlTool';
export default Form.create()(
forwardRef((props)=>{
const { getFieldDecorator, validateFields , setFieldsValue } = props && props.form;
const { resetUserInfo , current_user } = props;
const { current_user } = props;
const [loading, setLoading] = useState(false);
const [data, setData] = useState(undefined);
const [reload, setReload] = useState(undefined);
@ -18,7 +18,7 @@ export default Form.create()(
if(current_user && current_user.login){
setLoading(true);
//
axios.get(`http://ng.learnerhub.net/api/identity_verifications.json`).then(res=>{
axios.get(`/identity_verifications.json`).then(res=>{
setLoading(false);
if(res && res.status === 200){
if(res.data && res.data.status === 0){
@ -38,7 +38,7 @@ export default Form.create()(
validateFields((error, values) => {
if (!error) {
if(data){
axios.put(`http://ng.learnerhub.net/api/identity_verifications/${data.id}.json`, values).then(res => {
axios.put(`/identity_verifications/${data.id}.json`, values).then(res => {
if(res && res.status === 200){
message.success("提交成功");
setReload(Math.random());
@ -46,7 +46,7 @@ export default Form.create()(
}).catch((error) => {});
}else{
//
axios.post(`http://ng.learnerhub.net/api/identity_verifications.json`, values).then(res=>{
axios.post(`/identity_verifications.json`, values).then(res=>{
if(res && res.status === 200){
message.success("提交成功");
setReload(Math.random());
@ -67,15 +67,16 @@ export default Form.create()(
</div>
)}
{data.state === "已拒绝" && <div className="tipsBox mb20 errorTip">
您提交的身份信息未提供原因{data.description || "无,如有需要请联系平台管理员"}
您提交的身份信息未审核通过原因{data.description || "无,如有需要请联系平台管理员"}
</div>}
{data.state === "已通过" && <Alert className="mb20" type='success' message="您提交的身份信息已通过" />}
{data.state === "已通过" && <Alert className="mb20" type='success' message="您提交的身份信息已审核通过" />}
</Fragment>
)}
<Form layout={"inline"} className="verificationForm">
<Form.Item label="真实姓名" labelCol={{ span: 4 }}>
{getFieldDecorator("name", {
rules: [{ required: true, message: "请输入真实姓名" }],
rules: [{ required: true, message: "请输入真实姓名" },
{type: 'string', max: 50, min: 1, message: "长度1-50"}],
})(
<Input
placeholder="请输入与身份证一致的真实姓名"
@ -83,7 +84,7 @@ export default Form.create()(
/>
)}
</Form.Item>
<Form.Item label="身份证号" labelCol={{ span: 4 }}>
<Form.Item label="身份证号" labelCol={{ span: 4 }}>
{getFieldDecorator("number", {
rules: [
{ required: true, message: "请输入大陆身份证号码" },

View File

@ -285,4 +285,13 @@ export function formatParsedResult(setting, type= "range") {
return result;
}
}
// 是否通过中期考核
export function hasFinalExam(data) {
return fetch({
url: `/api/applyInformation/hasFinalExam`,
method: 'get',
data
});
}

View File

@ -22,8 +22,7 @@ import './index.scss';
// period: "repoApply"
// match:{params:{id=2023}}
export default (props) => {
const { current_user, history, round, match:{params:{id=2023}}, period, showLoginDialog, glccSettings, repoPublic, showMatchingBut, isResultPublic, hasRole, isMediumExamineByToTutor, checkedTaskId} = props;
const { current_user, history, round, match:{params:{id=2023}}, period, showLoginDialog, glccSettings, repoPublic, showMatchingBut, isResultPublic, hasRole, isMediumExamineByToTutor, isFinalExamineByToTutor, checkedTaskId} = props;
useEffect(() => {
if (!current_user.user_id) {
history.push('/403');
@ -131,6 +130,16 @@ export default (props) => {
</div>
<div className="pt6">学生提交考核材料</div>
</Link>}
{/* 学生结项考核 */}
{period === "finalExamine1" && checkedTaskId && <Link className="apply" to={`/glcc/submit`}>
<div>
<img src={img1} alt="" className="applyIcon" />
<span className="til">结项考核</span>
</div>
<div className="pt6">学生提交考核材料</div>
</Link>}
{/* 导师中期考核 */}
{hasRole && isMediumExamineByToTutor && <Link className="apply" to={`/glcc/middle/examination`}>
<div>
@ -139,7 +148,17 @@ export default (props) => {
</div>
<div className="pt6">导师拟定中期考核结果</div>
</Link>}
{/* 结项考核结果公示页 */}
{/* 导师结项考核 */}
{hasRole && isFinalExamineByToTutor && <Link className="apply" to={`/glcc/final/examination`}>
<div>
<img src={img1} alt="" className="applyIcon" />
<span className="til">结项考核</span>
</div>
<div className="pt6">导师拟定结项考核结果</div>
</Link>}
{/* 中期考核结果公示页 */}
{period === "mediumExamine3" && <Link className="apply" to={`/glcc/2023/result`}>
<div>
<img src={img1} alt="" className="applyIcon" />
@ -147,6 +166,15 @@ export default (props) => {
</div>
<div className="pt6">中期课题考核结果公示</div>
</Link>}
{/* 结项考核结果公示页 */}
{period === "finalExamine3" && <Link className="apply" to={`/glcc/2023/result`}>
<div>
<img src={img1} alt="" className="applyIcon" />
<span className="til">考核结果</span>
</div>
<div className="pt6">结项课题考核结果公示</div>
</Link>}
</div>}
{/* </div> */}
<div className="introduce">

View File

@ -102,6 +102,8 @@ const Glcc = (propsF) => {
const [isResultPublic, setIsResultPublic] = useState(false);
//
const [isMediumExamineByToTutor, setIsMediumExamineByToTutor] = useState(false);
//
const [isFinalExamineByToTutor, setIsFinalExamineByToTutor] = useState(false);
useEffect(()=>{
if(id && id === "2022"){
@ -129,8 +131,11 @@ const Glcc = (propsF) => {
//
const mediumExamine2 = res.data.filter(item=>item.name === "mediumExamine2");
mediumExamine2[0] && setIsMediumExamineByToTutor(judge(nowTime, mediumExamine2[0].value, "range"))
//
const finalExamine2 = res.data.filter(item=>item.name === "finalExamine2");
finalExamine2[0] && setIsFinalExamineByToTutor(judge(nowTime, finalExamine2[0].value, "range"))
//
const filterRepoPublic = res.data.filter(item=>["repoPublic", "repoApply1", "matching", "stuPublic", "mediumExamine2"].indexOf(item.name) === -1);
const filterRepoPublic = res.data.filter(item=>["repoPublic", "repoApply1", "matching", "stuPublic", "mediumExamine2", "finalExamine2"].indexOf(item.name) === -1);
const periodIndex = filterRepoPublic.findIndex(item=>judge(nowTime, item.value, "range"));
periodIndex !== -1 && setPeriod(filterRepoPublic[periodIndex].name);
}
@ -168,17 +173,17 @@ const Glcc = (propsF) => {
const [hasRole, setHasRole] = useState(false);
useEffect(() => {
// +-/
current_user && current_user.user_id && (isMatching || isMediumExamineByToTutor) && hasAuditRole({ userId: current_user.user_id, round: currentRound }).then(res => {
// +-//
current_user && current_user.user_id && (isMatching || isMediumExamineByToTutor || isFinalExamineByToTutor) && hasAuditRole({ userId: current_user.user_id, round: currentRound }).then(res => {
if (res && res.message === 'success' && res.data.hasRole) {
setHasRole(true);
}
})
}, [current_user, isMatching, isMediumExamineByToTutor])
}, [current_user, isMatching, isMediumExamineByToTutor, isFinalExamineByToTutor])
useEffect(()=>{
//
(period === "stuApply" || period === "stuApply1" || period === "mediumExamine1") && current_user && current_user.login && getStudentApplyInfo({userId: current_user.user_id, round: round}).then(response=>{
(period === "stuApply" || period === "stuApply1" || period === "mediumExamine1" || period === "finalExamine1") && current_user && current_user.login && getStudentApplyInfo({userId: current_user.user_id, round: round}).then(response=>{
if(response && response.message === "success"){
// setData(response.data.rows);
const data = {};
@ -312,27 +317,36 @@ const Glcc = (propsF) => {
)}
></Route>
{/* 中期审核-结果公示 */}
{(round === 1 || period === "mediumExamine3" || period === "finalExamine3") && <Route
{/* 中期/结项审核-结果公示 */}
{(round === 1 || period === "mediumExamine3" || period === "finalExamine3") &&
<Route
path="/glcc/:id/result"
render={(props) => (
<MiddleResult id={id} round={round} current_user={current_user} history={props.history}/>
<MiddleResult id={id} round={round} period={period} current_user={current_user} history={props.history}/>
)}
></Route>}
{/* 中期审核-学生 */}
{period === "mediumExamine1" && <Route
{/* 中期/结项审核-学生 */}
{(period === "mediumExamine1" || period === "finalExamine1") && <Route
path="/glcc/submit"
render={(props) => (
<StudentSubmit current_user={current_user} history={props.history} checkedTaskId={checkedTaskId} studentRegId={studentRegId} period={period} currentRound={currentRound} glccSettings={glccSettings} isMediumExamineByToTutor={isMediumExamineByToTutor}/>
<StudentSubmit current_user={current_user} history={props.history} checkedTaskId={checkedTaskId} studentRegId={studentRegId} period={period} currentRound={currentRound} glccSettings={glccSettings} isMediumExamineByToTutor={isMediumExamineByToTutor} isFinalExamineByToTutor={isFinalExamineByToTutor}/>
)}
></Route>}
{/* 中期审核-导师 */}
{isMediumExamineByToTutor && <Route
{(isMediumExamineByToTutor) && <Route
path="/glcc/middle/examination"
render={(props) => (
<TutorReview current_user={current_user} history={props.history} hasRole={hasRole} isMediumExamineByToTutor={isMediumExamineByToTutor} currentRound={currentRound} glccSettings={glccSettings}/>
<TutorReview current_user={current_user} period={'mediumExamine'} history={props.history} hasRole={hasRole} isMediumExamineByToTutor={isMediumExamineByToTutor} currentRound={currentRound} glccSettings={glccSettings}/>
)}
></Route>}
{/* 结项审核-导师 */}
{(isFinalExamineByToTutor) && <Route
path="/glcc/final/examination"
render={(props) => (
<TutorReview current_user={current_user} period={'finalExamine'} history={props.history} hasRole={hasRole} isMediumExamineByToTutor={isFinalExamineByToTutor} currentRound={currentRound} glccSettings={glccSettings}/>
)}
></Route>}
@ -340,7 +354,7 @@ const Glcc = (propsF) => {
<Route
path="/glcc/:id"
render={(props) => (
<Home repoPublic={repoPublic} period={period} round={round} {...propsF} {...props} checkedTaskId={checkedTaskId} glccSettings={glccSettings} showMatchingBut={isMatching && hasRole} isResultPublic={isResultPublic} hasRole={hasRole} isMediumExamineByToTutor={isMediumExamineByToTutor}/>
<Home repoPublic={repoPublic} period={period} round={round} {...propsF} {...props} checkedTaskId={checkedTaskId} glccSettings={glccSettings} showMatchingBut={isMatching && hasRole} isResultPublic={isResultPublic} hasRole={hasRole} isMediumExamineByToTutor={isMediumExamineByToTutor} isFinalExamineByToTutor={isFinalExamineByToTutor}/>
)}
></Route>
@ -348,7 +362,7 @@ const Glcc = (propsF) => {
<Route
path="/glcc"
render={(props) => (
<Home repoPublic={repoPublic} period={period} round={round} {...propsF} {...props} checkedTaskId={checkedTaskId} glccSettings={glccSettings} showMatchingBut={isMatching && hasRole} isResultPublic={isResultPublic} hasRole={hasRole} isMediumExamineByToTutor={isMediumExamineByToTutor}/>
<Home repoPublic={repoPublic} period={period} round={round} {...propsF} {...props} checkedTaskId={checkedTaskId} glccSettings={glccSettings} showMatchingBut={isMatching && hasRole} isResultPublic={isResultPublic} hasRole={hasRole} isMediumExamineByToTutor={isMediumExamineByToTutor} isFinalExamineByToTutor={isFinalExamineByToTutor}/>
)}
></Route>

View File

@ -275,4 +275,15 @@
.resultListTable .ant-table{
border: none;
}
}
.inputErrColor{
.ant-form-item-control-wrapper{
.has-error{
.ant-form-item-children{
input{
border-color: #f5222d;
}
}
}
}
}

View File

@ -12,7 +12,7 @@ import '../project/index.scss';
const { Search } = Input;
//
function CheckResult({round, id}) {
function CheckResult({round, id, period}) {
//
const [keyword, setKeyword] = useState(undefined);
const [data, setData] = useState([]);
@ -30,7 +30,7 @@ function CheckResult({round, id}) {
curPage: current,
keyword,
pageSize,
term: id === "2023" ? 1 : 2,
term: period === 'mediumExamine3' ? 1 : 2,
round
}
getMediumTermExamineInfoList(params).then(response => {
@ -98,10 +98,10 @@ function CheckResult({round, id}) {
return (
<div className="interimBox taskList resultListBox">
<img className="bannerInterim" src={id === "2023" ? resultBanner1 : resultBanner} alt=""></img>
<img className="bannerInterim" src={period === 'mediumExamine3' ? resultBanner1 : resultBanner} alt=""></img>
<div className='bgBox'>
<div className="resultList">
<div className='goBackBox'><a href={`/glcc/${id}`}>开源夏令营 / </a>{id === "2023" ? '中期' : '结项'}课题考核结果公示</div>
<div className='goBackBox'><a href={`/glcc/${id}`}>开源夏令营 / </a>{period === 'mediumExamine3' ? '中期' : '结项'}课题考核结果公示</div>
<div className='searchBox'>
<Search className='search' placeholder='请输入学生姓名或课题名称进行搜索' allowClear enterButton onSearch={(value) => { setCurrent(1); setKeyword(value) }} />
<div style={{width: 100}}></div>

View File

@ -1,24 +1,35 @@
import React, { useEffect, useState, Fragment } from "react";
import { Form, Upload, Input, Icon, Button, message, Modal, Checkbox } from "antd";
import { Form, Upload, Input, Icon, Button, message, Modal, Checkbox, notification} from "antd";
import { Link } from "react-router-dom";
import axios from "axios";
import banner from '../img/banner-interim.png';
import banner1 from '../img/banner-interim1.png';
import img1 from '../img/img1.png';
import bg from '../img/bgPng.png';
import { httpUrl, main_site_url } from '../fetch';
import {getMediumTermExamineInfo, submitMedium, formatParsedResult} from '../api';
import {getMediumTermExamineInfo, submitMedium, formatParsedResult, hasFinalExam} from '../api';
import './index.scss';
function StudentSubmit(props){
const {form, checkedTaskId, studentRegId, period, currentRound, glccSettings, isMediumExamineByToTutor} = props;
const {form, checkedTaskId, studentRegId, period, currentRound, glccSettings, isMediumExamineByToTutor, isFinalExamineByToTutor,history, current_user} = props;
const mediumExamine = glccSettings && glccSettings.filter(item=>item.name === "mediumExamine1");
const mediumExamine2 = glccSettings && glccSettings.filter(item=>item.name === "mediumExamine2");
const finalExamine = glccSettings && glccSettings.filter(item=>item.name === "finalExamine1");
const finalExamine2 = glccSettings && glccSettings.filter(item=>item.name === "finalExamine2");
let overtime = false;
if(mediumExamine2 && mediumExamine2[0]){
if (mediumExamine2 && mediumExamine2[0] && period === "mediumExamine1") {
const nowTime = new Date().getTime();
const timeArr = mediumExamine2[0].value.split(",").map(item=>{
const timeArr = mediumExamine2[0].value.split(",").map((item) => {
// replaceAll
const data = item && item.replace(/-/g,'/');
const data = item && item.replace(/-/g, "/");
return new Date(data).getTime();
});
overtime = nowTime > timeArr[1];
} else if (finalExamine2 && finalExamine2[0] && period != "mediumExamine1") {
const nowTime = new Date().getTime();
const timeArr = finalExamine2[0].value.split(",").map((item) => {
// replaceAll
const data = item && item.replace(/-/g, '/');
return new Date(data).getTime()
});
overtime = nowTime > timeArr[1];
@ -32,6 +43,17 @@ function StudentSubmit(props){
//
const [isAuthed, setIsAuthed] = useState(false);
useEffect(()=>{
if(period != "mediumExamine1"){
hasFinalExam({round:currentRound,userId:current_user.user_id}).then((res)=>{
if(res && res.data && res.data.hasRole) return;
history.push("/glcc");
}).catch((err)=>{
history.push("/glcc");
})
}
},[])
useEffect(()=>{
//
checkedTaskId && getMediumTermExamineInfo(checkedTaskId,{round: currentRound, term: period === "mediumExamine1" ? 1 : 2}).then(res=>{
@ -82,6 +104,10 @@ function StudentSubmit(props){
})
}else{
setDisabled(false);
const { codeOrPrUrl } = values;
if (codeOrPrUrl) {
checkPrLink("", codeOrPrUrl, () => {});
}
}
})
}
@ -108,6 +134,46 @@ function StudentSubmit(props){
}
return isLt100M;
}
function checkPrLink(rule, value, callback) {
const getUrl = `${
window.location.href.indexOf("gitlink.org.cn") > -1
? "https://www.gitlink.org.cn"
: "https://testforgeplus.trustie.net"
}`;
if (value) {
axios
.get(`${getUrl}/check_pr_url`, {
params: {
url: value,
task: checkedTaskId,
term: period === "mediumExamine1" ? 1 : 2,
},
})
.then((response) => {
if (response && response.data && response.data.state && response.data.state.length > 0) {
callback("");
const description = (
<div
dangerouslySetInnerHTML={{ __html: response.data.state_html }}
></div>
);
notification.open({
message: "提示",
description,
key:'notificationKey',
style: {
zIndex: 99999999,
},
});
}
callback();
}).catch(() => {
callback("请正确输入pr地址");
});
} else {
callback("请输入pr地址");
}
}
return <div className="interimBox">
<img src={period === "mediumExamine1" ? banner1 : banner} alt="" className="bannerInterim"/>
<img src={bg} alt="" className="bg1"/>
@ -116,18 +182,18 @@ function StudentSubmit(props){
<div className="navBox font-16"><Link to={`/glcc`} className="linkBox">开源夏令营 / </Link>提交{period === "mediumExamine1" ? '中期' : '结项'}考核材料</div>
<div className="tipBox mt30">
<div className="font-15 spanBox">材料提交说明:</div>
<div>1请各位学生<a href={main_site_url+`/api/attachments/${period === "mediumExamine1" ? '428107' : '427720'}`} className="blueSpan">下载PPT模板</a> 根据课题开发进展按照PPT模板要求填写课题学习调研方案开发进度及开发成果等考核材料</div>
<div>1请各位学生<a href={`https://www.gitlink.org.cn/api/attachments/${period === "mediumExamine1" ? '428107' : '427720'}`} className="blueSpan">下载PPT模板</a> 根据课题开发进展按照PPT模板要求填写课题学习调研方案开发进度及开发成果等考核材料</div>
<div>2欢迎各位学生录制本课题答辩视频将视频链接填写至下方视频介绍填写栏</div>
<div>3学生提交考核材料的时间为<span className="spanBox">{formatParsedResult(mediumExamine)}</span>请在截止时间前提交</div>
<div>3学生提交考核材料的时间为<span className="spanBox">{formatParsedResult( period === "mediumExamine1" ? mediumExamine : finalExamine)}</span>请在截止时间前提交</div>
<div>4若导师已给出评分各位学生可在此页面查看自己的结项考核成绩对考核成绩有异议的学生请及时联系您的导师</div>
</div>
<div className="titleBox mt25 font-18">
<img src={img1} alt="" width={24} className="mr5"/>
{period === "mediumExamine1" ? '中期' : '结项'}考核
</div>
{period === "mediumExamine1" && !detail ? <Form className="referBox" onSubmit={submit} colon={false}>
{(period === "mediumExamine1" || period === "finalExamine1") && !detail ? <Form className="referBox" onSubmit={submit} colon={false}>
{
period === "mediumExamine1"?
period === "mediumExamine1" || period === "finalExamine1" ?
<Fragment>
<Form.Item label="答辩视频" className="referItem oneCont oneLine">
{getFieldDecorator('defenceVideoUrl', {
@ -139,9 +205,17 @@ function StudentSubmit(props){
授权主办方进行下载和剪辑用作比赛宣传
</Checkbox>
</Form.Item>
<Form.Item label="pr地址" className="referItem oneCont oneLine">
<Form.Item label="pr地址" className="referItem oneCont oneLine inputErrColor">
{getFieldDecorator('codeOrPrUrl', {
rules: [{ required: true, message: '请输入视频链接!'},{pattern: /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/,message: "请正确输入链接"}],
rules: [
{
required: true,
validator: (rule, value, callback) => {
checkPrLink(rule, value, callback, checkedTaskId);
},
},
],
validateTrigger: "onBlur",
})(<Input placeholder="请输入pr地址" maxLength={900}/>)}
</Form.Item>
<Form.Item className="referItem oneCont referTips">
@ -170,6 +244,9 @@ function StudentSubmit(props){
{period === "mediumExamine1"?
<Button className="uploadBox" style={{color:'#466aff'}}>上传GLCC中期汇报ppt</Button>
:
period === 'finalExamine1' ?
<Button className="uploadBox" style={{ color: "#466aff" }}>上传GLCC结项汇报ppt</Button>
:
<Button className="uploadBox"><Icon type="upload" /> 上传</Button>
}
</Upload>)}
@ -180,11 +257,12 @@ function StudentSubmit(props){
</Form> : <div className={`reviewBox referBox resultBox ${!detail ? 'nullData' : ''}`}>
<div className="flexBox">
<div className="mustSpan mb20">&nbsp;&nbsp;答辩视频<span className="blueBg ml10"><a href={detail && detail.defenceVideoUrl} target="_blank">{detail && detail.defenceVideoUrl}</a></span></div>
<div style={{ margin: "-10px 0 20px 90px" }}><Checkbox checked={detail && detail.isAuthed ? true: false} disabled></Checkbox><span style={{ marginLeft: "5px" }}>授权主办方进行下载和剪辑用作比赛宣传</span></div>
<div>代码/pr地址<span className="blueBg ml10"><a href={detail && detail.codeOrPrUrl} target="_blank">{detail && detail.codeOrPrUrl}</a></span></div>
<div className="mustSpan ppt mb20">&nbsp;&nbsp;&nbsp;PPT附件{detail && <i className="iconfont icon-lianjie3 font-13 mr5 ml10"></i>}{detail ? <a className="pptAttachment mr10" href={`${httpUrl}/busiAttachments/download/${detail.pptAttachment.id}`}>{`${detail.pptAttachment.fileName}`}</a> : <Button className="uploadBox ml10" disabled><Icon type="upload" /> 上传</Button>}{detail && detail.pptAttachment && detail.pptAttachment.fileSizeString}</div>
</div>
{!detail && <div className="nullDateTip font-15">很遗憾您在指定时间内未提交考核材料</div>}
{detail && isMediumExamineByToTutor && !detail.glccTutorEvaluation && <div className="font-15 nullDateTip">您的课题导师暂未评分请提醒导师尽快提交结项考核评分</div>}
{detail && (isMediumExamineByToTutor || isFinalExamineByToTutor) && !detail.glccTutorEvaluation && <div className="font-15 nullDateTip">您的课题导师暂未评分请提醒导师尽快提交结项考核评分</div>}
{detail && overtime && !detail.glccTutorEvaluation && <div className="font-15 nullDateTip">很遗憾您的导师尚未评分您未通过结项考核</div>}
{detail && detail.glccTutorEvaluation && <div className="tutorRes">
<div>您的课题导师对您的结项考核评价如下如有异议请及时联系导师进行更改</div>

View File

@ -11,7 +11,7 @@ const { TabPane } = Tabs;
const { TextArea } = Input;
function TutorReview(props){
const {form, current_user, showNotification, history, hasRole, isMediumExamineByToTutor, currentRound, glccSettings} = props;
const {form, current_user, showNotification, history, hasRole, isMediumExamineByToTutor, period, currentRound, glccSettings} = props;
const {getFieldDecorator, setFieldsValue, validateFieldsAndScroll, resetFields } = form;
const [taskId, setTaskId] = useState();
const [taskList, setTaskList] = useState([]);
@ -24,15 +24,26 @@ function TutorReview(props){
const [disabled, setDisabled] = useState(false);
const mediumExamine2 = glccSettings && glccSettings.filter(item=>item.name === "mediumExamine2");
const mediumExamine3 = glccSettings && glccSettings.filter(item=>item.name === "mediumExamine3");
const finalExamine2 = glccSettings && glccSettings.filter(item=>item.name === "finalExamine2");
const finalExamine3 = glccSettings && glccSettings.filter(item=>item.name === "finalExamine3");
useEffect(() => {
if(!isMediumExamineByToTutor || !hasRole){
history.push("/glcc");
}else if (!current_user.login) {
history.push('/login?go_page=/glcc/middle/examination');
if(period === 'mediumExamine'){
history.push('/login?go_page=/glcc/middle/examination');
}else{
history.push('/login?go_page=/glcc/final/examination');
}
}
getLockedAuditList({ userId: current_user.user_id, pass: 1, round: currentRound }).then(res => {
if (res.message === 'success') {
const filterStuNull = res.data.rows.filter(item=>{return item.studentName !== null})
let filterStuNull = [];
if(period === 'mediumExamine'){
filterStuNull = res.data.rows.filter(item=>{return item.studentName !== null})
}else{
filterStuNull = res.data.rows.filter(item=>{return item.studentName !== null && item.canSubmitFinalExaminationMaterial})
}
setTaskList(filterStuNull);
filterStuNull.length && setTaskId(filterStuNull[0].id);
} else {
@ -44,7 +55,7 @@ function TutorReview(props){
useEffect(()=>{
//
//
taskId && getMediumTermExamineInfo(taskId,{round: currentRound, term: isMediumExamineByToTutor ? 1 : 2}).then(res=>{
taskId && getMediumTermExamineInfo(taskId,{round: currentRound, term: period === 'mediumExamine' ? 1 : 2}).then(res=>{
if(res && res.message === "success"){
setDetail(res.data);
if(res && res.data && res.data.glccTutorEvaluation){
@ -66,7 +77,7 @@ function TutorReview(props){
mediumTermExamineMaterialId: detail.id,
tutorUserId: detail.studentRegId,
round: currentRound,
term: isMediumExamineByToTutor ? 1 : 2
term: period === 'mediumExamine' ? 1 : 2
}
if(detail.glccTutorEvaluation){
params['id'] = detail.glccTutorEvaluation.id;
@ -115,9 +126,16 @@ function TutorReview(props){
<div className="tipBox mt30">
<div className="font-15 spanBox">导师考核说明:</div>
<div>1请各位导师从工作态度开发进度项目完成质量总体评分四个角度根据学生提交的考核材料与实际开发情况客观地进行打分打分标准分为S:特别优秀A:优秀B:良好C:合格D:不合格五个等级</div>
<div>2总体评分这一项将决定学生是否通过本次考核若总体评分为SABC则视为通过中期考核若该结果为D则该课题中期考核不通过课题将自动终止请各位导师谨慎做出评价</div>
<div>3导师提交打分结果后可对考核结果进行更改更改考核结果截止日期为<span className="spanBox">{mediumExamine2 && formatParsedResult(mediumExamine2, 'end')}</span></div>
<div>4北京时间<span className="spanBox">{mediumExamine3 && formatParsedResult(mediumExamine3, "start")}</span>前GLCC官网将公布中期考核结果敬请留意</div>
<div>2总体评分这一项将决定学生是否通过本次考核若总体评分为SABC则视为通过{period === 'mediumExamine'?'中期':'结项'}考核若该结果为D则该课题{period === 'mediumExamine'?'中期':'结项'}考核不通过课题将自动终止请各位导师谨慎做出评价</div>
<div>3导师提交打分结果后可对考核结果进行更改更改考核结果截止日期为{period === 'mediumExamine' ?
<span className="spanBox">{mediumExamine2 && formatParsedResult(mediumExamine2, 'end')}</span>
:
<span className="spanBox">{finalExamine2 && formatParsedResult(finalExamine2, 'end')}</span>}</div>
<div>4北京时间{period === 'mediumExamine' ?
<span className="spanBox">{mediumExamine3 && formatParsedResult(mediumExamine3, "start")}</span>
:
<span className="spanBox">{finalExamine3 && formatParsedResult(finalExamine3, "start")}</span>
}前GLCC官网将公布{period === 'mediumExamine'?'中期':'结项'}考核结果敬请留意</div>
{/* 结项考核文案 */}
{/* <div>2SABCD</div>
<div>3导师提交打分结果后可对考核结果进行更改期间考核结果也将实时反馈给学生更改考核结果截止时间为<span className="spanBox">2022年10月20日24点</span></div>

View File

@ -29,18 +29,18 @@ function ThirdEdition() {
return flag;
},[flag])
useEffect(()=>{
let box = document.getElementById('thirdUl');
var myTimer = setTimeout(intervalActive, 2500);
box.onmouseover = () => {
clearTimeout(myTimer);
setFlag(false);
}
box.onmouseleave = () => {
myTimer = setTimeout(intervalActive, 2500);
setFlag(true);
}
},[active,flag])
// useEffect(()=>{
// let box = document.getElementById('thirdUl');
// var myTimer = setTimeout(intervalActive, 2500);
// box.onmouseover = () => {
// clearTimeout(myTimer);
// setFlag(false);
// }
// box.onmouseleave = () => {
// myTimer = setTimeout(intervalActive, 2500);
// setFlag(true);
// }
// },[active,flag])
function intervalActive() {
if(doubleFlag){
@ -179,7 +179,7 @@ function ThirdEdition() {
return(
<li>
<a href={i.url} target="_blank">{i.title}</a>
<span className="listboxcount"><i className="iconfont icon-a-liulanicon2x mr5"></i>{i.visits}</span>
{i.from !== "other" && <span className="listboxcount"><i className="iconfont icon-a-liulanicon2x mr5"></i>{i.visits}</span> }
<span>{i.created_time && i.created_time.split(" ")[0]}</span>
</li>
)

View File

@ -9,7 +9,8 @@ import 'codemirror/lib/codemirror.css';
import './css/newquestion.css';
const $ = window.$
const mdIcons = ["bold", "italic", "del", "|", "list-ul", "list-ol", "|", "code", "code-block", "link", "|", "inline-latex", "latex", '|', "image", "table", '|', "line-break", "watch", "clear","fullscreen"];
const mdIcons = ["bold", "italic", "del", "|", "list-ul", "list-ol", "|", "code", "code-block", "link", "|", "image", "table", '|', "line-break", "watch", "clear","fullscreen"];
const mdIconsHasLatex = ["bold", "italic", "del", "|", "list-ul", "list-ol", "|", "code", "code-block", "link", "|", "inline-latex", "latex", '|', "image", "table", '|', "line-break", "watch", "clear","fullscreen"];
const NULL_CH = '▁';
@ -74,7 +75,7 @@ function md_elocalStorage(editor, mdu, id) {
return tid
}
export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch=true, showNullButton = false, showResizeBar = false, startInit = true , forMember = true , isCanAtme = false , isQuoteIssue = false , changeAtWhoLoginList, owner, projectsId , isFocus = true}) => {
export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch=true, showNullButton = false, showResizeBar = false, startInit = true , forMember = true , isCanAtme = false , isQuoteIssue = false , changeAtWhoLoginList, owner, projectsId , isFocus = true, showLatexButton = true}) => {
const editorEl = useRef();
const resizeBarEl = useRef();
@ -353,7 +354,7 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL: getUploadActionUrl(),
toolbarIcons: function () {
return showNullButton ? [...mdIcons, 'null-button'] : mdIcons
return showLatexButton ? mdIconsHasLatex : mdIcons
},
toolbarIconsClass: {
"line-break": "fa-minus",