Merge pull request 'add some base files' (#5) from binary/OpenPBL:pr01 into master

This commit is contained in:
OpenCT 2022-08-19 20:26:25 +08:00
commit fe8e5c4b6b
17 changed files with 1378 additions and 0 deletions

372
web/src/App.js Normal file
View File

@ -0,0 +1,372 @@
import React, {Component} from "react";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import {Avatar, BackTop, Dropdown, Layout, Menu} from "antd";
import {DownOutlined, LogoutOutlined, SettingOutlined, createFromIconfontCN} from "@ant-design/icons";
import "./App.less";
import * as Setting from "./Setting";
import * as AccountBackend from "./backend/AccountBackend";
import AuthCallback from "./AuthCallback";
import * as Conf from "./Conf";
import HomePage from "./HomePage";
import DatasetListPage from "./DatasetListPage";
import DatasetEditPage from "./DatasetEditPage";
import SigninPage from "./SigninPage";
import i18next from "i18next";
import PendingTaskPage from "./PendingTaskPage";
import ProjectManagementPage from "./ProjectManagementPage";
import PropositionPaperPage from "./PropositionPaperPage";
const {Header, Footer} = Layout;
const IconFont = createFromIconfontCN({
scriptUrl: "//at.alicdn.com/t/font_2680620_ffij16fkwdg.js",
});
class App extends Component {
constructor(props) {
super(props);
this.state = {
classes: props,
selectedMenuKey: 0,
account: undefined,
uri: null,
};
Setting.initServerUrl();
Setting.initCasdoorSdk(Conf.AuthConfig);
}
getRemSize = () => {
let whdef = 100 / 1920;
let wW = window.innerWidth;
let rem = wW * whdef;
document.documentElement.style.fontSize = rem + "px";
}
componentDidMount = () => {
window.resize = () => {
this.getRemSize();
};
this.getRemSize();
}
UNSAFE_componentWillMount() {
this.updateMenuKey();
this.getAccount();
}
componentDidUpdate() {
const uri = location.pathname;
if (this.state.uri !== uri) {
this.updateMenuKey();
}
}
updateMenuKey() {
const uri = location.pathname;
this.setState({
uri: uri,
});
if (uri === "/") {
this.setState({selectedMenuKey: "/"});
} else if (uri.includes("/datasets")) {
this.setState({selectedMenuKey: "/datasets"});
} else if (uri.includes("/pending-tasks")) {
this.setState({selectedMenuKey: "/pending-tasks"});
} else if (uri.includes("/project-management")) {
this.setState({selectedMenuKey: "/project-management"});
} else if (uri.includes("/proposition-paper")) {
this.setState({selectedMenuKey: "/proposition-paper"});
} else {
this.setState({selectedMenuKey: "null"});
}
}
onUpdateAccount(account) {
this.setState({
account: account,
});
}
setLanguage(account) {
// let language = account?.language;
let language = localStorage.getItem("language");
if (language !== "" && language !== i18next.language) {
Setting.setLanguage(language);
}
}
getAccount() {
AccountBackend.getAccount()
.then((res) => {
let account = res.data;
if (account !== null) {
this.setLanguage(account);
}
this.setState({
account: account,
});
});
}
signout() {
AccountBackend.signout()
.then((res) => {
if (res.status === "ok") {
this.setState({
account: null,
});
Setting.showMessage("success", "Successfully signed out, redirected to homepage");
Setting.goToLink("/");
// this.props.history.push("/");
} else {
Setting.showMessage("error", `Signout failed: ${res.msg}`);
}
});
}
handleRightDropdownClick(e) {
if (e.key === "/account") {
Setting.openLink(Setting.getMyProfileUrl(this.state.account));
} else if (e.key === "/logout") {
this.signout();
}
}
renderAvatar() {
if (this.state.account.avatar === "") {
return (
<Avatar style={{backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: "middle"}} size="large">
{Setting.getShortName(this.state.account.name)}
</Avatar>
);
} else {
return (
<Avatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size="large">
{Setting.getShortName(this.state.account.name)}
</Avatar>
);
}
}
renderRightDropdown() {
const menu = (
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
<Menu.Item key="/account">
<SettingOutlined />
{i18next.t("account:My Account")}
</Menu.Item>
<Menu.Item key="/logout">
<LogoutOutlined />
{i18next.t("account:Sign Out")}
</Menu.Item>
</Menu>
);
return (
<Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown">
<div className="ant-dropdown-link" style={{float: "right", cursor: "pointer"}}>
&nbsp;
&nbsp;
{
this.renderAvatar()
}
&nbsp;
&nbsp;
{Setting.isMobile() ? null : Setting.getShortName(this.state.account.displayName)} &nbsp; <DownOutlined />
&nbsp;
&nbsp;
&nbsp;
</div>
</Dropdown>
);
}
renderAccount() {
let res = [];
if (this.state.account === undefined) {
return null;
} else if (this.state.account === null) {
res.push(
<Menu.Item key="/signup" style={{float: "right", marginRight: "20px"}}>
<a href={Setting.getSignupUrl()}>
{i18next.t("account:Sign Up")}
</a>
</Menu.Item>
);
res.push(
<Menu.Item key="/signin" style={{float: "right"}}>
<a href={Setting.getSigninUrl()}>
{i18next.t("account:Sign In")}
</a>
</Menu.Item>
);
res.push(
<Menu.Item key="/" style={{float: "right"}}>
<a href="/">
{i18next.t("general:Home")}
</a>
</Menu.Item>
);
} else {
res.push(this.renderRightDropdown());
}
return res;
}
renderMenu() {
let res = [];
if (this.state.account === null || this.state.account === undefined) {
return [];
}
res.push(
<Menu.Item key="/">
<a href="/">
{i18next.t("general:Home")}
</a>
{/* <Link to="/">*/}
{/* Home*/}
{/* </Link>*/}
</Menu.Item>
);
res.push(
<Menu.Item key="/datasets">
<Link to="/datasets">
{i18next.t("general:Datasets")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/pending-tasks">
<Link to="/pending-tasks">
{i18next.t("general:Pending Tasks")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/proposition-paper">
<Link to="/proposition-paper">
{i18next.t("general:Proposition Paper")}
</Link>
</Menu.Item>
);
return res;
}
renderHomeIfSignedIn(component) {
if (this.state.account !== null && this.state.account !== undefined) {
return <Redirect to="/" />;
} else {
return component;
}
}
renderSigninIfNotSignedIn(component) {
if (this.state.account === null) {
sessionStorage.setItem("from", window.location.pathname);
return <Redirect to="/signin" />;
} else if (this.state.account === undefined) {
return null;
} else {
return component;
}
}
renderContent() {
return (
<div>
<Header style={{padding: "0", marginBottom: "3px"}}>
{
Setting.isMobile() ? null : <a className="logo" href={"/"} />
}
<Menu
// theme="dark"
mode={"horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px"}}
>
{
this.renderMenu()
}
{
this.renderAccount()
}
<Menu.Item key="en" className="rightDropDown" style={{float: "right", cursor: "pointer", marginLeft: "-10px", marginRight: "20px"}}>
<div className="rightDropDown" style={{float: "right", cursor: "pointer"}} onClick={() => {Setting.changeLanguage("en");}}>
&nbsp;&nbsp;&nbsp;&nbsp;<IconFont type="icon-en" />
&nbsp;
English
&nbsp;
&nbsp;
</div>
</Menu.Item>
<Menu.Item key="zh" className="rightDropDown" style={{float: "right", cursor: "pointer"}}>
<div className="rightDropDown" style={{float: "right", cursor: "pointer"}} onClick={() => {Setting.changeLanguage("zh");}}>
&nbsp;&nbsp;&nbsp;&nbsp;<IconFont type="icon-zh" />
&nbsp;
中文
&nbsp;
&nbsp;
</div>
</Menu.Item>
</Menu>
</Header>
<Switch>
<Route path="/callback" component={AuthCallback} />
<Route path="/signin" render={(props) => this.renderHomeIfSignedIn(<SigninPage {...props} />)} />
<Route path="/pending-tasks" render={(props) => this.renderSigninIfNotSignedIn(<PendingTaskPage account={this.state.account} {...props} />)} />
<Route path="/datasets" render={(props) => this.renderSigninIfNotSignedIn(<DatasetListPage account={this.state.account} {...props} />)} />
<Route path="/datasets/:datasetName" render={(props) => this.renderSigninIfNotSignedIn(<DatasetEditPage account={this.state.account} {...props} />)} />
<Route path="/project-management/:project_id/:role" render={(props) => this.renderSigninIfNotSignedIn(<ProjectManagementPage account={this.state.account} {...props} />)} />
<Route path="/proposition-paper" render={(props) => this.renderSigninIfNotSignedIn(<PropositionPaperPage account={this.state.account} {...props} />)} />
<Route path="/" render={(props) => <HomePage account={this.state.account} {...props} />} />
</Switch>
</div>
);
}
renderFooter() {
// How to keep your footer where it belongs ?
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
return (
<Footer id="footer" style={
{
borderTop: "1px solid #e8e8e8",
backgroundColor: "white",
textAlign: "center",
}
}>
Made with <span style={{color: "rgb(255, 255, 255)"}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" rel="noreferrer" href="https://item.open-ct.com">OpenItem</a>, {Setting.isMobile() ? "Mobile" : "Desktop"} View
</Footer>
);
}
render() {
return (
<div id="parent-area">
<BackTop />
<div id="content-wrap">
{
this.renderContent()
}
</div>
{
this.renderFooter()
}
</div>
);
}
}
export default withRouter(App);

64
web/src/App.less Normal file
View File

@ -0,0 +1,64 @@
@import '~antd/dist/antd.less';
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #09d3ac;
}
#parent-area {
position: relative;
min-height: 100vh;
}
#content-wrap {
padding-bottom: 70px; /* Footer height */
}
#footer {
position: absolute;
bottom: 0;
width: 100%;
height: 70px; /* Footer height */
}
.ant-table-body {
overflow-y: hidden !important
}
.language_box {
background: url("https://cdn.casbin.org/img/muti_language.svg");
background-size: 25px, 25px;
background-position: center;
background-repeat: no-repeat;
width: 45px;
height: 65px;
float: right;
cursor: pointer;
}
.language_box:hover {
background-color: #f5f5f5;
}
.rightDropDown:hover {
background-color: #f5f5f5;
}

71
web/src/AuthCallback.js Normal file
View File

@ -0,0 +1,71 @@
import React from "react";
import {Button, Result, Spin} from "antd";
import {withRouter} from "react-router-dom";
import * as Setting from "./Setting";
class AuthCallback extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
msg: null,
};
}
UNSAFE_componentWillMount() {
this.login();
}
getFromLink() {
const from = sessionStorage.getItem("from");
if (from === null) {
return "/";
}
return from;
}
login() {
Setting.signin().then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", "Logged in successfully");
const link = this.getFromLink();
Setting.goToLink(link);
} else {
this.setState({
msg: res.msg,
});
}
});
}
render() {
return (
<div style={{textAlign: "center"}}>
{this.state.msg === null ? (
<Spin
size="large"
tip="Signing in..."
style={{paddingTop: "10%"}}
/>
) : (
<div style={{display: "inline"}}>
<Result
status="error"
title="Login Error"
subTitle={this.state.msg}
extra={[
<Button type="primary" key="details">
Details
</Button>,
<Button key="help">Help</Button>,
]}
/>
</div>
)}
</div>
);
}
}
export default withRouter(AuthCallback);

11
web/src/Conf.js Normal file
View File

@ -0,0 +1,11 @@
export const AuthConfig = {
// serverUrl: "https://door.casdoor.com",
serverUrl: "http://localhost:7001",
clientId: "eb78c88fc0697611f3e3",
appName: "app-openitem",
organizationName: "openct",
redirectPath: "/callback",
};
export const ForceLanguage = "";
export const DefaultLanguage = "en";

206
web/src/Setting.js Normal file
View File

@ -0,0 +1,206 @@
import {message} from "antd";
import {isMobile as isMobileDevice} from "react-device-detect";
import i18next from "i18next";
import Sdk from "casdoor-js-sdk";
export let ServerUrl = "";
export let CasdoorSdk;
export function initServerUrl() {
const hostname = window.location.hostname;
if (hostname === "localhost") {
ServerUrl = `http://${hostname}:8300`;
}
}
export function initCasdoorSdk(config) {
CasdoorSdk = new Sdk(config);
}
function getUrlWithLanguage(url) {
if (url.includes("?")) {
return `${url}&language=${getLanguage()}`;
} else {
return `${url}?language=${getLanguage()}`;
}
}
export function getSignupUrl() {
return getUrlWithLanguage(CasdoorSdk.getSignupUrl());
}
export function getSigninUrl() {
return getUrlWithLanguage(CasdoorSdk.getSigninUrl());
}
export function getUserProfileUrl(userName, account) {
return getUrlWithLanguage(CasdoorSdk.getUserProfileUrl(userName, account));
}
export function getMyProfileUrl(account) {
return getUrlWithLanguage(CasdoorSdk.getMyProfileUrl(account));
}
export function signin() {
return CasdoorSdk.signin(ServerUrl);
}
export function parseJson(s) {
if (s === "") {
return null;
} else {
return JSON.parse(s);
}
}
export function myParseInt(i) {
const res = parseInt(i);
return isNaN(res) ? 0 : res;
}
export function openLink(link) {
// this.props.history.push(link);
const w = window.open("about:blank");
w.location.href = link;
}
export function goToLink(link) {
window.location.href = link;
}
export function goToLinkSoft(ths, link) {
ths.props.history.push(link);
}
export function showMessage(type, text) {
if (type === "") {
return;
} else if (type === "success") {
message.success(text);
} else if (type === "error") {
message.error(text);
}
}
export function isAdminUser(account) {
return account?.isAdmin;
}
export function deepCopy(obj) {
return Object.assign({}, obj);
}
export function insertRow(array, row, i) {
return [...array.slice(0, i), row, ...array.slice(i)];
}
export function addRow(array, row) {
return [...array, row];
}
export function prependRow(array, row) {
return [row, ...array];
}
export function deleteRow(array, i) {
// return array = array.slice(0, i).concat(array.slice(i + 1));
return [...array.slice(0, i), ...array.slice(i + 1)];
}
export function swapRow(array, i, j) {
return [...array.slice(0, i), array[j], ...array.slice(i + 1, j), array[i], ...array.slice(j + 1)];
}
export function isMobile() {
// return getIsMobileView();
return isMobileDevice;
}
export function getFormattedDate(date) {
if (date === undefined || date === null) {
return null;
}
date = date.replace("T", " ");
date = date.replace("+08:00", " ");
return date;
}
export function getFormattedDateShort(date) {
return date.slice(0, 10);
}
export function getShortName(s) {
return s.split("/").slice(-1)[0];
}
export function getShortText(s, maxLength = 35) {
if (s.length > maxLength) {
return `${s.slice(0, maxLength)}...`;
} else {
return s;
}
}
function getRandomInt(s) {
let hash = 0;
if (s.length !== 0) {
for (let i = 0; i < s.length; i++) {
let char = s.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
}
return hash;
}
export function getAvatarColor(s) {
const colorList = ["#f56a00", "#7265e6", "#ffbf00", "#00a2ae"];
let random = getRandomInt(s);
if (random < 0) {
random = -random;
}
return colorList[random % 4];
}
export function getLanguage() {
return i18next.language;
}
export function setLanguage(language) {
localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language);
}
export function changeLanguage(language) {
localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language);
window.location.reload(true);
}
export function changeMomentLanguage(lng) {
return;
// if (lng === "zh") {
// moment.locale("zh", {
// relativeTime: {
// future: "%s内",
// past: "%s前",
// s: "几秒",
// ss: "%d秒",
// m: "1分钟",
// mm: "%d分钟",
// h: "1小时",
// hh: "%d小时",
// d: "1天",
// dd: "%d天",
// M: "1个月",
// MM: "%d个月",
// y: "1年",
// yy: "%d年",
// },
// });
// }
}

14
web/src/SigninPage.js Normal file
View File

@ -0,0 +1,14 @@
import React from "react";
import * as Setting from "./Setting";
class SigninPage extends React.Component {
componentDidMount() {
window.location.replace(Setting.getSigninUrl());
}
render() {
return "";
}
}
export default SigninPage;

View File

@ -0,0 +1,22 @@
import * as Setting from "../Setting";
export function getAccount() {
return fetch(`${Setting.ServerUrl}/api/get-account`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function signin(code, state) {
return fetch(`${Setting.ServerUrl}/api/signin?code=${code}&state=${state}`, {
method: "POST",
credentials: "include",
}).then(res => res.json());
}
export function signout() {
return fetch(`${Setting.ServerUrl}/api/signout`, {
method: "POST",
credentials: "include",
}).then(res => res.json());
}

View File

@ -0,0 +1,49 @@
import * as Setting from "../Setting";
export function getGlobalDatasets() {
return fetch(`${Setting.ServerUrl}/api/get-global-datasets`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function getDatasets(owner) {
return fetch(`${Setting.ServerUrl}/api/get-datasets?owner=${owner}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function getDataset(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-dataset?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function updateDataset(owner, name, dataset) {
let newDataset = Setting.deepCopy(dataset);
return fetch(`${Setting.ServerUrl}/api/update-dataset?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newDataset),
}).then(res => res.json());
}
export function addDataset(dataset) {
let newDataset = Setting.deepCopy(dataset);
return fetch(`${Setting.ServerUrl}/api/add-dataset`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newDataset),
}).then(res => res.json());
}
export function deleteDataset(dataset) {
let newDataset = Setting.deepCopy(dataset);
return fetch(`${Setting.ServerUrl}/api/delete-dataset`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newDataset),
}).then(res => res.json());
}

View File

@ -0,0 +1,74 @@
import * as Setting from "../Setting";
export function GetDetailedInfo(pid) {
return fetch(`${Setting.ServerUrl}/api/review/proj/detailed?:pid=${pid}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function GetBasicInfo(pid) {
return fetch(`${Setting.ServerUrl}/api/review/proj/basic?:pid=${pid}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function UpdateProjectInfo() {
return fetch(`${Setting.ServerUrl}/api/review/proj`, {
method: "PUT",
credentials: "include",
}).then(res => res.json());
}
export function CreatTemplateProject(data) {
let project = new Object();
project.basic_info = data;
project.name = data.name;
return fetch(`${Setting.ServerUrl}/api/review/proj/template`, {
method: "POST",
credentials: "include",
body: JSON.stringify(project),
}).then(res => res.json());
}
export function CreateEmptyProject() {
return fetch(`${Setting.ServerUrl}/api/review/proj`, {
method: "POST",
credentials: "include",
}).then(res => res.json());
}
export function GetUserAssignments(uid) {
return fetch(`${Setting.ServerUrl}/api/review/proj/user?:uid=${uid}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function GetProjectList(id_list) {
let data = new Object();
data.id_list = id_list;
return fetch(`${Setting.ServerUrl}/api/review/query/proj`, {
method: "POST",
credentials: "include",
body: JSON.stringify(data),
}).then(res => res.json());
}
export function GetProjectAssignments(pid) {
return fetch(`${Setting.ServerUrl}/api/review/proj/assign?:pid=${pid}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function GetUserList(id_list) {
let data = new Object();
data.id_list = id_list;
return fetch(`${Setting.ServerUrl}/api/review/query/user`, {
method: "POST",
credentials: "include",
body: JSON.stringify(data),
}).then(res => res.json());
}

View File

@ -0,0 +1,50 @@
import * as Setting from "../Setting";
export function TraceQuestionVersion(qid) {
return fetch(`${Setting.ServerUrl}/api/qbank/question/trace?:qid=${qid}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function GetTempQuestionList(id_list) {
let data = new Object();
data.id_list = id_list;
return fetch(`${Setting.ServerUrl}/api/qbank/query/t_question`, {
method: "POST",
credentials: "include",
body: JSON.stringify(data),
}).then(res => res.json());
}
export function GetUserTempQuestions(uid) {
return fetch(`${Setting.ServerUrl}/api/qbank/question/user_t?:uid=${uid}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function GetUserTempTestpaper(uid) {
return fetch(`${Setting.ServerUrl}/api/qbank/testpaper/user_t?:uid=${uid}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function CreateNewQuestion(data) {
let newData = Setting.deepCopy(data);
return fetch(`${Setting.ServerUrl}/api/qbank/question`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newData),
}).then(res => res.json());
}
export function CreateNewTestpaper(data) {
let newData = Setting.deepCopy(data);
return fetch(`${Setting.ServerUrl}/api/qbank/testpaper`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newData),
}).then(res => res.json());
}

77
web/src/font.css Normal file
View File

@ -0,0 +1,77 @@
/* 默认字体 具体可能需要修改 */
#editor {
font-family: 'Microsoft-YaHei';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimSun]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimSun]::before {
content: "宋体";
font-family: "SimSun";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimHei]::before {
content: "黑体";
font-family: "SimHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Microsoft-YaHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Microsoft-YaHei]::before {
content: "微软雅黑";
font-family: "Microsoft YaHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=consolas]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=consolas]::before {
content: "consolas";
font-family: "consolas";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=KaiTi]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=KaiTi]::before {
content: "楷体";
font-family: "KaiTi";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=FangSong]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=FangSong]::before {
content: "仿宋";
font-family: "FangSong";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Arial]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Arial]::before {
content: "Arial";
font-family: "Arial";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Times-New-Roman]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Times-New-Roman]::before {
content: "New Roman";
font-family: "Times New Roman";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=sans-serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sans-serif]::before {
content: "sans-serif";
font-family: "sans-serif";
}
.ql-font-SimSun {
font-family: "SimSun";
}
.ql-font-SimHei {
font-family: "SimHei";
}
.ql-font-Microsoft-YaHei {
font-family: "Microsoft YaHei";
}
.ql-font-KaiTi {
font-family: "KaiTi";
}
.ql-font-FangSong {
font-family: "FangSong";
}
.ql-font-Arial {
font-family: "Arial";
}
.ql-font-Times-New-Roman {
font-family: "Times New Roman";
}
.ql-font-sans-serif {
font-family: "sans-serif";
}
.ql-font-consolas {
font-family: "consolas";
}

57
web/src/i18n.js Normal file
View File

@ -0,0 +1,57 @@
import i18n from "i18next";
import zh from "./locales/zh/data.json";
import en from "./locales/en/data.json";
import * as Conf from "./Conf";
import * as Setting from "./Setting";
const resources = {
en: en,
zh: zh,
};
function initLanguage() {
let language = localStorage.getItem("language");
if (language === undefined || language == null) {
if (Conf.ForceLanguage !== "") {
language = Conf.ForceLanguage;
} else {
let userLanguage;
userLanguage = navigator.language;
switch (userLanguage) {
case "zh-CN":
language = "zh";
break;
case "zh":
language = "zh";
break;
case "en":
language = "en";
break;
case "en-US":
language = "en";
break;
default:
language = Conf.DefaultLanguage;
}
}
}
Setting.changeMomentLanguage(language);
return language;
}
i18n.init({
lng: initLanguage(),
resources: resources,
keySeparator: false,
interpolation: {
escapeValue: false,
},
// debug: true,
saveMissing: true,
});
export default i18n;

80
web/src/index.css Normal file
View File

@ -0,0 +1,80 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif !important;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
.logo {
background: url("https://cdn.open-ct.com/logo/openct_logo_1082x328.png");
background-size: 108px, 33px;
width: 108px;
height: 33px;
/*background: rgba(0, 0, 0, 0.2);*/
margin: 17px 10px 16px 20px;
float: left;
}
.ant-table.ant-table-middle .ant-table-title, .ant-table.ant-table-middle .ant-table-footer, .ant-table.ant-table-middle thead > tr > th, .ant-table.ant-table-middle tbody > tr > td {
padding: 1px 8px !important;
}
.ant-list-sm .ant-list-item {
padding: 2px !important;
}
.highlight-row {
background: rgb(249,198,205);
}
.alert-row {
background: #ffccc7;
}
/*http://react-china.org/t/topic/33846/3*/
.ant-table-header {
scrollbar-color:transparent transparent
}
.ant-table-header::-webkit-scrollbar {
background-color:transparent
}
.import-output {
height: 500px;
}
.CodeMirror {
height: 100% !important;
}
.App {
padding: 20px;
}
.red-row {
background: #ffccc7;
}
.yellow-row {
background: #ffffb8;
}
.green-row {
background: #d9f7be;
}
/*https://stackoverflow.com/questions/64961752/how-do-i-change-the-color-of-selected-menu-item-in-ant-design*/
/*.conferenceMenu > .ant-menu-item-selected {*/
/* background-color: rgb(230,247,255) !important;*/
/*}*/
.conferenceMenu {
background-color: rgb(242,242,242) !important;
}

28
web/src/index.js Normal file
View File

@ -0,0 +1,28 @@
// create-react-app + IE9
// https://www.cnblogs.com/xuexia/p/12092768.html
// react-app-polyfill
// https://www.npmjs.com/package/react-app-polyfill
import "react-app-polyfill/ie9";
import "react-app-polyfill/stable";
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "./font.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import "antd/dist/antd.min.css";
import {BrowserRouter} from "react-router-dom";
import "./i18n";
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
),
document.getElementById("root"));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@ -0,0 +1,34 @@
{
"account": {
"My Account": "My Account",
"Sign In": "Sign In",
"Sign Out": "Sign Out",
"Sign Up": "Sign Up"
},
"dataset": {
"Address": "Address",
"Carousels": "Carousels",
"Default item": "Default item",
"Edit Dataset": "Edit Dataset",
"End date": "End date",
"Full name": "Full name",
"Introduction text": "Introduction text",
"Language": "Language",
"Location": "Location",
"Organizer": "Organizer",
"Start date": "Start date"
},
"general": {
"Action": "Action",
"Add": "Add",
"Datasets": "Datasets",
"Delete": "Delete",
"Edit": "Edit",
"Home": "Home",
"Name": "Name",
"Save": "Save",
"Status": "Status",
"Pendingtasks":"Pending Tasks",
"Proposition Paper":"Proposition Paper"
}
}

View File

@ -0,0 +1,34 @@
{
"account": {
"My Account": "我的账户",
"Sign In": "登录",
"Sign Out": "登出",
"Sign Up": "注册"
},
"dataset": {
"Address": "Address",
"Carousels": "Carousels",
"Default item": "Default item",
"Edit Dataset": "Edit Dataset",
"End date": "End date",
"Full name": "Full name",
"Introduction text": "Introduction text",
"Language": "Language",
"Location": "Location",
"Organizer": "Organizer",
"Start date": "Start date"
},
"general": {
"Action": "操作",
"Add": "添加",
"Datasets": "数据集",
"Delete": "删除",
"Edit": "编辑",
"Home": "首页",
"Name": "名称",
"Save": "保存",
"Status": "状态",
"Pending Tasks":"项目管理",
"Proposition Paper":"命题组卷"
}
}

135
web/src/serviceWorker.js Normal file
View File

@ -0,0 +1,135 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === "[::1]" ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
"This web app is being served cache-first by a service " +
"worker. To learn more, visit https://bit.ly/CRA-PWA"
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
"New content is available and will be used when all " +
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log("Content is cached for offline use.");
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error("Error during service worker registration:", error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get("content-type");
if (
response.status === 404 ||
(contentType != null && contentType.indexOf("javascript") === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
"No internet connection found. App is running in offline mode."
);
});
}
export function unregister() {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}