20241113-a

This commit is contained in:
unknown
2024-11-13 09:05:45 +08:00
commit 3bcaa20bc4
186 changed files with 100169 additions and 0 deletions

290
src/App.css Normal file
View File

@@ -0,0 +1,290 @@
.cxd-Layout-content {
background-color: #fff;
}
.user-page .cxd-Page-body {
padding: 0;
}
.cxd-DropDown-menu {
min-width: auto;
}
.cxd-Tree {
height: 84vh;
max-height: inherit!important;
}
.cxd-ListControl-item:hover:active:hover,
.cxd-ListControl-item.is-active:hover {
background-color: var(--ListControl-item-onActive-bg);
}
.plan-list .cxd-ListControl-items {
display: flex;
flex-direction: column;
}
.rbc-allday-cell {
display: none;
}
.rbc-month-row {
min-height: 128px;
border-color: rgba(0, 0, 0, 0.12);
}
.copy-list {
display: flex;
flex-flow: wrap;
}
.copy-list .cxd-GroupedSelection-item {
width: 50%;
}
/* https://github.com/rjsf-team/react-jsonschema-form/issues/2188 */
/* .rjsf .field-undefined> :nth-child(1) {
display: none;
} */
.rjsf [for='root']:first-child + #root__description {
display: none;
}
.rjsf .css-42:last-child {
display: none;
}
.rjsf .field-array span:last-child {
float: none !important;
}
.rjsf fieldset {
padding: 0;
border: 0;
}
.rjsf .btn-add::after {
content: '添加';
}
.rjsf.array-item-move-up::after {
content: '上移';
}
.rjsf.array-item-move-down::after {
content: '下移';
}
.rjsf.array-item-remove::after {
content: '删除';
}
.rjsf .form-group {
margin-bottom: 15px;
}
.rjsf .form-group label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
font-weight: 700;
}
.rjsf .form-control {
max-width: 100%;
width: 100%;
display: flex;
background: var(--Form-input-bg);
border: var(--Form-input-borderWidth) solid var(--Form-input-borderColor);
border-radius: var(--Form-input-borderRadius);
line-height: var(--Form-input-lineHeight);
padding: var(--Form-input-paddingY) var(--Form-input-paddingX);
font-size: var(--Form-input-fontSize);
flex-wrap: wrap;
justify-content: flex-start;
}
.preview table {
width: 100%;
border: 1px solid #000000;
border-collapse: separate;
border-left: 0;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
background-color: transparent;
border-spacing: 0;
}
.preview table thead:first-child tr:first-child > th:first-child,
.preview table tbody:first-child tr:first-child > td:first-child {
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px;
}
.preview table thead th {
vertical-align: bottom;
}
.preview table th,
.preview table td {
padding: 4px 8px !important;
line-height: 14px;
text-align: left;
vertical-align: top;
border-left: 1px solid #000000;
border-top: 1px solid #000000;
}
.preview table th {
padding: 4px 8px;
line-height: 20px;
text-align: left;
}
/*custom*/
.cxd-Select-popover {
z-index: 1500;
}
.tox {
z-index: 1400 !important;
}
.amis-scope .cxd-PopOver {
z-index: 1400 !important;
}
#print-preview .cxd-Table-fixedTop {
display: none;
}
.common-padding {
padding: 0px 0 4px 10px !important;
}
/* 部门管理 */
.partment-grid .cxd-Grid-col--md3 {
padding-right: 5px;
}
.partment-grid .cxd-Grid-col--md9 {
padding-left: 0;
}
.partment-height {
height: 84vh;
max-height: 100vh;
overflow: hidden;
}
/* 报告模板 */
.rpt-tp-height {
height: 81vh;
}
/* 教学计划 */
.experiment {
display: inline-block;
margin-left: 0;
width: 230px;
height: 20px;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.experiment:hover {
display: inline-block;
width: auto;
height: 20px;
min-width: 230px;
vertical-align: middle;
white-space: none;
overflow: unset;
text-overflow: unset;
}
.class {
display: inline-block;
width: 140px;
height: 20px;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.class:hover {
display: inline-block;
width: auto;
height: 20px;
min-width: 140px;
vertical-align: middle;
white-space: none;
overflow: unset;
text-overflow: unset;
}
.course_select .cxd-Select-menu {
width: 200px;
}
.trash {
padding: 0 5px;
}
.wrapper {
display: inline-block;
padding: 0;
}
.wrapper .is-disabled {
background: grey;
color: white !important;
}
.tab .cxd-Tabs-content {
padding: 0;
}
.table {
width: auto;
min-width: auto;
}
.text-purple {
color: rgb(175, 167, 175);
}
.question img {
max-width: 100%;
}
.question-option p {
display: inline-block;
}
.question .cxd-Button {
display: inline-block;
width: 480px;
overflow: hidden;
white-space: nowrap;
text-align: left;
text-overflow: ellipsis;
}
.menu-permission-tree {
max-height: none;
}
.evaluation-height {
height: 75vh;
max-height: 75vh;
}
.cxd-Modal {
overflow-y: auto;
}
.gallery .cxd-Modal-content {
border: 0;
max-width: 100%;
}
.gallery .cxd-Modal-content.in,
.gallery .cxd-Modal-content.out {
animation-fill-mode: none;
}
.device-frame {
overflow-y: auto;
}
.root-41 {
display: none;
}

171
src/App.tsx Normal file
View File

@@ -0,0 +1,171 @@
import { alert, confirm, toast } from 'amis-ui';
import axios from 'axios';
import copy from 'copy-to-clipboard';
import _, { isString } from 'lodash';
import { Provider } from 'mobx-react';
import * as React from 'react';
import RootRoute from './routes';
import { MainStore } from './stores';
import './utils/icons';
import { request } from './utils/requestInterceptor';
import { resRequest } from './utils/resRequestInterceptor';
import './renderer/AttScoreRule';
import './renderer/Calendar';
import './renderer/CourseList';
import './renderer/DataPreview';
import './renderer/DataTrial';
import './renderer/ProjectScoreItem';
import './renderer/Scheduler';
import './renderer/TimeslotPicker';
import './renderer/TimeslotPicker/Copy';
import './renderer/Workload';
// import './renderer/DataPreview1';
import './renderer/BatchGroup';
import './renderer/CustomImageUpload';
import './renderer/CustomRichtext';
import './renderer/DataSet';
import './renderer/DataSetPicker';
import './renderer/DownloadReport';
import './renderer/DownloadReportSchedule';
import './renderer/ExamImport';
import './renderer/ExamPaperRule';
import './renderer/ExcelImport';
import './renderer/ExcelImportDeduction';
import './renderer/ExcelImportGroup';
import './renderer/ExcelImportSchedule';
import './renderer/ExcelImportStudent';
import './renderer/ExcelImportStudentOrg';
import './renderer/Group';
import './renderer/ImageGallery';
import './renderer/Option';
import './renderer/TextbookSubmit';
import './renderer/Vditor';
// css
import 'font-awesome/css/font-awesome.css';
// import 'amis/lib/themes/antd.css';
import 'amis-ui/lib/themes/cxd.css';
// import './scss/style.scss'
// import 'amis/sdk/iconfont.css'
import 'amis/lib/helper.css';
import './App.css';
export default function (): JSX.Element {
console.log(React);
const store = ((window as any).store = MainStore.create(
{},
{
fetcher: ({ url, method, data, config, headers }: any) => {
config = config || {};
config.headers = config.headers || {};
config.withCredentials = true;
config.baseURL = '/api';
// config.baseURL = '/changzhou/api';
// config.baseURL = prefix ? `${prefix}/api` : '/api';
if (config.cancelExecutor) {
config.cancelToken = new axios.CancelToken(config.cancelExecutor);
}
config.headers = headers || {};
config.method = method;
if (method === 'get' && data) {
config.params = data;
} else if (data && data instanceof FormData) {
// config.headers = config.headers || {};
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data);
// config.headers = config.headers || {};
config.headers['Content-Type'] = 'application/json';
}
let match = url.match(/\[(.*?)\]/);
while (match) {
url = url.replace(match[0], `.${match[1]}`);
match = url.match(/\[(.*?)\]/);
}
let apiPrefix = url.match(/^[^/]*\//);
if (
config.headers.post2rest !== false &&
(config.method === 'put' ||
config.method === 'patch' ||
config.method === 'delete')
) {
url = _.replace(
url,
/^[^/]*\//,
`${apiPrefix}post2rest/${config.method}/`
);
config.method = 'post';
}
let token = window.sessionStorage.getItem('authenticated');
if (token && url !== '/api/rpc/login') {
config.headers['Authorization'] = `Bearer ${token}`;
}
data && (config.data = data);
config.url = url;
return request(config);
},
resFetcher: ({ url, method, data, config, headers }): any => {
config = config || {};
config.headers = config.headers || {};
config.withCredentials = true;
config.baseURL = '';
if (config.cancelExecutor) {
config.cancelToken = new axios.CancelToken(config.cancelExecutor);
}
config.headers = headers || {};
config.method = method;
let auth = window.sessionStorage.getItem('auth');
if (auth) {
config.headers['X-Auth'] = auth;
}
data && (config.data = data);
config.url = url;
return resRequest(config);
},
isCancel: (e: any) => axios.isCancel(e),
notify: (type: 'success' | 'error' | 'info', msg: string) => {
if ((isString(msg) && msg) || msg?.message) {
toast[type]
? toast[type](msg, type === 'error' ? '系统错误' : '系统消息')
: console.warn('[Notify]', type, msg);
console.log('[notify]', type, msg);
}
},
alert,
confirm,
copy: (contents: string, options: any = {}) => {
const ret = copy(contents, options);
ret &&
(!options || options.shutup !== true) &&
toast.info('内容已拷贝到剪切板');
return ret;
},
}
));
return (
<Provider store={store}>
<RootRoute store={store} />
</Provider>
);
}

View File

@@ -0,0 +1,61 @@
import * as React from 'react';
import { render as renderSchema } from 'amis';
import { IMainStore } from '@/stores';
import { inject, observer } from 'mobx-react';
import { withRouter } from 'react-router';
import { Action } from 'amis/lib/types';
import renderOptions from '@/utils/renderOptions';
interface RendererProps {
schema?: any;
[propName: string]: any;
}
@inject('store')
// @ts-ignore
@withRouter
@observer
export default class AMisRenderer extends React.Component<RendererProps, any> {
env: any = null;
handleAction = (e: any, action: Action) => {
this.env.alert(`没有识别的动作:${JSON.stringify(action)}`);
};
constructor(props: RendererProps) {
super(props);
const store = props.store as IMainStore;
const history = props.history;
this.env = renderOptions(store, history);
}
render() {
const { schema, store, onAction, ...rest } = this.props;
return renderSchema(
schema,
{
data: {
user: store.user,
currentSemester: store.currentSemester?.id,
semester: store.currentSemester,
semesterSelect: store.currentSemester?.id,
periods: store.periods,
basePath: store.basePath,
},
onAction: (e: any, action: any) => {
console.log(action)
if (!action || action.actionType === 'url') {
return;
}
if (action.actionType === 'logout') {
store.logout();
}
},
theme: store && store.theme,
...rest,
},
this.env
);
}
}

215
src/components/UserInfo.tsx Normal file
View File

@@ -0,0 +1,215 @@
import React from 'react';
import AMisRenderer from './AMisRenderer';
interface UserInfoProps {
user: any;
}
interface UserInfoState {
open?: boolean;
}
export default class UserInfo extends React.Component<
UserInfoProps,
UserInfoState
> {
constructor(props: UserInfoProps) {
super(props);
this.state = {
open: false,
};
this.open = this.open.bind(this);
this.close = this.close.bind(this);
}
static defaultProps = {};
open() {
this.setState({
open: true,
});
}
close() {
this.setState({
open: false,
});
}
handleClickOutside() {
this.close();
}
render() {
const user = this.props.user;
const schema = {
type: 'page',
className: 'user-page',
body: [
{
type: 'button',
label: '工作台',
onEvent: {
click: {
actions: [
{
ignoreError: false,
actionType: 'url',
args: {
url: '/hub/login?jwt=$user.token',
params: {},
},
},
],
},
},
id: 'u:05fc5e4362bc',
themeCss: {
className: {
'padding-and-margin:default': {
marginRight: '5px',
},
},
},
},
// {
// type: 'button',
// label: '探索',
// onEvent: {
// click: {
// actions: [
// {
// ignoreError: false,
// actionType: 'url',
// args: {
// url: '/explore?token=$user.token',
// params: {},
// },
// },
// ],
// },
// },
// id: 'u:05fc5e4362bd',
// themeCss: {
// className: {
// 'padding-and-margin:default': {
// marginRight: '5px',
// },
// },
// },
// },
{
type: 'button',
label: '资源管理',
onEvent: {
click: {
actions: [
{
ignoreError: false,
actionType: 'url',
args: {
url: '/res',
params: {},
},
},
],
},
},
id: 'u:05fc5e4362bc',
themeCss: {
className: {
'padding-and-margin:default': {
marginRight: '5px',
},
},
},
},
{
type: 'dropdown-button',
label: user.name,
align: 'right',
buttons: [
{
type: 'button',
icon: 'fa fa-cog',
label: '系统信息 ',
actionType: 'dialog',
dialog: {
closeOnEsc: true,
title: '系统信息',
size: 'sm',
type: 'dialog',
body: [
{
type: 'tpl',
tpl: '<p>智医教学实践平台</p>\n<p>OLMS II</p>',
inline: false,
id: 'u:369cb33ce9d0',
},
],
showCloseButton: true,
showErrorMsg: true,
showLoading: true,
id: 'u:289af4d19c37',
actions: [],
},
},
{
type: 'button',
icon: 'fa fa-key',
label: '修改密码 ',
actionType: 'dialog',
dialog: {
closeOnEsc: true,
title: '修改登录密码',
size: 'sm',
body: [
{
type: 'form',
api: {
url: `rest/rpc/change_password`,
method: 'post',
data: {
password: '${password}',
},
},
messages: {
saveSuccess: '[密码修改成功] 请使用新密码重新登录',
saveFailed: '密码修改失败',
},
// redirect: '/login',
mode: 'horizontal',
horizontal: {
left: 'col-sm-3',
right: 'col-sm-9',
},
controls: [
{
type: 'password',
name: 'password',
required: true,
label: '新密码',
},
// {
// type: 'password',
// name: 'confirmPassword',
// required: true,
// label: '重复密码',
// },
],
},
],
},
},
{
type: 'button',
actionType: 'logout',
level: 'link',
icon: 'fa fa-reply',
label: '退出登录 ',
},
],
},
],
};
return <AMisRenderer schema={schema} />;
}
}

13
src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

29
src/index.tsx Normal file
View File

@@ -0,0 +1,29 @@
import * as React from 'react';
import { render } from 'react-dom';
import App from './App';
import 'react-perfect-scrollbar/dist/css/styles.css';
import './index.css';
export function bootstrap(mountTo: HTMLElement) {
render(<App />, mountTo);
}
(self as any).MonacoEnvironment = {
getWorkerUrl: function (moduleId: any, label: string) {
if (label === 'json') {
return '/json.worker.bundle.js';
}
if (label === 'css') {
return '/css.worker.bundle.js';
}
if (label === 'html') {
return '/html.worker.bundle.js';
}
if (label === 'typescript' || label === 'javascript') {
return '/ts.worker.bundle.js';
}
return '/editor.worker.bundle.js';
},
};
bootstrap(document.getElementById('root')!);

16
src/pages/404.tsx Normal file
View File

@@ -0,0 +1,16 @@
import * as React from 'react';
import { Link } from 'react-router-dom';
import { NotFound } from 'amis';
export default () => (
<NotFound
links={
<Link to="/" className="list-group-item">
<i className="fa fa-chevron-right text-muted" />
<i className="fa fa-fw fa-mail-forward m-r-xs" />
</Link>
}
footerText={''}
/>
);

116
src/pages/Editor.tsx Normal file
View File

@@ -0,0 +1,116 @@
import React, { useEffect } from 'react';
import { Editor, ShortcutKey } from 'amis-editor';
import { inject, observer } from 'mobx-react';
import { RouteComponentProps } from 'react-router-dom';
import 'amis/lib/themes/cxd.css';
import 'amis/lib/helper.css';
import 'amis/sdk/iconfont.css';
import 'amis-editor-core/lib/style.css';
// import 'amis/lib/themes/default.css';
import '../scss/editor.scss';
import { IMainStore } from '../stores';
import loadPageByPath from '../utils/loadPageByPath';
import renderOptions from '../utils/renderOptions';
export default inject('store')(
observer(function ({
store,
history,
match,
}: { store: IMainStore } & RouteComponentProps<{ id: string }>) {
const pageId: number = parseInt(match.params.id, 10);
useEffect(() => {
(async () => {
let nav: any = store.flat_navigations.find((x: any) => x.id === pageId);
let pagePath = nav ? nav.schema_path : '';
loadPageByPath(pagePath).then((page: any) => {
store.updateSchema(page.schema);
});
})();
}, [pageId]);
function save() {
console.log(store.schema);
store.copy(JSON.stringify(store.schema));
}
function exit() {
history.goBack();
}
return (
<div className="Editor-Demo">
<div className="Editor-header">
<div className="Editor-title"></div>
<div className="Editor-view-mode-group-container">
<div className="Editor-view-mode-group">
<div
className={`Editor-view-mode-btn editor-header-icon ${
!store.isMobile ? 'is-active' : ''
}`}
onClick={() => {
store.setIsMobile(false);
}}
>
PC
</div>
<div
className={`Editor-view-mode-btn editor-header-icon ${
store.isMobile ? 'is-active' : ''
}`}
onClick={() => {
store.setIsMobile(true);
}}
>
H5
</div>
</div>
</div>
<div className="Editor-header-actions">
<ShortcutKey />
<div
className={`header-action-btn m-1 ${
store.preview ? 'primary' : ''
}`}
onClick={() => {
store.setPreview(!store.preview);
}}
>
{store.preview ? '编辑' : '预览'}
</div>
{!store.preview && (
<>
<div className={`header-action-btn`} onClick={save}>
</div>
<div className={`header-action-btn exit-btn`} onClick={exit}>
退
</div>
</>
)}
</div>
</div>
<div className="Editor-inner">
<Editor
theme={store.theme}
preview={store.preview}
isMobile={store.isMobile}
value={store.schema}
onChange={(value: any) => store.updateSchema(value)}
onPreview={() => {
store.setPreview(true);
}}
onSave={save}
className="is-fixed"
showCustomRenderersPanel={true}
amisEnv={renderOptions(store, history)}
/>
</div>
</div>
);
})
);

113
src/pages/Login.tsx Normal file
View File

@@ -0,0 +1,113 @@
import { MD5 } from 'crypto-js';
import { inject, observer } from 'mobx-react';
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import AMisRenderer from '../components/AMisRenderer';
import { IMainStore } from '../stores/index';
interface LoginProps extends RouteComponentProps<any> {
store: IMainStore;
}
const schema = {
type: 'page',
body: [
{
type: 'wrapper',
body: [
{
type: 'plain',
tpl: '用户登录',
inline: false,
className: 'block m-t-xxl m-b-xl text-center text-2x text-info',
},
{
type: 'form',
submitText: '登录',
api: {
method: 'post',
url: 'rest/rpc/login',
data: {
code: '${username}',
password: '${password}',
},
requestAdaptor: (api: any) => {
api.body.password = MD5(api.body.password).toString();
return api;
},
},
wrapWithPanel: false,
canAccessSuperData: false,
messages: {
saveSuccess: '',
},
body: [
{
label: '',
type: 'input-text',
name: 'username',
required: true,
placeholder: '用户名',
},
{
type: 'input-password',
label: '',
name: 'password',
placeholder: '密码',
required: true,
},
{
type: 'submit',
label: '登录',
size: 'lg',
block: true,
level: 'primary',
},
],
},
],
className: 'bg-white w-xxl Modal-content',
},
],
className: 'bg-white h-full',
};
@inject('store')
// @ts-ignore
@withRouter
@observer
export default class LoginRoute extends React.Component<LoginProps> {
handleFormSaved = (value: any) => {
const store = this.props.store;
const history = this.props.history;
let user = null;
if (value.items) {
user = value.items[0].login;
} else {
user = value;
}
store.notify('登录成功!');
store.user.login(user);
history.push('/');
store
.resFetcher({
url: '/res/api/login',
method: 'post',
data: {
username: '',
password: '',
recaptcha: '',
},
})
.then((res: any) => {
console.log(res);
document.cookie = `auth=${res.data.data}; Path=/; SameSite=Strict;"`;
sessionStorage.setItem('auth', res.data.data);
});
};
render() {
return <AMisRenderer onFinished={this.handleFormSaved} schema={schema} />;
}
}

124
src/pages/Preview.tsx Normal file
View File

@@ -0,0 +1,124 @@
import React from 'react';
import { inject, observer } from 'mobx-react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { IMainStore } from '../stores/index';
import AMisRenderer from '../components/AMisRenderer';
interface LoginProps extends RouteComponentProps<any> {
store: IMainStore;
}
const schema = {
type: 'page',
bodyClassName: 'w-4/5 m-auto',
body: [
{
type: 'plain',
tpl: '实验开放教学登记表',
inline: false,
className: 'text-center text-2x font-bold',
},
{
type: 'plain',
tpl: '实验名称:${project}',
inline: false,
className: 'm-t-xs',
},
{
type: 'plain',
tpl: '课堂编号:${id} 指导教师:${teacher} 地点:${location} \n 实验时间:${date} ${start_time}',
inline: false,
className: 'm-sm',
},
{
type: 'plain',
tpl: '备注:登记表请妥善保存,学期结束时必须交到实验室存档,不能遗失!',
inline: false,
className: '',
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/user2projects?select=id,student:users!user2project_student_id_fkey(id,code,name,user_orgs(orgs(name)))&schedule_id=eq.${id}&schedule_status=in.(elected,stopped)&order=id.asc',
adaptor:
'payload.data.items = payload.data.items.map((item, index) => {\r\n return {\r\n ...item,\r\n order_number: index + 1\r\n }\r\n});\r\nreturn payload;',
},
columns: [
{
name: 'order_number',
label: '序号',
type: 'text',
placeholder: '-',
},
{
name: 'student.code',
label: '学号',
type: 'text',
},
{
type: 'text',
label: '姓名',
name: 'student.name',
},
{
name: 'student.user_orgs[0].orgs.name',
label: '班级',
type: 'text',
},
{
type: 'text',
label: '考勤',
name: 'att',
placeholder: '',
},
{
type: 'text',
label: '预习',
name: 'prepare',
placeholder: '',
},
{
type: 'text',
label: '操作',
name: 'op',
placeholder: '',
},
{
type: 'text',
label: '报告',
name: 'report',
placeholder: '',
},
{
type: 'text',
label: '备注',
name: 'beizhu',
placeholder: '',
},
],
columnsTogglable: false,
affixHeader: false,
showHeader: true,
showFooter: true,
messages: {},
className: 'm-t-sm preview',
},
{
type: 'tpl',
tpl: '<p>教师签字:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</p>',
inline: false,
className: 'text-right',
},
],
};
@inject('store')
// @ts-ignore
@withRouter
@observer
export default class LoginRoute extends React.Component<LoginProps> {
render() {
return <AMisRenderer schema={schema} />;
}
}

41
src/pages/Schema.tsx Normal file
View File

@@ -0,0 +1,41 @@
import React, { useEffect, useState } from 'react';
import { inject, observer } from 'mobx-react';
import { RouteComponentProps, useParams } from 'react-router-dom';
import { IMainStore } from '../stores/index';
import AMisRenderer from '../components/AMisRenderer';
import loadPageByPath from '../utils/loadPageByPath';
export default inject('store')(
observer(function ({
store,
}: { store: IMainStore } & RouteComponentProps<{
id: string;
}>) {
let { centre_id, id } = useParams<any>();
const [schema, setSchema] = useState({
type: 'page',
body: {
type: 'spinner',
},
});
useEffect(() => {
setSchema({
type: 'page',
body: {
type: 'spinner',
},
});
let nav: any = store.flat_navigations
.filter((x: any) => x.path !== null)
.find((x: any) => x.id.toString() === id);
let pagePath = nav ? nav.schema_path : '';
loadPageByPath(pagePath).then((page: any) => {
setSchema(page.schema);
});
}, [id]);
// TODO: schema中的api有时没自动加载
return <AMisRenderer schema={schema} centre_id={centre_id} />;
})
);

35
src/pages/Student.tsx Normal file
View File

@@ -0,0 +1,35 @@
import React from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { inject, observer } from 'mobx-react';
import AMisRenderer from '../components/AMisRenderer';
import { IMainStore } from '../stores';
export default inject('store')(
observer(function ({
page,
store,
location,
history,
match,
}: { page: number } & { store: IMainStore } & RouteComponentProps<{
id: string;
}>) {
const schema = {
type: 'page',
body: [
{
type: 'tpl',
tpl: '请使用微信选课',
},
],
aside: [],
messages: {},
// "initApi": {
// "method": "get",
// "url": "/api/user2schedules"
// }
};
return <AMisRenderer schema={schema} />;
})
);

View File

@@ -0,0 +1,9 @@
import schema2component from '@/utils/schema2component';
const schema = {
type: 'page',
title: 'Dashboard',
body: [],
};
export default schema2component(schema);

346
src/pages/admin/index.tsx Normal file
View File

@@ -0,0 +1,346 @@
import RouterGuard from '@/routes/RouterGuard';
import { IMainStore } from '@/stores';
import { AsideNav, Button, Layout, toast } from 'amis';
import { inject, observer } from 'mobx-react';
import * as React from 'react';
import {
Link,
Redirect,
Route,
RouteComponentProps,
Switch,
matchPath,
} from 'react-router-dom';
import UserInfo from '../../components/UserInfo';
import SchemaRoute from '../Schema';
import Student from '../Student';
import AMisRenderer from '@/components/AMisRenderer';
type NavItem = {
label: string;
children?: Array<NavItem>;
icon?: string;
path?: string;
component?: React.ReactType;
getComponent?: () => Promise<React.ReactType>;
};
let PATH_PREFIX = '';
function isActive(link: any, location: any) {
const ret = matchPath(location.pathname, {
path: link ? link.replace(/\?.*$/, '') : '',
exact: true,
strict: true,
});
return !!ret;
}
export interface AdminProps extends RouteComponentProps<any> {
store: IMainStore;
}
@inject('store')
@observer
export default class Admin extends React.Component<AdminProps, any> {
state = {
pathname: '',
hasLoadMenu: false,
navigations: [],
};
logout = () => {
const store = this.props.store;
store.user.logout();
const history = this.props.history;
history.replace(`/login`);
};
componentDidMount() {
const store = this.props.store;
const history = this.props.history;
console.log('componentDidMount, store.user:', store.user);
if (!store.user.isAuthenticated) {
// toast['error']('用户未登陆,请先登陆!', '消息');
history.replace(`/login`);
}
this.refreshMenu();
}
refreshMenu = () => {
const store = this.props.store;
let pathname = this.props.location.pathname;
console.log('location:', pathname);
console.log('store.user:', store.user);
if (
pathname !== 'login' &&
!this.state.hasLoadMenu &&
store.user.isAuthenticated
) {
store.updateCentres();
store.updateSemesters();
store.updatePeriods();
}
};
renderHeader() {
const store = this.props.store;
const theme = 'cxd';
return (
<div>
<div className={`${theme}-Layout-brandBar`}>
<button
onClick={store.toggleOffScreen}
className="pull-right visible-xs"
>
<i className="glyphicon glyphicon-align-justify"></i>
</button>
<div className={`${theme}-Layout-brand`}>
<i className="fa "></i>
<span className="hidden-folded">{store.name}</span>
</div>
</div>
<div
className={`${theme}-Layout-headerBar`}
style={{ justifyContent: 'space-between' }}
>
<div className="nav navbar-nav hidden-xs">
<Button
level="link"
className="no-shadow navbar-btn"
onClick={store.toggleAsideFolded}
tooltip="展开或收起侧边栏"
placement="bottom"
iconOnly
>
<i
className={store.asideFolded ? 'fa fa-indent' : 'fa fa-dedent'}
/>
</Button>
<Button
onClick={()=>{
window.open('/doc')
}}
></Button>
</div>
<div className="hidden-xs">
<UserInfo user={store.user} />
</div>
</div>
</div>
);
}
renderCentreAside(
prefix: string,
items: Array<NavItem>,
centre_id: number
): Array<NavItem> {
if (!items) {
return [];
}
return items.map((item: any) => {
if (item.children && item.children.length > 0) {
return {
...item,
children: this.renderCentreAside(prefix, item.children, centre_id),
};
}
// let centreSubPath = item.path && item.path.replace('centre/:id/', '');
return {
...item,
path: `${prefix}/${item.id}?centre_id=${centre_id}`,
children: undefined,
};
});
}
renderAside() {
const location = this.props.location;
const store = this.props.store;
let navigations = store.navigations.filter(
(item: any) => item.label !== '教学中心'
);
let centre_navigations = store.navigations.filter(
(item: any) => item.label === '教学中心'
);
let dynamic_navigations: Array<NavItem> = [];
this.props.store.centres?.forEach((item: any) => {
let centre_path = `centre/${item.id}`;
dynamic_navigations.push({
label: item.name,
icon: 'fa fa-institution',
// path: centre_path,
children: centre_navigations[0]
? this.renderCentreAside(
centre_path,
centre_navigations[0].children,
item.id
)
: [],
});
});
return (
<AsideNav
key={store.asideFolded ? 'folded-aside' : 'aside'}
navigations={[{ children: navigations.concat(dynamic_navigations) }]}
renderLink={({ link, toggleExpand, classnames: cx, depth }: any) => {
if (link.hidden) {
return null;
}
let children = [];
if (link.children && link.children.length > 0) {
children.push(
<span
key="expand-toggle"
className={cx('AsideNav-itemArrow')}
onClick={(e) => toggleExpand(link, e)}
></span>
);
} else {
if (
process.env.NODE_ENV === 'development' &&
store.user.role === 'dev'
) {
children.push(
<i
key="edit"
data-tooltip="编辑"
data-position="bottom"
className={'navbtn fa fa-pencil'}
onClick={(e: React.MouseEvent) => {
e.preventDefault();
this.props.history.push(`/edit/${link.page}`);
}}
/>
);
}
}
link.badge &&
children.push(
<b
key="badge"
className={cx(
`AsideNav-itemBadge`,
link.badgeClassName || 'bg-info'
)}
>
{link.badge}
</b>
);
if (link.icon) {
children.push(
<i key="icon" className={cx(`AsideNav-itemIcon`, link.icon)} />
);
} else if (store.asideFolded && depth === 1) {
children.push(
<i
key="icon"
className={cx(
`AsideNav-itemIcon`,
link.children ? 'fa fa-folder' : 'fa fa-info'
)}
/>
);
}
children.push(
<span key="label" className={cx('AsideNav-itemLabel')}>
{link.label}
</span>
);
return link.schema_path ? (
link.active ? (
<a>{children}</a>
) : (
<Link to={`/page/${link.path}`}>{children}</Link>
)
) : (
<a
onClick={
link.onClick
? link.onClick
: link.children
? () => toggleExpand(link)
: undefined
}
>
{children}
</a>
);
}}
// renderSubLinks={({link,renderLink, depth}:any) =>{
// return (link.children && link.children.length ? (
// <ul className={'AsideNav-subList'}>
// {link.label ? (
// <li key="subHeader" className={'AsideNav-subHeader'}>
// <a>{link.label}</a>
// </li>
// ) : null}
// {link.children.map((link:any, key:any) =>
// renderLink(link, key, {}, depth + 1)
// )}
// </ul>
// ) : link.label && depth === 1 ? (
// <div className={'AsideNav-tooltip'}>{link.label}</div>
// ) : null)
// }}
isActive={(link: any) =>
isActive(
link.path && link.path[0] === '/'
? link.path
: `${PATH_PREFIX}/page/${link.path}`,
location
)
}
/>
);
}
render() {
const store = this.props.store;
let pathname = this.props.location.pathname;
console.log('location:', pathname);
if (pathname === 'login') {
return (
<Switch>
<RouterGuard />
<Redirect to={`/404`} />
</Switch>
);
} else {
return (
<>
<Layout
theme="cxd"
aside={this.renderAside()}
header={this.renderHeader()}
folded={store.asideFolded}
offScreen={store.offScreen}
>
<Switch>
<Redirect to={`/${store.defaultNav}`} from={`/`} exact />
<Route
path="/page/centre/:centre_id/:id"
component={SchemaRoute}
/>
<Route path="/page/:id" component={SchemaRoute} />
<RouterGuard />
<Redirect to={`/404`} />
</Switch>
</Layout>
</>
);
}
}
}

View File

@@ -0,0 +1,66 @@
const schema = {
body: [
{
type: 'button',
label: '软件下载',
size: 'md',
level: 'primary',
block: false,
onEvent: {
click: {
actions: [
{
args: {
url: 'https://www.platosoft.org/download/ATT.zip',
},
actionType: 'url',
},
],
weight: 0,
},
},
},
{
type: 'button',
label: '使用说明',
actionType: 'url',
size: 'md',
level: 'primary',
className: 'm-l',
onEvent: {
click: {
actions: [
{
args: {
url: 'https://www.platosoft.org/doc/olms/manual/admin/att_qr.html',
},
actionType: 'url',
},
],
weight: 0,
},
},
},
{
type: 'formula',
name: 'api_url',
formula: 'data.api_url=location.origin + this.basePath + "/api"',
},
{
type: 'property',
title: '配置参数',
items: [
{ label: '接口地址', content: '$api_url', span: 1 },
{ label: '本地秘钥', content: 'olms', span: 1 },
],
column: 1,
mode: 'table',
className: 'w-xxl m-t',
},
],
title: '二维码考勤',
type: 'page',
messages: {},
};
export { schema };

View File

@@ -0,0 +1,28 @@
const schema = {
body: [{ type: 'html', html: '<h1>人脸考勤使用说明</h1>' }],
type: 'page',
title: '人脸考勤使用说明',
toolbar: [
{
type: 'button',
label: '下载客户端',
level: 'success',
blank: true,
onEvent: {
click: {
actions: [
{
args: {
url: '/download/faceApp.zip',
},
actionType: 'url',
},
],
weight: 0,
},
},
},
],
};
export { schema };

View File

@@ -0,0 +1,461 @@
const schema = {
body: [
{
type: 'form',
className: '',
name: 'filter',
title: '表单',
wrapWithPanel: false,
reload:
'adopt?organization_id=eq.$centre_id&project_id=$project_id&teacher_id=$teacher_id&location_id=$location_id&date=$date&class_list=$class_list&no_tch=$no_tch',
submitOnInit: true,
submitOnChange: true,
initApi: {
method: 'get',
url: 'rest/users?role=eq.teacher&id=eq.${user.id}&order=code',
adaptor:
"if (payload.data.items.length !== 0) {\r\n return {\r\n teacher_id: 'eq.'.concat(payload.data.items[0].id)\r\n }\r\n} else {\r\n return {}\r\n}",
},
body: [
{
type: 'group',
body: [
{
type: 'select',
name: 'project_id',
label: '实验',
mode: 'inline',
source: {
method: 'get',
url: 'rest/projects?organization_id=eq.$centre_id&semester_id=eq.$currentSemester&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
size: 'md',
checkAll: false,
clearable: true,
inputClassName: 'm-l-xs',
},
],
},
{
type: 'group',
body: [
{
type: 'input-date-range',
name: 'date',
mode: 'inline',
label: '日期',
clearable: true,
inputClassName: 'm-l-xs',
value: '',
format: 'YYYY-MM-DD',
ranges:
'yesterday,today,7daysago,prevweek,thismonth,prevmonth,prevquarter',
},
{
type: 'select',
label: '地点',
name: 'location_id',
mode: 'inline',
checkAll: false,
clearable: true,
source: {
method: 'get',
url: 'rest/locations?organization_id=eq.$centre_id&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
size: 'md',
className: 'm-l-sm',
inputClassName: 'm-l-xs',
},
{
type: 'select',
name: 'teacher_id',
label: '教师',
checkAll: false,
source: {
method: 'get',
url: 'rest/users?role=eq.teacher&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
mode: 'inline',
size: 'md',
clearable: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
},
],
},
{
type: 'group',
body: [
{
type: 'tree-select',
label: '预留班级',
name: 'class_list',
mode: 'horizontal',
searchable: true,
clearable: true,
multiple: true,
joinValues: true,
onlyChildren: true,
source: {
method: 'get',
url: 'rest/orgs?select=*&path=cd.root&order=name',
adaptor:
"const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\n\r\nlet school = payload.data.items.find(x => x.type === 'school')\r\n\r\nreturn {\r\n ...payload,\r\n data: recursive(school.id)\r\n}\r\n",
},
withChildren: true,
initiallyOpen: false,
heightAuto: true,
virtualThreshold: false,
},
],
},
{
type: 'group',
body: [
{
type: 'switch',
name: 'no_tch',
mode: 'inline',
value: 0,
option: '显示无教师场次',
optionAtLeft: false,
trueValue: 1,
falseValue: 0,
},
],
},
],
mode: 'inline',
},
{ type: 'divider', lineStyle: 'solid' },
{
type: 'crud',
messages: {},
api: {
method: 'get',
url: 'rest/schedule_stats?organization_id=eq.$centre_id&semester_id=eq.$currentSemester&is_publish=is.true&order=date.asc,start_time.asc',
data: { page: '$page', perPage: '$perPage', '&': '$$' },
adaptor:
"let supportDateTimeFormat = typeof(Intl.DateTimeFormat) === 'function';\r\npayload.data.items.forEach((item,index) => {\r\n item.day = supportDateTimeFormat ? `${item.date}${new Intl.DateTimeFormat('zh-CN', { weekday: 'short'}).format(new Date(item.date))}` : item.date;\r\n item.row_number = index + 1\r\n + (api.body.page - 1)\r\n * api.body.perPage\r\n let att_count = item.att_y_count\r\n + item.att_y_late_count\r\n + item.att_y_unclean_count\r\n item.att_num = \"\".concat(att_count, '/', item.current_student_number)\r\n item.att_num_status = att_count === item.current_student_number\r\n ? 1\r\n : 0\r\n item.stu_title = item.project_name.concat(\r\n ' :: ', item.date, ' ', item.period_name,\r\n ' :: ', item.location_name,\r\n ' :: ', item.teacher_name ? item.teacher_name : '无教师认领'\r\n )\r\n})\r\n\r\nreturn {\r\n ...payload\r\n}",
requestAdaptor:
"let url = api.url.replace(/project_id=&|teacher_id=&|date=&|location_id=&|no_tch=&|no_tch=0/g, '')\r\n\r\nif (api.body.class_list === '') {\r\n url = url.replace('&class_list=', '')\r\n} else {\r\n let class_list = api.body.class_list && api.body.class_list.replace(',', '%2C')\r\n url = url.replace(class_list, 'cs.{'.concat(class_list, '}'))\r\n}\r\n\r\nconst pattern = /(?:date=)(\\d+-\\d+-\\d+)%2C(\\d+-\\d+-\\d+)/g\r\nconst result = pattern.exec(url)\r\nif (result && result[1] === result[2]) {\r\n url = url.replace(result[0], 'date=eq.'.concat(result[1]))\r\n} else if (result) {\r\n url = url.replace(result[0], 'date=gte.'.concat(result[1], '&date=lte.', result[2]))\r\n} else {\r\n url = url.replace('&date=','')\r\n}\r\nconst is_no_tch = /no_tch=1/g.test(url)\r\nconst has_teacher = /teacher_id=.+?(?=&)/g.test(url)\r\nif (is_no_tch && has_teacher) {\r\n const teacher = /teacher_id=.+?(?=&)/g.exec(url)\r\n url = url.replace(teacher, 'or=('.concat(teacher, ',teacher_id.is.null)')).replace('teacher_id=', 'teacher_id.')\r\n url = url.replace(/no_tch=1/g, '')\r\n} else if (is_no_tch && !has_teacher) {\r\n url = url.replace(/no_tch=1/g, 'teacher_id=is.null')\r\n}\r\nreturn {\r\n ...api,\r\n url: url\r\n}",
},
syncLocation: false,
name: 'adopt',
columns: [
{
name: 'row_number',
type: 'plain',
label: '序号',
tpl: '',
inline: false,
},
{
name: '',
type: 'tpl',
placeholder: '-',
label: '实验',
tpl: '<% if (this.scheduled_class_name) { %>\n <%= this.project_name %><%= this.scheduled_class_name %>\n<% } else { %>\n <%= this.project_name %>\n<% } %>',
},
{ name: 'day', label: '日期', type: 'text', placeholder: '-' },
{ type: 'text', name: 'period_name', label: '节次', placeholder: '-' },
{
type: 'text',
name: 'location_name',
placeholder: '-',
label: '地点',
},
{
type: 'tpl',
label: '教师',
name: '',
placeholder: '-',
tpl: '<% if (this.teacher_name) { %>\n <span class="label label-info" style="font-size: smaller;"><%= this.teacher_name %></span>\n<% } else { %>\n <span class="label label-warning" style="font-size: smaller;">未认领</span>\n<% } %>',
inline: false,
},
{
type: 'tpl',
label: '考勤状态',
placeholder: '-',
tpl: '<% if(this.att_num_status) { %>\n <span class="label label-success"><%= this.att_num %></span>\n<% } else { %>\n <span class="label label-warning"><%= this.att_num %></span>\n<% } %>',
inline: false,
},
{
type: 'button-group',
label: '操作',
buttons: [
{
label: '认领场次',
type: 'button',
actionType: 'ajax',
level: 'success',
size: 'xs',
visibleOn:
"this.enableClaim && (this.user.role === 'teacher' && !this.teacher_id)",
api: {
method: 'patch',
url: 'rest/schedules/$id',
data: { teacher_id: '${user.id}' },
},
},
{
type: 'button',
label: '取消认领',
actionType: 'ajax',
level: 'danger',
size: 'xs',
visibleOn:
'this. enableClaim && this.teacher_code === this.user.code',
api: {
method: 'patch',
url: 'rest/schedules/$id',
data: { teacher_id: null },
},
},
{
label: '查看场次',
type: 'button',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '学生信息',
body: [
{
type: 'crud',
api: {
method: 'get',
url: 'rest/user2projects?select=id,att_status,att_status_text:user2project_att_status_dict_fk(dictvalue),att_time,score,schedule_status:user2project_schedule_status_dict_fk(dictvalue),student:users!user2project_student_id_fkey(id,code,name,user_orgs(orgs(name)))&schedule_id=eq.${id}&schedule_status=in.(elected,stopped)&order=ordernumber_of_schedule',
data: null,
adaptor:
'payload.data.items.forEach((item,index) => {\r\n item.row_number=index+1;\r\n})\r\nreturn {\r\n ...payload\r\n}',
},
columns: [
{
label: '序号',
type: 'plain',
inline: false,
name: 'row_number',
tpl: '',
},
{
label: '选课状态',
type: 'plain',
name: 'schedule_status.dictvalue',
placeholder: '-',
inline: false,
},
{
type: 'text',
label: '学生',
name: 'student.name',
placeholder: '-',
},
{
type: 'text',
label: '学号',
name: 'student.code',
placeholder: '-',
},
{
type: 'text',
label: '班级',
name: 'student.user_orgs[0].orgs.name',
placeholder: '-',
},
{
type: 'date',
label: '出勤',
name: 'att_time',
placeholder: '-',
format: 'YYYY-MM-DD HH:mm:ss',
},
{
type: 'text',
label: '考勤状态',
name: 'att_status_text.dictvalue',
placeholder: '-',
groupName: '',
remark: '',
inline: true,
visibleOn:
"this.user.role !== 'teacher' || (this.user.role === 'teacher' && this.teacher_id === user.id)",
quickEdit: {
mode: 'popOver',
type: 'select',
label: '',
name: 'att_status',
checkAll: false,
source: {
method: 'get',
url: 'rest/dicts?typecode=eq.014',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.dictvalue,\r\n value: item.dictkey\r\n }\r\n })\r\n}',
},
size: 'sm',
saveImmediately: {
api: {
method: 'patch',
url: 'rest/user2projects/${id}',
data: { att_status: '${att_status}' },
dataType: 'json',
requestAdaptor:
"let temp = api.data.att_status.slice(0, 5);\r\n\r\nif (temp === 'att_y') {\r\n let date = new Date();\r\n api.data.att_time=`${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;\r\n} else {\r\n api.data.att_time = null;\r\n}\r\n\r\nreturn api;",
},
},
},
},
{
type: 'text',
label: '考勤状态',
name: 'att_status_text.dictvalue',
placeholder: '-',
groupName: '',
remark: '',
inline: true,
visibleOn:
"this.user.role === 'teacher' && this.teacher_id !== user.id",
},
],
messages: {},
syncLocation: false,
bodyClassName: '',
className: '',
headerToolbar: [
'bulkActions',
{
type: 'tpl',
tpl: '${stu_title}',
},
{
type: 'button',
label: '打印',
size: 'sm',
level: 'primary',
className: 'pull-right',
id: 'u:114eb8de43c8',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: true,
actionType: 'url',
args: {
url: '/admin/preview?id=${id}&project=${project_name}&location=${location_name}&teacher=${teacher_name}&date=${date}&start_time=${start_time}',
},
},
],
},
},
},
{
type: 'export-excel',
label: '全量导出 Excel',
api: {
method: 'get',
url: 'rest/user2projects?select=id,att_status,att_status_text:user2project_att_status_dict_fk(dictvalue),att_time,score,schedule_status:user2project_schedule_status_dict_fk(dictvalue),student:users!user2project_student_id_fkey(id,code,name,user_orgs(orgs(name)))&schedule_id=eq.${id}&schedule_status=in.(elected,stopped)&order=id.asc',
data: null,
adaptor:
'payload.data.items.forEach((item,index) => {\r\n item.row_number=index+1;\r\n})\r\nreturn {\r\n ...payload\r\n}',
},
filename: '考勤信息',
align: 'right',
id: 'u:d74fe0b275db',
},
],
bulkActions: [
{
label: '批量考勤',
actionType: 'dialog',
type: 'button',
dialog: {
type: 'dialog',
title: '批量考勤',
body: [
{
type: 'form',
title: '表单',
controls: [
{
type: 'select',
label: '选项',
name: 'att',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/dicts?typecode=eq.014',
adaptor:
'return {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.dictvalue,\r\n value: item.dictkey\r\n }\r\n })\r\n}',
},
},
{ type: 'hidden', name: 'id', value: '${ids}' },
],
wrapWithPanel: false,
initApi: '',
api: {
method: 'patch',
url: 'rest/user2projects?id=in.(${ids})',
data: { att_status: '$att' },
requestAdaptor:
"let temp = api.data.att_status.slice(0, 5);\r\n\r\nif (temp === 'att_y') {\r\n let date = new Date();\r\n api.data.att_time=`${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;\r\n} else {\r\n api.data.att_time = null;\r\n}\r\n\r\nreturn api;",
},
},
],
closeOnEsc: true,
showCloseButton: true,
},
visibleOn:
"this.user.role !== 'teacher' || (this.user.role === 'teacher' && this.teacher_id === user.id)",
},
],
footerToolbar: [],
perPageField: '',
pageField: '',
affixHeader: false,
perPageAvailable: [10],
hideQuickSaveBtn: true,
},
],
closeOnEsc: true,
closeOnOutside: false,
showCloseButton: true,
size: 'lg',
actions: [],
},
level: 'primary',
size: 'xs',
reload: 'adopt',
},
],
},
],
headerToolbar: [],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
perPageAvailable: [10, 20, 30, 40, 50],
},
],
type: 'page',
messages: {},
bodyClassName: '',
title: '',
style: {},
initApi: {
method: 'get',
url: 'rest/config?key=eq.system&organization_id=eq.${centre_id}',
adaptor:
'let item = payload.data.items[0];\r\nreturn {\r\n ...payload,\r\n data: {\r\n enableClaim: payload.data.items[0].value.enableClaim\r\n }\r\n}',
},
};
export { schema };

View File

@@ -0,0 +1,305 @@
const schema = {
body: [
{
type: 'panel',
className: 'Panel--default',
title: '',
body: [
{
type: 'crud',
api: {
method: 'get',
url: 'rest/users_ex?select=organization_id:org_id,id,code,name,machine_locations(locations(id,name))&role=eq.machine&org_id=eq.$centre_id&order=code',
adaptor:
"return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n id: item.id,\r\n code: item.code,\r\n name: item.name,\r\n machine_locations: item.machine_locations,\r\n select_loc: item.machine_locations.map(item => {\r\n return ''.concat(item.locations.id)\r\n })\r\n }\r\n })\r\n}",
},
mode: 'cards',
messages: {},
card: {
type: 'card',
header: { title: '${code}', subTitle: '${name}' },
body: [
{ type: 'divider', label: '关联场地' },
{
type: 'button-group',
label: '',
buttons: [
{
type: 'button',
label: '',
actionType: 'dialog',
dialog: {
title: '关联场地',
body: [
{
type: 'form',
api: {
method: 'post',
url: 'rest/machine_locations',
dataType: 'json',
requestAdaptor:
'return {\r\n ...api,\r\n data: api.data.location_id.map(item => {\r\n return {\r\n user_id: api.data.id,\r\n location_id: item\r\n }\r\n })\r\n}',
adaptor: '',
},
title: '表单',
initApi: '',
body: [
{
type: 'checkboxes',
label: '场地',
mode: '',
name: 'location_id',
required: true,
options: [],
joinValues: false,
source: {
method: 'get',
url: 'rest/locations?id=not.in.(${select_loc})&organization_id=eq.$centre_id&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => ({\r\n label: item.name,\r\n value: item.id\r\n }))\r\n}',
requestAdaptor: '',
},
extractValue: true,
},
],
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
},
icon: 'fa fa-plus text-primary',
level: 'link',
size: 'md',
tooltip: '关联场地',
tooltipPlacement: 'top',
},
],
},
{
type: 'each',
name: 'machine_locations',
items: [
{
type: 'container',
body: [
{
type: 'tpl',
tpl: '${locations.name}',
inline: true,
},
{
type: 'button',
actionType: 'ajax',
confirmText: '确认取消关联"${locations.name}"',
api: {
method: 'delete',
url: 'rest/machine_locations?user_id=eq.$id&location_id=eq.${locations.id}',
},
size: 'xs',
icon: 'fa fa-times text-danger',
level: 'link',
tooltip: '取消场地关联',
tooltipPlacement: 'top',
},
],
className: 'wrapper',
},
],
placeholder: '暂无内容',
className: '',
},
],
actions: [
{
type: 'button',
label: '',
actionType: 'dialog',
dialog: {
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/users/$id',
data: {
code: '$code',
name: '$name',
},
dataType: 'json',
},
body: [
{
type: 'input-text',
label: '账号',
name: 'code',
required: true,
mode: 'normal',
},
{
label: '名称',
type: 'input-text',
name: 'name',
required: true,
mode: 'normal',
},
],
},
],
type: 'dialog',
title: '修改考勤账号',
closeOnEsc: true,
showCloseButton: true,
},
icon: 'fa fa-edit text-info',
level: 'link',
tooltip: '修改考勤账号',
tooltipPlacement: 'top',
},
{
type: 'button',
label: '',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '修改密码',
body: [
{
type: 'form',
title: '表单',
rules: [
{
rule: 'this.new_password === this.password',
message: '两次密码输入不一致',
},
],
api: {
method: 'post',
url: 'rest/rpc/change_password',
data: { password: '$password', user_id: '$id' },
},
body: [
{
label: '新密码',
type: 'input-password',
name: 'new_password',
required: true,
mode: 'normal',
},
{
type: 'input-password',
label: '确认密码',
name: 'password',
required: true,
mode: 'normal',
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'md',
},
icon: 'fa fa-cog text-warning',
tooltip: '修改密码',
tooltipPlacement: 'top',
},
{
type: 'button',
label: '',
actionType: 'ajax',
icon: 'fa fa-trash text-danger',
confirmText: '确认删除"${name}(${code})"',
api: { method: 'delete', url: 'rest/users/$id' },
level: 'link',
tooltip: '删除考勤账号',
tooltipPlacement: 'top',
},
],
},
footerToolbar: [],
bulkActions: [],
className: '',
},
],
header: [
{
type: 'button',
label: '添加考勤账号',
actionType: 'dialog',
dialog: {
title: '添加考勤账号',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/rpc/create_user',
data: {
role: 'machine',
orgs_id: '$centre_id',
code: '$code',
name: '$name',
password: '$password',
},
dataType: 'json',
headers: { Prefer: 'params=single-object' },
requestAdaptor: '',
},
rules: [
{
rule: 'this.new_password === this.password',
message: '两次密码输入不一致',
},
],
messages: {},
body: [
{
type: 'input-text',
name: 'code',
label: '账号',
required: true,
mode: 'normal',
},
{
type: 'input-text',
label: '名称',
name: 'name',
required: true,
mode: 'normal',
},
{
label: '密码',
type: 'input-password',
name: 'new_password',
required: true,
mode: 'normal',
},
{
type: 'input-password',
label: '确认密码',
name: 'password',
required: true,
mode: 'normal',
},
],
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
},
level: 'primary',
align: 'left',
className: 'm-t-xs m-l',
},
],
headerClassName: 'panel-heading',
},
],
type: 'page',
};
export { schema };

View File

@@ -0,0 +1,316 @@
const schema = {
body: [
{
type: 'crud',
columns: [
{
type: 'text',
name: 'code',
label: '账号',
placeholder: '-',
id: 'u:c3eb8f9b177e',
},
{
type: 'text',
name: 'name',
label: '姓名',
placeholder: '-',
id: 'u:6f74a11657ef',
},
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
level: 'link',
size: 'md',
icon: 'fa fa-pencil text-info',
visibleOn:
"this.user.role === 'dev' || this.user.role === 'admin'",
tooltip: '修改',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:13770aa6289a',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
title: '修改账号',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/users/$id',
data: {
code: '$code',
name: '$name',
},
dataType: 'json',
},
body: [
{
label: '账号',
type: 'input-text',
name: 'code',
required: true,
mode: 'normal',
id: 'u:a614008003f6',
},
{
type: 'input-text',
label: '姓名',
name: 'name',
required: true,
mode: 'normal',
id: 'u:5973b1eb26ab',
},
],
id: 'u:c59fb46c3665',
actions: [
{
type: 'submit',
label: 'Submit',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:366fea2f5de5',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
},
},
],
debounce: { wait: 100 },
},
},
},
{
type: 'button',
confirmText: '确认删除账号"${name}(${code})"',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除',
tooltipPlacement: 'bottom',
visibleOn:
"this.user.role === 'dev' || this.user.role === 'admin'",
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:221693061e9e',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/users/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:366fea2f5de5',
ignoreError: false,
actionType: 'reload',
},
],
debounce: { wait: 100 },
},
},
},
],
placeholder: '-',
width: 200,
id: 'u:96b309d3bf00',
},
],
messages: {},
api: {
method: 'get',
url: 'rest/users?select=*&role=eq.centreadmin&user_orgs.orgs.id=eq.$centre_id&order=code',
data: { page: '$page', perPage: '$perPage' },
adaptor:
'payload.data.items.sort(\r\n (a, b) => {\r\n if (a.name === b.name) {\r\n return a.code.localeCompare(b.code)\r\n } else {\r\n return a.name.localeCompare(b.name)\r\n }\r\n }\r\n)\r\n\r\nreturn {\r\n ...payload\r\n}',
},
headerToolbar: [
{
type: 'button',
icon: 'fa fa-plus text-primary',
size: 'lg',
level: 'link',
tooltip: '添加',
tooltipPlacement: 'left',
align: 'right',
label: '',
visibleOn: "this.user.role === 'dev' || this.user.role === 'admin'",
className: 'p-l-none',
id: 'u:76187972a571',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加管理员',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/rpc/create_user',
data: {
code: '$code',
name: '$name',
password: '$password',
new_password: '$new_password',
role: 'centreadmin',
orgs_id: '$centre_id',
},
dataType: 'json',
headers: { Prefer: 'params=single-object' },
},
body: [
{
type: 'input-text',
label: '账号',
name: 'code',
required: true,
mode: 'normal',
id: 'u:6dca4e78622c',
},
{
type: 'input-text',
name: 'name',
label: '姓名',
mode: 'normal',
required: true,
id: 'u:56021033191e',
},
{
type: 'input-password',
name: 'password',
label: '密码',
mode: 'normal',
required: true,
id: 'u:1b5ed3926797',
},
{
type: 'input-password',
name: 'new_password',
label: '确认密码',
mode: 'normal',
required: true,
id: 'u:7c1e9ce050c0',
},
],
rules: [
{
rule: 'this.password === this.new_password',
message: '两次密码输入不一致,请重新输入',
},
],
id: 'u:6ecfef8353a7',
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:366fea2f5de5',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:6179e3a79d93',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:e62e24a0fc22',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:f7524893881b',
},
],
},
},
],
debounce: { wait: 100 },
},
},
},
],
syncLocation: false,
perPageAvailable: [10, 20, 30, 40, 50],
name: 'admin',
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
perPageField: 'perPage',
bulkActions: [],
itemActions: [],
pageField: 'page',
affixHeader: true,
id: 'u:366fea2f5de5',
},
],
type: 'page',
messages: {},
title: '',
perPageAvailable: [10, 20, 30, 40, 50],
id: 'u:4c19431ee5db',
};
export { schema };

View File

@@ -0,0 +1,474 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
columns: [
{
type: 'text',
name: 'code',
label: '账号',
sortable: true,
searchable: true,
id: 'u:91821b736555',
},
{
type: 'text',
name: 'name',
label: '姓名',
sortable: true,
searchable: true,
id: 'u:a81612f6d91c',
},
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
level: 'link',
size: 'md',
icon: 'fa fa-pencil text-info',
tooltip: '修改',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
label: '',
api: { method: 'get', url: 'rest/users/$id' },
id: 'u:483a2e18377b',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
title: '修改账号',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/users/$id',
data: { code: '$code', name: '$name' },
dataType: 'json',
},
mode: 'normal',
body: [
{
label: '账号',
type: 'input-text',
name: 'code',
required: true,
id: 'u:bcbd9ab0fd9e',
},
{
type: 'input-text',
label: '姓名',
name: 'name',
required: true,
id: 'u:3b7829fa16f8',
},
],
id: 'u:7a2ac32b9e2b',
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:f1b328630134',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
id: 'u:49ea67f4ccca',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:e22fba76fe33',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:46273e3a4090',
},
],
actionType: 'dialog',
},
},
],
},
},
},
{
type: 'button',
confirmText: '确认删除"${name}(${code})"',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
label: '',
id: 'u:c3026515a2de',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/users/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:f1b328630134',
ignoreError: false,
actionType: 'reload',
args: { resetPage: false },
},
],
},
},
},
],
placeholder: '-',
width: 200,
id: 'u:e8398dbbd899',
},
],
messages: {},
api: {
method: 'get',
url: 'rest/users?select=*,user_orgs(orgs(id))&code=${code}&name=${name}&role=eq.assistant&user_orgs.orgs.id=eq.$centre_id&order=code',
data: {
page: '${page}',
perPage: '${perPage}',
orderBy: '${orderBy}',
orderDir: '${orderDir}',
code: 'like.%${code}%',
name: 'like.%${name}%',
},
requestAdaptor:
"if (api.query.code === 'like.%%') {\r\n api.url = api.url.replace('&code=like.%25%25', '')\r\n}\r\n\r\nif (api.query.name === 'like.%%') {\r\n api.url = api.url.replace('&name=like.%25%25', '')\r\n}\r\n\r\nif (api.query.orderDir) {\r\n api.url = api.url.replace('&order=code%2Cname', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '&order=' + api.query.orderBy + '.' + api.query.orderDir);\r\n} else if (api.query.orderBy) {\r\n api.url = api.url.replace('&order=code%2Cname', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=', '&order=' + api.query.orderBy);\r\n} else {\r\n api.url = api.url.replace('&orderBy=&orderDir=', '');\r\n}\r\n\r\nreturn api;",
},
headerToolbar: [
{
type: 'button',
icon: 'fa fa-plus',
level: 'default',
align: 'right',
label: '添加',
className: 'm-l-xs',
id: 'u:ab332275c1fa',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加助教',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'input-text',
name: 'code',
label: '账号',
mode: 'normal',
required: true,
id: 'u:487b8805e663',
},
{
type: 'input-text',
name: 'name',
label: '姓名',
mode: 'normal',
required: true,
id: 'u:89de05a91f91',
},
{
type: 'input-password',
name: 'password',
label: '密码',
mode: 'normal',
required: true,
id: 'u:0fb31870de06',
},
{
type: 'input-password',
name: 'new_password',
label: '确认密码',
mode: 'normal',
required: true,
id: 'u:c78103928eff',
},
],
api: {
method: 'post',
url: 'rest/rpc/create_user',
data: {
code: '$code',
name: '$name',
password: '$password',
new_password: '$new_password',
role: 'assistant',
orgs_id: '$centre_id',
},
requestAdaptor: '',
dataType: 'json',
headers: { Prefer: 'params=single-object' },
},
rules: [
{
rule: 'this.password === this.new_password',
message: '两次密码输入不一致,请重新输入',
},
],
id: 'u:b610d21f422f',
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:f1b328630134',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
closeOnOutside: false,
id: 'u:2fbf0844aca1',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:deb6bbf65621',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:bbdffe4baf61',
},
],
actionType: 'dialog',
},
},
],
},
},
tooltip: '',
tooltipPlacement: 'left',
},
{
type: 'button',
align: 'right',
label: '导入',
icon: 'fa fa-upload',
level: 'warning',
id: 'u:6581af7e35f0',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '助教导入',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'input-excel',
name: 'assistants',
label: '',
mode: 'normal',
id: 'u:5f0eca8a64fc',
},
{
type: 'input-table',
name: 'assistants',
label: '',
columns: [
{
label: '账号',
name: '账号',
placeholder: '-',
quickEdit: {
mode: 'inline',
id: 'u:fcf8c77a0302',
},
id: 'u:328345552f87',
},
{
label: '姓名',
name: '姓名',
placeholder: '-',
quickEdit: {
mode: 'inline',
id: 'u:3801de195dcc',
},
id: 'u:e3d8eb8dae09',
},
],
mode: 'normal',
visibleOn: 'this.assistants',
strictMode: true,
removable: true,
editable: false,
id: 'u:d73c846629ed',
clearValueOnHidden: true,
},
],
api: {
method: 'post',
url: 'rest/rpc/create_user',
data: {
orgs_id: '${centre_id}',
assistants: '${assistants}',
},
requestAdaptor:
"return {\r\n ...api,\r\n data: api.data.assistants.map(item => {\r\n return {\r\n name: item.姓名,\r\n code: item.账号,\r\n role: 'assistant',\r\n orgs_id: api.data.orgs_id\r\n }\r\n })\r\n}",
dataType: 'json',
headers: { Prefer: 'params=single-object' },
},
visibleOn: '',
name: '',
id: 'u:feda7d9a6c8e',
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:f1b328630134',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'xl',
actionType: 'dialog',
id: 'u:7cc3f8103868',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:908813bc5040',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:280fe6e082d4',
},
],
closeOnOutside: false,
showErrorMsg: true,
showLoading: true,
draggable: false,
},
},
],
},
},
},
{
type: 'export-excel',
align: 'right',
label: '导出',
icon: 'fa fa-download',
level: 'primary',
api: 'rest/users?select=*,user_orgs(orgs(id))&role=eq.assistant&user_orgs.orgs.id=eq.$centre_id&order=code',
filename: '助教管理',
exportColumns: [
{ label: '账号', name: 'code' },
{ name: 'name', label: '姓名' },
],
id: 'u:556c3974c544',
},
],
syncLocation: false,
perPageAvailable: [10, 20, 30, 40, 50],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
perPageField: 'perPage',
pageField: 'page',
affixHeader: true,
title: '',
initApi: '',
bodyClassName: '',
id: 'u:f1b328630134',
},
],
messages: {},
title: '',
id: 'u:31753e0403ea',
asideResizor: false,
pullRefresh: { disabled: true },
definitions: {},
};
export { schema };

View File

@@ -0,0 +1,493 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
columns: [
{
type: 'text',
name: 'code',
label: '编号',
sortable: true,
searchable: true,
id: 'u:f01745729d6f',
},
{
type: 'text',
name: 'name',
label: '名称',
sortable: true,
searchable: true,
id: 'u:0db1a83903b6',
},
{
type: 'text',
label: '类型',
id: 'u:00332fcfb6b6',
name: 'dicts.dictvalue',
searchable: true,
},
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
actionType: 'dialog',
dialog: { $ref: 'dialog-ref-2' },
level: 'link',
size: 'md',
icon: 'fa fa-pencil text-info',
tooltip: '修改',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:2fbcae5dec1e',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: { $ref: 'dialog-ref-2' },
},
],
},
},
},
{
type: 'button',
confirmText: '确认删除"${name}(${code})"',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:259ec9a26f44',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/locations/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:96cb5c2c87a4',
ignoreError: false,
actionType: 'reload',
args: { resetPage: false },
},
],
},
},
},
],
placeholder: '-',
width: 200,
id: 'u:328bf6132313',
},
],
messages: {},
api: {
method: 'get',
url: 'rest/locations?select=id,code,name,type,dicts!inner(dictvalue)&code=${code}&name=${name}&dicts.dictvalue=${dicts.dictvalue}&order=code',
data: {
page: '${page}',
perPage: '${perPage}',
orderBy: '${orderBy}',
orderDir: '${orderDir}',
code: 'like.%${code}%',
name: 'like.%${name}%',
'dicts.dictvalue': 'like.%${dicts.dictvalue}%',
},
requestAdaptor:
"if (api.query.code === 'like.%%') {\r\n api.url = api.url.replace('&code=like.%25%25', '')\r\n}\r\n\r\nif (api.query.name === 'like.%%') {\r\n api.url = api.url.replace('&name=like.%25%25', '')\r\n}\r\n\r\nif (api.query.dicts.dictvalue === 'like.%%') {\r\n api.url = api.url.replace('&dicts[dictvalue]=like.%25%25', '')\r\n}\r\n\r\nif (api.query.orderDir) {\r\n api.url = api.url.replace('&order=code%2Cname', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '&order=' + api.query.orderBy + '.' + api.query.orderDir);\r\n} else if (api.query.orderBy) {\r\n api.url = api.url.replace('&order=code%2Cname', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=', '&order=' + api.query.orderBy);\r\n} else {\r\n api.url = api.url.replace('&orderBy=&orderDir=', '');\r\n}\r\n\r\nreturn api;",
},
headerToolbar: [
{
type: 'button',
icon: 'fa fa-plus',
level: 'default',
align: 'right',
label: '添加',
className: 'm-l-xs',
id: 'u:64fb6bec0fff',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: { $ref: 'dialog-ref-1' },
args: { fromCurrentDialog: true },
},
],
},
},
actionType: 'dialog',
dialog: { $ref: 'dialog-ref-1' },
},
{
type: 'button',
align: 'right',
label: '导入',
icon: 'fa fa-upload',
level: 'warning',
id: 'u:a2c775bee2a9',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '场地导入',
size: 'lg',
body: [
{
type: 'form',
id: 'u:2d97745994e5',
title: '表单',
body: [
{
type: 'input-excel',
name: 'locations',
label: '',
mode: 'normal',
id: 'u:5f0eca8a64fc',
},
{
type: 'input-table',
name: 'locations',
label: '',
columns: [
{
label: '编号',
name: '编号',
placeholder: '-',
quickEdit: {
mode: 'inline',
id: 'u:931a41e1011d',
},
id: 'u:0c6672aa1858',
},
{
label: '名称',
name: '名称',
placeholder: '-',
quickEdit: {
mode: 'inline',
id: 'u:6ebc04992e03',
},
id: 'u:a03852887668',
},
{
label: '类型',
name: '类型',
placeholder: '-',
quickEdit: {
mode: 'inline',
id: 'u:a75f5e419328',
},
id: 'u:efedd284d265',
},
],
mode: 'normal',
visibleOn: 'this.locations',
strictMode: true,
removable: true,
editable: false,
id: 'u:d73c846629ed',
clearValueOnHidden: true,
},
],
api: {
method: 'post',
url: 'rest/locations',
data: {
orgs_id: '${centre_id}',
locations: '${locations}',
},
requestAdaptor:
"return {\r\n ...api,\r\n data: api.data.locations.map(item => {\r\n let type;\r\n switch (item.类型)\r\n {\r\n case '实验室':\r\n type = 'laboratory';\r\n break;\r\n case '教室':\r\n type = 'classroom';\r\n break;\r\n case '办公室':\r\n type = 'office';\r\n break;\r\n case '仓库':\r\n typ = 'warehouse';\r\n break;\r\n }\r\n\r\n return {\r\n name: item.名称,\r\n code: item.编号,\r\n type: type,\r\n organization_id: api.data.orgs_id\r\n }\r\n })\r\n}",
dataType: 'json',
},
visibleOn: '',
name: '',
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
dsType: 'api',
labelAlign: 'left',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:96cb5c2c87a4',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
actionType: 'dialog',
id: 'u:438767684afb',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:c664b67b3e6b',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:f8fa25c49d82',
},
],
closeOnEsc: true,
showCloseButton: true,
},
},
],
},
},
},
{
type: 'export-excel',
align: 'right',
label: '导出',
icon: 'fa fa-download',
level: 'primary',
api: 'rest/locations?select=id,code,name,type,dicts(dictvalue)&order=code',
filename: '实验场地',
exportColumns: [
{ label: '编号', name: 'code' },
{ name: 'name', label: '名称' },
{ name: 'dicts.dictvalue', label: '类型' },
],
id: 'u:ab01cbc28399',
perPageAvailable: [10],
},
],
syncLocation: false,
perPageAvailable: [10, 20, 30, 40, 50],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
perPageField: 'perPage',
pageField: 'page',
title: '',
initApi: '',
bodyClassName: '',
id: 'u:96cb5c2c87a4',
initFetch: '',
},
],
messages: {},
title: '',
id: 'u:4ffd31a5304f',
definitions: {
'dialog-ref-1': {
type: 'dialog',
title: '添加实验场地',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'input-text',
name: 'code',
label: '编号',
mode: 'normal',
required: true,
id: 'u:9736564ec7fc',
},
{
type: 'input-text',
name: 'name',
label: '名称',
mode: 'normal',
required: true,
id: 'u:dcb5e881be78',
},
{
type: 'select',
name: 'type',
label: '类型',
mode: 'normal',
required: true,
checkAll: false,
source: {
url: 'rest/dicts?typecode=eq.006',
method: 'get',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.dictvalue,\r\n value: item.dictkey\r\n };\r\n })\r\n}',
},
value: 'laboratory',
options: [{ label: '实验室', value: 'laboratory' }],
id: 'u:af3f658e33cf',
},
],
api: {
method: 'post',
url: 'rest/locations',
data: {
code: '$code',
name: '$name',
type: '$type',
organization_id: '${centre_id}',
},
dataType: 'json',
},
id: 'u:510d0236a371',
actions: [{ type: 'submit', label: '提交', primary: true }],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:96cb5c2c87a4',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:9cae782cb189',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:5217a0785f6c',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:1e10c45aa1a2',
},
],
$$ref: 'dialog-ref-1',
},
'dialog-ref-2': {
type: 'dialog',
title: '修改实验场地',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/locations?id=eq.$id',
data: { code: '$code', name: '$name', type: '$type' },
dataType: 'json',
},
body: [
{
label: '编号',
type: 'input-text',
name: 'code',
required: true,
mode: 'normal',
id: 'u:339d0f396a34',
},
{
type: 'input-text',
label: '名称',
name: 'name',
required: true,
mode: 'normal',
id: 'u:3f1f8e6f2f13',
},
{
name: 'type',
type: 'select',
label: '类型',
source: {
url: 'rest/dicts?typecode=eq.006',
method: 'get',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.dictvalue,\r\n value: item.dictkey,\r\n };\r\n }),\r\n};',
},
required: true,
checkAll: false,
mode: 'normal',
id: 'u:fda206c44f88',
},
],
id: 'u:3ab6c9e29cf6',
actions: [{ type: 'submit', label: '提交', primary: true }],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:96cb5c2c87a4',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:786fa30ccac8',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:d81e2b6865c1',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:e76ad8a24b48',
},
],
$$ref: 'dialog-ref-2',
},
},
};
export { schema };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,485 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
columns: [
{
type: 'text',
name: 'code',
label: '账号',
sortable: true,
searchable: true,
id: 'u:815ab28420f2',
},
{
type: 'text',
name: 'name',
label: '姓名',
sortable: true,
searchable: true,
id: 'u:14ae63b2e9b2',
},
{
type: 'button-group',
label: '操作',
id: 'u:70fc12c2dc93',
buttons: [
{
type: 'button',
level: 'link',
size: 'md',
icon: 'fa fa-pencil text-info',
tooltip: '修改',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
label: '',
api: { method: 'get', url: 'rest/users/$id' },
id: 'u:0e1e6868a15e',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
title: '修改账号',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/users/$id',
data: { code: '$code', name: '$name' },
dataType: 'json',
},
mode: 'normal',
id: 'u:752cc90e2438',
body: [
{
label: '账号',
type: 'input-text',
name: 'code',
required: true,
id: 'u:dab5b6824746',
},
{
type: 'input-text',
label: '姓名',
name: 'name',
required: true,
id: 'u:95086c61998e',
},
],
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:de2533791a72',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
id: 'u:41bebd8f0aba',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:7aabbd45b9b9',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:d1ba4fdace4d',
},
],
actionType: 'dialog',
},
},
],
},
},
},
{
type: 'button',
confirmText: '确认删除"${name}(${code})"',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除',
tooltipPlacement: 'bottom',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
label: '',
id: 'u:b9864828c5ea',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/users/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:de2533791a72',
ignoreError: false,
actionType: 'reload',
args: { resetPage: false },
},
],
},
},
},
{
type: 'button',
label: '',
actionType: 'ajax',
size: 'md',
level: 'link',
icon: 'fa fa-cog text-warning',
api: {
method: 'post',
url: 'rest/rpc/reset_password',
data: { user_id: '$id' },
},
confirmText: '确认重置"${name}"的密码?',
visibleOn: '',
tooltip: '重置密码',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:b7b4f65ce939',
},
],
placeholder: '-',
width: 200,
},
],
messages: {},
api: {
method: 'get',
url: 'rest/users?select=*,user_orgs(orgs(id))&code=${code}&name=${name}&role=eq.teacher&user_orgs.orgs.id=eq.$centre_id&order=code',
data: {
page: '${page}',
perPage: '${perPage}',
orderBy: '${orderBy}',
orderDir: '${orderDir}',
code: 'like.%${code}%',
name: 'like.%${name}%',
},
requestAdaptor:
"if (api.query.code === 'like.%%') {\r\n api.url = api.url.replace('&code=like.%25%25', '')\r\n}\r\n\r\nif (api.query.name === 'like.%%') {\r\n api.url = api.url.replace('&name=like.%25%25', '')\r\n}\r\n\r\nif (api.query.orderDir) {\r\n api.url = api.url.replace('&order=code%2Cname', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '&order=' + api.query.orderBy + '.' + api.query.orderDir);\r\n} else if (api.query.orderBy) {\r\n api.url = api.url.replace('&order=code%2Cname', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=', '&order=' + api.query.orderBy);\r\n} else {\r\n api.url = api.url.replace('&orderBy=&orderDir=', '');\r\n}\r\n\r\nreturn api;",
},
headerToolbar: [
{
type: 'button',
icon: 'fa fa-plus',
level: 'default',
align: 'right',
label: '添加',
className: 'm-l-xs',
id: 'u:f4a47bb1a6d3',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加任课教师',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'input-text',
name: 'code',
label: '账号',
mode: 'normal',
required: true,
id: 'u:bda0263ab8b8',
},
{
type: 'input-text',
name: 'name',
label: '姓名',
mode: 'normal',
required: true,
id: 'u:36976961498d',
},
{
type: 'input-password',
name: 'password',
label: '密码',
mode: 'normal',
required: true,
id: 'u:ec15585cc409',
},
{
type: 'input-password',
name: 'new_password',
label: '确认密码',
mode: 'normal',
required: true,
id: 'u:e364b38586a8',
},
],
api: {
method: 'post',
url: 'rest/rpc/create_user',
data: {
code: '$code',
name: '$name',
password: '$password',
new_password: '$new_password',
role: 'teacher',
orgs_id: '$centre_id',
},
requestAdaptor: '',
dataType: 'json',
headers: { Prefer: 'params=single-object' },
},
rules: [
{
rule: 'this.password === this.new_password',
message: '两次密码输入不一致,请重新输入',
},
],
id: 'u:9b27303ee8c7',
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:de2533791a72',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:feafc93d557c',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:836089a67684',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:51adce35589e',
},
],
actionType: 'dialog',
},
},
],
},
},
},
{
type: 'button',
align: 'right',
label: '导入',
icon: 'fa fa-upload',
level: 'warning',
id: 'u:25df91a187d4',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '教师导入',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'input-excel',
name: 'teachers',
label: '',
mode: 'normal',
id: 'u:5f0eca8a64fc',
},
{
type: 'input-table',
name: 'teachers',
label: '',
columns: [
{
label: '账号',
name: '账号',
placeholder: '-',
quickEdit: {
mode: 'inline',
id: 'u:e92d65ada484',
},
id: 'u:67eb5050eb4d',
},
{
label: '姓名',
name: '姓名',
placeholder: '-',
quickEdit: {
mode: 'inline',
id: 'u:2d4bdf8c7035',
},
id: 'u:74137b900acf',
},
],
mode: 'normal',
visibleOn: 'this.teachers',
strictMode: true,
removable: true,
editable: false,
id: 'u:d73c846629ed',
clearValueOnHidden: true,
},
],
api: {
method: 'post',
url: 'rest/rpc/create_user',
data: {
orgs_id: '${centre_id}',
teachers: '${teachers}',
},
requestAdaptor:
"return {\r\n ...api,\r\n data: api.data.teachers.map(item => {\r\n return {\r\n name: item.姓名,\r\n code: item.账号,\r\n role: 'teacher',\r\n orgs_id: api.data.orgs_id\r\n }\r\n })\r\n}",
dataType: 'json',
headers: { Prefer: 'params=single-object' },
},
id: 'u:e60b2ab481ec',
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
dsType: 'api',
labelAlign: 'left',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:de2533791a72',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
actionType: 'dialog',
id: 'u:249cb4918177',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:2d7cd1af378c',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:2dca052d9127',
},
],
},
},
],
},
},
},
{
type: 'export-excel',
align: 'right',
label: '导出',
icon: 'fa fa-download',
level: 'primary',
api: 'rest/users?select=*,user_orgs(orgs(id))&role=eq.teacher&user_orgs.orgs.id=eq.$centre_id&order=code',
filename: '任课教师',
exportColumns: [
{ label: '账号', name: 'code' },
{ name: 'name', label: '姓名' },
],
id: 'u:e2ad66d916a5',
},
],
syncLocation: false,
perPageAvailable: [10, 20, 30, 40, 50],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
perPageField: 'perPage',
pageField: 'page',
title: '',
initApi: '',
bodyClassName: '',
id: 'u:de2533791a72',
affixHeader: true,
placeholder: '-',
},
],
messages: {},
title: '',
id: 'u:1d83fac554fc',
definitions: {},
};
export { schema };

View File

@@ -0,0 +1,485 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
messages: {},
api: {
method: 'get',
url: 'rest/teams?select=*,team_members(users(id,code,name))&organization_id=eq.$centre_id&order=code',
data: {
page: '$page',
perPage: '$perPage',
},
adaptor:
'return {\r\n ...payload.data,\r\n items: payload.data.items.map(item=> {\r\n return {\r\n ...item,\r\n selected_members: item.team_members.map(x => x.users.id)\r\n }\r\n })\r\n}\r\n',
},
headerToolbar: [
{
type: 'button',
icon: 'fa fa-plus',
level: 'primary',
align: 'right',
label: '添加',
className: 'm-l-xs',
id: 'u:3c4c79023e6d',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加教学团队',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'input-text',
name: 'code',
label: '编号',
mode: 'normal',
required: true,
id: 'u:9c6265d6605a',
},
{
type: 'input-text',
name: 'name',
label: '名称',
mode: 'normal',
required: true,
id: 'u:fb25ddbf9ea3',
},
{
type: 'input-rich-text',
name: 'intro',
label: '描述',
mode: 'normal',
required: false,
options: {
menubar: false,
},
id: 'u:c88442289c84',
},
],
api: {
method: 'post',
url: 'rest/teams',
data: {
organization_id: '$centre_id',
code: '$code',
name: '$name',
intro: '$intro',
},
dataType: 'json',
},
id: 'u:5e5757012047',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:6b104aa599d9',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:f020a946e699',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:d93f54d6db83',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:cc9454abf750',
},
],
},
},
],
},
},
},
],
syncLocation: false,
perPageAvailable: [9, 27, 54],
footerToolbar: [
{
type: 'pagination',
},
{
type: 'switch-per-page',
tpl: '内容',
},
],
perPageField: 'perPage',
pageField: 'page',
bulkActions: [],
itemActions: [],
mode: 'cards',
card: {
type: 'card',
header: {
title: '$name',
subTitle: '$code',
},
body: [
{
type: 'tpl',
tpl: '${intro|raw}',
id: 'u:e89fc74f9f0a',
},
{
type: 'each',
name: 'team_members',
placeholder: '未添加团队成员',
items: [
{
type: 'plain',
tpl: '${users.name}',
inline: true,
className: 'm-l-sm',
id: 'u:53d23e452416',
},
{
type: 'button',
label: '',
level: 'link',
icon: 'fa fa-times text-danger',
iconClassName: 'text-danger',
confirmText: '确认删除"${users.name}"',
tooltip: '删除成员',
tooltipPlacement: 'top',
id: 'u:13cfdb0af771',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/team_members?team_id=eq.${id}&teacher_id=eq.${users.id}',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:6b104aa599d9',
ignoreError: false,
actionType: 'reload',
},
],
},
},
},
],
id: 'u:c6b9bef8b674',
},
],
actions: [
{
label: '',
type: 'button',
icon: 'fa fa-edit text-info',
tooltip: '修改教学团队',
tooltipPlacement: 'top',
size: 'md',
id: 'u:15a30654e625',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/teams/${id}',
data: {
code: '$code',
name: '$name',
intro: '$intro',
teacher_list: '{${teacher_list}}',
},
dataType: 'json',
},
body: [
{
type: 'input-text',
label: '编号',
name: 'code',
required: true,
mode: 'normal',
id: 'u:21b2f1785596',
},
{
label: '名称',
type: 'input-text',
name: 'name',
required: true,
mode: 'normal',
id: 'u:765ddf946178',
},
{
label: '描述',
type: 'input-rich-text',
name: 'intro',
options: {
menubar: false,
},
mode: 'normal',
id: 'u:454ab08215fc',
},
],
id: 'u:51ad45e350ee',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:6b104aa599d9',
ignoreError: false,
actionType: 'reload',
data: {},
dataMergeMode: 'override',
args: {
resetPage: false,
},
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
title: '修改教学团队',
id: 'u:071e18a84182',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:07f56791977b',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:5e08c0b4a2c2',
},
],
},
},
],
},
},
},
{
type: 'button',
label: '',
icon: 'fa fa-trash text-danger',
confirmText: '确认删除"${name}(${code})"',
iconClassName: 'text-danger',
tooltip: '删除教学团队',
tooltipPlacement: 'top',
size: 'md',
id: 'u:481d5762073e',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/teams?id=eq.${id}',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:6b104aa599d9',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
{
type: 'button',
label: '',
icon: 'fa fa-group text-success',
size: 'md',
tooltip: '添加成员',
tooltipPlacement: 'top',
id: 'u:8ab4c946e195',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
title: '添加成员',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/team_members',
data: {
select: '$select',
team_id: '${id}',
},
dataType: 'json',
requestAdaptor:
"return {\r\n ...api,\r\n data: api.data.select.split(',').map(item => {\r\n return {\r\n team_id: api.data.team_id,\r\n teacher_id: item\r\n }\r\n })\r\n}",
},
body: [
{
type: 'select',
label: '',
name: 'select',
required: true,
mode: 'normal',
checkAll: false,
defaultCheckAll: false,
checkAllLabel: '全选',
source: {
method: 'get',
url: 'rest/users?select=*,user_orgs(organization_id)&role=eq.teacher&user_orgs.organization_id=eq.${centre_id}&id=not.in.(${selected_members})&order=code',
adaptor:
'return {\r\n data: payload.data.items.map(x=> {\r\n return {\r\n label: x.name,\r\n value: x.id\r\n }\r\n })\r\n}',
},
multiple: true,
joinValues: true,
id: 'u:d8d80248a021',
},
],
id: 'u:518d0cab0bb2',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:6b104aa599d9',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
id: 'u:ca9566592590',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:537639203d7b',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:bdee93cc8455',
},
],
},
},
],
},
},
},
],
id: 'u:c2cda94e1dbb',
},
placeholder: '暂无数据',
columnsCount: 3,
defaultParams: {
perPage: 9,
},
affixHeader: true,
id: 'u:6b104aa599d9',
},
],
messages: {},
title: '',
id: 'u:418dc967756c',
};
export { schema };

View File

@@ -0,0 +1,592 @@
const schema = {
body: [
{
type: 'form',
title: '表单',
wrapWithPanel: false,
submitOnChange: true,
reload: '',
messages: {},
target:
'teacher2?semesterSelect=${semesterSelect}&course_id=${course_id}&parent_id=${parent_id},teacher1?semesterSelect=${semesterSelect}&course_id=${course_id}',
body: [
{
type: 'group',
body: [
{
type: 'select',
label: '学期',
name: 'semesterSelect',
mode: 'inline',
size: 'lg',
clearable: false,
source: {
method: 'get',
url: 'rest/semesters',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
},
checkAll: false,
searchable: true,
selectFirst: false,
inputClassName: 'm-l-xs',
},
{
type: 'select',
label: '课程',
name: 'course_id',
mode: 'inline',
size: 'md',
clearable: false,
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.${centre_id}&semester_id=eq.${semesterSelect}&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.length > 0 && payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
sendOn: 'this.semesterSelect',
},
checkAll: false,
searchable: true,
selectFirst: false,
visibleOn: '',
clearValueOnHidden: false,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
},
],
},
{
type: 'group',
body: [
{
type: 'select',
label: '一级指标',
name: 'parent_id',
mode: 'inline',
size: 'md',
source: {
method: 'get',
url: 'rest/teaching_evaluation_items?organization_id=eq.${centre_id}&semester_id=eq.${semesterSelect}&parent=is.null&order=order',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
sendOn: 'this.course_id',
},
checkAll: false,
searchable: true,
selectFirst: false,
visibleOn: 'this.course_id',
clearValueOnHidden: true,
inputClassName: 'm-l-xs',
},
],
},
],
},
{
type: 'grid',
columns: [
{
body: [
{
type: 'flex',
items: [
{
type: 'wrapper',
body: [
{
type: 'chart',
config: {
title: [{ text: '教师统计(一级指标)' }],
series: [
{
data: '${series_data}',
type: 'radar',
emphasis: { lineStyle: { width: 4 } },
tooltip: { trigger: 'item' },
},
],
tooltip: { trigger: 'axis' },
legend: {
type: 'scroll',
orient: 'vertical',
right: 10,
top: 20,
bottom: 20,
data: '${legend}',
},
radar: [
{
indicator: '${items}',
radius: 200,
startAngle: 90,
splitNumber: 5,
name: {
textStyle: {
color: '#fff',
backgroundColor: '#666',
borderRadius: 3,
padding: [3, 5],
},
},
center: ['35%', '55%'],
},
],
toolbox: {
show: true,
feature: {
dataView: {
show: true,
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
},
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
},
},
left: 170,
top: -2,
},
},
_mode: '2',
api: {
method: 'post',
url: 'rest/rpc/evaluation_stats_teacher',
adaptor:
'let legend_set = new Set()\r\n\r\npayload.data.rows && payload.data.rows.forEach(item => {\r\n if(item.teacher_id) {\r\n legend_set.add(item.teacher_name.concat("(", item.teacher_id, ")"));\r\n } else {\r\n legend_set.add(\'未指定\');\r\n }\r\n})\r\n\r\nlet legend = Array.from(legend_set)\r\n\r\nlet series_data = legend\r\n ? legend.map(legend => {\r\n return {\r\n value: payload.data.rows.filter(\r\n item => item.teacher_id ? item.teacher_name.concat("(", item.teacher_id, ")") === legend : legend === \'未指定\'\r\n ).map(item => item.score),\r\n name: legend,\r\n symbol: "rect",\r\n symbolSize: 12,\r\n lineStyle: {\r\n type: "dashed"\r\n }\r\n }\r\n })\r\n : []\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items\r\n ? payload.data.items.sort(\r\n (a, b) => a.id - b.id\r\n ).map(item => {\r\n return {\r\n text: item.name,\r\n max: 5\r\n }\r\n })\r\n : [],\r\n legend: legend,\r\n series_data: series_data\r\n }\r\n}\r\n',
sendOn: 'this.course_id',
data: {
course_id: '${course_id}',
organization_id: '${centre_id}',
},
requestAdaptor: '',
},
dataFilter: '',
height: 480,
name: 'teacher1',
},
],
size: 'xs',
className: 'bg-white',
},
],
direction: 'column',
justify: 'start',
alignItems: 'stretch',
className: '',
__mode: true,
style: {},
},
],
},
{
body: [
{
type: 'flex',
items: [
{
type: 'wrapper',
body: [
{
type: 'chart',
config: {
title: [{ text: '教师统计(二级指标)' }],
series: [
{
data: '${series_data}',
type: 'radar',
emphasis: { lineStyle: { width: 4 } },
tooltip: { trigger: 'item' },
},
],
tooltip: { trigger: 'axis' },
legend: {
type: 'scroll',
orient: 'vertical',
right: 10,
top: 20,
bottom: 20,
data: '${legend}',
},
radar: [
{
indicator: '${items}',
radius: 200,
startAngle: 90,
splitNumber: 5,
name: {
textStyle: {
color: '#fff',
backgroundColor: '#666',
borderRadius: 3,
padding: [3, 5],
},
},
center: ['35%', '55%'],
},
],
toolbox: {
show: true,
feature: {
dataView: {
show: true,
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
},
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
},
},
left: 170,
top: -2,
},
},
_mode: '2',
api: {
method: 'post',
url: 'rest/rpc/evaluation_stats_teacher',
adaptor:
'let legend_set = new Set()\r\n\r\npayload.data.rows && payload.data.rows.forEach(item => {\r\n if(item.teacher_id) {\r\n legend_set.add(item.teacher_name.concat("(", item.teacher_id, ")"));\r\n } else {\r\n legend_set.add(\'未指定\');\r\n }\r\n})\r\n\r\nlet legend = Array.from(legend_set)\r\n\r\nlet series_data = legend\r\n ? legend.map(legend => {\r\n return {\r\n value: payload.data.rows.filter(\r\n item => item.teacher_id ? item.teacher_name.concat("(", item.teacher_id, ")") === legend : legend === \'未指定\'\r\n ).map(item => item.score),\r\n name: legend,\r\n symbol: "rect",\r\n symbolSize: 12,\r\n lineStyle: {\r\n type: "dashed"\r\n }\r\n }\r\n })\r\n : []\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items\r\n ? payload.data.items.sort(\r\n (a, b) => a.id - b.id\r\n ).map(item => {\r\n return {\r\n text: item.name,\r\n max: 5\r\n }\r\n })\r\n : [],\r\n legend: legend,\r\n series_data: series_data\r\n }\r\n}\r\n',
sendOn: 'this.parent_id',
data: {
course_id: '${course_id}',
organization_id: '${centre_id}',
parent: '${parent_id}',
},
requestAdaptor: '',
},
dataFilter: '',
height: 480,
name: 'teacher2',
},
],
size: 'xs',
className: 'bg-white',
},
],
direction: 'column',
justify: 'start',
alignItems: 'stretch',
className: '',
style: {},
},
],
},
],
},
{ type: 'divider', lineStyle: 'solid' },
{
type: 'form',
title: '表单',
wrapWithPanel: false,
submitOnChange: true,
reload: '',
messages: {},
target:
'location2?semesterSelect=${semesterSelect}&course_id=${course_id}&group_id=${group_id}&parent_id=${parent_id},location1?semesterSelect=${semesterSelect}&course_id=${course_id}&group_id=${group_id}',
body: [
{
type: 'group',
body: [
{
type: 'select',
label: '学期',
name: 'semesterSelect',
mode: 'inline',
size: 'lg',
clearable: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
},
checkAll: false,
searchable: true,
selectFirst: false,
inputClassName: 'm-l-xs',
},
{
type: 'select',
label: '课程',
name: 'course_id',
mode: 'inline',
size: 'md',
clearable: false,
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.${centre_id}&semester_id=eq.${semesterSelect}&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.length > 0 && payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
sendOn: 'this.semesterSelect',
},
checkAll: false,
searchable: true,
selectFirst: false,
visibleOn: '',
clearValueOnHidden: false,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
},
{
type: 'select',
label: '场地组合',
name: 'group_id',
mode: 'inline',
size: 'lg',
clearable: true,
source: {
method: 'get',
url: 'rest/locations?organization_id=eq.${centre_id}&type=eq.laboratory&is_valid=is.true&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.length > 0 && payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n}',
},
checkAll: true,
searchable: true,
selectFirst: false,
visibleOn: '',
clearValueOnHidden: false,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
multiple: true,
joinValues: true,
valuesNoWrap: false,
defaultCheckAll: false,
checkAllLabel: '全选',
},
],
},
{
type: 'group',
body: [
{
type: 'select',
label: '一级指标',
name: 'parent_id',
mode: 'inline',
size: 'md',
source: {
method: 'get',
url: 'rest/teaching_evaluation_items?organization_id=eq.${centre_id}&semester_id=eq.${semesterSelect}&parent=is.null&order=order',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
sendOn: 'this.course_id',
},
checkAll: false,
searchable: true,
selectFirst: false,
visibleOn: 'this.course_id',
clearValueOnHidden: true,
inputClassName: 'm-l-xs',
},
],
},
],
},
{
type: 'grid',
columns: [
{
body: [
{
type: 'flex',
items: [
{
type: 'wrapper',
body: [
{
type: 'chart',
config: {
title: [{ text: '场地统计(一级指标)' }],
series: [
{
data: '${series_data}',
type: 'radar',
emphasis: { lineStyle: { width: 4 } },
tooltip: { trigger: 'item' },
},
],
tooltip: { trigger: 'axis' },
legend: {
type: 'scroll',
orient: 'vertical',
right: 10,
top: 20,
bottom: 20,
data: '${legend}',
},
radar: [
{
indicator: '${items}',
radius: 200,
startAngle: 90,
splitNumber: 5,
name: {
textStyle: {
color: '#fff',
backgroundColor: '#666',
borderRadius: 3,
padding: [3, 5],
},
},
center: ['35%', '55%'],
},
],
toolbox: {
show: true,
feature: {
dataView: {
show: true,
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
},
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
},
},
left: 170,
top: -2,
},
},
_mode: '2',
api: {
method: 'post',
url: 'rest/rpc/evaluation_stats_location',
adaptor:
'let legend_set = new Set()\r\n\r\npayload.data.rows && payload.data.rows.forEach(item => {\r\n legend_set.add(item.location_name)\r\n})\r\n\r\nlet legend = Array.from(legend_set)\r\n\r\nlet series_data = legend\r\n ? legend.map(legend => {\r\n return {\r\n value: payload.data.rows.filter(\r\n item => item.location_name === legend\r\n ).map(item => item.score),\r\n name: legend,\r\n symbol: "rect",\r\n symbolSize: 12,\r\n lineStyle: {\r\n type: "dashed"\r\n }\r\n }\r\n })\r\n : []\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items\r\n ? payload.data.items.sort(\r\n (a, b) => a.id - b.id\r\n ).map(item => {\r\n return {\r\n text: item.name,\r\n max: 5\r\n }\r\n })\r\n : [],\r\n legend: legend,\r\n series_data: series_data\r\n }\r\n}\r\n',
sendOn: 'this.course_id',
data: {
course_id: '${course_id}',
organization_id: '${centre_id}',
group: '${group_id}',
},
requestAdaptor:
"if (api.body.group === '') {\r\n delete api.body.group\r\n}\r\n\r\nreturn {\r\n ...api\r\n}",
},
dataFilter: '',
height: 480,
name: 'location1',
},
],
size: 'xs',
className: 'bg-white',
},
],
direction: 'column',
justify: 'start',
alignItems: 'stretch',
className: '',
__mode: true,
style: {},
},
],
},
{
body: [
{
type: 'flex',
items: [
{
type: 'wrapper',
body: [
{
type: 'chart',
config: {
title: [{ text: '场地统计(二级指标)' }],
series: [
{
data: '${series_data}',
type: 'radar',
emphasis: { lineStyle: { width: 4 } },
tooltip: { trigger: 'item' },
},
],
tooltip: { trigger: 'axis' },
legend: {
type: 'scroll',
orient: 'vertical',
right: 10,
top: 20,
bottom: 20,
data: '${legend}',
},
radar: [
{
indicator: '${items}',
radius: 200,
startAngle: 90,
splitNumber: 5,
name: {
textStyle: {
color: '#fff',
backgroundColor: '#666',
borderRadius: 3,
padding: [3, 5],
},
},
center: ['35%', '55%'],
},
],
toolbox: {
show: true,
feature: {
dataView: {
show: true,
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
},
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
},
},
left: 170,
top: -2,
},
},
_mode: '2',
api: {
method: 'post',
url: 'rest/rpc/evaluation_stats_location',
adaptor:
'let legend_set = new Set()\r\n\r\npayload.data.rows && payload.data.rows.forEach(item => {\r\n legend_set.add(item.location_name)\r\n})\r\n\r\nlet legend = Array.from(legend_set)\r\n\r\nlet series_data = legend\r\n ? legend.map(legend => {\r\n return {\r\n value: payload.data.rows.filter(\r\n item => item.location_name === legend\r\n ).map(item => item.score),\r\n name: legend,\r\n symbol: "rect",\r\n symbolSize: 12,\r\n lineStyle: {\r\n type: "dashed"\r\n }\r\n }\r\n })\r\n : []\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items\r\n ? payload.data.items.sort(\r\n (a, b) => a.id - b.id\r\n ).map(item => {\r\n return {\r\n text: item.name,\r\n max: 5\r\n }\r\n })\r\n : [],\r\n legend: legend,\r\n series_data: series_data\r\n }\r\n}\r\n',
sendOn: 'this.parent_id',
data: {
course_id: '${course_id}',
organization_id: '${centre_id}',
parent: '${parent_id}',
group: '${group_id}',
},
requestAdaptor:
"if (api.body.group === '') {\r\n delete api.body.group\r\n}\r\n\r\nreturn {\r\n ...api\r\n}",
},
dataFilter: '',
height: 480,
name: 'location2',
},
],
size: 'xs',
className: 'bg-white',
},
],
direction: 'column',
justify: 'start',
alignItems: 'stretch',
className: '',
style: {},
},
],
},
],
},
],
type: 'page',
};
export { schema };

View File

@@ -0,0 +1,209 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
title: '表单',
submitOnChange: true,
reload: 'statistic?course_id=$course_id&org_id=$org_id',
wrapWithPanel: false,
messages: {
fetchFailed: '初始化失败',
saveSuccess: '统计成功',
saveFailed: '保存失败',
},
body: [
{
label: '学期',
type: 'select',
name: 'semesterSelect',
mode: 'inline',
clearable: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'lg',
searchable: true,
selectFirst: false,
id: 'u:b1122bff1df9',
},
{
type: 'select',
label: '课程',
name: 'course_id',
mode: 'inline',
clearable: false,
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.length > 0 && payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
sendOn: 'this.semesterSelect',
},
checkAll: false,
size: 'lg',
searchable: true,
selectFirst: false,
visibleOn: '',
clearValueOnHidden: false,
id: 'u:2e2358141406',
},
{
type: 'select',
label: '班级',
name: 'org_id',
mode: 'inline',
clearable: true,
size: 'lg',
source: {
method: 'get',
url: 'rest/orgs?select=*,course2orgs!inner(*)&path=cd.root&course2orgs.course_id=eq.$course_id&type=in.(school,centre,faculty,class,reelectclass)&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => ({\r\n label: item.name,\r\n value: item.id\r\n }))\r\n }\r\n}',
sendOn: 'this.course_id',
requestAdaptor: '',
messages: {},
},
searchable: true,
visibleOn: 'this.course_id',
clearValueOnHidden: true,
multiple: true,
joinValues: true,
delimiter: ',',
cascade: false,
initiallyOpen: false,
unfoldedLevel: 2,
onlyChildren: true,
withChildren: false,
extractValue: true,
id: 'u:84b51bcafda6',
autoCheckChildren: true,
enableNodePath: false,
showIcon: true,
checkAll: false,
},
{
type: 'control',
label: '',
remark: null,
mode: 'inline',
visibleOn: 'this.is_cursem && this.course_id',
body: [
{
type: 'button',
level: 'primary',
actionType: 'ajax',
api: {
method: 'post',
url: 'rest/rpc/calculate_grade',
data: {
course_id: '$course_id',
org_id: '$org_id',
},
requestAdaptor:
'const { course_id, org_id } = api.data\r\n\r\nif (org_id) {\r\n return {\r\n ...api\r\n }\r\n} else {\r\n return {\r\n ...api,\r\n data: { course_id }\r\n }\r\n}',
},
reload: 'statistic',
messages: {},
label: '统计',
id: 'u:a147092cff8f',
},
],
id: 'u:4944fd7962d5',
},
{
type: 'service',
messages: {},
api: {
method: 'get',
url: 'rest/semesters?id=eq.$semesterSelect',
sendOn: 'this.semesterSelect',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n is_cursem: payload.data.items.length > 0 ? payload.data.items[0].is_open : false\r\n }\r\n}',
},
body: [
{
type: 'hidden',
name: 'is_cursem',
id: 'u:39ca4db8501a',
},
],
id: 'u:3e13ab87d15b',
dsType: 'api',
},
],
id: 'u:970b1b98cc6a',
feat: 'Insert',
},
{
type: 'crud',
api: {
method: 'post',
url: 'rest/rpc/grade_stat',
data: {
page: '$page',
course_id: '$course_id',
perpage: '$perPage',
org_id: '$org_id',
},
adaptor: '',
requestAdaptor:
'const { course_id, org_id, page, perpage } = api.data\r\n\r\nif (course_id && org_id) {\r\n return {\r\n ...api\r\n }\r\n} else if (course_id) {\r\n return {\r\n ...api,\r\n data: { course_id, page, perpage }\r\n }\r\n} else {\r\n return {\r\n ...api,\r\n data: {}\r\n }\r\n}',
dataType: 'form',
sendOn: '',
},
columns: [],
messages: {},
syncLocation: false,
pageField: 'page',
perPageField: 'perPage',
headerToolbar: [
{
type: 'export-excel',
label: '全量导出 Excel',
api: {
method: 'post',
url: 'rest/rpc/grade_stat',
data: {
course_id: '$course_id',
org_id: '$org_id',
},
dataType: 'form',
requestAdaptor:
'const { course_id, org_id } = api.data\r\n\r\nif (course_id && org_id) {\r\n return {\r\n ...api\r\n }\r\n} else if (course_id) {\r\n return {\r\n ...api,\r\n data: { course_id }\r\n }\r\n} else {\r\n return {\r\n ...api,\r\n data: {}\r\n }\r\n}',
},
filename: '$filename',
id: 'u:8d80c123da31',
},
],
footerToolbar: [
{
type: 'pagination',
tpl: '内容',
},
{
type: 'switch-per-page',
tpl: '内容',
},
],
name: 'statistic',
title: '',
bodyClassName: 'common-height',
body: [],
toolbar: [],
alwaysShowPagination: false,
perPageAvailable: [10, 20, 30, 40, 50],
affixHeader: false,
id: 'u:ec1754a6f5a9',
},
],
title: '',
messages: {},
id: 'u:e3bc67e03bea',
};
export { schema };

View File

@@ -0,0 +1,411 @@
const schema = {
type: 'page',
body: [
{
type: 'grid',
columns: [
{
md: 8,
body: [
{
type: 'chart',
config: {
title: [
{
text: '实验项目开课次数统计',
},
],
series: [
{
data: '$yAxis_les',
type: 'pie',
name: '项目',
radius: '55%',
center: ['40%', '50%'],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
legend: {
type: 'scroll',
orient: 'vertical',
top: 20,
bottom: 20,
data: '$xAxis_exp',
right: 10,
},
toolbox: {
show: true,
feature: {
dataView: {
show: true,
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
},
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
},
},
left: 190,
top: -2,
},
},
_mode: '2',
api: {
method: 'get',
url: 'rest/schedules?select=*,projects:projects!schedule_project_id_fkey(id,name)&semester_id=eq.${semesterSelect}&teacher_id=in.(${teacherSelect})',
requestAdaptor:
"api.url = api.url.replace('&teacher_id=in.%28%29', '')\r\n\r\nreturn api",
adaptor:
'let xAxis_exp = []\r\nlet yAxis_les = []\r\npayload.data.items.forEach(item => {\r\n const index_exp = xAxis_exp.findIndex(i => i === item.projects.name)\r\n if (index_exp === -1) {\r\n xAxis_exp.push(item.projects.name)\r\n const index_exp = xAxis_exp.findIndex(i => i === item.projects.name)\r\n yAxis_les[index_exp] = {\r\n name: item.projects.name,\r\n value: 1\r\n }\r\n } else {\r\n yAxis_les[index_exp].value += 1\r\n }\r\n})\r\nreturn {\r\n ...payload,\r\n data: {\r\n xAxis_exp: xAxis_exp,\r\n yAxis_les: yAxis_les\r\n }\r\n}',
error: {
message: '',
},
},
dataFilter: '',
height: 450,
name: 'chart1',
id: 'u:4f3d6cd9e92a',
},
],
id: 'u:3a343388441f',
},
{
body: [
{
type: 'chart',
config: {
title: [
{
text: '教师课堂次数统计',
},
],
series: [
{
data: '$yAxis_les',
type: 'pie',
name: '教师',
radius: '55%',
center: ['40%', '50%'],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
legend: {
type: 'scroll',
orient: 'vertical',
right: 10,
top: 20,
bottom: 20,
data: '$xAxis_exp',
},
toolbox: {
show: true,
feature: {
dataView: {
show: true,
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
},
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
},
},
left: 155,
top: -2,
},
},
_mode: '2',
dataFilter: '',
api: {
method: 'get',
url: 'rest/users?select=*,schedules:schedules!schedule_teacher_id_fkey(*,projects:projects!schedule_project_id_fkey(id,name))&role=eq.teacher&schedules.semester_id=eq.${semesterSelect}&id=in.(${teacherSelect})&order=code',
requestAdaptor:
"api.url = api.url.replace('&id=in.%28%29', '')\r\n\r\nreturn api",
adaptor:
'let xAxis_tch = []\r\nlet yAxis_les = []\r\npayload.data.items.forEach(payload_item => {\r\n let elected = 0\r\n xAxis_tch.push(payload_item.name)\r\n yAxis_les.push({\r\n name: payload_item.name,\r\n value: payload_item.schedules.length ? payload_item.schedules.length : 0\r\n })\r\n})\r\nreturn {\r\n ...payload,\r\n data: {\r\n xAxis_tch: xAxis_tch,\r\n yAxis_les: yAxis_les\r\n }\r\n}',
},
height: 450,
name: 'chart2',
id: 'u:ed31d767d8d5',
},
],
id: 'u:989c40ed8233',
},
],
id: 'u:63c06d3a12fc',
},
{
type: 'divider',
lineStyle: 'solid',
id: 'u:b0c8cc501851',
},
{
type: 'chart',
config: {
title: [
{
text: '实验项目相关人次统计',
},
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#283b56',
},
},
},
legend: {
data: ['可选人数', '已选人数', '签到人数', '未签到人数'],
},
xAxis: [
{
type: 'category',
data: '$xAxis_exp',
axisLabel: {
rotate: -10,
interval: 0,
},
},
],
yAxis: {
type: 'value',
name: '人数',
},
series: [
{
data: '$yAxis_unsel',
type: 'bar',
name: '可选人数',
barWidth: 20,
},
{
data: '$yAxis_sel',
type: 'bar',
name: '已选人数',
barWidth: 20,
},
{
data: '$yAxis_fin',
type: 'bar',
name: '签到人数',
barWidth: 10,
stack: '已选人数',
},
{
data: '$yAxis_unfin',
type: 'bar',
name: '未签到人数',
barWidth: 10,
stack: '已选人数',
},
],
toolbox: {
show: true,
feature: {
dataView: {
show: true,
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
},
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
},
},
left: 190,
top: -2,
},
},
_mode: '2',
api: {
method: 'get',
url: 'rest/workload_stats?select=*,projects:projects!schedule_project_id_fkey(id,name)&semester_id=eq.$semesterSelect&teacher_id=in.(${teacherSelect})&organization_id=eq.$centre_id',
requestAdaptor:
"api.url = api.url.replace('&teacher_id=in.%28%29', '')\r\n\r\nreturn api",
adaptor:
'let xAxis_exp = []\r\nlet yAxis_sel = []\r\nlet yAxis_unsel = []\r\nlet yAxis_fin = []\r\nlet yAxis_unfin = []\r\n\r\npayload.data.items.forEach(item => {\r\n const index_exp = xAxis_exp.findIndex(i => i === item.projects.name)\r\n\r\n if (index_exp === -1) {\r\n xAxis_exp.push(item.projects.name)\r\n const index_exp = xAxis_exp.findIndex(i => i === item.projects.name)\r\n yAxis_sel[index_exp] = item.current_student_number\r\n yAxis_unsel[index_exp] = item.max_student_number - item.current_student_number\r\n yAxis_fin[index_exp] = item.finished_count\r\n yAxis_unfin[index_exp] = item.unfinished_count\r\n } else {\r\n yAxis_sel[index_exp] += item.current_student_number\r\n yAxis_unsel[index_exp] += (item.max_student_number - item.current_student_number)\r\n yAxis_fin[index_exp] += item.finished_count\r\n yAxis_unfin[index_exp] += item.unfinished_count\r\n }\r\n})\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n xAxis_exp: xAxis_exp,\r\n yAxis_sel: yAxis_sel,\r\n yAxis_unsel: yAxis_unsel,\r\n yAxis_fin: yAxis_fin,\r\n yAxis_unfin: yAxis_unfin\r\n }\r\n}',
},
replaceChartOption: false,
dataFilter: '',
height: 450,
name: 'chart3',
id: 'u:5357767552e5',
},
{
type: 'divider',
lineStyle: 'solid',
id: 'u:01c9e40a887d',
},
{
type: 'chart',
config: {
title: [
{
text: '教师课堂相关人次统计',
},
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#283b56',
},
},
},
legend: {
data: ['已选人数', '签到人数', '未签到人数'],
},
yAxis: {
type: 'value',
name: '人数',
},
series: [
{
data: '$yAxis_sel',
type: 'bar',
name: '已选人数',
barWidth: 20,
},
{
data: '$yAxis_fin',
type: 'bar',
name: '签到人数',
barWidth: 10,
stack: '已选人数',
},
{
data: '$yAxis_unfin',
type: 'bar',
name: '未签到人数',
barWidth: 10,
stack: '已选人数',
},
],
xAxis: [
{
type: 'category',
data: '$xAxis_tch',
},
],
toolbox: {
show: true,
feature: {
dataView: {
show: true,
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
},
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
},
},
left: 190,
top: -2,
},
},
_mode: '2',
api: {
method: 'get',
url: 'rest/users?select=*,workload_stats:workload_stats!schedule_teacher_id_fkey(*,projects:projects!schedule_project_id_fkey(id,name))&role=eq.teacher&workload_stats.semester_id=eq.$semesterSelect&id=in.(${teacherSelect})&workload_stats.organization_id=eq.$centre_id&order=code',
requestAdaptor:
"api.url = api.url.replace('&id=in.%28%29', '')\r\n\r\nreturn api",
adaptor:
'let xAxis_tch = []\r\nlet yAxis_sel = []\r\nlet yAxis_fin = []\r\nlet yAxis_unfin = []\r\n\r\npayload.data.items.forEach(payload_item => {\r\n let elected = 0\r\n let finished = 0\r\n let unfinished = 0\r\n\r\n payload_item.workload_stats.length > 0 && payload_item.workload_stats.forEach(workload_item => {\r\n elected += workload_item.current_student_number\r\n finished += workload_item.finished_count\r\n unfinished += workload_item.unfinished_count\r\n })\r\n\r\n xAxis_tch.push(payload_item.name)\r\n yAxis_sel.push(elected)\r\n yAxis_fin.push(finished)\r\n yAxis_unfin.push(unfinished)\r\n})\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n xAxis_tch: xAxis_tch,\r\n yAxis_sel: yAxis_sel,\r\n yAxis_fin: yAxis_fin,\r\n yAxis_unfin: yAxis_unfin\r\n }\r\n}',
},
replaceChartOption: false,
dataFilter: '',
height: 450,
name: 'chart4',
id: 'u:9c6e8236a44f',
},
{
type: 'divider',
lineStyle: 'solid',
id: 'u:e448f96e6b83',
},
],
toolbar: [
{
type: 'form',
title: '表单',
wrapWithPanel: false,
submitOnChange: true,
target: 'chart1,chart2,chart3,chart4',
canAccessSuperData: true,
body: [
{
label: '学期',
type: 'select',
name: 'semesterSelect',
mode: 'inline',
clearable: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'lg',
searchable: true,
selectFirst: false,
className: 'm',
id: 'u:bdeea7faf818',
},
{
label: '教师',
type: 'select',
name: 'teacherSelect',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/users?role=eq.teacher&order=name,code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
},
checkAll: true,
size: 'lg',
searchable: true,
selectFirst: false,
multiple: true,
className: 'm',
id: 'u:bdeea7faf818',
},
],
id: 'u:5003bb8ac5e6',
},
],
id: 'u:b5376a388009',
};
export { schema };

View File

@@ -0,0 +1,148 @@
const schema = {
type: 'page',
body: [
{
type: 'dataset-renderer',
name: 'storage',
bucket: 'dataset',
addFolder: {
type: 'button',
actionType: 'dialog',
reload: 'storage',
dialog: {
type: 'dialog',
title: '新建文件夹',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'storage/v1/object/dataset/$path/$name/.empty',
requestAdaptor:
"api.url = api.url.replace('//', '/');return api",
},
body: [
{
type: 'input-text',
label: '文件夹名称',
name: 'name',
mode: 'normal',
required: true,
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
},
moveFile: {
type: 'button',
actionType: 'dialog',
reload: 'storage',
dialog: {
type: 'dialog',
title: '重命名',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'storage/v1/object/move',
data: {
bucketId: 'dataset',
sourceKey: '$sourceKey',
dest: '$dest',
path: '$path',
},
requestAdaptor:
"console.log(api)\r\nreturn {\r\n ...api,\r\n data: {\r\n bucketId: api.data.bucketId,\r\n sourceKey: api.data.sourceKey,\r\n destinationKey: api.data.path === '' ? api.data.dest : api.data.path + '/' + api.data.dest\r\n }\r\n}",
},
body: [
{
name: 'dest',
type: 'input-text',
label: '名称',
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
},
delFile: {
type: 'button',
actionType: 'ajax',
reload: 'storage',
label: false,
api: {
method: 'delete',
url: '/storage/v1/object/dataset',
data: {
prefixes: '$prefixes',
},
headers: {
post2rest: false,
},
},
confirmText: '确认删除吗?',
},
delFiles: {
type: 'button',
actionType: 'dialog',
reload: 'storage',
dialog: {
type: 'dialog',
title: '批量删除文件',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'delete',
url: 'storage/v1/object/dataset',
data: { prefixes: '$prefixes', path: '$path' },
headers: { post2rest: false },
requestAdaptor:
"return {\r\n ...api,\r\n data: {\r\n prefixes: api.data.prefixes.map(item => api.data.path === '' ? item : api.data.path + '/' + item)\r\n }\r\n}",
},
body: [
{
name: 'prefixes',
type: 'checkboxes',
label: false,
inline: false,
checkAll: true,
joinValues: false,
extractValue: true,
required: true,
source: {
method: 'post',
url: '/storage/v1/object/list/dataset',
data: {
prefix: '$path',
sortBy: {
column: 'name',
order: 'asc',
},
},
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.filter(item => item.id && item.name.charAt(0) !== ".").map(item => {\r\n return {\r\n label: item.name,\r\n value: item.name\r\n }\r\n })\r\n}',
},
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
},
},
,
],
};
export { schema };

View File

@@ -0,0 +1,667 @@
const schema = {
type: 'page',
body: [
{
type: 'container',
body: [
{
type: 'form',
title: '表单',
className: '',
submitOnChange: true,
reload: 'notebooks?author=$author&type=$type&publish_at=$publish_at',
wrapWithPanel: false,
canAccessSuperData: false,
mode: 'inline',
name: 'filter',
body: [
{
type: 'group',
body: [
{
type: 'select',
mode: 'inline',
label: '作者',
name: 'author',
source: {
method: 'get',
url: 'rest/users?order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name + "(" + item.code + ")",\r\n value: "eq." + item.code\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
clearable: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
id: 'u:f2ef198e2dcf',
},
{
type: 'select',
label: '类型',
name: 'type',
source: {
method: 'get',
url: 'rest/dicts?typecode=eq.028&order=id',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.dictvalue,\r\n value: "eq." + item.dictkey\r\n }\r\n })\r\n}',
},
mode: 'inline',
inputClassName: 'm-l-xs',
size: 'md',
clearable: true,
id: 'u:28cd5a8d0d4f',
},
{
type: 'input-date-range',
name: 'publish_at',
mode: 'inline',
label: '发布日期',
clearable: true,
inputClassName: 'm-l-xs',
value: '',
format: 'YYYY-MM-DD',
ranges:
'yesterday,today,7daysago,prevweek,thismonth,prevmonth,prevquarter',
id: 'u:0b09573c1ed9',
},
],
id: 'u:e19f8840c465',
},
],
id: 'u:83f2045dc8aa',
feat: 'Insert',
},
{ type: 'divider', lineStyle: 'solid', id: 'u:b12bc5cfec5e' },
{
type: 'crud',
name: 'notebooks',
syncLocation: false,
api: {
method: 'get',
url: 'rest/notebooks?select=*,type:dicts!notebook_type_fkey(*),status:dicts!notebook_status_fkey(*)&name=like.*$keywords*&isprivate=is.false&author=$author&type=$type&order=publish_at.desc.nullslast',
data: {
page: '$page',
perPage: 8,
orderBy: '$orderBy',
orderDir: '$orderDir',
publish_at: '$publish_at',
},
requestAdaptor:
"if (api.query.author === '') api.url = api.url.replace('&author=', '')\r\n\r\nif (api.query.type === '') api.url = api.url.replace('&type=', '')\r\n\r\nif (api.body.publish_at === '') {\r\n api.url = api.url.replace('&publish_at=', '')\r\n} else {\r\n let date_former = api.body.publish_at && api.body.publish_at.split(',')[0]\r\n let date_latter = api.body.publish_at && api.body.publish_at.split(',')[1]\r\n\r\n if (date_former === date_latter) {\r\n api.url = api.url.replace(\r\n '&publish_at=' + date_former + '%2C' + date_latter,\r\n '&publish_at=gte.' + date_former + ' 00:00:00&publish_at=lte.' + date_former + ' 23:59:59'\r\n )\r\n } else {\r\n api.url = api.url.replace(\r\n '&publish_at=' + date_former + '%2C' + date_latter,\r\n '&publish_at=gte.' + date_former + ' 00:00:00&publish_at=lte.' + date_latter + ' 23:59:59'\r\n )\r\n }\r\n}\r\n\r\nif (api.query.orderBy && api.query.orderDir) {\r\n api.url = api.url.replace('order=publish_at.desc.nullslast', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '&order=' + api.query.orderBy + '.' + api.query.orderDir);\r\n} else {\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '');\r\n}\r\n\r\nreturn api;",
},
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
perPageAvailable: [8,],
bulkActions: [],
itemActions: [],
id: 'u:46a2b5faa785',
filter: {
title: '',
body: [
{
type: 'input-text',
name: 'keywords',
label: '名称关键字',
id: 'u:a9a2048f6f68',
},
{
type: 'submit',
label: '搜索',
actionType: 'submit',
id: 'u:decd16bff04d',
level: 'primary',
},
],
reload: 'notebooks?keywords=$keywords',
canAccessSuperData: false,
wrapWithPanel: false,
autoFocus: false,
mode: 'inline',
id: 'u:1888123468b3',
feat: 'Insert',
},
messages: {},
card: {
type: 'card',
header: {
title: '',
subTitle: '',
avatar: '/olms/notebook/${pid}/cover.png',
desc: '',
},
body: [
{
name: 'name',
label: '',
type: 'text',
sortable: true,
id: 'u:02b589f37127',
inline: true,
tpl: '名称:${name}',
placeholder: '-',
},
{
name: 'author',
label: '',
type: 'text',
sortable: true,
id: 'u:3c98eb214a1b',
inline: true,
tpl: '创建者:${author}'
},
{
type: 'input-tag',
label: '',
name: 'tag',
id: 'u:7302818b79ff',
source: {
method: 'get',
url: 'rest/tags?order=name',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: item.name,\r\n };\r\n }),\r\n};',
},
multiple: true,
joinValues: false,
extractValue: true,
mode: 'normal',
clearable: false,
creatable: true,
createBtnLabel: '新增标签',
addControls: [
{
label: '名称',
type: 'text',
name: 'name',
value: '',
required: true,
mode: 'normal',
},
],
addApi: {
method: 'post',
url: 'rest/tags',
data: { name: '${name}' },
dataType: 'json',
},
editable: false,
editControls: [
{
label: '名称',
type: 'text',
name: 'name',
value: '',
required: true,
mode: 'normal',
},
],
editApi: {
method: 'patch',
url: 'rest/tags?id=eq.${id}',
data: { name: '${name}' },
dataType: 'json',
},
removable: true,
deleteApi: { method: 'delete', url: 'rest/tags?id=eq.${id}' },
optionsTip: '最近您使用的标签',
className: 'm-b-sm m-t-sm',
},
{
name: 'description',
label: '描述',
type: 'text',
sortable: true,
id: 'u:67cf5873f23e',
inline: true,
hidden: true,
},
{
name: 'type.dictvalue',
label: '类型',
type: 'text',
sortable: true,
id: 'u:c3eb052e801c',
inline: true,
hidden: true,
},
{
name: 'publish_at',
label: '发布日期',
type: 'date',
format: 'YYYY-MM-DD',
sortable: true,
id: 'u:fdb56224ba1b',
hidden: true,
},
{
type: 'text',
name: 'tag',
label: '标签',
placeholder: '-',
id: 'u:d3e2a072de3c',
inline: true,
hidden: true,
},
{
type: 'mapping',
map: {
false: '<span class="label label-danger">否</span>',
true: '<span class="label label-success">是</span>',
},
label: '首页展示',
name: 'isrecommend',
sortable: true,
id: 'u:c717e7e057a9',
hidden: true,
},
{
name: 'recommendOrder',
label: '展示次序',
type: 'text',
sortable: true,
id: 'u:77fb26a7f92e',
inline: true,
hidden: true,
},
// {
// type: 'button-group',
// buttons: [
// {
// type: 'button',
// actionType: 'dialog',
// dialog: {
// title: '修改标签',
// body: [
// {
// type: 'form',
// title: '表单',
// api: {
// method: 'patch',
// url: 'rest/notebooks?id=eq.${id}',
// data: { tag: '${tag}' },
// dataType: 'json',
// },
// body: [
// {
// label: '标签',
// type: 'select',
// name: 'tag',
// checkAll: true,
// source: {
// method: 'get',
// url: 'rest/tags?order=name',
// adaptor:
// 'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: item.name,\r\n };\r\n }),\r\n};',
// },
// multiple: true,
// joinValues: false,
// extractValue: true,
// mode: 'normal',
// clearable: true,
// creatable: true,
// createBtnLabel: '新增标签',
// addControls: [
// {
// label: '名称',
// type: 'text',
// name: 'name',
// value: '',
// required: true,
// mode: 'normal',
// },
// ],
// addApi: {
// method: 'post',
// url: 'rest/tags',
// data: { name: '${name}' },
// dataType: 'json',
// },
// editable: true,
// editControls: [
// {
// label: '名称',
// type: 'text',
// name: 'name',
// value: '',
// required: true,
// mode: 'normal',
// },
// ],
// editApi: {
// method: 'patch',
// url: 'rest/tags?id=eq.${id}',
// data: { name: '${name}' },
// dataType: 'json',
// },
// removable: true,
// deleteApi: {
// method: 'delete',
// url: 'rest/tags?id=eq.${id}',
// },
// },
// ],
// },
// ],
// type: 'dialog',
// closeOnEsc: true,
// showCloseButton: true,
// },
// level: 'link',
// icon: 'fa fa-pencil text-info',
// size: 'md',
// tooltip: '修改标签',
// tooltipPlacement: 'top',
// iconClassName: 'pull-left',
// className: 'p-r-none p-l-none',
// id: 'u:39235263c3cb',
// },
// {
// type: 'button',
// label: '',
// level: 'link',
// size: 'md',
// icon: 'fa fa-eye text-warning',
// iconClassName: 'pull-left',
// actionType: 'dialog',
// tooltip: '预览',
// tooltipPlacement: 'top',
// className: 'p-r-none p-l-none',
// dialog: {
// title: '预览',
// body: [
// {
// type: 'form',
// title: '表单',
// body: [
// {
// type: 'formula',
// name: 'iframeSrc',
// formula:
// '"http://192.168.71.19"+ "/notebook/" + pid + "/" + post',
// },
// {
// type: 'iframe',
// src: '${iframeSrc}',
// height: '700px',
// },
// ],
// },
// ],
// type: 'dialog',
// closeOnEsc: false,
// closeOnOutside: false,
// showCloseButton: true,
// size: 'xl',
// actions: [],
// },
// id: 'u:1aa082ee65f0',
// },
// {
// type: 'button',
// label: '',
// actionType: 'dialog',
// size: 'md',
// tooltip: '首页展示',
// tooltipPlacement: 'top',
// iconClassName: 'pull-left',
// className: 'p-r-none p-l-none',
// icon: 'fa fa-thumbs-up text-info',
// level: 'link',
// visibleOn: 'this.type.dictkey === "notebook"',
// dialog: {
// type: 'dialog',
// title: '首页展示',
// body: [
// {
// type: 'form',
// title: '表单',
// api: {
// method: 'patch',
// url: 'rest/notebooks/$id',
// data: { '&': '$$' },
// requestAdaptor:
// 'if (api.data.recommendOrder) api.data.isrecommend = true;\r\nelse api.data.isrecommend = false;\r\n\r\nreturn api;',
// },
// reload: 'notebooks',
// body: [
// {
// type: 'select',
// label: '序号',
// name: 'recommendOrder',
// clearable: true,
// options: [
// { label: '1', value: 1 },
// { label: '2', value: 2 },
// { label: '3', value: 3 },
// { label: '4', value: 4 },
// { label: '5', value: 5 },
// { label: '6', value: 6 },
// { label: '7', value: 7 },
// { label: '8', value: 8 },
// ],
// },
// ],
// },
// ],
// closeOnEsc: true,
// showCloseButton: true,
// closeOnOutside: false,
// size: 'sm',
// },
// id: 'u:98e68e904df9',
// },
// {
// type: 'button',
// label: '',
// level: 'link',
// icon: 'fa fa-times text-danger',
// size: 'md',
// tooltip: '删除',
// tooltipPlacement: 'top',
// iconClassName: 'pull-left',
// className: 'p-r-none p-l-none',
// actionType: 'ajax',
// api: { method: 'post', url: 'nb/cancel_notebook/$id/$pid' },
// id: 'u:713ccf6aa5fc',
// },
// ],
// id: 'u:2408808996c7',
// label: '',
// },
{
type: 'button-group',
buttons: [
{
type: 'button',
label: '预览',
onEvent: {
click: {
actions: [
{
actionType: 'hidden',
componentId: 'u:5fe9a2ffdbe1',
ignoreError: false,
},
{
componentId: 'u:16d9090a3803',
ignoreError: false,
actionType: 'show',
},
{
componentId: 'my_service1',
actionType: 'setValue',
args: { value: '${event.data}' },
},
],
},
},
level: 'light',
size: 'sm',
id: 'u:1aa082ee65f0',
themeCss: {
className: {
'border:default': {
'top-border-style': 'solid',
'left-border-style': 'solid',
'right-border-style': 'solid',
'bottom-border-style': 'solid',
'top-border-width': 'none',
'left-border-width': 'none',
'right-border-width': 'none',
'bottom-border-width': 'none',
},
},
},
},
],
},
// {
// type: 'button',
// label: '预览',
// onEvent: {
// click: {
// actions: [
// {
// actionType: 'hidden',
// componentId: 'u:5fe9a2ffdbe1',
// ignoreError: false,
// },
// {
// componentId: 'u:16d9090a3803',
// ignoreError: false,
// actionType: 'show',
// },
// {
// componentId: 'my_service1',
// actionType: 'setValue',
// args: { value: '${event.data}' },
// },
// ],
// },
// },
// id: 'u:60093cd4c74f',
// level: 'light',
// size: 'xs',
// },
],
actions: [],
id: 'u:8f1fae00368b',
imageClassName: 'w-full h-sm',
avatarClassName: 'w-full',
},
mode: 'cards',
},
],
style: {
position: 'relative',
display: 'flex',
inset: 'auto',
flexWrap: 'nowrap',
flexDirection: 'column',
alignItems: 'flex-start',
},
size: 'none',
wrapperBody: false,
id: 'u:5fe9a2ffdbe1',
},
{
type: 'container',
body: [
{
type: 'button',
label: '返回',
onEvent: {
click: {
actions: [
{
componentId: 'u:5fe9a2ffdbe1',
ignoreError: false,
actionType: 'show',
},
{
componentId: 'u:16d9090a3803',
ignoreError: false,
actionType: 'hidden',
},
],
},
},
id: 'u:a0f4fc5477c0',
themeCss: {
className: {
'border:default': {
'top-border-width': 'none',
'left-border-width': 'none',
'right-border-width': 'none',
'bottom-border-width': 'none',
},
'font:default': {
color: '#36586b',
fontWeight: '500',
fontSize: '24px',
},
},
},
size: 'lg',
block: false,
level: 'link',
},
{
type: 'page',
body: [
{
type: 'service',
data: {
pid: '490f5308c4bd486fb2225f1e70198768',
post: '00_实践总览.ipynb',
},
body: [
{
type: 'formula',
name: 'iframeSrc',
formula:
'location.origin + "/notebook/" + this.pid + "/" + this.post',
id: 'u:479855e2164e',
},
{
type: 'iframe',
src: '${iframeSrc}/?token=$user.token',
height: '700px',
id: 'u:d2171dd0883e',
},
],
id: 'my_service1',
dsType: 'api',
},
],
id: 'u:d57881a83b7f',
},
],
style: {
position: 'relative',
display: 'flex',
inset: 'auto',
flexWrap: 'nowrap',
flexDirection: 'column',
alignItems: 'flex-start',
},
size: 'none',
wrapperBody: false,
id: 'u:16d9090a3803',
isFixedHeight: false,
isFixedWidth: false,
hidden: true,
visible: false,
},
],
id: 'u:4b1560f3b2db',
asideResizor: false,
pullRefresh: { disabled: true },
definitions: {},
};
export { schema };

View File

@@ -0,0 +1,295 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
title: '表单',
className: '',
submitOnChange: true,
submitOnInit: true,
reload:
'manage_loc?semesterSelect=$semesterSelect&course_id=$course_id&project_id=$project_id&organization_id=$org_id',
wrapWithPanel: false,
canAccessSuperData: false,
mode: 'inline',
name: 'filter',
body: [
{
type: 'group',
body: [
{
type: 'select',
label: '学期',
name: 'semesterSelect',
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n };\r\n }),\r\n};',
},
submitOnChange: true,
mode: 'inline',
inputClassName: 'm-l-xs',
size: 'md',
searchable: true,
selectFirst: true,
},
{
type: 'select',
mode: 'inline',
label: '课程',
name: 'course_id',
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
size: 'md',
searchable: true,
selectFirst: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
},
{
type: 'select',
label: '项目',
name: 'project_id',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/course2projects?select=projects(id,name)&projects.organization_id=eq.${centre_id}&projects.semester_id=eq.${semesterSelect}&projects.free_schedule=eq.false&course_id=${course_id}',
adaptor:
'let projects = [];\r\npayload.data.items.filter(item => item.projects !== null).forEach(item => {\r\n if (projects.findIndex(x => x.value === item.projects.id) === -1) {\r\n projects.push({\r\n label: item.projects.name,\r\n value: "eq.".concat(item.projects.id)\r\n });\r\n }\r\n});\r\nreturn {\r\n data: projects\r\n}',
requestAdaptor:
"if (api.query.course_id === '') {\r\n api.url = api.url.replace('&course_id=', '');\r\n}\r\n\r\nreturn api;",
sendOn: 'this.course_id',
},
checkAll: false,
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
},
{
type: 'select',
label: '班级',
name: 'org_id',
mode: 'inline',
size: 'md',
clearable: true,
searchable: true,
source: {
method: 'get',
url: 'rest/orgs?select=*,course2orgs!inner(*)&type=eq.class&path=cd.root.1&course2orgs.course_id=${course_id}&order=name,code',
requestAdaptor:
'if (api.query.course2orgs.course_id === "eq.") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.", "")\r\n}\r\nif (api.query.course2orgs.course_id === "eq.undefined") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.undefined", "")\r\n}',
adaptor:
'return {\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq." + item.id\r\n }\r\n })\r\n}',
sendOn: 'this.course_id',
},
},
],
},
],
},
{
type: 'divider',
lineStyle: 'solid',
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/users_ex1?semester_id=eq.$semesterSelect&name=like.*$keywords*&role=eq.student&course_id=$course_id&project_id=$project_id&organization_id=$organization_id&organization_type=eq.class&order=name,code',
data: {
page: '$page',
perPage: '$perPage',
orderBy: '$orderBy',
orderDir: '$orderDir',
},
adaptor: '',
requestAdaptor:
"api.url = api.url.replace('&semesterSelect=', '&semester_id=eq.')\r\napi.url = api.url.replace('&keywords=' + api.query.keywords, '')\r\n\r\nif (api.query.project_id === '') {\r\n api.url = api.url.replace('&project_id=', '')\r\n}\r\n\r\nif (api.query.organization_id === '') {\r\n api.url = api.url.replace('&organization_id=', '')\r\n}\r\n\r\nif (api.query.id === '') {\r\n api.url = api.url.replace('&id=', '')\r\n}\r\n\r\nif (api.query.orderBy && api.query.orderDir) {\r\n api.url = api.url.replace('&order=name%2Ccode', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '&order=' + api.query.orderBy + '.' + api.query.orderDir);\r\n} else {\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '');\r\n}\r\n\r\nreturn {\r\n ...api\r\n}",
sendOn: 'this.course_id',
},
columns: [
{
type: 'text',
label: '学生',
name: 'name',
placeholder: '-',
sortable: true,
},
{
type: 'text',
label: '学号',
name: 'code',
placeholder: '-',
sortable: true,
},
{
type: 'text',
label: '班级',
name: 'organization_name',
placeholder: '-',
sortable: true,
},
{
type: 'text',
label: '课程',
name: 'course_name',
placeholder: '-',
sortable: true,
},
{
type: 'text',
label: '项目',
name: 'project_name',
placeholder: '-',
sortable: true,
},
{
type: 'text',
label: '作业',
name: 'notebook_name',
placeholder: '-',
sortable: true,
},
{
type: 'text',
label: '作业状态',
name: 'status_name',
placeholder: '-',
sortable: true,
},
{
type: 'text',
label: '作业分数',
name: 'score',
placeholder: '-',
sortable: true,
},
{
type: 'button-group',
buttons: [
{
type: 'button',
label: '批阅',
level: 'primary',
size: 'xs',
actionType: 'dialog',
visibleOn:
'this.status === "notebook_submit" || this.status === "notebook_checked"',
dialog: {
title: '批阅作业',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/project_scores?on_conflict=user_id,project_id,item',
data: {
score: '$score',
user_id: '$id',
project_id: '$project_id',
item: 'exp_notebook',
},
headers: {
Prefer: 'resolution=merge-duplicates',
},
},
initApi: {
method: 'get',
url: 'rest/project_scores?user_id=eq.$id&project_id=eq.$project_id&item=eq.exp_notebook',
},
body: [
{
type: 'formula',
name: 'iframeSrc',
formula:
'location.origin + "/notebook/" + notebook_pid + "/" + notebook_post',
},
{
type: 'iframe',
src: '${iframeSrc}',
height: '700px',
},
{
type: 'input-number',
label: '分数',
name: 'score',
required: true,
mode: '',
value: 0,
min: '0',
max: '100',
step: 1,
},
],
},
],
type: 'dialog',
closeOnEsc: false,
closeOnOutside: false,
showCloseButton: true,
size: 'xl',
},
placeholder: '-',
},
],
id: 'u:aab0a651813d',
label: '操作',
placeholder: '-',
},
],
messages: {},
syncLocation: false,
pageField: 'page',
perPageField: 'perPage',
headerToolbar: [],
footerToolbar: [
{
type: 'pagination',
},
{
type: 'switch-per-page',
},
{
type: 'statistics',
},
],
name: 'manage_loc',
perPageAvailable: [10, 20, 30, 40, 50],
filter: {
title: '',
body: [
{
type: 'input-text',
name: 'keywords',
label: '学生关键字',
},
{
type: 'submit',
label: '搜索',
actionType: 'submit',
id: 'u:decd16bff04d',
level: 'primary',
},
],
reload: 'manage_loc?keywords=$keywords',
canAccessSuperData: false,
wrapWithPanel: false,
autoFocus: false,
mode: 'inline',
},
},
],
messages: {},
bodyClassName: '',
title: '',
};
export { schema };

View File

@@ -0,0 +1,329 @@
const schema = {
type: 'page',
body: [
{
type: 'service',
body: [
{
type: 'flex',
items: [
{
type: 'container',
id: 'u:cf018a54788b',
style: {
flexGrow: 1,
flexBasis: '0px',
flex: '1 1 auto',
display: 'flex',
position: 'static',
flexWrap: 'nowrap',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
},
isFixedHeight: false,
body: [
{
type: 'progress',
id: 'u:9ae23711aada',
value: '$cpu',
placeholder: '-',
progressClassName: '',
strokeWidth: 10,
map: [
{ color: '#28a745', value: 30 },
{ color: '#fad733', value: 70 },
{ color: '#dc3545', value: 100 },
],
gapDegree: 75,
gapPosition: 'bottom',
mode: 'dashboard',
},
{
type: 'tpl',
tpl: 'CPU',
inline: false,
wrapperComponent: '',
id: 'u:44a9ee228557',
themeCss: {
baseControlClassName: {
'font:default': { 'text-align': 'center' },
},
},
},
],
},
{
type: 'container',
body: [
{
type: 'progress',
id: 'u:2fc35c429a6c',
value: '$memory',
placeholder: '-',
progressClassName: '',
strokeWidth: 10,
map: [
{ color: '#28a745', value: 30 },
{ color: '#fad733', value: 70 },
{ color: '#dc3545', value: 100 },
],
gapDegree: 75,
gapPosition: 'bottom',
mode: 'dashboard',
},
{
type: 'tpl',
tpl: '内存',
inline: false,
wrapperComponent: '',
id: 'u:fd25e194e7b0',
themeCss: {
baseControlClassName: {
'font:default': { 'text-align': 'center' },
},
},
},
],
size: 'none',
style: {
position: 'static',
display: 'flex',
flex: '1 1 auto',
flexGrow: 1,
flexWrap: 'nowrap',
alignItems: 'center',
flexBasis: '0px',
justifyContent: 'center',
flexDirection: 'column',
},
wrapperBody: false,
isFixedHeight: false,
isFixedWidth: false,
id: 'u:10e16e97e026',
},
{
type: 'container',
body: [
{
type: 'progress',
id: 'u:c1f3e318e1d2',
value: '$disk',
placeholder: '-',
progressClassName: '',
strokeWidth: 10,
map: [
{ color: '#28a745', value: 30 },
{ color: '#fad733', value: 70 },
{ color: '#dc3545', value: 100 },
],
gapDegree: 75,
gapPosition: 'bottom',
mode: 'dashboard',
},
{
type: 'tpl',
tpl: '磁盘',
inline: false,
wrapperComponent: '',
id: 'u:11b1d296b866',
themeCss: {
baseControlClassName: {
'font:default': { 'text-align': 'center' },
},
},
},
],
size: 'none',
style: {
position: 'static',
display: 'flex',
flex: '1 1 auto',
flexGrow: 1,
flexWrap: 'nowrap',
alignItems: 'center',
flexBasis: '0px',
justifyContent: 'center',
overflowY: 'visible',
flexDirection: 'column',
},
wrapperBody: false,
isFixedHeight: false,
isFixedWidth: false,
id: 'u:d63bc21af893',
},
{
type: 'container',
body: [
{
type: 'tpl',
tpl: '当前驱动版本:${gpu.driver}',
inline: true,
wrapperComponent: '',
id: 'u:b66aa4e0241d',
},
{
type: 'cards',
columnsCount: 1,
card: {
type: 'container',
body: [
{
type: 'tpl',
tpl: '${name}',
inline: true,
wrapperComponent: '',
id: 'u:702c8fcd1378',
},
{
type: 'progress',
value: '$percent',
placeholder: '-',
progressClassName: '',
strokeWidth: 10,
map: [
{ color: '#28a745', value: 30 },
{ color: '#fad733', value: 70 },
{ color: '#dc3545', value: 100 },
],
gapDegree: 75,
gapPosition: 'bottom',
mode: 'dashboard',
id: 'u:376de1b837de',
},
],
wrapperBody: false,
style: {
position: 'relative',
display: 'flex',
width: '100%',
flexWrap: 'nowrap',
inset: 'auto',
justifyContent: 'space-evenly',
alignItems: 'center',
},
themeCss: {
baseControlClassName: {
'radius:default': {
'top-left-border-radius': '6px',
'top-right-border-radius': '6px',
'bottom-left-border-radius': '6px',
'bottom-right-border-radius': '6px',
},
'boxShadow:default':
' 0px 0px 10px 0px var(--colors-neutral-line-8)',
'border:default': {
'top-border-width': 'var(--borders-width-1)',
'left-border-width': 'var(--borders-width-1)',
'right-border-width': 'var(--borders-width-1)',
'bottom-border-width': 'var(--borders-width-1)',
'top-border-style': 'var(--borders-style-1)',
'left-border-style': 'var(--borders-style-1)',
'right-border-style': 'var(--borders-style-1)',
'bottom-border-style': 'var(--borders-style-1)',
'top-border-color': '#3be157',
'left-border-color': '#3be157',
'right-border-color': '#3be157',
'bottom-border-color': '#3be157',
},
'padding-and-margin:default': {
paddingTop: '10px',
paddingRight: '10px',
paddingBottom: '10px',
paddingLeft: '10px',
},
},
},
id: 'u:537200fb743f',
isFixedHeight: false,
isFixedWidth: false,
},
placeholder: '',
style: { gutterY: 10 },
id: 'u:f886cae9f2af',
name: 'gpu.list',
className: 'mt-5',
},
],
size: 'none',
style: {
position: 'static',
display: 'block',
flex: '1 1 auto',
flexGrow: 2,
flexBasis: 0,
},
wrapperBody: false,
isFixedHeight: false,
isFixedWidth: false,
id: 'u:6915a8d9638a',
},
],
style: { position: 'relative', rowGap: '10px', columnGap: '10px' },
id: 'u:e4d85d1072cf',
},
],
id: 'u:6399a46b9a99',
dsType: 'api',
ws: { url: 'ws://peiyun.host.platosoft.org:7080/api/nb/ws/metrics' },
},
{
type: 'crud',
syncLocation: false,
api: {
method: 'get',
url: '/hub/users',
data: {},
messages: {},
requestAdaptor: '',
adaptor: '',
dataType: 'json',
},
bulkActions: [],
itemActions: [],
id: 'u:bd445262be74',
perPageAvailable: [5, 10, 20, 50, 100],
messages: {},
listItem: {
body: [
{
name: 'name',
label: '用户',
type: 'text',
id: 'u:83674005c420',
inline: true,
},
{
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
value: 1729753248,
name: 'last_activity',
id: 'u:92420a25ff12',
},
],
actions: [
{
type: 'button',
label: '停止',
onEvent: { click: { actions: [] } },
id: 'u:2384629a386e',
level: 'danger',
icon: 'fa fa-power-off',
},
],
id: 'u:aaf60b5849b5',
},
mode: 'list',
loadDataOnce: true,
matchFunc: '',
className: 'mt-10',
title: '用户列表',
showHeader: false,
},
],
id: 'u:4b1560f3b2db',
asideResizor: false,
pullRefresh: { disabled: true },
};
export { schema };

View File

@@ -0,0 +1,148 @@
const schema = {
type: 'page',
body: [
{
type: 'dataset-renderer',
name: 'storage',
bucket: 'model',
addFolder: {
type: 'button',
actionType: 'dialog',
reload: 'storage',
dialog: {
type: 'dialog',
title: '新建文件夹',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'storage/v1/object/model/$path/$name/.empty',
requestAdaptor:
"api.url = api.url.replace('//', '/');return api",
},
body: [
{
type: 'input-text',
label: '文件夹名称',
name: 'name',
mode: 'normal',
required: true,
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
},
moveFile: {
type: 'button',
actionType: 'dialog',
reload: 'storage',
dialog: {
type: 'dialog',
title: '重命名',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'storage/v1/object/move',
data: {
bucketId: 'model',
sourceKey: '$sourceKey',
dest: '$dest',
path: '$path',
},
requestAdaptor:
"console.log(api)\r\nreturn {\r\n ...api,\r\n data: {\r\n bucketId: api.data.bucketId,\r\n sourceKey: api.data.sourceKey,\r\n destinationKey: api.data.path === '' ? api.data.dest : api.data.path + '/' + api.data.dest\r\n }\r\n}",
},
body: [
{
name: 'dest',
type: 'input-text',
label: '名称',
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
},
delFile: {
type: 'button',
actionType: 'ajax',
reload: 'storage',
label: false,
api: {
method: 'delete',
url: '/storage/v1/object/model',
data: {
prefixes: '$prefixes',
},
headers: {
post2rest: false,
},
},
confirmText: '确认删除吗?',
},
delFiles: {
type: 'button',
actionType: 'dialog',
reload: 'storage',
dialog: {
type: 'dialog',
title: '批量删除文件',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'delete',
url: 'storage/v1/object/model',
data: { prefixes: '$prefixes', path: '$path' },
headers: { post2rest: false },
requestAdaptor:
"return {\r\n ...api,\r\n data: {\r\n prefixes: api.data.prefixes.map(item => api.data.path === '' ? item : api.data.path + '/' + item)\r\n }\r\n}",
},
body: [
{
name: 'prefixes',
type: 'checkboxes',
label: false,
inline: false,
checkAll: true,
joinValues: false,
extractValue: true,
required: true,
source: {
method: 'post',
url: '/storage/v1/object/list/model',
data: {
prefix: '$path',
sortBy: {
column: 'name',
order: 'asc',
},
},
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.filter(item => item.id && item.name.charAt(0) !== ".").map(item => {\r\n return {\r\n label: item.name,\r\n value: item.name\r\n }\r\n })\r\n}',
},
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
},
},
,
],
};
export { schema };

View File

@@ -0,0 +1,657 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
messages: {},
api: {
method: 'get',
url: 'rest/subjects?order=name',
data: {
name: 'like.%${name}%',
},
requestAdaptor:
"if (api.query.name === 'like.%%') {\r\n api.url = api.url.replace('&name=like.%25%25', '')\r\n}\r\n\r\nreturn api;",
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n sname: item.name,\r\n temp: item.content.map(item => {\r\n return {\r\n ...item,\r\n mooc: item.session.filter(item => item.split("-")[1] === "mooc"),\r\n notebook: item.session.filter(item => item.split("-")[1] === "notebook")\r\n }\r\n })\r\n }\r\n })\r\n }\r\n}',
},
headerToolbar: [
{
type: 'button',
align: 'right',
label: '添加',
icon: 'fa fa-plus',
level: 'default',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加专题',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'control',
label: '',
mode: 'normal',
body: [
{
type: 'grid',
columns: [
{
body: [
{
label: '名称',
name: 'sname',
type: 'input-text',
mode: 'normal',
required: true,
id: 'u:f72c2d77e3a4',
},
],
id: 'u:8c6f142936c2',
},
],
id: 'u:c60d1c6c17ae',
},
],
id: 'u:1aab6d05c2f2',
},
{
type: 'button',
label: '内容管理',
onEvent: {
click: {
actions: [
{
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '内容管理',
body: [
{
type: 'form',
title: '表单',
reload: 'subject?content=$content',
body: [
{
label: '内容',
type: 'combo',
name: 'temp',
multiple: true,
multiLine: true,
items: [
{
label: '描述',
type: 'textarea',
name: 'intro',
mode: 'normal',
id: 'u:86fa1674235c',
},
{
type: 'picker',
label: '课程',
name: 'mooc',
valueField: 'value',
labelField: 'name',
modalClassName: 'app-popover',
id: 'u:c8e1e52ea0b1',
modalMode: 'dialog',
strictMode: false,
multiple: true,
source: {
method: 'get',
url: 'rest/mooc?name=${name}&order=name',
data: {
page: '${page}',
perPage: '${perPage}',
name: 'like.%${name}%',
},
requestAdaptor:
"if (api.query.name === 'like.%%') {\r\n api.url = api.url.replace('&name=like.%25%25', '')\r\n}\r\n\r\nreturn api;",
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n value: item.id + "-" + item.type + "-" + item.name\r\n }\r\n })\r\n }\r\n}',
},
joinValues: false,
extractValue: true,
embed: true,
pickerSchema: {
mode: 'table',
id: 'u:ae755822f652',
perPageAvailable: [10],
messages: {},
syncLocation: false,
columns: [
{
type: 'text',
label: '编号',
name: 'code',
id: 'u:636f3f0a2ed3',
},
{
type: 'text',
label: '名称',
name: 'name',
searchable: true,
id: 'u:636f3f0a2ed3',
},
],
},
},
{
type: 'picker',
label: '文档',
name: 'notebook',
valueField: 'value',
labelField: 'name',
modalClassName: 'app-popover',
id: 'u:0a92044686ee',
modalMode: 'dialog',
strictMode: false,
multiple: true,
source: {
method: 'get',
url: 'rest/notebooks?name=${name}&author=${author}&order=name',
data: {
page: '${page}',
perPage: '${perPage}',
name: 'like.%${name}%',
author: 'like.%${author}%',
},
requestAdaptor:
"if (api.query.name === 'like.%%') {\r\n api.url = api.url.replace('&name=like.%25%25', '')\r\n}\r\n\r\nif (api.query.author === 'like.%%') {\r\n api.url = api.url.replace('&author=like.%25%25', '')\r\n}\r\n\r\nreturn api;",
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n value: item.id + "-" + item.type + "-" + item.name\r\n }\r\n })\r\n }\r\n}',
},
joinValues: false,
extractValue: true,
embed: true,
pickerSchema: {
mode: 'table',
id: 'u:ae755822f652',
perPageAvailable: [10],
messages: {},
syncLocation: false,
columns: [
{
type: 'text',
label: '名称',
name: 'name',
searchable: true,
id: 'u:636f3f0a2ed3',
},
{
type: 'text',
label: '作者',
name: 'author',
searchable: true,
id: 'u:636f3f0a2ed3',
},
],
},
},
],
},
{
type: 'formula',
name: 'content',
formula:
'data.temp.map(item => { return { intro: item.intro, session: item.mooc.concat(item.notebook).filter(item => item) } })',
},
],
id: 'u:d2e6bd28f354',
},
],
showCloseButton: true,
showErrorMsg: true,
showLoading: true,
className: 'app-popover',
id: 'u:b6ff11656496',
closeOnEsc: true,
size: 'lg',
withDefaultData: false,
dataMapSwitch: true,
data: {
'&': '$$',
},
},
},
],
},
},
id: 'u:739469c288af',
level: 'primary',
},
{
type: 'input-array',
label: '内容',
name: 'content',
id: 'u:561926ea3025',
items: {
type: 'combo',
label: '',
controls: [
{
type: 'tpl',
label: '介绍',
tpl: '$intro',
},
{
type: 'input-array',
label: '内容',
name: 'session',
id: 'u:561926ea3025',
items: {
type: 'tpl',
id: 'u:f95cfcfcc0fb',
},
addable: false,
removable: true,
draggable: true,
isSlot: true,
},
],
id: 'u:f95cfcfcc0fb',
multiLine: false,
multiple: false,
messages: {},
},
addable: false,
removable: false,
},
],
api: {
method: 'post',
url: 'rest/subjects',
data: {
name: '$sname',
content: '$content',
},
dataType: 'json',
},
name: 'subject',
id: 'u:6ddf84e7e06c',
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
id: 'u:2f4c08ecc48e',
},
name: 'semesterSelect',
options: [],
checkAll: false,
submitOnChange: true,
autoComplete: '',
mode: 'inline',
className: '',
id: 'u:8eee4a809327',
},
],
syncLocation: false,
name: 'plan',
footerToolbar: [],
affixHeader: true,
placeholder: '暂无数据',
mode: 'cards',
card: {
type: 'card',
header: {
title: '$name',
},
body: [
{
label: '',
type: 'button-group',
buttons: [
{
type: 'button',
label: '',
actionType: 'dialog',
dialog: {
title: '修改专题',
body: [
{
type: 'form',
title: '表单',
data: { '&': '$$', name: '__undefined' },
body: [
{
type: 'control',
label: '',
mode: 'normal',
body: [
{
type: 'grid',
columns: [
{
body: [
{
label: '名称',
name: 'sname',
type: 'input-text',
mode: 'normal',
required: true,
id: 'u:f72c2d77e3a4',
},
],
id: 'u:8c6f142936c2',
},
],
id: 'u:c60d1c6c17ae',
},
],
id: 'u:1aab6d05c2f2',
},
{
type: 'button',
label: '内容管理',
onEvent: {
click: {
actions: [
{
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '内容管理',
data: { '&': '$$', name: '__undefined' },
body: [
{
type: 'form',
title: '表单',
reload: 'subject?content=$content',
body: [
{
label: '内容',
type: 'combo',
name: 'temp',
multiple: true,
multiLine: true,
items: [
{
label: '描述',
type: 'textarea',
name: 'intro',
mode: 'normal',
id: 'u:86fa1674235c',
},
{
type: 'picker',
label: '课程',
name: 'mooc',
valueField: 'value',
labelField: 'name',
modalClassName: 'app-popover',
id: 'u:c8e1e52ea0b1',
modalMode: 'dialog',
strictMode: false,
multiple: true,
source: {
method: 'get',
url: 'rest/mooc?name=${name}&order=name',
data: {
page: '${page}',
perPage: '${perPage}',
name: 'like.%${name}%',
},
requestAdaptor:
"if (api.query.name === 'like.%%') {\r\n api.url = api.url.replace('&name=like.%25%25', '')\r\n}\r\n\r\nreturn api;",
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n value: item.id + "-" + item.type + "-" + item.name\r\n }\r\n })\r\n }\r\n}',
},
joinValues: false,
extractValue: true,
embed: true,
pickerSchema: {
mode: 'table',
id: 'u:ae755822f652',
perPageAvailable: [10],
messages: {},
syncLocation: false,
columns: [
{
type: 'text',
label: '编号',
name: 'code',
id: 'u:636f3f0a2ed3',
},
{
type: 'text',
label: '名称',
name: 'name',
searchable: true,
id: 'u:636f3f0a2ed3',
},
],
},
},
{
type: 'picker',
label: '文档',
name: 'notebook',
valueField: 'value',
labelField: 'name',
modalClassName: 'app-popover',
id: 'u:0a92044686ee',
modalMode: 'dialog',
strictMode: false,
multiple: true,
source: {
method: 'get',
url: 'rest/notebooks?name=${name}&author=${author}&order=name',
data: {
page: '${page}',
perPage: '${perPage}',
name: 'like.%${name}%',
author: 'like.%${author}%',
},
requestAdaptor:
"if (api.query.name === 'like.%%') {\r\n api.url = api.url.replace('&name=like.%25%25', '')\r\n}\r\n\r\nif (api.query.author === 'like.%%') {\r\n api.url = api.url.replace('&author=like.%25%25', '')\r\n}\r\n\r\nreturn api;",
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n value: item.id + "-" + item.type + "-" + item.name\r\n }\r\n })\r\n }\r\n}',
},
joinValues: false,
extractValue: true,
embed: true,
pickerSchema: {
mode: 'table',
id: 'u:ae755822f652',
perPageAvailable: [10],
messages: {},
syncLocation: false,
columns: [
{
type: 'text',
label: '名称',
name: 'name',
searchable: true,
id: 'u:636f3f0a2ed3',
},
{
type: 'text',
label: '作者',
name: 'author',
searchable: true,
id: 'u:636f3f0a2ed3',
},
],
},
},
],
},
{
type: 'formula',
name: 'content',
formula:
'data.temp.map(item => { return { intro: item.intro, session: item.mooc.concat(item.notebook).filter(item => item) } })',
},
],
id: 'u:d2e6bd28f354',
},
],
showCloseButton: true,
showErrorMsg: true,
showLoading: true,
className: 'app-popover',
id: 'u:b6ff11656496',
closeOnEsc: true,
size: 'lg',
withDefaultData: false,
dataMapSwitch: false,
},
},
],
},
},
id: 'u:739469c288af',
level: 'primary',
},
{
type: 'input-array',
label: '内容',
name: 'content',
id: 'u:561926ea3025',
items: {
type: 'combo',
label: '',
controls: [
{
type: 'tpl',
label: '介绍',
tpl: '$intro',
},
{
type: 'input-array',
label: '内容',
name: 'session',
id: 'u:561926ea3025',
items: {
type: 'tpl',
id: 'u:f95cfcfcc0fb',
},
addable: false,
removable: true,
draggable: true,
isSlot: true,
},
],
id: 'u:f95cfcfcc0fb',
multiLine: false,
multiple: false,
messages: {},
},
addable: false,
removable: false,
},
],
api: {
method: 'patch',
url: 'rest/subjects/${id}',
data: {
name: '$sname',
content: '$content',
},
dataType: 'json',
},
name: 'subject',
id: 'u:6ddf84e7e06c',
},
],
type: 'dialog',
size: 'md',
closeOnEsc: true,
showCloseButton: true,
},
icon: 'fa fa-pencil text-info',
level: 'link',
size: 'md',
target: '',
block: false,
tooltip: '修改专题',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
},
{
type: 'button',
label: '',
actionType: 'ajax',
icon: 'fa fa-trash text-danger',
iconClassName: 'pull-left',
confirmText: '确认删除"${name}"',
api: {
method: 'delete',
url: 'rest/subjects/${id}',
},
level: 'link',
size: 'md',
tooltip: '删除专题',
tooltipPlacement: 'top',
className: 'p-r-none p-l-none',
},
],
},
{
type: 'divider',
label: '',
},
{
type: 'tabs',
label: '',
tabs: [
{
title: '内容',
body: [
{
type: 'list',
listItem: {
body: [
{
type: 'html',
html: '$intro',
wrapperComponent: '',
id: 'u:d6c7aa76d24f',
},
{
type: 'input-array',
name: 'session',
items: {
type: 'html',
},
addable: false,
removable: false,
},
],
actions: [],
id: 'u:1ddb9863eba8',
},
id: 'u:6a6d7608b012',
source: '$content',
placeholder: '没有数据',
},
],
},
],
className: 'tab',
},
],
actions: [],
actionsCount: 4,
},
columnsCount: 1,
loadDataOnce: true,
masonryLayout: false,
itemClassName: '',
checkAll: false,
id: 'u:f2d78d4b8af6',
},
],
cssVars: {
'--Form-item-gap': 0,
},
headerToolbar: [],
id: 'u:9891040f8810',
};
export { schema };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,850 @@
const schema = {
type: 'page',
body: [
{
type: 'tabs',
className: '',
tabs: [
{
title: '日程一览',
body: [
{
type: 'form',
title: '表单',
className: '',
submitOnChange: true,
submitOnInit: true,
reload:
'manage_loc?course_id=$course_id&project_id=$project_id&teacher_id=$teacher_id&location_id=$location_id&date=$date&no_tch=$no_tch&public=$public&semesterSelect=$semesterSelect',
initApi: {
method: 'get',
url: 'rest/users?role=eq.teacher&id=eq.${user.id}&order=code',
requestAdaptor: '',
adaptor:
"if (payload.data.items.length !== 0) {\r\n return {\r\n teacher_id: 'eq.'.concat(payload.data.items[0].id)\r\n }\r\n} else {\r\n return {}\r\n}",
},
wrapWithPanel: false,
mode: 'inline',
name: 'filter',
body: [
{
type: 'group',
body: [
{
type: 'select',
label: '历史',
name: 'semesterSelect',
mode: 'inline',
clearable: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
options: [],
id: 'u:66e672b9a4eb',
},
{
type: 'select',
label: '课程',
name: 'course_id',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
id: 'u:834c027e3f4a',
},
{
type: 'select',
mode: 'inline',
label: '项目',
name: 'project_id',
clearable: true,
source: {
method: 'get',
url: 'rest/projects?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
id: 'u:ce7be11036a7',
},
],
id: 'u:5014fc9e9daf',
},
{
type: 'group',
body: [
{
type: 'input-date-range',
label: '日期',
name: 'date',
mode: 'inline',
clearable: true,
value: '',
format: 'YYYY-MM-DD',
ranges:
'yesterday,today,7daysago,prevweek,thismonth,prevmonth,prevquarter',
inputClassName: 'm-l-xs',
id: 'u:105a2c1566be',
},
{
label: '地点',
type: 'select',
name: 'location_id',
mode: 'inline',
clearable: true,
checkAll: false,
source: {
method: 'get',
url: 'rest/locations?organization_id=eq.$centre_id&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
id: 'u:da7fb6289bbf',
},
{
type: 'select',
name: 'teacher_id',
mode: 'inline',
label: '教师',
clearable: true,
checkAll: false,
source: {
method: 'get',
url: 'rest/users?role=eq.teacher&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
id: 'u:9945a410d682',
},
],
id: 'u:7ef4cbfbbc4c',
},
{
type: 'group',
body: [
{
type: 'switch',
name: 'no_tch',
option: '显示无教师场次',
mode: 'inline',
optionAtLeft: false,
trueValue: 1,
falseValue: 0,
value: 0,
inputClassName: '',
id: 'u:e9b5ab665ddf',
},
{
type: 'switch',
name: 'public',
option: '显示公共场次',
mode: 'inline',
optionAtLeft: false,
trueValue: 1,
falseValue: 0,
value: 0,
inputClassName: 'm-l-sm',
id: 'u:984816e1593e',
},
],
id: 'u:1de38c7bb180',
},
{
type: 'divider',
lineStyle: 'solid',
id: 'u:f99b4d53b5dc',
},
],
id: 'u:ca42a8e96c02',
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/schedule_stats?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&is_publish=is.true&order=project_name,date,period_name,location_name,teacher_name',
data: {
page: '$page',
perPage: '$perPage',
'&': '$$',
},
adaptor:
"let supportDateTimeFormat = typeof(Intl.DateTimeFormat) === 'function';\r\npayload.data.items.forEach(item => {\r\n item.day = supportDateTimeFormat ? `${item.date}${new Intl.DateTimeFormat('zh-CN', { weekday: 'short'}).format(new Date(item.date))}` : item.date;\r\n let att_count = item.att_y_count\r\n + item.att_y_late_count\r\n + item.att_y_unclean_count\r\n item.att_num = \"\".concat(\r\n att_count, \"/\", item.current_student_number\r\n )\r\n item.data_num = \"\".concat(\r\n item.data_count, '/', att_count\r\n )\r\n item.data_num_status =\r\n item.data_count === att_count\r\n ? 1\r\n : 0\r\n item.stu_title = item.project_name.concat(\r\n ' :: ', item.date, ' ', item.period_name,\r\n ' :: ', item.location_name,\r\n ' :: ', item.teacher_name ? item.teacher_name : '无教师认领'\r\n )\r\n})\r\n\r\nreturn {\r\n ...payload\r\n}",
requestAdaptor:
"if (api.query.semesterSelect) {\r\n api.url = api.url.replace(/&semesterSelect=\\d*/g, '')\r\n api.url = api.url.replace(/(?<=semester_id=eq\\.)\\d*(?=&)/g, api.query.semesterSelect)\r\n} else {\r\n api.url = api.url.replace(/&semesterSelect=/g, '')\r\n}\r\nif (api.body.course_id === '') {\r\n api.url = api.url.replace(/&course_id=/g, '')\r\n}\r\nif (api.body.project_id === '') {\r\n api.url = api.url.replace(/&project_id=/g, '')\r\n}\r\nif (api.body.date === '') {\r\n api.url = api.url.replace(/&date=/g, '')\r\n}\r\nif (api.body.location_id === '') {\r\n api.url = api.url.replace(/&location_id=/g, '')\r\n}\r\nif (api.body.teacher_id === '') {\r\n api.url = api.url.replace(/&teacher_id=/g, '')\r\n}\r\nif (api.body.no_tch === 0) {\r\n api.url = api.url.replace(/&no_tch=0/g, '')\r\n}\r\nif (api.body.public === 0) {\r\n api.url = api.url.replace(/&public=0/g, '')\r\n}\r\nconst pattern = /(?:date=)(\\d+-\\d+-\\d+)%2C(\\d+-\\d+-\\d+)/g\r\nconst result = pattern.exec(api.url)\r\nif (result && result[1] === result[2]) {\r\n api.url = api.url.replace(result[0], 'date=eq.'.concat(result[1]))\r\n} else if (result) {\r\n api.url = api.url.replace(result[0], 'date=gte.'.concat(result[1], '&date=lte.', result[2]))\r\n}\r\nconst is_no_tch = /no_tch=1/g.test(api.url)\r\nconst has_teacher = /teacher_id=.+?(?=&)/g.test(api.url)\r\nif (is_no_tch && has_teacher) {\r\n const teacher = /teacher_id=.+?(?=&)/g.exec(api.url)\r\n api.url = api.url.replace(teacher, 'or=('.concat(teacher, ',teacher_id.is.null)')).replace('teacher_id=', 'teacher_id.')\r\n api.url = api.url.replace(/no_tch=1/g, '')\r\n} else if (is_no_tch && !has_teacher) {\r\n api.url = api.url.replace(/no_tch=1/g, 'teacher_id=is.null')\r\n}\r\nconst is_public = /public=1/g.test(api.url)\r\nconst has_course = /course_id=.+?(?=&)/g.test(api.url)\r\nif (is_public && has_course) {\r\n const course = /course_id=.+?(?=&)/g.exec(api.url)\r\n api.url = api.url.replace(course, 'or=('.concat(course, ',course_id.is.null)')).replace('course_id=', 'course_id.')\r\n api.url = api.url.replace(/public=1/g, '')\r\n} else if (is_public && !has_course) {\r\n api.url = api.url.replace(/public=1/g, 'course_id=is.null')\r\n}\r\nreturn {\r\n ...api\r\n}",
},
columns: [
{
type: 'tpl',
label: '项目',
tpl: '<% if(!this.course_id) { %>\n <span class="label label-info">公共</span><span> <%= this.project_name %> <span>\n<% } else {%>\n <span> <%= this.project_name %> <span>\n<% } %>',
inline: false,
placeholder: '-',
id: 'u:a41eff5b6b05',
},
{
name: 'day',
label: '日期',
type: 'text',
placeholder: '-',
id: 'u:d4c925b1426d',
},
{
type: 'text',
label: '节次',
name: 'period_name',
placeholder: '-',
id: 'u:97591d821213',
},
{
type: 'text',
label: '地点',
name: 'location_name',
placeholder: '-',
id: 'u:d15f6350ec5c',
},
{
type: 'text',
label: '教师',
name: 'teacher_name',
placeholder: '-',
id: 'u:bb4bb20fe052',
},
{
type: 'text',
label: '考勤数',
placeholder: '-',
name: 'att_num',
id: 'u:d25f22581135',
},
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
label: '查看学生',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '学生信息',
body: [
{
type: 'crud',
api: {
method: 'get',
url: 'rest/user2projects?select=*,att_status_text:user2project_att_status_dict_fk(dictvalue),schedule_status:user2project_schedule_status_dict_fk(dictvalue),student:users!user2project_student_id_fkey(*),projects(*)&schedule_id=eq.${id}&schedule_status=neq.canceled&order=id.asc',
data: {},
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map((item,index) => {\r\n return {\r\n ...item,\r\n row_number: index+1\r\n }\r\n })\r\n }\r\n}',
},
columns: [
{
label: '序号',
type: 'plain',
inline: false,
name: 'row_number',
tpl: '',
},
{
name: 'schedule_status.dictvalue',
label: '选课状态',
type: 'plain',
placeholder: '-',
inline: false,
},
{
name: 'student.name',
label: '学生',
type: 'text',
placeholder: '-',
},
{
type: 'text',
label: '学号',
name: 'student.code',
placeholder: '-',
},
{
type: 'text',
label: '考勤状态',
name: 'att_status_text.dictvalue',
placeholder: '-',
groupName: '',
remark: '',
},
{
type: 'operation',
label: '操作',
buttons: [
{
type: 'button',
label: '查看',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '$student.name($student.code)',
body: [
{
type: 'crud',
syncLocation: false,
api: {
method: 'get',
url: 'rest/tasks?select=*,dict_status:dicts!task_status_fkey(*),dict_type:dicts!task_type_fkey(*)&schedule_id=eq.$schedule_id&user_id=eq.$student.id',
messages: {},
requestAdaptor: '',
adaptor: '',
},
columns: [
{
name: 'content',
label: '任务',
type: 'text',
id: 'u:b7848b797bf9',
placeholder: '-',
},
{
name: 'dict_type.dictvalue',
label: '类型',
type: 'text',
id: 'u:f7ed595f5c57',
placeholder: '-',
},
{
type: 'text',
wrapperComponent: '',
id: 'u:80864ebd90a9',
label: '状态',
placeholder: '-',
name: 'dict_status.dictvalue',
},
],
bulkActions: [],
itemActions: [],
id: 'u:3458736acc74',
perPageAvailable: [10],
messages: {},
},
],
size: 'lg',
actions: [],
closeOnEsc: true,
closeOnOutside: false,
showCloseButton: true,
id: 'u:bc2fcc411e2a',
},
size: 'xs',
level: 'primary',
closeOnEsc: true,
},
],
},
],
messages: {},
headerToolbar: [
{
type: 'tpl',
tpl: '${stu_title}',
},
],
syncLocation: false,
affixHeader: false,
bodyClassName: '',
className: '',
perPageAvailable: [10],
name: 'schedule_data',
silentPolling: true,
},
],
size: 'lg',
actions: [],
closeOnEsc: true,
showCloseButton: true,
closeOnOutside: false,
},
level: 'primary',
size: 'xs',
id: 'u:a05b0e0b829a',
},
],
placeholder: '-',
id: 'u:8bd7633bd4c7',
},
],
messages: {},
syncLocation: false,
pageField: 'page',
perPageField: 'perPage',
headerToolbar: [],
footerToolbar: [
{
type: 'pagination',
},
{
type: 'switch-per-page',
},
{
type: 'statistics',
},
],
name: 'manage_loc',
perPageAvailable: [10, 20, 30, 40, 50],
id: 'u:6488ee7bd91a',
},
],
className: 'p-t-none',
id: 'u:66531bc383d6',
},
{
title: '学生一览',
body: [
{
type: 'grid',
columns: [
{
md: 3,
body: [
{
type: 'form',
reload:
'stu?parent=${tree}&tree=${tree}&org_name=${name}&type=${type}&filter=${filter}',
data: {
parentId: 1,
},
wrapWithPanel: false,
title: '表单',
submitOnChange: true,
target: '',
resetAfterSubmit: false,
initApi: {
method: 'get',
url: 'rest/orgs?type=eq.school',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n tree: payload.data.id\r\n }\r\n}',
headers: {
Accept: 'application/vnd.pgrst.object+json',
},
},
body: [
{
type: 'service',
name: 'service',
messages: {},
api: {
method: 'get',
url: 'rest/orgs/${tree}',
sendOn: 'this.tree',
},
body: [
{
type: 'input-text',
label: '部门类型',
name: 'type',
visibleOn: 'false',
},
{
type: 'input-text',
label: '部门名称',
name: 'name',
visibleOn: 'false',
},
{
type: 'input-text',
label: '部门Path',
name: 'path',
visibleOn: 'false',
value: 'root',
},
{
type: 'input-text',
label: '过滤条件',
name: 'filter',
visibleOn: 'false',
},
{
type: 'formula',
name: 'filter',
formula:
"'org_path.cd.' + data.path + '.' + data.tree + ',org_id.eq.' + data.tree",
condition: 'data.tree',
},
{
type: 'formula',
name: 'filter',
formula: "'org_path.cd.root'",
condition: '!data.tree',
},
],
},
{
type: 'input-tree',
label: '',
name: 'tree',
inputClassName: 'partment-height',
creatable: false,
removable: false,
editable: false,
deleteApi: 'rest/orgs/$id',
rootCreatable: false,
unfoldedLevel: 1,
initiallyOpen: false,
virtualThreshold: false,
source: {
method: 'get',
url: 'rest/orgs?select=*&path=cd.root&order=code',
adaptor:
"const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet school = payload.data.items.find(x => x.type === 'school');\r\nreturn {\r\n ...payload,\r\n data: [{\r\n label: school.name,\r\n value: `${school.id}`,\r\n children: recursive(school.id)\r\n }]\r\n}",
},
},
],
},
],
},
{
md: 9,
body: [
{
name: 'header',
type: 'wrapper',
body: [
{
type: 'service',
name: 'service',
messages: {},
api: {
method: 'get',
url: 'rest/orgs/${tree}',
sendOn: 'this.tree',
adaptor:
"let role_items = [];\r\nif(payload.data.type === '')\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n role_items: payload.data.\r\n }\r\n}",
},
body: [
{
type: 'input-text',
label: '部门类型',
name: 'type',
visibleOn: 'false',
},
{
type: 'input-text',
label: '部门名称',
name: 'name',
visibleOn: 'false',
},
],
},
{
type: 'crud',
name: 'stu',
api: {
method: 'get',
url: 'rest/users_ex?and=(or(${filter}),or(code.like.*${keywords}*,name.like.*${keywords}*),role.eq.student)&org_type=eq.class&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n stu_id: item.id\r\n }\r\n })\r\n }\r\n}',
data: {
page: '$page',
perPage: '${perPage}',
},
requestAdaptor:
'api.url = api.url.replace(/and=([^&]*?)&/g, (match, p1) => {\r\n let encodeAnd = window.btoa(p1);\r\n\treturn `encodeAnd=${encodeAnd}&`;\r\n});',
sendOn: 'this.filter',
},
columns: [
{
name: 'code',
type: 'text',
label: '账号',
placeholder: '-',
},
{
type: 'text',
label: '姓名',
name: 'name',
placeholder: '-',
},
{
type: 'text',
label: '部门',
name: 'org_name',
placeholder: '-',
},
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
label: '查看场次',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '场次信息',
body: [
{
type: 'crud',
api: {
method: 'get',
url: 'rest/user2projects?select=*,courses(id,name),projects(id,code,name,free_schedule),att_status_text:user2project_att_status_dict_fk(dictvalue),schedule_status:user2project_schedule_status_dict_fk(dictvalue)&user_id=eq.$id&schedule_status=neq.canceled&semester_id=eq.$currentSemester&order=created_at.asc',
data: {
page: '$page',
perPage: '$perPage',
},
},
columns: [
{
name: 'schedule_status.dictvalue',
label: '选课状态',
type: 'plain',
placeholder: '-',
inline: false,
},
{
name: 'courses.name',
label: '课程',
type: 'text',
placeholder: '-',
},
{
type: 'tpl',
label: '项目',
name: 'projects.name',
placeholder: '-',
tpl: '<% if(this.projects && this.projects.free_schedule) { %>\n <span class="label label-info">自由安排</span><span><%= this.projects.name %></span>\n<% } else if(this.projects) { %>\n <span><%= this.projects.name %></span>\n<% } %>',
inline: false,
},
{
type: 'text',
label: '考勤状态',
name: 'att_status_text.dictvalue',
placeholder: '-',
groupName: '',
remark: '',
},
{
type: 'operation',
label: '操作',
buttons: [
{
type: 'button',
label: '查看',
actionType: 'dialog',
dialog: {
type: 'dialog',
body: [
{
type: 'crud',
syncLocation: false,
api: {
method: 'get',
url: 'rest/tasks?select=*,dict_status:dicts!task_status_fkey(*),dict_type:dicts!task_type_fkey(*)&schedule_id=eq.$schedule_id&user_id=eq.$user_id',
messages: {},
requestAdaptor: '',
adaptor: '',
},
columns: [
{
name: 'content',
label: '任务',
type: 'text',
id: 'u:b7848b797bf9',
placeholder: '-',
},
{
name: 'dict_type.dictvalue',
label: '类型',
type: 'text',
id: 'u:f7ed595f5c57',
placeholder: '-',
},
{
type: 'text',
wrapperComponent: '',
id: 'u:80864ebd90a9',
label: '状态',
placeholder: '-',
name: 'dict_status.dictvalue',
},
],
bulkActions: [],
itemActions: [],
id: 'u:3458736acc74',
perPageAvailable: [10],
messages: {},
},
],
size: 'lg',
actions: [],
closeOnEsc: true,
closeOnOutside: false,
showCloseButton: true,
id: 'u:bc2fcc411e2a',
},
size: 'xs',
level: 'primary',
closeOnEsc: true,
},
],
},
],
messages: {},
syncLocation: false,
affixHeader: false,
bodyClassName: '',
className: '',
perPageAvailable: [10],
},
],
actions: [],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
},
level: 'primary',
size: 'xs',
},
],
placeholder: '-',
},
],
messages: {},
className: '',
headerToolbar: [],
md: 9,
syncLocation: false,
footerToolbar: [
{
type: 'form',
wrapWithPanel: false,
reload: 'stu?perPage=${perPage}',
submitOnChange: true,
submitOnInit: false,
body: [
{
type: 'select',
name: 'perPage',
value: '10',
options: [
{
label: '默认',
value: '17',
},
{
label: '10',
value: '10',
},
{
label: '20',
value: '20',
},
{
label: '50',
value: '50',
},
{
label: '100',
value: '100',
},
{
label: '500',
value: '500',
},
],
label: '每页显示',
mode: 'inline',
checkAll: false,
className: 'm-t-sm',
},
],
},
{
type: 'pagination',
},
],
perPageAvailable: '',
columnsTogglable: 'auto',
filter: {
title: '',
affixFooter: false,
actions: [],
body: [
{
type: 'input-group',
name: 'keywords',
label: '学号或姓名 ',
body: [
{
type: 'input-text',
placeholder: '',
inputClassName: 'b-r-none p-r-none',
name: 'keywords',
size: 'md',
clearable: true,
value: '',
},
{
type: 'control',
label: '',
body: [
{
type: 'submit',
level: 'primary',
actionType: 'submit',
label: '搜索',
},
],
},
],
},
],
},
bulkActions: [],
},
],
size: 'xs',
},
],
},
],
className: 'partment-grid',
},
],
},
],
tabsMode: 'line',
tabClassName: '',
id: 'u:4f5cf5424791',
},
],
messages: {},
bodyClassName: '',
title: '',
id: 'u:976cfa59becd',
};
export { schema };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
const schema = {
body: [
{
type: 'form',
api: {
method: 'patch',
url: 'rest/config?key=eq.evaluation&organization_id=eq.$centre_id',
data: {
evaluationInstruStatus: '$evaluationInstruStatus',
evaluationInstru: '$evaluationInstru',
evaluationIntroStatus: '$evaluationIntroStatus',
evaluationIntro: '$evaluationIntro',
},
dataType: 'json',
requestAdaptor:
'return {\r\n ...api,\r\n data: {\r\n value: api.data\r\n }\r\n}',
},
name: 'form',
title: '',
wrapWithPanel: true,
initApi: {
method: 'get',
url: 'rest/config?key=eq.evaluation&organization_id=eq.$centre_id',
adaptor:
'let item = payload.data.items[0]\r\nreturn {\r\n data: {\r\n ...item.value\r\n }\r\n}',
},
body: [
{
type: 'fieldset',
title: '评教指引及评教说明配置',
collapsable: true,
body: [
{
type: 'switch',
name: 'evaluationInstruStatus',
value: true,
option: '评教指引',
optionAtLeft: false,
trueValue: true,
falseValue: false,
onText: '开启',
offText: '关闭',
},
{
type: 'input-rich-text',
label: '',
name: 'evaluationInstru',
mode: 'normal',
visibleOn: '',
clearValueOnHidden: false,
},
{
type: 'switch',
option: '评教说明',
name: 'evaluationIntroStatus',
optionAtLeft: false,
trueValue: true,
falseValue: false,
value: true,
onText: '开启',
offText: '关闭',
},
{
type: 'input-rich-text',
name: 'evaluationIntro',
label: '',
mode: 'normal',
visibleOn: '',
clearValueOnHidden: false,
},
],
},
],
submitText: '保存',
submitOnChange: false,
submitOnInit: false,
reload: '',
affixFooter: false,
},
],
type: 'page',
messages: {},
title: '评教参数设置',
toolbar: [
{ type: 'submit', label: '保存', level: 'primary', target: 'form' },
],
bodyClassName: '',
style: {},
subTitle: '设置评教指引、评教说明等参数',
};
export { schema };

View File

@@ -0,0 +1,789 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
name: 'eval',
id: 'u:1eda9b4880ff',
columns: [
{
type: 'text',
name: 'parent_name',
label: '一级指标',
id: 'u:6dbfe25ccbea',
placeholder: '-',
quickEdit: {
mode: 'popOver',
saveImmediately: {
api: {
method: 'patch',
url: 'rest/teaching_evaluation_items/$id',
data: {
name: '$parent_name',
},
},
},
type: 'input-text',
name: 'parent_name',
size: 'lg',
},
quickEditEnabledOn: '!this.parent',
},
{
name: 'name',
type: 'text',
label: '二级指标',
id: 'u:a42f65cf3947',
placeholder: '-',
quickEdit: {
mode: 'popOver',
type: 'input-text',
label: '',
name: 'name',
saveImmediately: {
api: {
method: 'patch',
url: 'rest/teaching_evaluation_items/$id',
data: {
name: '$name',
},
},
},
size: 'lg',
},
quickEditEnabledOn: 'this.parent',
},
{
name: 'order',
label: '顺序',
type: 'tpl',
id: 'u:a617718a149f',
placeholder: '-',
tpl: '${order}',
inline: true,
quickEdit: {
type: 'input-number',
name: 'order',
label: '数字',
min: '1',
step: 1,
value: 1,
saveImmediately: {
api: {
method: 'patch',
url: 'rest/teaching_evaluation_items?id=eq.${id}',
data: {
order: '$order',
},
dataType: 'form',
},
},
mode: 'popOver',
},
},
{
type: 'text',
label: '权重',
name: 'weight',
id: 'u:956ac49cc933',
placeholder: '-',
quickEdit: {
type: 'input-number',
name: 'weight',
label: '',
step: 0.5,
min: '0',
saveImmediately: {
api: {
method: 'patch',
url: 'rest/teaching_evaluation_items/$id',
data: {
weight: '$weight',
},
},
},
mode: 'popOver',
},
},
{
type: 'operation',
label: '操作',
id: 'u:6a7475988e72',
buttons: [
{
type: 'button-group',
buttons: [
{
type: 'button',
label: '',
visibleOn: '!this.parent',
icon: 'fa fa-plus text-primary',
level: 'link',
size: 'md',
tooltip: '添加二级指标',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:e0d17fb5cf3e',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加二级指标',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/teaching_evaluation_items',
data: {
name: '$name',
weight: '$weight',
organization_id: '$centre_id',
semester_id: '$currentSemester',
parent: '$id',
},
},
body: [
{
type: 'input-text',
label: '名称',
name: 'name',
mode: 'normal',
required: true,
id: 'u:6ba0f199321c',
},
{
type: 'input-number',
name: 'weight',
label: '权重',
mode: 'normal',
min: '0',
step: 0.5,
required: true,
id: 'u:fa0cff8dbbc5',
},
],
id: 'u:a7a2df747e8d',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:1eda9b4880ff',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
id: 'u:18243755deb7',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:188cf1c021ad',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:c83910750b73',
},
],
},
},
],
},
},
},
{
type: 'button',
level: 'link',
size: 'md',
icon: 'fa fa-pencil text-info',
visibleOn: '!this.parent',
tooltip: '修改一级指标',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:e2ca6634c8a6',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '修改一级指标',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/teaching_evaluation_items/$id',
data: {
name: '$parent_name',
weight: '$weight',
},
dataType: 'json',
},
body: [
{
label: '名称',
type: 'input-text',
name: 'parent_name',
mode: 'normal',
required: true,
id: 'u:e694855bd5a0',
},
{
type: 'input-number',
label: '权重',
name: 'weight',
mode: 'normal',
min: '0',
step: 0.5,
required: true,
id: 'u:6ef32f0c3c51',
},
],
id: 'u:0045f9b77be4',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:1eda9b4880ff',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
id: 'u:3e72890a4b8e',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:7472c80f7c68',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:d860e25841e9',
},
],
},
},
],
},
},
},
{
type: 'button',
confirmText: '确认删除"${parent_name}"',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除一级指标',
tooltipPlacement: 'top',
reload: '',
visibleOn: '!this.parent && !this.children.length',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:967e36c71712',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/teaching_evaluation_items/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:1eda9b4880ff',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
{
type: 'button',
level: 'link',
size: 'md',
icon: 'fa fa-pencil text-info',
visibleOn: 'this.parent',
tooltip: '修改二级指标',
tooltipPlacement: 'top',
className: 'p-r-none p-l-none',
iconClassName: 'pull-left',
id: 'u:8290e35c33ea',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '修改二级指标',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/teaching_evaluation_items/$id',
data: {
name: '$name',
weight: '$weight',
parent: '$parent',
},
dataType: 'json',
},
body: [
{
label: '名称',
type: 'input-text',
name: 'name',
mode: 'normal',
required: true,
id: 'u:c2480af665ff',
},
{
type: 'input-number',
label: '权重',
name: 'weight',
mode: 'normal',
min: '0',
step: 0.5,
required: true,
id: 'u:b401731a558d',
},
{
type: 'select',
label: '一级指标',
name: 'parent',
mode: 'normal',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/teaching_evaluation_items?parent=is.null&organization_id=eq.$centre_id&semester_id=eq.$currentSemester&order=order',
adaptor:
'return {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n}',
},
searchable: true,
required: true,
id: 'u:d1ed468a2aa3',
},
],
id: 'u:d0af5e81cfa8',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:1eda9b4880ff',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
id: 'u:44a6ba40d2f2',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:05a728e1cf94',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:8bed7ea1617c',
},
],
},
},
],
},
},
},
{
type: 'button',
confirmText: '确认删除"${name}"',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除二级指标',
tooltipPlacement: 'top',
reload: '',
visibleOn: 'this.parent',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:46e8dd7835fd',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/teaching_evaluation_items/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:1eda9b4880ff',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
label: '操作',
id: 'u:ae8cc0b046b6',
},
],
},
],
messages: {},
api: {
method: 'get',
url: 'rest/teaching_evaluation_items?semester_id=eq.$currentSemester&organization_id=eq.$centre_id&order=order',
data: null,
requestAdaptor: '',
adaptor:
'let evaluations = []\r\nlet parents = payload.data.items.filter(item => !item.parent)\r\n\r\nparents.length && parents.forEach((parent, index) => {\r\n evaluations.push({\r\n id: parent.id,\r\n order: parent.order,\r\n parent_name: parent.name,\r\n weight: parent.weight,\r\n parent: parent.parent,\r\n children: payload.data.items\r\n .filter(item => item.parent === parent.id)\r\n })\r\n})\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: evaluations\r\n }\r\n}',
sendOn: '',
},
syncLocation: false,
headerToolbar: [
{
type: 'button',
align: 'right',
icon: '',
level: 'primary',
label: '添加一级指标',
id: 'u:37f0878b29a0',
reload: '',
tpl: '内容',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加一级指标',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/teaching_evaluation_items',
data: {
name: '$name',
weight: '$weight',
organization_id: '$centre_id',
semester_id: '$currentSemester',
},
},
body: [
{
type: 'input-text',
label: '名称',
name: 'name',
required: true,
mode: 'normal',
id: 'u:53e7e8e5c2f6',
},
{
type: 'input-number',
name: 'weight',
label: '权重',
mode: 'normal',
required: true,
min: '0',
step: 0.5,
id: 'u:a85fdbfc8915',
},
],
id: 'u:4f356ae00643',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:1eda9b4880ff',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
id: 'u:bdb5abe2835a',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:0b55901a86eb',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:6dbf2b39f297',
},
],
},
},
],
},
},
},
{
type: 'button',
label: '指定学期导入',
align: 'right',
level: 'primary',
visibleOn: '',
className: '',
id: 'u:595e4f3e93ab',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '指定学期',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/rpc/create_evaluation_items_from_history',
data: {
organization_id: '$centre_id',
semester_id: '$semester_id',
},
},
target: '',
name: 'form',
actions: [
{
actionType: 'reload',
target: 'recognize',
},
],
body: [
{
label: '学期',
type: 'select',
name: 'semester_id',
mode: 'inline',
options: [],
source: {
method: 'get',
url: 'rest/semesters?is_open=is.false&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
requestAdaptor: '',
},
size: 'md',
inputClassName: 'm-l-xs',
checkAll: false,
clearable: false,
searchable: true,
id: 'u:7ee00833ea34',
},
],
id: 'u:551a790ac5aa',
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:1eda9b4880ff',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'sm',
id: 'u:8d46598e4801',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:39cbcc618226',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:cab0fce5d690',
},
],
},
},
],
},
},
},
],
footerToolbar: [],
perPageAvailable: [10, 20, 30, 40, 50],
filter: null,
bulkActions: [],
className: '',
md: 9,
columnsTogglable: false,
quickSaveItemApi: '',
hideQuickSaveBtn: true,
visibleOn: '',
perPage: null,
loadDataOnce: true,
},
],
id: 'u:23e12f269e33',
messages: {},
title: '',
bodyClassName: '',
};
export { schema };

View File

@@ -0,0 +1,690 @@
const schema = {
body: [
{
type: 'tabs',
className: '',
tabs: [
{
title: '日程一览',
body: [
{
type: 'form',
title: '表单',
className: '',
submitOnChange: true,
submitOnInit: true,
reload:
'manage_loc?course_id=$course_id&project_id=$project_id&teacher_id=$teacher_id&location_id=$location_id&date=$date&no_tch=$no_tch&public=$public&semesterSelect=$semesterSelect',
initApi: {
method: 'get',
url: 'rest/users?role=eq.teacher&id=eq.${user.id}&order=code',
requestAdaptor: '',
adaptor:
"if (payload.data.items.length !== 0) {\r\n return {\r\n teacher_id: 'eq.'.concat(payload.data.items[0].id)\r\n }\r\n} else {\r\n return {}\r\n}",
},
wrapWithPanel: false,
mode: 'inline',
name: 'filter',
body: [
{
type: 'group',
body: [
{
type: 'select',
label: '历史',
name: 'semesterSelect',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
},
searchable: true,
clearable: false,
mode: 'inline',
size: 'md',
inputClassName: 'm-l-xs',
id: 'u:a4b2e9a131fa',
},
{
label: '课程',
type: 'select',
name: 'course_id',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
id: 'u:22532e0d2a66',
},
{
type: 'select',
label: '实验',
name: 'project_id',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/projects?select=*,course2projects(*)&organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&course2projects.course_id=$course_id&order=code',
adaptor:
"return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.filter(\r\n f => f.course2projects.length > 0\r\n ).map(item => {\r\n return {\r\n label: item.name,\r\n value: 'eq.'.concat(item.id)\r\n }\r\n })\r\n }\r\n}",
requestAdaptor:
'if (api.query.course2projects.course_id === \'\') {\r\n api.url = api.url.replace("&course2projects[course_id]=", "")\r\n}\r\n\r\nreturn api',
},
checkAll: false,
size: 'md',
searchable: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
id: 'u:cf571eb6cc1a',
},
],
id: 'u:0cf1ac6473cc',
},
{
type: 'group',
body: [
{
type: 'input-date-range',
label: '日期',
name: 'date',
mode: 'inline',
clearable: true,
value: '',
format: 'YYYY-MM-DD',
ranges:
'yesterday,today,7daysago,prevweek,thismonth,prevmonth,prevquarter',
inputClassName: 'm-l-xs',
id: 'u:b78bf613761a',
},
{
type: 'select',
label: '地点',
name: 'location_id',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/locations?organization_id=eq.$centre_id&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
id: 'u:d90502be2317',
},
{
label: '教师',
type: 'select',
name: 'teacher_id',
mode: 'inline',
clearable: true,
checkAll: false,
source: {
method: 'get',
url: 'rest/users?role=eq.teacher&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
id: 'u:2fd7d9613116',
},
],
id: 'u:9df1f6742c55',
},
{
type: 'group',
body: [
{
type: 'switch',
name: 'no_tch',
option: '仅显示无教师场次',
mode: 'inline',
optionAtLeft: false,
trueValue: 1,
falseValue: 0,
value: 0,
disabledOn: 'this.teacher_id',
id: 'u:16519e5c1c12',
},
{
type: 'switch',
name: 'public',
option: '仅显示公共场次',
mode: 'inline',
optionAtLeft: false,
trueValue: 1,
falseValue: 0,
value: 0,
disabledOn: 'this.course_id',
inputClassName: 'm-l-sm',
id: 'u:0d1445244f47',
},
],
id: 'u:6add1eda4c2a',
},
],
id: 'u:42c847232809',
},
{ type: 'divider', lineStyle: 'solid', id: 'u:3eb06bbf6fa8' },
{
type: 'crud',
api: {
method: 'get',
url: 'rest/teaching_evaluation_stats?select=*?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&is_publish=is.true&order=project_name,date,teacher_name',
data: { '&': '$$', page: '$page', perPage: '$perPage' },
requestAdaptor:
"if (api.query.semesterSelect) {\r\n api.url = api.url.replace(/&semesterSelect=\\d*/g, '')\r\n api.url = api.url.replace(/(?<=semester_id=eq\\.)\\d*(?=&)/g, api.query.semesterSelect)\r\n} else {\r\n api.url = api.url.replace(/&semesterSelect=/g, '')\r\n}\r\nif (api.body.course_id === '') {\r\n api.url = api.url.replace(/&course_id=/g, '')\r\n}\r\nif (api.body.project_id === '') {\r\n api.url = api.url.replace(/&project_id=/g, '')\r\n}\r\nif (api.body.date === '') {\r\n api.url = api.url.replace(/&date=/g, '')\r\n}\r\nif (api.body.location_id === '') {\r\n api.url = api.url.replace(/&location_id=/g, '')\r\n}\r\nif (api.body.teacher_id === '') {\r\n api.url = api.url.replace(/&teacher_id=/g, '')\r\n}\r\nif (api.body.no_tch === 0) {\r\n api.url = api.url.replace(/&no_tch=0/g, '')\r\n}\r\nif (api.body.public === 0) {\r\n api.url = api.url.replace(/&public=0/g, '')\r\n}\r\nconst pattern = /(?:date=)(\\d+-\\d+-\\d+)%2C(\\d+-\\d+-\\d+)/g\r\nconst result = pattern.exec(api.url)\r\nif (result && result[1] === result[2]) {\r\n api.url = api.url.replace(result[0], 'date=eq.'.concat(result[1]))\r\n} else if (result) {\r\n api.url = api.url.replace(result[0], 'date=gte.'.concat(result[1], '&date=lte.', result[2]))\r\n}\r\nconst is_no_tch = /no_tch=1/g.test(api.url)\r\nconst has_teacher = /teacher_id=.+?(?=&)/g.test(api.url)\r\nif (is_no_tch && has_teacher) {\r\n const teacher = /teacher_id=.+?(?=&)/g.exec(api.url)\r\n api.url = api.url.replace(teacher, 'or=('.concat(teacher, ',teacher_id.is.null)')).replace('teacher_id=', 'teacher_id.')\r\n api.url = api.url.replace(/no_tch=1/g, '')\r\n} else if (is_no_tch && !has_teacher) {\r\n api.url = api.url.replace(/no_tch=1/g, 'teacher_id=is.null')\r\n}\r\nconst is_public = /public=1/g.test(api.url)\r\nconst has_course = /course_id=.+?(?=&)/g.test(api.url)\r\nif (is_public && has_course) {\r\n const course = /course_id=.+?(?=&)/g.exec(api.url)\r\n api.url = api.url.replace(course, 'or=('.concat(course, ',course_id.is.null)')).replace('course_id=', 'course_id.')\r\n api.url = api.url.replace(/public=1/g, '')\r\n} else if (is_public && !has_course) {\r\n api.url = api.url.replace(/public=1/g, 'course_id=is.null')\r\n}\r\nreturn {\r\n ...api\r\n}",
adaptor:
"let supportDateTimeFormat = typeof(Intl.DateTimeFormat) === 'function';\r\npayload.data.items.forEach(item => {\r\n item.day = supportDateTimeFormat ? `${item.date}${new Intl.DateTimeFormat('zh-CN', { weekday: 'short'}).format(new Date(item.date))}` : item.date;\r\n item.stu_title = item.project_name.concat(\r\n ' :: ', item.date, ' ', item.period_name,\r\n ' :: ', item.location_name,\r\n ' :: ', item.teacher_name ? item.teacher_name : '无教师认领'\r\n )\r\n})\r\n\r\nreturn {\r\n ...payload\r\n}",
},
columns: [
{
label: '实验',
type: 'tpl',
placeholder: '-',
tpl: '<% if(!this.course_id) { %>\n <span class="label label-info">公共</span><span> <%= this.project_name %> <span>\n<% } else {%>\n <span> <%= this.project_name %> <span>\n<% } %>',
inline: false,
id: 'u:821411c19e45',
},
{
name: 'day',
label: '日期',
type: 'text',
placeholder: '-',
id: 'u:65690067149b',
},
{
type: 'text',
label: '节次',
name: 'period_name',
placeholder: '-',
id: 'u:2aed39988a12',
},
{
type: 'text',
label: '地点',
name: 'location_name',
placeholder: '-',
id: 'u:9298ea2c6eb8',
},
{
type: 'tpl',
tpl: '${teacher_name}',
inline: false,
label: '教师姓名',
name: 'teacher_name',
placeholder: '-',
id: 'u:1b5d4fe03c4f',
},
{
type: 'tpl',
tpl: '${teacher_code}',
inline: false,
label: '教师工号',
name: 'teacher_code',
placeholder: '-',
id: 'u:01a76262c44b',
},
{
type: 'tpl',
tpl: '<% if (data.participation_count<1) { %> \n<span>暂无</span>\n<% } else { %>\n<%= data.evaluation_rating %>\n<% } %>',
inline: false,
label: '统计总分',
name: 'evaluation_rating',
placeholder: '-',
id: 'u:01b6021e08ff',
},
{
type: 'tpl',
tpl: '${participation_count}',
inline: false,
label: '学生评价人数',
name: 'participation_count',
placeholder: '-',
id: 'u:fb9b688b6471',
},
{
type: 'tpl',
tpl: '${current_student_number}',
inline: false,
label: '总选课人数',
name: 'current_student_number',
placeholder: '-',
id: 'u:10215551a08f',
},
{
type: 'tpl',
tpl: '${participation_rating}',
inline: false,
label: '参评率',
name: 'participation_rating',
id: 'u:c3abec775099',
},
{
type: 'operation',
label: '操作',
buttons: [
{
label: '评教明细',
type: 'button',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '评教明细',
body: [
{
type: 'grid',
columns: [
{
xs: 5,
body: [
{
type: 'form',
title: '表单',
wrapWithPanel: false,
submitOnChange: true,
reload:
'evaluation?selected_user=$selected_user',
name: 'filter',
debug: false,
body: [
{
type: 'switch',
name: 'open',
option: '显示姓名',
optionAtLeft: false,
trueValue: true,
falseValue: false,
value: false,
mode: 'normal',
submitOnChange: false,
id: 'u:ae62dc94f85c',
},
{
label: '',
type: 'input-tree',
name: 'selected_user',
mode: 'inline',
size: 'lg',
source: {
method: 'get',
url: 'rest/user2projects?select=*,users:users!user2project_student_id_fkey(*)&schedule_id=eq.$schedule_id&schedule_status=eq.elected&open=$open',
adaptor:
"return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map((item, index) => {\r\n let temp = null\r\n\r\n if ((index + 1).toString().length === 1) {\r\n temp = '00' + (index + 1)\r\n } else if ((index + 1).toString().length === 2) {\r\n temp = '0' + (index + 1)\r\n } else {\r\n temp = '' + (index + 1)\r\n }\r\n\r\n return {\r\n label: item.users.name\r\n ? '' + temp + ' - 学号: ' + item.users.code + ' - 姓名: ' + item.users.name\r\n : '' + temp + ' - 学号: xxx - 姓名: xxx',\r\n value: item.users.id\r\n }\r\n })\r\n }\r\n}",
data: null,
requestAdaptor:
"if (api.query.open) {\r\n api.url = api.url.replace('&open=true', '')\r\n} else {\r\n api.url = api.url.replace('&open=false', '')\r\n api.url = api.url.replace('%2Cusers%3Ausers%21user2project_student_id_fkey%28%2A%29', '%2Cusers%3Ausers%21user2project_student_id_fkey%28id%29')\r\n}",
sendOn: '',
},
inputClassName: 'evaluation-height',
className: '',
labelClassName: '',
id: 'u:2234b8e979fc',
multiple: false,
enableNodePath: false,
hideRoot: true,
showIcon: true,
initiallyOpen: true,
virtualThreshold: false,
},
],
id: 'u:f15b97a9e90e',
},
],
md: 4,
id: 'u:f5120659ae2f',
},
{
body: [
{
type: 'crud',
api: {
method: 'get',
url: 'rest/user2projects?select=*,teaching_evaluations(*,teaching_evaluation_items(*)),teaching_evaluation_message(content)&schedule_id=eq.$schedule_id&user_id=eq.$selected_user&schedule_status=eq.elected',
sendOn: 'this.selected_user',
adaptor:
'let evaluations = []\r\n\r\npayload.data.items[0].teaching_evaluations\r\n .filter(item => !item.teaching_evaluation_items.parent)\r\n .sort((a, b) => {\r\n return a.teaching_evaluation_items.order - b.teaching_evaluation_items.order\r\n })\r\n .forEach(parent => {\r\n evaluations.push({\r\n ...parent,\r\n parent_name: parent.teaching_evaluation_items.name\r\n })\r\n payload.data.items[0].teaching_evaluations\r\n .filter(item => item.teaching_evaluation_items.parent\r\n === parent.teaching_evaluation_items.id)\r\n .sort((a, b) => {\r\n return a.teaching_evaluation_items.order - b.teaching_evaluation_items.order\r\n })\r\n .forEach(item => evaluations.push({\r\n ...item,\r\n child_name: item.teaching_evaluation_items.name\r\n }))\r\n })\r\n\r\nconsole.log(payload.data.items[0].teaching_evaluation_message[0]);return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n content:payload.data.items[0].teaching_evaluation_message[0]?payload.data.items[0].teaching_evaluation_message[0].content:null, items: evaluations\r\n }\r\n}\r\n',
requestAdaptor: '',
},
columns: [
{
name: 'parent_name',
label: '一级指标',
type: 'text',
id: 'u:b0fbb3e1005c',
},
{
name: 'child_name',
label: '二级指标',
type: 'text',
id: 'u:cf4a41c690ef',
},
{
name: 'evaluation_rating',
label: '评分',
type: 'text',
id: 'u:15f856dabafe',
},
],
bulkActions: [],
itemActions: [],
perPageAvailable: [10],
messages: {},
headerToolbar: [
{
type: 'tpl',
tpl: '${stu_title}',
id: 'u:6ee9e9f6eea1',
},
],
footerToolbar: [
{
type: 'tpl',
tpl: '评语:${content}',
id: 'u:6ee9e9f6eea1',
visibleOn: 'this.content'
},
],
syncLocation: false,
name: 'evaluation',
affixHeader: false,
id: 'u:9c49baa041b9',
},
],
md: 8,
id: 'u:65c044dae897',
},
],
id: 'u:9a86c3778456',
},
],
closeOnEsc: true,
showCloseButton: true,
actions: [],
data: null,
size: 'lg',
closeOnOutside: false,
bodyClassName: '',
id: 'u:43821eb65ad8',
},
level: 'primary',
size: 'xs',
id: 'u:bda614e0a3f8',
},
],
id: 'u:2b35aaf1e56c',
},
],
bulkActions: [],
itemActions: [],
syncLocation: false,
headerToolbar: [
{
type: 'export-excel',
label: '导出',
icon: 'fa fa-download',
level: 'primary',
align: 'right',
api: {
method: 'get',
url: 'rest/teaching_evaluation_stats?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&is_publish=is.true&order=project_name,date,teacher_name',
dataType: 'form',
data: { semesterSelect: '$semesterSelect' },
requestAdaptor:
"if (api.query.semesterSelect) {\r\n api.url = api.url.replace(/&semesterSelect=\\d*/g, '')\r\n api.url = api.url.replace(/(?<=semester_id=eq\\.)\\d*(?=&)/g, api.query.semesterSelect)\r\n} else {\r\n api.url = api.url.replace(/&semesterSelect=/g, '')\r\n}\r\n\r\nreturn {\r\n ...api\r\n}",
adaptor:
'let columns = [{\r\n "name": "project_name",\r\n "label": "实验" },\r\n {\r\n "name": "date",\r\n "label": "日期" },\r\n {\r\n "name": "teacher_name",\r\n "label": "教师姓名"\r\n },\r\n {\r\n "name": "teacher_code",\r\n "label": "教师工号"\r\n },\r\n {\r\n "name": "evaluation_rating",\r\n "label": "总计总分"\r\n },\r\n {\r\n "name": "participation_count",\r\n "label": "学生评价人数"\r\n },\r\n {\r\n "name": "current_student_number",\r\n "label": "总选课人数"\r\n },\r\n {\r\n "name": "participation_rating",\r\n "label": "参评率"\r\n }];\r\nreturn {\r\n ...payload,\r\n data: {\r\n items: payload.data.items,\r\n columns: columns\r\n }\r\n}',
},
filename: '评教统计',
id: 'u:1b59142be0d3',
},
],
name: 'manage_loc',
perPageAvailable: [10, 20, 30, 40, 50],
messages: {},
id: 'u:367dac93b9ef',
},
],
className: 'p-t-none',
id: 'u:804603aed3fd',
},
{
title: '自由安排',
body: [
{
type: 'crud',
columns: [
{ type: 'text', label: '编号', name: 'code' },
{ name: 'name', type: 'text', label: '课程' },
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
label: '查看项目',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '项目信息',
body: [
{
type: 'crud',
api: {
method: 'get',
url: 'rest/course2projects?select=*,projects(*)&course_id=eq.$id',
data: { page: '$page', perPage: '$perPage' },
adaptor:
'let free, not_free\r\nfree = payload.data.items.filter(\r\n item => item.projects.free_schedule\r\n ).sort(\r\n (a, b) => a.projects.code.localeCompare(b.projects.code)\r\n )\r\nnot_free = payload.data.items.filter(\r\n item => !item.projects.free_schedule\r\n ).sort(\r\n (a, b) => a.projects.code.localeCompare(b.projects.code)\r\n )\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: not_free.concat(free)\r\n }\r\n}',
},
columns: [
{
name: 'projects.code',
label: '编号',
type: 'text',
placeholder: '-',
},
{
name: 'projects.name',
label: '项目',
type: 'tpl',
placeholder: '-',
tpl: '<% if(this.projects && this.projects.free_schedule) { %>\n <span class="label label-info">自由安排</span><span><%= this.projects.name %></span>\n<% } else if(this.projects) { %>\n <span><%= this.projects.name %></span>\n<% } %>',
inline: false,
},
{
type: 'button-group',
label: '操作',
placeholder: '-',
buttons: [
{
type: 'button',
label: '评教明细',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '评教明细',
body: [
{
type: 'grid',
columns: [
{
name: 'filter',
type: 'form',
title: '表单',
controls: [
{
type: 'switch',
name: 'open',
option: '显示姓名',
optionAtLeft: false,
trueValue: true,
falseValue: false,
value: false,
mode: 'inline',
submitOnChange: false,
},
{
label: '',
type: 'tree',
name: 'selected_user',
options: [],
mode: 'inline',
size: 'md',
source: {
method: 'get',
url: 'rest/user2projects?select=*,users:users!user2project_student_id_fkey(*)&schedule_id=eq.$id&schedule_status=eq.elected&open=$open',
adaptor:
"return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.users.name ? item.users.name : 'xxx',\r\n value: item.users.id\r\n }\r\n })\r\n }\r\n}",
data: null,
requestAdaptor:
"if (api.query.open) {\r\n api.url = api.url.replace('&open=true', '')\r\n} else {\r\n api.url = api.url.replace('&open=false', '')\r\n api.url = api.url.replace('%2Cusers%3Ausers%21user2project_student_id_fkey%28%2A%29', '%2Cusers%3Ausers%21user2project_student_id_fkey%28id%29')\r\n}",
sendOn: '',
},
},
],
wrapWithPanel: false,
submitOnChange: true,
xs: 4,
reload:
'evaluation?selected_user=$selected_user',
debug: false,
},
{
name: 'evaluation',
type: 'crud',
api: {
method: 'get',
url: 'rest/user2projects?select=*,teaching_evaluations(*,teaching_evaluation_items(*)),teaching_evaluation_message(content)&project_id=eq.$id&user_id=eq.$selected_user&schedule_status=eq.free_schedule',
sendOn: 'this.selected_user',
adaptor:
'let evaluations = []\r\n\r\npayload.data.items[0].teaching_evaluations\r\n .filter(item => !item.teaching_evaluation_items.parent)\r\n .forEach(parent => {\r\n evaluations.push({\r\n ...parent,\r\n parent_name: parent.teaching_evaluation_items.name\r\n })\r\n payload.data.items[0].teaching_evaluations\r\n .filter(item => item.teaching_evaluation_items.parent\r\n === parent.teaching_evaluation_items.id)\r\n .forEach(item => evaluations.push({\r\n ...item,\r\n child_name: item.teaching_evaluation_items.name\r\n }))\r\n })\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: evaluations\r\n }\r\n}\r\n',
requestAdaptor: '',
},
columns: [
{
name: 'parent_name',
label: '一级指标',
type: 'text',
},
{
name: 'child_name',
label: '二级指标',
type: 'text',
},
{
name: 'evaluation_rating',
label: '评分',
type: 'text',
},
],
bulkActions: [],
itemActions: [],
perPageAvailable: [10],
messages: {},
syncLocation: false,
},
],
},
],
actions: [],
closeOnEsc: true,
showCloseButton: true,
data: null,
size: 'lg',
},
size: 'xs',
level: 'primary',
visibleOn: 'this.projects.free_schedule',
},
],
},
],
messages: {},
syncLocation: false,
headerToolbar: [],
affixHeader: false,
name: 'project_view',
footerToolbar: [{ type: 'pagination' }],
},
],
size: 'lg',
actions: [],
closeOnEsc: true,
showCloseButton: true,
},
level: 'primary',
size: 'xs',
},
],
},
],
api: {
method: 'get',
url: 'rest/courses?organization_id=eq.$centre_id&semester_id=eq.$semesterSelect&order=code',
data: { page: '$page', perPage: '$perPage' },
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n selected_course_id: item.id\r\n }\r\n })\r\n }\r\n}',
},
messages: {},
syncLocation: false,
pageField: 'page',
perPageField: 'perPage',
},
],
className: 'p-t-none',
},
],
tabsMode: 'line',
tabClassName: '',
id: 'u:264fc2e5b77b',
},
],
type: 'page',
messages: {},
bodyClassName: '',
title: '',
definitions: {
options: {
type: 'combo',
multiple: true,
multiLine: true,
controls: [
{
type: 'group',
controls: [
{ label: '选项', name: 'key', type: 'text' },
{ label: '内容', name: 'label', type: 'text' },
{
type: 'switch',
option: '开关',
name: 'is_fixed',
label: '是否固定',
},
],
label: false,
},
],
},
},
id: 'u:21edf657a725',
};
export { schema };

View File

@@ -0,0 +1,323 @@
const schema = {
body: [
{
type: 'crud',
name: 'custom_tp',
api: {
method: 'get',
url: 'rest/project_process_templates?select=*,exam_paper:exam_paper!project_preparation_exam_exam_paper_id_fk(*)&organization_id=eq.$centre_id&semester_id=eq.${currentSemester}&order=code',
adaptor: '',
data: { page: '$page', perPage: '$perPage' },
requestAdaptor: '',
},
columns: [
{ name: 'code', label: '编号', type: 'text', placeholder: '-' },
{ name: 'name', label: '名称', type: 'text', placeholder: '-' },
{
type: 'text',
label: '考试试卷',
name: 'exam_paper.name',
placeholder: '-',
},
{
type: 'tpl',
inline: false,
name: 'exam_start_fix',
label: '上课多久允许答题',
style: {},
},
{
type: 'text',
label: '持续时间',
placeholder: '-',
name: 'exam_duration',
},
{
type: 'button-group',
buttons: [
{
type: 'button',
label: '设置考试',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '设置实验考试',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/project_preparation_exam',
data: {
name: '$name',
exam_paper_id: '$exam_paper_id',
type: '$type',
exam_start_time: '$exam_start_time',
exam_start_fix: '$exam_start_fix',
exam_duration: '$exam_duration',
organization_id: '${centre_id}',
project_code: '$code',
exam_type: 'process_assessment',
},
requestAdaptor:
'if(!api.data.exam_start_fix) {\r\n api.data.exam_start_fix=0;\r\n}\r\nif(!api.data.exam_duration) {\r\n api.data.exam_duration=10;\r\n}\r\nreturn api;',
headers: { Prefer: 'resolution=merge-duplicates' },
},
initApi: {
method: 'get',
url: 'rest/project_preparation_exam/$template_id',
sendOn: 'this.template_id !== null',
data: null,
adaptor:
'if(payload.data.exam_start_time){payload.data.exam_start_time=payload.data.exam_start_time.substring(0,19).replace("T", " ");}\r\nreturn payload;',
},
body: [
{
label: '名称',
name: 'name',
mode: 'normal',
type: 'input-text',
required: true,
},
{
type: 'select',
label: '选择实验考试试卷',
name: 'exam_paper_id',
options: [],
mode: 'normal',
required: true,
checkAll: false,
source: {
method: 'get',
url: 'rest/exam_paper?select=label:name,value:id&type=eq.exam_template_preparation',
},
},
{
type: 'formula',
name: 'type',
condition: 'data.free_schedule',
formula: "'exam_time_fixed'",
},
{
type: 'list-select',
label: '',
name: 'type',
mode: 'normal',
options: [
{
label: '以上课时间为参考',
value: 'exam_time_offset',
},
{ label: '固定开始时间', value: 'exam_time_fixed' },
],
value: 'exam_time_offset',
disabledOn: 'this.free_schedule',
},
{
type: 'input-datetime',
label: '考试时间',
name: 'exam_start_time',
mode: 'normal',
required: true,
visibleOn: "this.type === 'exam_time_fixed'",
placeholder: '请选择考试日期时间',
value: '',
minDate: '',
maxDate: '',
format: 'YYYY-MM-DD HH:mm:ss',
inputFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
type: 'input-text',
label: '上课多久允许答题',
name: 'exam_start_fix',
mode: 'normal',
description:
'使用d/h/m或D/H/M表示天/小时/分钟,默认为分钟,负值为提前',
validations: {
matchRegexp1:
/^(\d+|\d+[Dd]|\d+[Hh]|\d+[Mm])$|^-(\d+|\d+[Dd]|\d+[Hh]|\d+[Mm])$/,
},
validationErrors: {
matchRegexp1:
'请输入正确的格式使用d/h/m或D/H/M表示天/小时/分钟',
},
validateOnChange: true,
step: 1,
required: true,
visibleOn: "this.type === 'exam_time_offset'",
},
{
type: 'input-text',
label: '持续时间',
name: 'exam_duration',
mode: 'normal',
description:
'使用d/h/m或D/H/M表示天/小时/分钟,默认为分钟',
validations: {
matchRegexp1: /^(\d+|\d+[Dd]|\d+[Hh]|\d+[Mm])$/,
},
validationErrors: {
matchRegexp1:
'请输入正确的格式使用d/h/m或D/H/M表示天/小时/分钟',
},
validateOnChange: true,
step: 1,
required: true,
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
closeOnOutside: false,
},
level: 'primary',
icon: 'fa fa-cog',
size: 'sm',
className: 'pull-right',
},
],
label: '操作',
},
],
messages: {},
syncLocation: false,
headerToolbar: [],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
bodyClassName: 'rpt-tp-height',
affixHeader: false,
perPageAvailable: [10, 20, 30, 40, 50],
perPageField: 'perPage',
},
],
type: 'page',
title: '',
messages: {},
bodyClassName: '',
definitions: {
variable: {
type: 'combo',
label: '组合输入',
name: 'combo',
multiple: true,
multiLine: false,
joinValues: true,
messages: {},
mode: 'normal',
conditions: [
{
label: '变量',
test: 'this.type === "text"',
controls: [{ type: 'text', label: '变量', name: 'string' }],
scaffold: { type: 'text', label: '变量', name: '' },
},
{
label: '对象',
test: 'this.type === "object"',
controls: [
{ type: 'text', label: '变量', name: 'string' },
{ $ref: 'variable' },
],
scaffold: { type: 'object', label: '对象', name: '' },
},
{
label: '数组',
test: 'this.type === "array"',
controls: [{ $ref: 'variable' }],
scaffold: { type: 'array', label: '数组', name: '' },
},
],
},
textItem: {
type: 'group',
controls: [
{
name: 'type',
value: 'text',
type: 'select',
clearable: false,
size: 'sm',
options: [
{ label: '变量', value: 'string' },
{ label: '对象', value: 'object' },
{ label: '数组', value: 'array' },
],
},
{ name: 'name', type: 'text', placeholder: '名称', required: true },
{ name: 'title', type: 'text', placeholder: '说明', required: true },
{
type: 'text',
name: 'default',
placeholder: '例值',
required: true,
visibleOn: "this.type==='text'",
},
],
},
objectItem: {
type: 'combo',
multiLine: true,
controls: [{ name: 'data', value: {}, $ref: 'fieldItem', minLength: 1 }],
multiple: false,
},
arrayItem: {
type: 'combo',
multiLine: true,
controls: [
{ name: 'data', value: {}, $ref: 'elementItem', minLength: 1 },
],
multiple: false,
},
fieldItem: {
type: 'combo',
multiple: true,
multiLine: true,
typeSwitchable: false,
controls: [
{ $ref: 'textItem', minLength: 0 },
{
$ref: 'objectItem',
minLength: 0,
visibleOn: "this.type==='object'",
name: 'children',
},
{
$ref: 'arrayItem',
minLength: 0,
visibleOn: "this.type==='array'",
name: 'children',
},
],
},
elementItem: {
type: 'combo',
multiple: false,
multiLine: true,
typeSwitchable: false,
controls: [
{ $ref: 'textItem', minLength: 0, unique: true },
{
$ref: 'objectItem',
minLength: 0,
visibleOn: "this.type==='object'",
name: 'children',
},
{
$ref: 'arrayItem',
minLength: 0,
visibleOn: "this.type==='array'",
name: 'children',
},
],
},
},
};
export { schema };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,322 @@
const schema = {
body: [
{
type: 'crud',
name: 'custom_tp',
api: {
method: 'get',
url: 'rest/project_exam_templates?select=*,exam_paper:exam_paper!project_preparation_exam_exam_paper_id_fk(*)&organization_id=eq.$centre_id&semester_id=eq.${currentSemester}&order=code',
adaptor: '',
data: { page: '$page', perPage: '$perPage' },
requestAdaptor: '',
},
columns: [
{ name: 'code', label: '编号', type: 'text', placeholder: '-' },
{ name: 'name', label: '名称', type: 'text', placeholder: '-' },
{
type: 'text',
label: '考试试卷',
name: 'exam_paper.name',
placeholder: '-',
},
{
type: 'tpl',
inline: false,
name: 'exam_start_fix',
label: '上课多久允许答题',
style: {},
},
{
type: 'text',
label: '持续时间',
placeholder: '-',
name: 'exam_duration',
},
{
type: 'button-group',
buttons: [
{
type: 'button',
label: '设置考试',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '设置实验考试',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/project_preparation_exam',
data: {
name: '$name',
exam_paper_id: '$exam_paper_id',
type: '$type',
exam_start_time: '$exam_start_time',
exam_start_fix: '$exam_start_fix',
exam_duration: '$exam_duration',
organization_id: '${centre_id}',
project_code: '$code',
},
requestAdaptor:
'if(!api.data.exam_start_fix) {\r\n api.data.exam_start_fix=0;\r\n}\r\nif(!api.data.exam_duration) {\r\n api.data.exam_duration=10;\r\n}\r\nreturn api;',
headers: { Prefer: 'resolution=merge-duplicates' },
},
initApi: {
method: 'get',
url: 'rest/project_preparation_exam/$template_id',
sendOn: 'this.template_id !== null',
data: null,
adaptor:
'if(payload.data.exam_start_time){payload.data.exam_start_time=payload.data.exam_start_time.substring(0,19).replace("T", " ");}\r\nreturn payload;',
},
body: [
{
label: '名称',
name: 'name',
mode: 'normal',
type: 'input-text',
required: true,
},
{
type: 'select',
label: '选择实验考试试卷',
name: 'exam_paper_id',
options: [],
mode: 'normal',
required: true,
checkAll: false,
source: {
method: 'get',
url: 'rest/exam_paper?select=label:name,value:id&type=eq.exam_template_preparation',
},
},
{
type: 'formula',
name: 'type',
condition: 'data.free_schedule',
formula: "'exam_time_fixed'",
},
{
type: 'list-select',
label: '',
name: 'type',
mode: 'normal',
options: [
{
label: '以上课时间为参考',
value: 'exam_time_offset',
},
{ label: '固定开始时间', value: 'exam_time_fixed' },
],
value: 'exam_time_offset',
disabledOn: 'this.free_schedule',
},
{
type: 'input-datetime',
label: '考试时间',
name: 'exam_start_time',
mode: 'normal',
required: true,
visibleOn: "this.type === 'exam_time_fixed'",
placeholder: '请选择考试日期时间',
value: '',
minDate: '',
maxDate: '',
format: 'YYYY-MM-DD HH:mm:ss',
inputFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
type: 'input-text',
label: '上课多久允许答题',
name: 'exam_start_fix',
mode: 'normal',
description:
'使用d/h/m或D/H/M表示天/小时/分钟,默认为分钟,负值为提前',
validations: {
matchRegexp1:
/^(\d+|\d+[Dd]|\d+[Hh]|\d+[Mm])$|^-(\d+|\d+[Dd]|\d+[Hh]|\d+[Mm])$/,
},
validationErrors: {
matchRegexp1:
'请输入正确的格式使用d/h/m或D/H/M表示天/小时/分钟',
},
validateOnChange: true,
step: 1,
required: true,
visibleOn: "this.type === 'exam_time_offset'",
},
{
type: 'input-text',
label: '持续时间',
name: 'exam_duration',
mode: 'normal',
description:
'使用d/h/m或D/H/M表示天/小时/分钟,默认为分钟',
validations: {
matchRegexp1: /^(\d+|\d+[Dd]|\d+[Hh]|\d+[Mm])$/,
},
validationErrors: {
matchRegexp1:
'请输入正确的格式使用d/h/m或D/H/M表示天/小时/分钟',
},
validateOnChange: true,
step: 1,
required: true,
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
closeOnOutside: false,
},
level: 'primary',
icon: 'fa fa-cog',
size: 'sm',
className: 'pull-right',
},
],
label: '操作',
},
],
messages: {},
syncLocation: false,
headerToolbar: [],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
bodyClassName: 'rpt-tp-height',
affixHeader: false,
perPageAvailable: [10, 20, 30, 40, 50],
perPageField: 'perPage',
},
],
type: 'page',
title: '',
messages: {},
bodyClassName: '',
definitions: {
variable: {
type: 'combo',
label: '组合输入',
name: 'combo',
multiple: true,
multiLine: false,
joinValues: true,
messages: {},
mode: 'normal',
conditions: [
{
label: '变量',
test: 'this.type === "text"',
controls: [{ type: 'text', label: '变量', name: 'string' }],
scaffold: { type: 'text', label: '变量', name: '' },
},
{
label: '对象',
test: 'this.type === "object"',
controls: [
{ type: 'text', label: '变量', name: 'string' },
{ $ref: 'variable' },
],
scaffold: { type: 'object', label: '对象', name: '' },
},
{
label: '数组',
test: 'this.type === "array"',
controls: [{ $ref: 'variable' }],
scaffold: { type: 'array', label: '数组', name: '' },
},
],
},
textItem: {
type: 'group',
controls: [
{
name: 'type',
value: 'text',
type: 'select',
clearable: false,
size: 'sm',
options: [
{ label: '变量', value: 'string' },
{ label: '对象', value: 'object' },
{ label: '数组', value: 'array' },
],
},
{ name: 'name', type: 'text', placeholder: '名称', required: true },
{ name: 'title', type: 'text', placeholder: '说明', required: true },
{
type: 'text',
name: 'default',
placeholder: '例值',
required: true,
visibleOn: "this.type==='text'",
},
],
},
objectItem: {
type: 'combo',
multiLine: true,
controls: [{ name: 'data', value: {}, $ref: 'fieldItem', minLength: 1 }],
multiple: false,
},
arrayItem: {
type: 'combo',
multiLine: true,
controls: [
{ name: 'data', value: {}, $ref: 'elementItem', minLength: 1 },
],
multiple: false,
},
fieldItem: {
type: 'combo',
multiple: true,
multiLine: true,
typeSwitchable: false,
controls: [
{ $ref: 'textItem', minLength: 0 },
{
$ref: 'objectItem',
minLength: 0,
visibleOn: "this.type==='object'",
name: 'children',
},
{
$ref: 'arrayItem',
minLength: 0,
visibleOn: "this.type==='array'",
name: 'children',
},
],
},
elementItem: {
type: 'combo',
multiple: false,
multiLine: true,
typeSwitchable: false,
controls: [
{ $ref: 'textItem', minLength: 0, unique: true },
{
$ref: 'objectItem',
minLength: 0,
visibleOn: "this.type==='object'",
name: 'children',
},
{
$ref: 'arrayItem',
minLength: 0,
visibleOn: "this.type==='array'",
name: 'children',
},
],
},
},
};
export { schema };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
const schema = {
body: [
{
type: 'crud',
messages: {},
api: {
method: 'get',
url: 'rest/projects?select=*&organization_id=eq.${centre_id}&semester_id=eq.${currentSemester}&order=code',
data: { page: '$page', perPage: '$perPage' },
},
headerToolbar: [],
syncLocation: false,
pageField: 'page',
perPageField: 'perPage',
name: 'score',
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
mode: 'table',
affixHeader: true,
perPageAvailable: [10, 20, 30, 40, 50],
columns: [
{ name: 'code', type: 'text', label: '编号' },
{ type: 'text', name: 'name', label: '名称' },
{
type: 'operation',
label: '操作',
buttons: [
{
type: 'button',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '附件管理',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/projects?id=eq.${id}',
data: {
files: '${files}',
},
dataType: 'json',
},
body: [
{
type: 'combo',
label: '',
name: 'files',
mode: 'inline',
inputClassName: 'm-l-xs',
multiple: true,
controls: [
{
type: 'dataset-picker',
bucket: 'mooc',
name: 'path',
required: true,
},
{ type: 'tpl', tpl: '$path' },
],
multiLine: false,
joinValues: false,
draggable: false,
strictMode: false,
delimiter: ',',
scaffold: '',
disabled: false,
disabledOn: '',
removable: true,
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
},
level: 'link',
size: 'md',
icon: 'fa fa-cog text-warning',
tooltip: '附件管理',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'm-r-none m-l-none',
},
],
placeholder: '-',
},
],
bodyClassName: '',
initApi: '',
initFetch: '',
label: '名称',
},
],
type: 'page',
};
export { schema };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
const schema = {
type: 'page',
body: {
type: 'iframe',
src: '/res',
height: window.innerHeight - 80,
},
};
window.parent.postMessage(
{
type: 'amis:resize',
data: {
height: window.innerHeight - 80
}
},
'*'
);
export { schema };

View File

@@ -0,0 +1,423 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
id: 'u:d0a463529569',
columns: [
{
type: 'text',
name: 'title',
label: '标题',
id: 'u:068708870a6a',
},
{
name: 'creator.name',
type: 'text',
label: '发送人',
id: 'u:118fc9ffbf89',
},
{
name: 'created_at',
label: '发送时间',
type: 'date',
id: 'u:9f0439c43297',
},
{
type: 'button-group',
label: '操作',
id: 'u:537781aa8eec',
placeholder: '-',
buttons: [
{
type: 'button',
level: 'link',
icon: 'fa fa-eye',
size: 'md',
tooltip: '删除',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:9731651193a3',
onEvent: {
click: {
weight: 0,
actions: [
{
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '${title}',
body: [
{
type: 'tpl',
tpl: '${data}',
wrapperComponent: 'p',
inline: false,
id: 'u:2f15ac0923a7',
},
],
showCloseButton: true,
showErrorMsg: true,
showLoading: true,
className: 'app-popover',
id: 'u:93d802668933',
closeOnEsc: true,
actions: [],
},
},
],
},
},
},
{
type: 'button',
confirmText: '确认删除?',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:9731651193a3',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/notifications/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:d0a463529569',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
width: 200,
},
],
messages: {},
api: {
method: 'get',
url: 'rest/notifications?select=*,creator:users!notification_creator_id_fkey(id,name),user_notifications!user_notification_notification_id_fkey(users(name))&order=created_at.desc&organization_id=eq.${centre_id}&type=eq.announcement',
data: null,
},
headerToolbar: [
{
type: 'button',
align: 'right',
icon: 'fa fa-plus text-primary',
level: 'link',
label: '',
id: 'u:6adf561275c3',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '发送消息',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/notifications',
data: {
'&': '$$',
organization_id: '$centre_id',
},
dataType: 'json',
requestAdaptor:
"let notification = {\r\n title: api.data.title,\r\n data: api.data.data,\r\n push2wechat: api.data.push2wechat,\r\n organization_id: api.data.organization_id\r\n};\r\nconsole.log(api)\r\nif (!api.data.push2wechat && api.data.target === 'toorg') {\r\n notification.org_list = `{${api.data.org_list}}`\r\n} else if (!api.data.push2wechat && api.data.target === 'touser') {\r\n notification.user_id_list = `{${api.data.user_id_list}}`\r\n}\r\nreturn {\r\n ...api,\r\n data: notification\r\n}",
},
body: [
{
type: 'group',
label: false,
name: 'title',
id: 'u:5cb966ef8b55',
body: [
{
type: 'input-text',
label: '',
name: 'title',
mode: 'normal',
required: true,
columnRatio: 9,
placeholder: '消息标题',
id: 'u:2e6661386694',
},
{
type: 'switch',
name: 'push2wechat',
option: '是否推送微信消息',
mode: 'normal',
label: '',
optionAtLeft: false,
trueValue: true,
falseValue: false,
value: false,
id: 'u:90d1abf14f6c',
},
],
},
{
type: 'control',
label: '发送给谁',
mode: 'normal',
id: 'u:9064f08b91d6',
className: 'm-t-sm',
visibleOn: '!this.push2wechat',
body: [
{
type: 'grid',
columns: [
{
body: [
{
type: 'select',
label: '',
name: 'target',
options: [
{
label: '所有人',
value: 'toall',
},
{
label: '指定班级',
value: 'toorg',
},
{
label: '指定人',
value: 'touser',
},
],
checkAll: false,
required: true,
value: 'touser',
visibleOn: '',
className: '1css',
labelClassName: '2css',
inputClassName: '3css',
id: 'u:150a4240cd56',
},
],
id: 'u:65ffd686a587',
},
{
body: [
{
label: '',
name: 'org_list',
type: 'picker',
visibleOn: "this.target === 'toorg'",
required: true,
pickerSchema: {
mode: 'list',
listItem: {
title: '${label}',
},
messages: {},
syncLocation: false,
defaultParams: {
page: 1,
perPage: 10,
},
},
source: {
method: 'get',
url: 'rest/orgs?type=eq.class',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
},
multiple: true,
mode: 'normal',
modalMode: 'dialog',
joinValues: true,
id: 'u:8cb5375b4bfb',
},
{
type: 'picker',
name: 'user_id_list',
visibleOn: "this.target === 'touser'",
required: true,
pickerSchema: {
mode: 'list',
listItem: {
title: '${label}',
},
messages: {},
footerToolbar: [
{
type: 'pagination',
tpl: '内容',
},
],
defaultParams: {
perPage: 10,
page: 1,
},
syncLocation: false,
filter: {
type: 'form',
title: '表单',
wrapWithPanel: false,
submitOnInit: false,
submitOnChange: false,
target: '',
resetAfterSubmit: false,
body: [
{
type: 'input-text',
name: 'keywords',
clearable: true,
size: 'md',
label: '学号',
id: 'u:8a6776c917cd',
mode: 'inline',
},
{
type: 'control',
id: 'u:fa7b6a89a71b',
label: '',
body: [
{
type: 'button',
level: 'primary',
actionType: 'submit',
label: '搜索',
id: 'u:544f3de716b9',
reload:
'userlist?code=like.*${keywords}*',
},
],
mode: 'inline',
},
],
id: 'u:e95d6e701a18',
},
name: 'userlist',
},
source: {
method: 'get',
url: 'rest/users?role=eq.student&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: `${item.code}-${item.name}`,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
data: {
'&': '$$',
keywords: '__undefined',
},
},
modalMode: 'dialog',
multiple: true,
joinValues: false,
mode: 'normal',
label: '',
extractValue: true,
id: 'u:eff885fa85ae',
},
],
id: 'u:5352be84ebdb',
},
],
className: '',
id: 'u:7a1bff6ac12a',
},
],
},
{
type: 'input-rich-text',
label: '内容',
name: 'data',
mode: 'normal',
required: true,
inputClassName: 'm-t-sm',
options: {
menubar: false,
},
id: 'u:1ff80f85808d',
},
],
id: 'u:b2bb8e58c569',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:d0a463529569',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'md',
id: 'u:50bb302aecfa',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:2a8cd9b988ff',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:2e2b83a9fcd0',
},
],
},
},
],
},
},
size: 'lg',
},
],
},
],
id: 'u:cd729a982c31',
};
export { schema };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,929 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
id: 'u:90c6fa536042',
mode: 'inline',
name: 'filter',
title: '表单',
className: 'm-b-sm',
submitOnChange: true,
submitOnInit: true,
reload:
'group?course_id=$course_id&class_ids=$class_ids&studentgroup_ids=$studentgroup_ids',
wrapWithPanel: false,
body: [
{
type: 'group',
body: [
{
type: 'select',
mode: 'inline',
label: '课程',
name: 'course_id',
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.$centre_id&semester_id=eq.$currentSemester&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
},
size: 'md',
searchable: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
id: 'u:23277f1965f7',
},
],
id: 'u:8575c6f7cefc',
},
{
type: 'group',
body: [
{
type: 'tree-select',
label: '班级',
name: 'class_ids',
mode: 'horizontal',
multiple: true,
joinValues: false,
extractValue: true,
onlyChildren: true,
clearable: true,
searchable: true,
source: {
method: 'get',
url: 'rest/orgs?select=*,dicts:dicts!organization_type_fkey(*),course2orgs(*)&path=cd.root&course2orgs.course_id=eq.${course_id}&order=name',
adaptor:
"const recursive = id => {\r\n const nodes = payload.data.items.filter(item => {\r\n return item.parent === id && (\r\n (item.type === 'class' ||\r\n item.type === 'reelectclass') &&\r\n item.course2orgs.length ||\r\n item.type !== 'class' &&\r\n item.type !== 'reelectclass' &&\r\n item.type !== 'studentgroup'\r\n )\r\n })\r\n const result = []\r\n\r\n if (nodes.length) {\r\n nodes.forEach(item => {\r\n const children = recursive(item.id)\r\n\r\n if (children)\r\n result.push({\r\n ...item,\r\n label: item.name,\r\n children,\r\n })\r\n else if (\r\n item.type === 'class' ||\r\n item.type === 'reelectclass'\r\n ) result.push({\r\n ...item,\r\n label: item.name,\r\n value: item.id,\r\n })\r\n })\r\n\r\n if (result.length) return result\r\n } else return null\r\n}\r\n\r\nlet school = payload.data.items.find(x => x.type === 'school')\r\n\r\nreturn {\r\n ...payload,\r\n data: recursive(school.id)\r\n}\r\n",
requestAdaptor:
'if (api.query.course2orgs.course_id === "eq.") {\r\n \n api.url = api.url.replace("&course2orgs[course_id]=eq.", "")\r\n}\r\nif (api.query.course2orgs.course_id === "eq.undefined") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.undefined", "")\r\n}',
sendOn: 'course_id',
messages: {},
},
withChildren: true,
initiallyOpen: false,
id: 'u:42ede681efa8',
autoCheckChildren: true,
enableNodePath: false,
showIcon: true,
heightAuto: true,
virtualThreshold: false,
},
],
id: 'u:f2885926e84d',
},
{
type: 'group',
body: [
{
type: 'tree-select',
label: '分组',
name: 'studentgroup_ids',
mode: 'horizontal',
multiple: true,
joinValues: false,
extractValue: true,
onlyChildren: true,
clearable: true,
searchable: true,
source: {
method: 'get',
url: 'rest/orgs?select=*,dicts:dicts!organization_type_fkey(*),course2orgs(*)&path=cd.root&course2orgs.course_id=eq.${course_id}&order=name',
adaptor:
"const recursive = id => {\r\n const nodes = payload.data.items.filter(item => {\r\n return item.parent === id && (\r\n item.type === 'studentgroup' &&\r\n item.course2orgs.length ||\r\n item.type !== 'class' &&\r\n item.type !== 'reelectclass' &&\r\n item.type !== 'studentgroup'\r\n )\r\n })\r\n const result = []\r\n\r\n if (nodes.length) {\r\n nodes.forEach(item => {\r\n const children = recursive(item.id)\r\n\r\n if (children)\r\n result.push({\r\n ...item,\r\n label: item.name,\r\n children,\r\n })\r\n else if (item.type === 'studentgroup')\r\n result.push({\r\n ...item,\r\n label: item.name,\r\n value: item.id,\r\n })\r\n })\r\n\r\n if (result.length) return result\r\n } else return null\r\n}\r\n\r\nlet school = payload.data.items.find(x => x.type === 'school')\r\n\r\nreturn {\r\n ...payload,\r\n data: recursive(school.id)\r\n}\r\n",
requestAdaptor:
'if (api.query.course2orgs.course_id === "eq.") {\r\n \n api.url = api.url.replace("&course2orgs[course_id]=eq.", "")\r\n}\r\nif (api.query.course2orgs.course_id === "eq.undefined") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.undefined", "")\r\n}',
sendOn: 'course_id',
messages: {},
},
withChildren: true,
initiallyOpen: false,
id: 'u:42ede681efa8',
autoCheckChildren: true,
enableNodePath: false,
showIcon: true,
heightAuto: true,
virtualThreshold: false,
},
],
id: 'u:bfa2b4c7ed01',
},
],
feat: 'Insert',
},
{
type: 'divider',
lineStyle: 'solid',
id: 'u:e807ff502b0a',
},
{
type: 'crud',
messages: {},
api: {
method: 'get',
url: 'rest/groupusers?semester_id=eq.$currentSemester&course_id=eq.$course_id&or=(class_id.in.($class_ids),studentgroup_id.in.($studentgroup_ids))&order=student_code',
sendOn: 'this.course_id && (this.class_ids || this.studentgroup_ids)',
data: {
orderBy: '$orderBy',
orderDir: '$orderDir',
},
requestAdaptor:
"if (api.query.orderDir) {\r\n api.url = api.url.replace('&order=student_code', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '&order=' + api.query.orderBy + '.' + api.query.orderDir);\r\n} else if (api.query.orderBy) {\r\n api.url = api.url.replace('&order=student_code', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=', '&order=' + api.query.orderBy);\r\n} else {\r\n api.url = api.url.replace('&orderBy=&orderDir=', '');\r\n}\r\n\r\nreturn api;",
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map((item, index) => {\r\n return {\r\n ...item,\r\n n: index + 1\r\n }\r\n })\r\n }\r\n}',
},
headerToolbar: [
{
type: 'button',
align: 'right',
label: '导入分组',
icon: 'fa fa-upload',
level: 'warning',
id: 'u:ce034375ceff',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '分组导入',
size: 'xl',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'select',
mode: 'inline',
label: '课程',
name: 'course_id',
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.$centre_id&semester_id=eq.$currentSemester&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: item.id\r\n }\r\n })\r\n}',
},
size: 'md',
searchable: true,
className: 'm-l-sm',
inputClassName: 'm-l-xs',
id: 'u:7cfa89be2243',
},
{
type: 'tree-select',
label: '上级部门',
name: 'parent_id',
mode: 'inline',
extractValue: true,
onlyChildren: true,
source: {
method: 'get',
url: 'rest/orgs?select=*&or=(type.eq.school,type.eq.faculty,type.eq.centre)&path=cd.root&order=code',
adaptor:
"const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\n\r\nlet school = payload.data.items.find(x => x.type === 'school')\r\n\r\nreturn {\r\n ...payload,\r\n data: recursive(school.id)\r\n}\r\n",
sendOn: '',
},
withChildren: true,
initiallyOpen: false,
searchable: true,
clearable: false,
heightAuto: true,
virtualThreshold: false,
id: 'u:37ca2401871c',
},
{
type: 'excel-import-group',
name: 'groups',
label: '',
mode: 'normal',
hiddenOn: '${!course_id || !parent_id}',
id: 'u:a8c9dbf3d073',
},
{
type: 'input-table',
name: 'groups',
label: '',
columns: [
{
label: 'Excel 行号',
name: 'no',
id: 'u:a31efd3393b0',
},
{
name: 'user_code',
label: '*学号',
classNameExpr:
"<%= data.no && !data.user_id ? 'bg-danger' : '' %>",
id: 'u:8d68435bf70e',
},
{
label: '姓名',
name: 'user_name',
id: 'u:fc9cb5a03b8f',
},
{
label: '分组',
name: 'group',
classNameExpr:
"<%= !data.no ? 'bg-secondary' : !data.group ? 'bg-danger' : '' %>",
id: 'u:ca487d89fd7c',
},
],
mode: 'normal',
visibleOn: 'this.groups',
strictMode: true,
removable: true,
editable: false,
clearValueOnHidden: true,
id: 'u:625db3dac0ec',
},
],
reload: 'grp',
initApi: {
method: 'get',
url: 'rest/report_check_items?organization_id=eq.$centre_id&order=type_order,type,display_order,comment',
},
id: 'u:ccd0d1410b18',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'View',
},
],
closeOnEsc: true,
closeOnOutside: false,
showCloseButton: true,
actions: [],
id: 'u:d85ab95adb25',
},
},
],
},
},
},
{
type: 'export-excel',
align: 'right',
label: '导出',
icon: 'fa fa-download',
level: 'primary',
hiddenOn: '${!class_ids && !studentgroup_ids}',
api: {
method: 'get',
url: 'rest/groupusers?semester_id=eq.$currentSemester&course_id=eq.$course_id&or=(class_id.in.($class_ids),studentgroup_id.in.($studentgroup_ids))&order=student_code',
data: {
orderBy: '$orderBy',
orderDir: '$orderDir',
},
requestAdaptor:
"if (api.query.orderDir) {\r\n api.url = api.url.replace('&order=student_code', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=' + api.query.orderDir, '&order=' + api.query.orderBy + '.' + api.query.orderDir);\r\n} else if (api.query.orderBy) {\r\n api.url = api.url.replace('&order=student_code', '');\r\n api.url = api.url.replace('&orderBy=' + api.query.orderBy + '&orderDir=', '&order=' + api.query.orderBy);\r\n} else {\r\n api.url = api.url.replace('&orderBy=&orderDir=', '');\r\n}\r\n\r\nreturn api;",
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map((item, index) => {\r\n return {\r\n ...item,\r\n n: index + 1\r\n }\r\n })\r\n }\r\n}',
},
filename: '分组',
exportColumns: [
{
label: '*学号',
name: 'student_code',
},
{
label: '姓名',
name: 'student_name',
},
{
label: '*分组',
name: 'studentgroup_name',
},
],
id: 'u:a44ab3164746',
},
{
type: 'bulk-actions',
},
],
bulkActions: [
{
label: '批量设置',
type: 'button',
id: 'u:99fa372537dc',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '批量设置',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/rpc/batch_group',
data: {
org_id: '$org_id',
selectedItems: '$selectedItems',
},
heades: {
Prefer: 'params=single-object',
},
requestAdaptor:
'const user2org = api.data.selectedItems.map(item => ({\r\n user_id: item.student_id,\r\n old_org_id: item.studentgroup_id,\r\n new_org_id: api.data.org_id,\r\n}))\r\n\r\napi.data = { user2org }\r\n\r\nreturn api',
},
body: [
{
type: 'select',
label: '分组',
name: 'org_id',
mode: 'normal',
cascade: true,
searchable: true,
required: true,
source: {
method: 'get',
url: 'rest/orgs?select=*,course2orgs!inner(*),dicts:dicts!organization_type_fkey(*)&type=eq.studentgroup&path=cd.root.1&course2orgs.course_id=eq.${course_id}&order=name',
requestAdaptor:
'if (api.query.course2orgs.course_id === "eq.") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.", "")\r\n}\r\nif (api.query.course2orgs.course_id === "eq.undefined") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.undefined", "")\r\n}',
adaptor:
'return {\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n}',
sendOn: 'course_id',
},
extractValue: true,
id: 'u:8527f8d399e6',
},
],
id: 'u:e9215f23110f',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:148f700a4963',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:ce48989de1f9',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:f8c9f57fa665',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:8cccdee1e21b',
},
],
},
},
],
},
},
},
{
label: '批量清除',
confirmText: '确认清除分组吗?',
type: 'button',
id: 'u:5eb51d3e6692',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/user_orgs',
method: 'delete',
requestAdaptor:
'api.url = api.url + `?id=in.(${api.data.selectedItems.filter(item => item.user_studentgroup_id).map(item => item.user_studentgroup_id).toString()})`\r\ndelete api.data.selectedItems\r\n\r\nreturn api',
adaptor: '',
messages: {},
data: {
selectedItems: '$selectedItems',
},
},
},
{
componentId: 'u:148f700a4963',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
{
label: '批量分组',
type: 'button',
id: 'u:0371266b981c',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '批量分组',
body: [
{
type: 'form',
title: '表单',
id: 'u:f39e60c373bc',
body: [
{
type: 'tree-select',
label: '上级部门',
name: 'parent',
mode: 'normal',
extractValue: true,
onlyChildren: true,
source: {
method: 'get',
url: 'rest/orgs?select=*&or=(type.eq.school,type.eq.faculty,type.eq.centre)&path=cd.root&order=code',
adaptor:
"const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\n\r\nlet school = payload.data.items.find(x => x.type === 'school')\r\n\r\nreturn {\r\n ...payload,\r\n data: recursive(school.id)\r\n}\r\n",
sendOn: '',
},
withChildren: true,
required: true,
initiallyOpen: false,
searchable: true,
clearable: false,
heightAuto: true,
virtualThreshold: false,
id: 'u:1efc22d916f7',
},
{
name: 'groups',
label: '新分组',
type: 'input-array',
items: {
type: 'input-text',
},
removable: true,
required: true,
mode: 'normal',
id: 'u:0a41abbda787',
},
{
label: '选择分组方式',
type: 'select',
name: 'way',
required: true,
mode: 'normal',
options: [
{
label: '顺序分组',
value: 'order',
},
{
label: '随机分组',
value: 'random',
},
],
id: 'u:436b4893fdfc',
},
{
type: 'batch-group',
label: false,
id: 'u:04fbf3b1b31c',
},
],
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:148f700a4963',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
actions: [
{
type: 'button',
label: '关闭',
onEvent: {
click: {
actions: [
{
componentId: 'u:148f700a4963',
actionType: 'reload',
data: null,
},
{
actionType: 'closeDialog',
},
],
},
},
id: 'u:f7831022039a',
},
],
id: 'u:3ead95907fbd',
},
},
],
},
},
},
],
syncLocation: false,
footerToolbar: [],
columns: [
{
type: 'text',
name: 'n',
label: '序号',
id: 'u:dc3b48ab2a2a',
},
{
type: 'text',
name: 'student_code',
sortable: true,
label: '学号',
id: 'u:9233f140d314',
},
{
type: 'text',
name: 'student_name',
sortable: true,
label: '姓名',
id: 'u:cb913014ed05',
},
{
type: 'text',
sortable: true,
label: '班级',
name: 'class_name',
id: 'u:d0de67b68472',
},
{
type: 'text',
label: '课程',
name: 'course_name',
id: 'u:ecd0d94b6c58',
},
{
type: 'text',
sortable: true,
label: '分组',
name: 'studentgroup_name',
id: 'u:de1610df96e6',
},
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
level: 'link',
icon: 'fa fa-cog text-info',
size: 'md',
tooltip: '设置分组',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
hiddenOn: '${studentgroup_id}',
id: 'u:ccec6786dabf',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
title: '设置分组',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/user_orgs',
data: {
user_id: '$student_id',
organization_id: '$group_id',
},
dataType: 'json',
},
id: 'u:a20070f8b13c',
body: [
{
type: 'select',
label: '分组',
name: 'group_id',
mode: 'normal',
cascade: true,
searchable: true,
required: true,
source: {
method: 'get',
url: 'rest/orgs?select=*,course2orgs!inner(*),dicts:dicts!organization_type_fkey(*)&type=eq.studentgroup&path=cd.root.1&course2orgs.course_id=eq.${course_id}&order=name',
requestAdaptor:
'if (api.query.course2orgs.course_id === "eq.") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.", "")\r\n}\r\nif (api.query.course2orgs.course_id === "eq.undefined") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.undefined", "")\r\n}',
adaptor:
'return {\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n}',
sendOn: 'course_id',
},
extractValue: true,
id: 'u:75a5e1ef2a35',
},
],
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:148f700a4963',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
id: 'u:05921ba2d106',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:5a3bed9137d5',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:9982eb0126f2',
},
],
},
},
],
},
},
},
{
type: 'button',
level: 'link',
icon: 'fa fa-pencil text-warning',
size: 'md',
tooltip: '修改分组',
tooltipPlacement: 'top',
hiddenOn: '${!studentgroup_id}',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:b1bda5ad847a',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
title: '修改分组',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/user_orgs?user_id=eq.$student_id&organization_id=eq.$studentgroup_id',
data: {
organization_id: '$group_id',
},
dataType: 'json',
},
id: 'u:2b0b7a8f5877',
body: [
{
type: 'tree-select',
label: '原分组',
name: 'studentgroup_id',
searchable: true,
readOnly: true,
onlyChildren: true,
source: {
method: 'get',
url: 'rest/orgs?select=*&or=(type.eq.school,type.eq.faculty,type.eq.centre,type.eq.studentgroup)&path=cd.root&order=code',
adaptor:
"const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\n\r\nlet school = payload.data.items.find(x => x.type === 'school')\r\n\r\nreturn {\r\n ...payload,\r\n data: recursive(school.id)\r\n}\r\n",
},
withChildren: true,
initiallyOpen: false,
heightAuto: true,
virtualThreshold: false,
id: 'u:9b803b7fffbe',
},
{
type: 'select',
label: '新分组',
name: 'group_id',
mode: 'normal',
cascade: true,
searchable: true,
required: true,
source: {
method: 'get',
url: 'rest/orgs?select=*,course2orgs!inner(*),dicts:dicts!organization_type_fkey(*)&type=eq.studentgroup&path=cd.root.1&course2orgs.course_id=eq.${course_id}&order=name',
requestAdaptor:
'if (api.query.course2orgs.course_id === "eq.") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.", "")\r\n}\r\nif (api.query.course2orgs.course_id === "eq.undefined") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.undefined", "")\r\n}',
adaptor:
'return {\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n}',
sendOn: 'course_id',
},
extractValue: true,
id: 'u:fd14fbccb8fc',
},
],
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:148f700a4963',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
id: 'u:94b2f1f09462',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:0c74d636f491',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:2e60b730d920',
},
],
},
},
],
},
},
},
{
type: 'button',
confirmText: '确认清除分组吗?',
level: 'link',
icon: 'fa fa-trash text-danger',
size: 'md',
tooltip: '清除分组',
tooltipPlacement: 'top',
hiddenOn: '${!studentgroup_id}',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:e719d85aa542',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/user_orgs?user_id=eq.$student_id&organization_id=eq.$studentgroup_id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:148f700a4963',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
placeholder: '-',
width: 200,
id: 'u:b59ecbce78a6',
},
],
name: 'grp',
bodyClassName: '',
title: '',
initFetch: true,
columnsTogglable: true,
id: 'u:148f700a4963',
},
],
title: '',
messages: {},
id: 'u:9e76719da6ac',
};
export { schema };

View File

@@ -0,0 +1,525 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
messages: {},
api: {
method: 'get',
url: 'rest/projects?select=*&organization_id=eq.${centre_id}&semester_id=eq.${currentSemester}&order=code',
data: {
page: '$page',
perPage: '$perPage',
},
},
headerToolbar: [
{
type: 'button',
id: 'u:b88c442cf1c9',
align: 'right',
label: '添加',
icon: 'fa fa-plus',
level: 'default',
className: 'm-l-xs',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加实验',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/projects',
data: {
code: '${project_code}',
name: '${project_name}',
intro: '${project_intro}',
organization_id: '${centre_id}',
type: '${type}',
free_schedule: '${free_schedule}',
},
dataType: 'json',
requestAdaptor:
'if(api.data.type===\'examination\') {\r\n api.data.score_item=[{"item": "exp_preparation", "label": "实验预习", "weight": 1}];\r\n}\r\nreturn api;',
},
body: [
{
type: 'input-text',
label: '编号',
name: 'project_code',
value: '',
required: true,
mode: 'normal',
id: 'u:4a4471a8f6fd',
},
{
label: '名称',
type: 'input-text',
name: 'project_name',
value: '',
required: true,
mode: 'normal',
id: 'u:5d11604bb560',
},
{
label: '类型',
type: 'select',
name: 'type',
required: true,
mode: 'normal',
checkAll: false,
source: {
method: 'get',
url: 'rest/dicts?select=dictkey,dictvalue&typecode=eq.012',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.dictvalue,\r\n value: item.dictkey\r\n }\r\n })\r\n }\r\n}',
},
value: 'experiment',
options: [
{
label: '实验课',
value: 'experiment',
},
],
id: 'u:4f93120dc92a',
},
{
label: '学生自主安排时间',
type: 'checkbox',
name: 'free_schedule',
mode: 'inline',
value: false,
id: 'u:d8de66ea2a6b',
},
{
label: '描述',
type: 'input-rich-text',
name: 'project_intro',
mode: 'normal',
options: {
menubar: false,
},
id: 'u:41a4ab0a0f14',
},
],
id: 'u:87d3612f0e3f',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:faff85507083',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:d6cc5749dac1',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:25b4e52d9f7a',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:6f9f855502d6',
},
],
},
},
],
},
},
},
{
type: 'export-excel',
align: 'right',
label: '实验导出',
icon: 'fa fa-download',
level: 'primary',
id: 'u:d57d6b7f1525',
api: 'rest/projects?select=*,dicts:dicts!project_type_fk(dictkey,dictvalue)&organization_id=eq.$centre_id&semester_id=eq.$currentSemester&order=code',
filename: '实验',
exportColumns: [
{
label: '*编号',
name: 'code',
},
{
name: 'name',
label: '*名称',
},
{
name: 'dicts.dictvalue',
label: '*类型',
},
],
},
],
syncLocation: false,
perPageAvailable: [10, 20, 30, 40, 50],
footerToolbar: [
{
type: 'pagination',
},
{
type: 'switch-per-page',
},
{
type: 'statistics',
},
],
perPageField: 'perPage',
pageField: 'page',
mode: 'table',
affixHeader: true,
id: 'u:faff85507083',
name: 'score',
columns: [
{
name: 'code',
type: 'text',
label: '编号',
id: 'u:bad42a88a366',
},
{
type: 'text',
name: 'name',
label: '名称',
id: 'u:d9f55c020ac5',
},
{
type: 'tpl',
label: '评分项',
name: 'score_item',
tpl: "<% if (this.score_item.length > 0) {\n this.score_item.forEach(score => { %>\n <% if (score.item === 'exp_report') { %>\n <span class='label label-info' style='margin-right: 1px; font-size: smaller;'> <%= score.label %> (<%= score.weight %>)\n <% if (score.isRecorded) { %>\n <i class='fa fa-pencil'></i>\n <% } %>\n </span>\n <% } else if (score.item === 'exp_data') { %>\n <span class='label label-success' style='margin-right: 1px; font-size: smaller;'> <%= score.label %> (<%= score.weight %>)\n <% if (score.isRecorded) { %>\n <i class='fa fa-pencil'></i>\n <% } %>\n </span>\n <% } else if (score.item === 'exp_preparation') { %>\n <span class='label label-danger' style='margin-right: 1px; font-size: smaller;'> <%= score.label %> (<%= score.weight %>)\n <% if (score.isRecorded) { %>\n <i class='fa fa-pencil'></i>\n <% } %>\n </span>\n <% } else if (score.item === 'exp_operation') { %>\n <span class='label label-warning' style='margin-right: 1px; font-size: smaller;'> <%= score.label %> (<%= score.weight %>)\n <% if (score.isRecorded) { %>\n <i class='fa fa-pencil'></i>\n <% } %>\n </span>\n <% } else if (score.item === 'exp_notebook') { %>\n <span class='label' style='margin-right: 1px; background: purple; font-size: smaller;'> <%= score.label %> (<%= score.weight %>)\n <% if (score.isRecorded) { %>\n <i class='fa fa-pencil'></i>\n <% } %>\n </span>\n <% } else if (score.item === 'exp_process_assessment') { %>\n <span class='label' style='margin-right: 1px; background: orange; font-size: smaller;'> <%= score.label %> (<%= score.weight %>)\n <% if (score.isRecorded) { %>\n <i class='fa fa-pencil'></i>\n <% } %>\n </span>\n <% } else if (score.item === 'exp_team') { %>\n <span class='label' style='margin-right: 1px; background: pink; font-size: smaller;'> <%= score.label %> (<%= score.weight %>)\n <% if (score.isRecorded) { %>\n <i class='fa fa-pencil'></i>\n <% } %>\n </span>\n <% } else { %>\n <span class='label' style=\"margin-right: 1px; background: #374151; font-size: smaller;\"> <%= score.label %> (<%= score.weight %>)\n <% if (score.isRecorded) { %>\n <i class='fa fa-pencil'></i>\n <% } %>\n </span>\n <% } %>\n <% }) %>\n <% } %>",
inline: false,
id: 'u:6fb5baeecfab',
},
{
type: 'operation',
label: '操作',
buttons: [
{
type: 'button',
level: 'link',
size: 'md',
icon: 'fa fa-pencil text-info',
tooltip: '修改',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'm-r-none m-l-none',
id: 'u:74e446e57e2b',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '修改实验',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/projects?id=eq.${id}',
data: {
code: '${code}',
name: '${name}',
intro: '${intro}',
type: '${type}',
free_schedule: '${free_schedule}',
},
dataType: 'json',
},
body: [
{
type: 'input-text',
label: '编号',
name: 'code',
value: '',
required: true,
mode: 'normal',
id: 'u:45350664b507',
},
{
label: '名称',
type: 'input-text',
name: 'name',
value: '',
required: true,
mode: 'normal',
id: 'u:8e70cec48464',
},
{
label: '类型',
type: 'select',
name: 'type',
required: true,
mode: 'normal',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/dicts?select=dictkey,dictvalue&typecode=eq.012',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.dictvalue,\r\n value: item.dictkey\r\n }\r\n })\r\n }\r\n}',
},
id: 'u:425ae91adc74',
},
{
label: '学生自主安排时间',
type: 'checkbox',
name: 'free_schedule',
mode: 'inline',
id: 'u:b43e9dc2a55d',
},
{
label: '描述',
type: 'input-rich-text',
name: 'intro',
value: '',
mode: 'normal',
id: 'u:84e41b04cf8c',
},
],
id: 'u:3c25e724c688',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:faff85507083',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:98e4cae66fe9',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:8dddacfd03ef',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:9a7c3432ab97',
},
],
},
},
],
},
},
},
{
type: 'button',
confirmText: '确认删除"${name}(${code})"',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'm-r-none m-l-none',
id: 'u:58e504e36911',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/projects/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:faff85507083',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
{
type: 'button',
icon: 'fa fa-cog text-warning',
level: 'link',
messages: {},
title: '报告模板',
size: 'md',
confirmText: '',
tooltip: '修改实验评分项',
tooltipPlacement: 'top',
className: 'm-r-none m-l-none',
iconClassName: 'pull-left',
id: 'u:a5fd11fac042',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
title: '修改实验评分项',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/projects/${id}',
data: {
score_item: '$score_item',
},
requestAdaptor: '',
dataType: 'json',
},
body: [
{
type: 'projectscoreitem',
name: 'score_item',
api: {
url: 'rest/dicts?typecode=eq.007',
},
mode: 'normal',
id: 'u:e3a860ff8f80',
},
],
id: 'u:3510789a5776',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:faff85507083',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
closeOnOutside: false,
showCloseButton: true,
size: 'md',
id: 'u:34a7904cefc0',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:6fd9b88723bf',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:457223ebeaee',
},
],
},
},
],
},
},
},
],
placeholder: '-',
id: 'u:33cc39b76ad4',
},
],
bodyClassName: '',
initApi: '',
initFetch: '',
label: '名称',
},
],
id: 'u:b7c1c11acda6',
};
export { schema };

View File

@@ -0,0 +1,69 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
title: '',
body: [
{
type: 'input-text',
label: '数字化教材名称',
mode: 'horizontal',
required: true,
name: 'name',
id: 'u:1e67e22855ce',
},
{
type: 'combo',
label: false,
name: 'cells',
required: true,
multiple: true,
addable: true,
removable: true,
removableMode: 'icon',
addBtn: {
label: '新增',
level: 'primary',
size: 'sm',
id: 'u:3300178a20b6',
},
items: [
{
type: 'vditor',
name: 'source',
id: 'u:48cbfec9c64d',
},
],
id: 'u:1e67e22855be',
},
],
id: 'u:ccd0d1410b18',
feat: 'View',
api: {
method: 'post',
url: 'rest/test',
data: {
cells: '$cells',
},
},
actions: [
{
type: 'textbook-submit',
label: '提交',
},
],
wrapWithPanel: true,
dsType: 'api',
},
],
title: '',
id: 'u:9e76719da6ac',
messages: {},
asideResizor: false,
pullRefresh: {
disabled: true,
},
};
export { schema };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
const schema = {
type: 'page',
body: [
{
type: 'button',
label: '使用说明',
onEvent: {
click: {
actions: [
{
args: {
url: 'https://www.platosoft.org/doc/olms/plugin/mark.html',
},
actionType: 'url',
},
],
weight: 0,
},
},
size: 'md',
level: 'primary',
block: false,
},
{
type: 'button',
label: '进入批阅',
onEvent: {
click: {
actions: [
{
args: {
url: '/check',
},
actionType: 'url',
},
],
weight: 0,
},
},
level: 'primary',
blank: true,
className: 'm-l',
},
],
title: '报告上传与在线批阅',
messages: {},
};
export { schema };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,513 @@
const schema = {
type: 'page',
body: [
{
type: 'scheduler-renderer',
name: 'scheduler',
body: [],
plan: {
type: 'button',
label: '按钮',
actionType: 'drawer',
dialog: { title: '系统提示', body: '对你点击了' },
drawer: {
type: 'drawer',
title: '弹框标题',
body: [
{ type: 'tpl', tpl: '<p>对,你刚刚点击了</p>', inline: false },
],
},
},
planlistbutton: {
type: 'button',
label: '',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '排课计划',
body: [
{
type: 'service',
body: [
{
type: 'button',
label: '添加计划',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加计划',
body: [
{
type: 'wizard',
reload: 'plancrud',
steps: [
{
title: '计划设置',
mode: 'normal',
controls: [
{
type: 'text',
label: '计划名称',
name: 'name',
mode: 'normal',
required: true,
},
{
type: 'date-range',
label: '日期范围',
name: 'date',
required: true,
format: 'YYYY-MM-DD',
minDate: '${semester.since}',
maxDate: '${semester.to}',
},
{
type: 'number',
label: '老师每周最多上课数量',
name: 'teacherFreqWeekly',
required: true,
mode: 'normal',
min: '1',
step: 1,
value: 10,
},
{
type: 'number',
label: '老师每天最多上课数量',
name: 'teacherFreqDaily',
mode: 'normal',
required: true,
min: '1',
step: 1,
value: 1,
},
{
type: 'number',
label: '老师同时上课数量',
name: 'maxConcurrentOfTeacher',
mode: 'normal',
required: true,
min: '1',
step: 1,
value: 1,
},
{
type: 'number',
name: 'maxConcurrent',
mode: 'normal',
label: '同一时段最多排课数量',
required: true,
value: 4,
min: '1',
step: 1,
},
{
type: 'number',
name: 'maxFreqWeekly',
mode: 'normal',
label: '学生每周上课数',
required: true,
value: 1,
min: '1',
step: 1,
},
{
type: 'switch',
name: 'fixTeacherMode',
mode: 'normal',
label: '',
required: true,
value: false,
option: ' 教师跟班模式',
optionAtLeft: false,
trueValue: true,
falseValue: false,
},
],
},
{
title: '资源设置',
controls: [
{
type: 'course-list',
label: '课程',
name: 'courselist',
source: {
method: 'get',
url: 'rest/courses?select=id,name,course2projects(projects(id,name,validRoomList,preSubjectList)),course2orgs(orgs(id,name, validTimeslotList:timeslot_class_id_fkey(id,date,period:periods(id,name,startTime:start_time,endTime:end_time))))&organization_id=eq.${centre_id}&semester_id=eq.${currentSemester}&order=code',
},
required: true,
multiple: true,
},
],
mode: 'normal',
},
{
mode: 'normal',
title: '工作量设置',
controls: [
{
type: 'workload',
name: 'workload',
source:
'rest/users?select=id,name:name,role,subjectList,studentGroupList,user_orgs(organization_id),timeslotList:timeslot_teacher_id_fkey(id,date,period:periods(id,name,startTime:start_time,endTime:end_time)),secondChoiceTimeslotList:timeslot_teacher_secondary_id_fkey(id,date,period:periods(id,name,startTime:start_time,endTime:end_time))&role=eq.teacher&user_orgs.organization_id=eq.${centre_id}&order=code',
},
],
},
],
mode: 'horizontal',
api: {
method: 'post',
url: 'rest/schedule_plans',
requestAdaptor:
"return {\r\n ...api,\r\n data: {\r\n name: api.data.name,\r\n date: api.data.date,\r\n status: '待求解',\r\n organization_id: api.data.organization_id,\r\n problem: {\r\n problemConfig: {\r\n fixTeacherMode: api.data.fixTeacherMode,\r\n maxConcurrent: api.data.maxConcurrent,\r\n maxConcurrentOfTeacher: api.data.maxConcurrentOfTeacher,\r\n maxFreqWeekly: api.data.maxFreqWeekly,\r\n teacherFreqWeekly: api.data.teacherFreqWeekly,\r\n teacherFreqDaily: api.data.teacherFreqDaily,\r\n excludeRoomTime: api.data.excludeRoomTime,\r\n excludeTeacherStudentGroup: api.data.excludeTeacherStudentGroup,\r\n excludeTeacherSubject: api.data.excludeTeacherSubject,\r\n excludeTeacherTime: api.data.excludeTeacherTime,\r\n },\r\n courseList: api.data.courselist.map((item) => {\r\n return {\r\n id: item.id,\r\n name: item.name,\r\n subjectList: item.subjectList.map((e) => {\r\n return {\r\n id: e.id,\r\n name: e.name,\r\n abbreviation: item.name,\r\n validRoomList: e.validRoomList,\r\n preSubjectList: e.preSubjectList\r\n };\r\n }),\r\n studentGroupList: item.studentGroupList.map((e) => {\r\n return {\r\n id: e.id,\r\n name: e.name,\r\n validTimeslotList: e.validTimeslotList,\r\n };\r\n }),\r\n };\r\n }),\r\n validTeacherList: api.data.workload.filter(item => item.workload > 0).map((item) => {\r\n return {\r\n id: item.id,\r\n name: item.name,\r\n workload: item.workload,\r\n timeslotList: item.timeslotList,\r\n secondChoiceTimeslotList: item.secondChoiceTimeslotList,\r\n subjectList: item.subjectList,\r\n studentGroupList: item.studentGroupList,\r\n };\r\n }),\r\n },\r\n },\r\n};\r\n",
data: {
'&': '$$',
organization_id: '${centre_id}',
excludeRoomTime: '${excludeRoomTime}',
excludeTeacherStudentGroup:
'${excludeTeacherStudentGroup}',
excludeTeacherSubject: '${excludeTeacherSubject}',
excludeTeacherTime: '${excludeTeacherTime}',
},
},
name: 'stepper',
actionNextSaveLabel: '下一步',
},
],
closeOnEsc: true,
showCloseButton: true,
actions: [],
data: {
currentSemester: '${currentSemester}',
centre_id: '${centre_id}',
semester: '${semester}',
excludeRoomTime: '${excludeRoomTime}',
excludeTeacherStudentGroup:
'${excludeTeacherStudentGroup}',
excludeTeacherSubject: '${excludeTeacherSubject}',
excludeTeacherTime: '${excludeTeacherTime}',
},
},
className: 'm-b-sm ',
block: false,
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/schedule_plans?semester_id=eq.${currentSemester}&organization_id=eq.${centre_id}&order=updated_at.desc',
data: { '&': '$$', page: '${page}', perPage: '${perPage}' },
},
columns: [
{ name: 'name', label: '排课计划', type: 'text' },
{ type: 'text', label: '状态', name: 'status' },
{
type: 'operation',
label: '操作',
buttons: [
{
label: '选择',
type: 'button',
visibleOn: '!this.is_current',
actionType: 'ajax',
confirmText: '确认切换排课计划?',
className: '',
api: {
method: 'get',
url: 'rest/schedule_plans/${id}',
},
size: 'sm',
level: 'primary',
reload: 'scheduler?plan=${id}',
},
{
type: 'button',
visibleOn: '!this.is_current',
label: '编辑',
actionType: 'dialog',
dialog: {
title: '修改计划',
body: [
{
type: 'wizard',
reload: 'plancrud',
api: {
method: 'patch',
url: 'rest/schedule_plans?id=eq.${id}',
requestAdaptor:
"return {\r\n ...api,\r\n data: {\r\n name: api.data.name,\r\n date: api.data.date,\r\n status: '待求解',\r\n organization_id: api.data.organization_id,\r\n problem: {\r\n problemConfig: {\r\n fixTeacherMode: api.data.fixTeacherMode,\r\n maxConcurrent: api.data.maxConcurrent,\r\n maxConcurrentOfTeacher: api.data.maxConcurrentOfTeacher,\r\n maxFreqWeekly: api.data.maxFreqWeekly,\r\n teacherFreqWeekly: api.data.teacherFreqWeekly,\r\n teacherFreqDaily: api.data.teacherFreqDaily,\r\n excludeRoomTime: api.data.excludeRoomTime,\r\n excludeTeacherStudentGroup: api.data.excludeTeacherStudentGroup,\r\n excludeTeacherSubject: api.data.excludeTeacherSubject,\r\n excludeTeacherTime: api.data.excludeTeacherTime,\r\n },\r\n courseList: api.data.courselist.map((item) => {\r\n return {\r\n id: item.id,\r\n name: item.name,\r\n subjectList: item.subjectList.map((e) => {\r\n return {\r\n id: e.id,\r\n name: e.name,\r\n abbreviation: item.name,\r\n validRoomList: e.validRoomList,\r\n preSubjectList: e.preSubjectList\r\n };\r\n }),\r\n studentGroupList: item.studentGroupList.map((e) => {\r\n return {\r\n id: e.id,\r\n name: e.name,\r\n validTimeslotList: e.validTimeslotList\r\n\r\n };\r\n }),\r\n };\r\n }),\r\n validTeacherList: api.data.workload.filter(item => item.workload > 0).map((item) => {\r\n return {\r\n id: item.id,\r\n name: item.name,\r\n workload: item.workload,\r\n timeslotList: item.timeslotList,\r\n secondChoiceTimeslotList: item.secondChoiceTimeslotList,\r\n subjectList: item.subjectList,\r\n studentGroupList: item.studentGroupList,\r\n };\r\n }),\r\n },\r\n },\r\n};\r\n",
data: {
'&': '$$',
organization_id: '${centre_id}',
excludeRoomTime: '${excludeRoomTime}',
excludeTeacherStudentGroup:
'${excludeTeacherStudentGroup}',
excludeTeacherSubject:
'${excludeTeacherSubject}',
excludeTeacherTime: '${excludeTeacherTime}',
},
},
steps: [
{
title: '计划设置',
mode: 'normal',
controls: [
{
type: 'text',
label: '计划名称',
name: 'name',
mode: 'normal',
required: true,
},
{
type: 'date-range',
label: '日期范围',
name: 'date',
required: true,
format: 'YYYY-MM-DD',
minDate: '${semester.since}',
maxDate: '${semester.to}',
},
{
type: 'number',
label: '老师每周最多上课数量',
name: 'teacherFreqWeekly',
required: true,
mode: 'normal',
min: '1',
step: 1,
value: 10,
},
{
type: 'number',
label: '老师每天最多上课数量',
name: 'teacherFreqDaily',
mode: 'normal',
required: true,
min: '1',
step: 1,
value: 1,
},
{
type: 'number',
label: '老师同时上课数量',
name: 'maxConcurrentOfTeacher',
mode: 'normal',
required: true,
min: '1',
step: 1,
value: 1,
},
{
type: 'number',
name: 'maxConcurrent',
mode: 'normal',
label: '同一时段最多排课数量',
required: true,
value: 4,
min: '1',
step: 1,
},
{
type: 'number',
name: 'maxFreqWeekly',
mode: 'normal',
label: '学生每周上课数',
required: true,
value: 1,
min: '1',
step: 1,
},
{
type: 'switch',
name: 'fixTeacherMode',
mode: 'normal',
label: '',
required: true,
value: false,
option: ' 教师跟班模式',
optionAtLeft: false,
trueValue: true,
falseValue: false,
},
],
},
{
title: '资源设置',
controls: [
{
type: 'course-list',
label: '课程',
name: 'courselist',
source: {
method: 'get',
url: 'rest/courses?select=id,name,course2projects(projects(id,name,validRoomList,preSubjectList)),course2orgs(orgs(id,name, validTimeslotList:timeslot_class_id_fkey(id,date,period:periods(id,name,startTime:start_time,endTime:end_time))))&organization_id=eq.${centre_id}&semester_id=eq.${currentSemester}&order=code',
},
required: true,
multiple: true,
},
],
mode: 'normal',
},
{
mode: 'normal',
title: '工作量设置',
controls: [
{
type: 'workload',
name: 'workload',
source:
'rest/users?select=id,name:name,role,subjectList,studentGroupList,user_orgs(organization_id),timeslotList:timeslot_teacher_id_fkey(id,date,period:periods(id,name,startTime:start_time,endTime:end_time)),secondChoiceTimeslotList:timeslot_teacher_secondary_id_fkey(id,date,period:periods(id,name,startTime:start_time,endTime:end_time))&role=eq.teacher&user_orgs.organization_id=eq.${centre_id}&order=code',
},
],
},
],
mode: 'horizontal',
name: 'stepper',
actionNextSaveLabel: '下一步',
initApi: {
method: 'get',
url: 'rest/schedule_plans?id=eq.${id}',
adaptor:
'const data = payload.data.items[0]\r\nreturn {\r\n data: {\r\n ...data.problem,\r\n ...data.problem.problemConfig,\r\n courselist: data.problem.courseList,\r\n workload: data.problem.validTeacherList,\r\n id: data.id,\r\n name: data.name,\r\n date: data.date\r\n }\r\n}',
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
actions: [],
data: {
'&': '$$',
name: '${name}',
maxConcurrent:
'${config.problemConfig.maxConcurrent}',
teacherFreqDaily:
'${config.problemConfig.teacherFreqDaily}',
date: '${config.date}',
maxConcurrentOfTeacher:
'${config.problemConfig.maxConcurrentOfTeacher}',
maxFreqWeekly:
'${config.problemConfig.maxFreqWeekly}',
fixTeacherMode:
'${config.problemConfig.fixTeacherMode}',
courselist: '${config.courseList}',
workload: '${config.validTeacherList}',
centre_id: '${centre_id}',
id: '${id}',
currentSemester: '${currentSemester}',
semester: '${semester}',
teacherFreqWeekly:
'${config.problemConfig.teacherFreqWeekly}',
},
},
size: 'sm',
icon: 'fa fa-pencil',
tooltip: '修改计划配置',
tooltipPlacement: 'bottom',
className: 'no-border',
},
{
type: 'button',
visibleOn: '!this.is_current',
label: '删除',
actionType: 'ajax',
size: 'sm',
confirmText: '确认删除该计划?',
api: {
method: 'delete',
url: 'rest/schedule_plans?id=eq.${id}',
},
reload: 'plancrud',
level: 'danger',
icon: 'fa fa-trash',
},
{
type: 'tpl',
tpl: '当前计划',
inline: true,
visibleOn: 'this.is_current',
},
],
},
],
messages: {},
name: 'plancrud',
syncLocation: false,
defaultParams: {},
filter: null,
headerToolbar: [{ type: 'bulk-actions' }],
perPageAvailable: '',
},
],
messages: {},
api: {
method: 'get',
url: 'rest/config?key=eq.system',
adaptor:
'let config = payload.data.items && payload.data.items.length > 0 ? payload.data.items[0].value : null\r\nreturn {\r\n data: {\r\n excludeRoomTime: config ? config.excludeRoomTime : true,\r\n excludeTeacherStudentGroup: config ? config.excludeTeacherStudentGroup : true,\r\n excludeTeacherSubject: config ? config.excludeTeacherSubject : true,\r\n excludeTeacherTime: config ? config.excludeTeacherTime : true,\r\n }\r\n}',
},
},
],
closeOnEsc: true,
showCloseButton: true,
actions: [],
},
size: 'xs',
icon: 'fa fa-list',
tooltip: '查看计划列表',
tooltipPlacement: 'bottom',
className: 'no-border',
},
planstartbutton: {
type: 'button',
actionType: 'ajax',
reload: 'window',
target: '',
blank: true,
api: {
method: 'post',
url: 'rest/timetable/${id}/solve',
dataType: 'json',
},
},
planstopbutton: {
type: 'button',
actionType: 'ajax',
reload: 'window',
target: '',
confirmText: '确认停止计算?',
blank: true,
api: {
method: 'post',
url: 'rest/timetable/${id}/stopSolving',
dataType: 'json',
},
},
plansavebutton: {
type: 'button',
actionType: 'ajax',
reload: '',
target: '',
confirmText: '确认生效该计划安排的场次?',
blank: true,
api: {
method: 'post',
url: 'rest/schedules',
dataType: 'json',
requestAdaptor:
'return {\r\n ...api,\r\n data: api.data.schedules\r\n}',
},
},
},
],
title: '',
messages: {},
bodyClassName: 'p-none flex',
className: 'bg-white h-full',
toolbar: [],
aside: [],
};
export { schema };

View File

@@ -0,0 +1,280 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
submitOnChange: true,
reload: 'class_set?id=$orgSelect&org=$org',
wrapWithPanel: false,
body: [
{
type: 'tree-select',
name: 'orgSelect',
label: '查询',
mode: 'inline',
size: 'lg',
clearable: true,
searchable: true,
source: {
method: 'get',
url: 'rest/orgs?select=*,course2orgs(*,courses(*))&path=cd.root&course2orgs.courses.semester_id=eq.${currentSemester}&type=in.(school,centre,faculty)&order=name,code',
adaptor:
"const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet school = payload.data.items.find(x => x.type === 'school');\r\nreturn {\r\n ...payload,\r\n data: [{\r\n label: school.name,\r\n value: `${school.id}`,\r\n children: recursive(school.id)\r\n }]\r\n}\r\n",
},
inputClassName: 'm-l-xs',
options: [],
initiallyOpen: false,
unfoldedLevel: 2,
multiple: true,
joinValues: true,
onlyChildren: true,
withChildren: true,
extractValue: false,
heightAuto: true,
virtualThreshold: false,
id: 'u:3224d44c5c5d',
},
{
type: 'input-text',
label: '组织或组织编号',
placeholder: '',
name: 'org',
size: 'md',
clearable: true,
inputClassName: 'm-l-xs',
id: 'u:89966cee9c0b',
},
],
mode: 'inline',
debug: false,
id: 'u:254f488290d5',
feat: 'Insert',
},
{ type: 'divider', lineStyle: 'solid', id: 'u:c6f2f106da08' },
{
type: 'crud',
api: {
method: 'get',
url: 'rest/orgs?select=*,course2orgs!inner(*,courses!inner(*))&or=(name.like.*${org}*,code.like.*${org}*)&path=cd.root&type=in.(class,studentgroup,reelecclass)&course2orgs.courses.semester_id=eq.$currentSemester&order=name,code',
data: { page: '${page}', perPage: '${perPage}', parent: 'in.(${id})' },
adaptor: '',
requestAdaptor:
"if (api.query.parent === 'in.()') {\r\n api.url = api.url.replace('&parent=in.%28%29', '')\r\n}\r\n\r\nreturn {\r\n ...api\r\n}",
},
messages: {},
mode: 'cards',
card: {
type: 'card',
header: { title: '$name', subTitle: '$code' },
body: [],
toolbar: [
{
type: 'button',
label: '可用时间',
icon: 'fa fa-group',
iconClassName: '',
reload: '',
id: 'u:2681ac5e5b28',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
body: [
{
type: 'timeslot-renderer',
api: {
method: 'get',
url: 'rest/timeslots?select=id,date,organization_id,class_id,periods(id,start_time,end_time)&class_id=eq.${id}&organization_id=eq.${centre_id}',
adaptor:
'return {\r\n data: {\r\n timeslots: payload.data.items.map(item => {\r\n return {\r\n id: item.id,\r\n date: item.date,\r\n semester_id: item.semester_id,\r\n organization_id: item.organization_id,\r\n class_id: item.class_id,\r\n period: item.periods\r\n }\r\n })\r\n }\r\n}',
},
copy: {
type: 'button',
label: '按钮',
actionType: 'dialog',
dialog: {
title: '复制本周设置到其他周',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/rpc/timeslots',
dataType: 'json',
headers: { Prefer: 'params=single-object' },
requestAdaptor:
'return {\r\n ...api,\r\n data: api.data.timeslotcopy\r\n}',
},
id: 'u:f4b826ee7ac0',
body: [
{
type: 'timeslot-copy',
name: 'timeslotcopy',
mode: 'normal',
target: 'class',
id: 'u:ff2c4be14afe',
},
],
actions: [
{ type: 'submit', label: '提交', primary: true },
],
feat: 'Insert',
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
id: 'u:c7de31e65b52',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:9451198740aa',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:f874015df55f',
},
],
},
},
target: 'class',
id: 'u:8467642842c2',
},
],
title: '班级时间设置',
closeOnEsc: true,
showCloseButton: true,
size: 'md',
actions: [],
id: 'u:5ddbcf531f34',
},
},
],
},
},
},
{
type: 'button',
label: ' 预览',
icon: 'fa fa-eye',
level: 'link',
size: 'md',
iconClassName: '',
id: 'u:f44e5cfab0c3',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '班级可用时间',
body: [
{
type: 'tpl',
tpl: '<span class="label" style="background: #e5e7eb;">X</span>\n<span style="vertical-align: middle;">不可用时间</span>\n<span class="label label-info">X</span>\n<span style="vertical-align: middle;">可用时间</span>',
inline: false,
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/periods?select=*,timeslots(*)&timeslots.organization_id=eq.$centre_id&timeslots.date=gte.${semester.since}&timeslots.date=lte.${semester.to}&timeslots.class_id=eq.$id&order=code',
adaptor:
"let items = []\r\nconst week_list = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']\r\nconst oneday = 24 * 60 * 60 * 1000\r\nconst get_week_num = (start, end) => {\r\n let s = new Date(start)\r\n let s_ms = s.getTime()\r\n let s_day = s.getDay() ? s.getDay() : 7\r\n s.setTime(s_ms - (s_day - 1) * oneday)\r\n s_ms = s.getTime()\r\n let e = new Date(end)\r\n let e_ms = e.getTime()\r\n let e_day = e.getDay() ? e.getDay() : 7\r\n return {\r\n sem_start: s,\r\n day: e_day,\r\n week_num: Math.ceil(((e_ms - s_ms) / oneday + 1) / 7)\r\n }\r\n}\r\nlet start = /(?<=\\[date\\]\\[0\\]=gte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nlet end = /(?<=\\[date\\]\\[1\\]=lte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nconst { sem_start, week_num } = get_week_num(start, end)\r\nfor (let i = 0; i < 7; i++) {\r\n let date_temp = new Date(sem_start)\r\n let date_temp_ms = date_temp.getTime()\r\n payload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n }).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n }).forEach(item => {\r\n let week_num_list = []\r\n for (let j = 0; j < week_num; j++) {\r\n date_temp.setTime(date_temp_ms + j * 7 * oneday + i * oneday)\r\n week_num_list.push({\r\n week: j + 1,\r\n date: null,\r\n tpl: \"<span title='\".concat(date_temp.toISOString().split('T')[0], \"' class='label' style='background: #e5e7eb;'>\", j + 1, \"</span>\")\r\n })\r\n }\r\n items.push({\r\n day: {\r\n lab: week_list[i],\r\n val: i + 1\r\n },\r\n period: {\r\n lab: item.name,\r\n val: item.id\r\n },\r\n weeks: week_num_list\r\n })\r\n })\r\n}\r\nconst row_mem = payload.data.items.length\r\npayload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n}).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n}).forEach((item, index) => {\r\n item.timeslots && item.timeslots.forEach(i => {\r\n const { day, week_num } = get_week_num(sem_start, i.date)\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].date = i.date\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].id = i.id\r\n const week = items[(day - 1) * row_mem + index].weeks[week_num - 1].week\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span title='\".concat(i.date, \"' class='label label-primary'>\", week, \"</span>\")\r\n })\r\n})\r\nreturn {\r\n ...payload,\r\n data: {\r\n items: items\r\n }\r\n}",
requestAdaptor: '',
},
messages: {},
columns: [
{
name: 'day.lab',
label: '',
type: 'text',
inline: true,
className: 'common-padding',
},
{
type: 'text',
label: '',
name: 'period.lab',
placeholder: '-',
className: 'common-padding',
inline: true,
},
{
name: 'weeks',
label: '',
type: 'each',
items: [
{
type: 'tpl',
tpl: '<%= data.tpl %>',
inline: true,
className: 'm-r-xs',
},
],
placeholder: '-',
className: 'common-padding',
},
],
combineNum: 1,
columnsTogglable: false,
syncLocation: false,
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
data: null,
bodyClassName: 'p-t-none',
actions: [],
},
},
],
},
},
},
],
actionsCount: 3,
id: 'u:140b3d90b196',
},
syncLocation: false,
placeholder: '暂无数据',
columnsCount: 4,
name: 'class_set',
headerToolbar: [],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
perPageAvailable: [20, 40, 80],
defaultParams: { perPage: 20 },
bulkActions: [],
itemActions: [],
loadDataOnce: false,
id: 'u:06cd3a22ee0c',
},
],
messages: {},
title: '',
cssVars: { '--Checkbox-onDisabled-color': 'transparent' },
id: 'u:1cbc663fae2b',
};
export { schema };

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,289 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
title: '表单',
className: 'm-b-sm',
submitOnChange: true,
submitOnInit: true,
reload:
'manage_list?project_id=$project_id&teacher_id=$teacher_id&location_id=$location_id&date=$date&period_id=$period_id',
wrapWithPanel: false,
mode: 'inline',
name: 'filter',
body: [
{
type: 'group',
body: [
{
type: 'select',
label: '项目',
name: 'project_id',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/projects?organization_id=eq.$centre_id&semester_id=eq.$currentSemester&order=name',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
},
],
},
{
type: 'group',
body: [
{
type: 'input-date-range',
name: 'date',
mode: 'inline',
label: '日期',
clearable: true,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
value: '',
format: 'YYYY-MM-DD',
ranges:
'yesterday,today,7daysago,prevweek,thismonth,prevmonth,prevquarter',
},
{
label: '节次',
type: 'select',
name: 'period_id',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/periods?order=name',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}`,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
labelClassName: '',
},
],
},
{
type: 'group',
body: [
{
type: 'select',
name: 'location_id',
mode: 'inline',
label: '地点',
clearable: true,
source: {
method: 'get',
url: 'rest/locations?organization_id=eq.$centre_id&order=name',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
checkAll: false,
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
className: 'm-l-sm',
},
{
type: 'select',
name: 'teacher_id',
mode: 'inline',
className: 'm-l-sm',
label: '教师',
clearable: true,
inputClassName: 'm-l-xs',
size: 'md',
checkAll: false,
source: {
method: 'get',
url: 'rest/users?role=eq.teacher&order=name',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: `${item.name}(${item.code})`,\r\n value: "eq.".concat(item.id)\r\n }\r\n })\r\n}',
},
searchable: true,
},
],
mode: '',
subFormMode: '',
gap: '',
className: '',
},
],
},
{
type: 'divider',
lineStyle: 'solid',
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/user2projects?select=*,att_status_text:user2project_att_status_dict_fk(dictvalue),schedule_status_text:user2project_schedule_status_dict_fk(dictvalue),users_ex:users_ex!user2project_student_id_fkey!inner(*),schedule_stats!inner(*)&semester_id=eq.${currentSemester}&schedule_status=neq.canceled&users_ex.role=eq.student&users_ex.org_type=eq.class&schedule_stats.organization_id=eq.${centre_id}',
data: {
page: '${page}',
perPage: '${perPage}',
'&': '$$',
},
adaptor:
"let supportDateTimeFormat = typeof(Intl.DateTimeFormat) === 'function'\r\n\r\npayload.data.items.forEach(item => {\r\n item.schedule_stats.day = supportDateTimeFormat ? `${item.schedule_stats.date}${new Intl.DateTimeFormat('zh-CN', { weekday: 'short'}).format(new Date(item.schedule_stats.date))}` : item.schedule_stats.date;\r\n})\r\n\r\nreturn {\r\n ...payload\r\n}",
requestAdaptor:
"if (api.body.project_id === '') {\r\n api.url = api.url.replace('&project_id=', '')\r\n}\r\n\r\nif (api.body.date === '') {\r\n api.url = api.url.replace('&date=', '')\r\n} else {\r\n let date_former = api.body.date && api.body.date.split(',')[0]\r\n let date_latter = api.body.date && api.body.date.split(',')[1]\r\n\r\n if (date_former === date_latter) {\r\n api.url = api.url.replace(\r\n '&date=' + date_former + '%2C' + date_latter,\r\n '&schedule_stats.date=eq.' + date_former\r\n )\r\n } else {\r\n api.url = api.url.replace(\r\n '&date=' + date_former + '%2C' + date_latter,\r\n '&schedule_stats.date=gte.' + date_former + '&schedule_stats.date=lte.' + date_latter\r\n )\r\n }\r\n}\r\n\r\nif (api.body.period_id === '') {\r\n api.url = api.url.replace('&period_id=', '')\r\n} else {\r\n api.url = api.url.replace(\r\n '&period_id=' + api.body.period_id,\r\n '&schedule_stats.period_id=' + api.body.period_id\r\n )\r\n}\r\n\r\nif (api.body.location_id === '') {\r\n api.url = api.url.replace('&location_id=', '')\r\n} else {\r\n api.url = api.url.replace(\r\n '&location_id=' + api.body.location_id,\r\n '&schedule_stats.location_id=' + api.body.location_id\r\n )\r\n}\r\n\r\nif (api.body.teacher_id === '') {\r\n api.url = api.url.replace('&teacher_id=', '')\r\n} else {\r\n api.url = api.url.replace(\r\n '&teacher_id=' + api.body.teacher_id,\r\n '&schedule_stats.teacher_id=' + api.body.teacher_id\r\n )\r\n}\r\n\r\nreturn api;",
},
columns: [
{
name: 'schedule_stats.project_name',
label: '项目',
type: 'text',
placeholder: '-',
},
{
name: 'schedule_stats.day',
label: '日期',
type: 'text',
placeholder: '-',
},
{
type: 'text',
label: '节次',
name: 'schedule_stats.period_name',
placeholder: '-',
},
{
type: 'text',
label: '地点',
name: 'schedule_stats.location_name',
placeholder: '-',
},
{
type: 'text',
label: '教师',
name: 'schedule_stats.teacher_name',
placeholder: '-',
},
{
type: 'text',
label: '学号',
name: 'users_ex.code',
placeholder: '-',
},
{
type: 'text',
label: '姓名',
name: 'users_ex.name',
placeholder: '-',
},
{
type: 'text',
label: '班级',
name: 'users_ex.org_name',
placeholder: '-',
},
{
type: 'text',
label: '选课状态',
name: 'schedule_status_text.dictvalue',
placeholder: '-',
},
{
type: 'text',
label: '考勤状态',
name: 'att_status_text.dictvalue',
placeholder: '-',
},
],
messages: {},
syncLocation: false,
pageField: 'page',
perPageField: 'perPage',
headerToolbar: [
{
type: 'export-excel',
align: 'right',
label: '导出',
icon: 'fa fa-download',
level: 'primary',
api: {
method: 'get',
url: 'rest/user2projects?select=*,att_status_text:user2project_att_status_dict_fk(dictvalue),schedule_status_text:user2project_schedule_status_dict_fk(dictvalue),users_ex:users_ex!user2project_student_id_fkey!inner(*),schedule_stats!inner(*)&semester_id=eq.${currentSemester}&schedule_status=neq.canceled&users_ex.role=eq.student&users_ex.org_type=eq.class&schedule_stats.organization_id=eq.${centre_id}',
data: {
project_id: '$project_id',
date: '$date',
period_id: '$period_id',
location_id: '$location_id',
teacher_id: '$teacher_id',
},
requestAdaptor:
"if (api.body.project_id === '') {\r\n api.url = api.url.replace('&project_id=', '')\r\n}\r\n\r\nif (api.body.date === '') {\r\n api.url = api.url.replace('&date=', '')\r\n} else {\r\n let date_former = api.body.date && api.body.date.split(',')[0]\r\n let date_latter = api.body.date && api.body.date.split(',')[1]\r\n\r\n if (date_former === date_latter) {\r\n api.url = api.url.replace(\r\n '&date=' + date_former + '%2C' + date_latter,\r\n '&schedule_stats.date=eq.' + date_former\r\n )\r\n } else {\r\n api.url = api.url.replace(\r\n '&date=' + date_former + '%2C' + date_latter,\r\n '&schedule_stats.date=gte.' + date_former + '&schedule_stats.date=lte.' + date_latter\r\n )\r\n }\r\n}\r\n\r\nif (api.body.period_id === '') {\r\n api.url = api.url.replace('&period_id=', '')\r\n} else {\r\n api.url = api.url.replace(\r\n '&period_id=' + api.body.period_id,\r\n '&schedule_stats.period_id=' + api.body.period_id\r\n )\r\n}\r\n\r\nif (api.body.location_id === '') {\r\n api.url = api.url.replace('&location_id=', '')\r\n} else {\r\n api.url = api.url.replace(\r\n '&location_id=' + api.body.location_id,\r\n '&schedule_stats.location_id=' + api.body.location_id\r\n )\r\n}\r\n\r\nif (api.body.teacher_id === '') {\r\n api.url = api.url.replace('&teacher_id=', '')\r\n} else {\r\n api.url = api.url.replace(\r\n '&teacher_id=' + api.body.teacher_id,\r\n '&schedule_stats.teacher_id=' + api.body.teacher_id\r\n )\r\n}\r\n\r\nreturn api;",
},
exportColumns: [
{
label: '项目',
name: 'schedule_stats.project_name',
},
{
name: 'schedule_stats.date',
label: '日期',
},
{
name: 'schedule_stats.period_name',
label: '节次',
},
{
name: 'schedule_stats.location_name',
label: '地点',
},
{
name: 'schedule_stats.teacher_name',
label: '教师',
},
{
name: 'users_ex.code',
label: '学号',
},
{
name: 'users_ex.name',
label: '姓名',
},
{
name: 'users_ex.org_name',
label: '班级',
},
{
name: 'schedule_status_text.dictvalue',
label: '选课状态',
},
{
name: 'att_status_text.dictvalue',
label: '考勤状态',
},
],
filename: '选课名单',
},
],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
name: 'manage_list',
perPageAvailable: [10, 20, 30, 40, 50],
placeholder: '-',
},
],
title: '',
messages: {},
bodyClassName: '',
className: '',
headerClassName: '',
};
export { schema };

View File

@@ -0,0 +1,332 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
className: '',
submitOnChange: true,
reload: 'loc_set?locationSelect=$locationSelect',
wrapWithPanel: false,
body: [
{
type: 'select',
name: 'locationSelect',
label: '查询',
mode: 'inline',
size: 'md',
clearable: true,
searchable: true,
source: {
method: 'get',
url: 'rest/locations?organization_id=eq.$centre_id&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
},
inputClassName: 'm-l-xs',
checkAll: false,
perPageAvailable: [10],
options: [],
},
],
},
{ type: 'divider', lineStyle: 'solid' },
{
type: 'tpl',
className: 'm-b-sm',
tpl: '<% if (data.excludeRoomTime) { %>\n <span class="label label-warning">选择不可用时间<span>\n<% } else { %>\n <span class="label label-info">选择可用时间<span>\n<% } %>',
inline: false,
},
{
type: 'crud',
syncLocation: false,
api: {
method: 'get',
url: 'rest/locations?organization_id=eq.$centre_id&order=code',
data: { page: '$page', perPage: '$perPage', id: 'eq.$locationSelect' },
requestAdaptor:
"if (api.query.id === 'eq.') {\r\n api.url = api.url.replace('&id=eq.', '')\r\n}\r\n\r\nreturn {\r\n ...api\r\n}",
adaptor: '',
replaceData: false,
},
perPageAvailable: [20, 40, 80],
messages: {},
name: 'loc_set',
defaultParams: { perPage: 20 },
card: {
type: 'card',
header: { title: '$name', subTitle: '$code' },
body: [{ type: 'tpl', tpl: '${intro|raw}' }],
toolbar: [
{
type: 'button',
label: '时间选择',
icon: 'fa fa-group',
iconClassName: '',
reload: '',
id: 'u:f67c49dcfe29',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
body: [
{
type: 'timeslot-renderer',
api: {
method: 'get',
url: 'rest/timeslots?select=id,date,organization_id,location_id,periods(id,start_time,end_time)&location_id=eq.${id}&organization_id=eq.${centre_id}',
adaptor:
'return {\r\n data: {\r\n timeslots: payload.data.items.map(item => {\r\n return {\r\n id: item.id,\r\n date: item.date,\r\n semester_id: item.semester_id,\r\n organization_id: item.organization_id,\r\n location_id: item.location_id,\r\n period: item.periods\r\n }\r\n })\r\n }\r\n}',
},
copy: {
type: 'button',
label: '按钮',
actionType: 'dialog',
dialog: {
title: '复制本周设置到其他周',
body: [
{
type: 'form',
title: '表单',
controls: [
{
type: 'timeslot-copy',
name: 'timeslotcopy',
mode: 'normal',
target: 'location',
},
],
api: {
method: 'post',
url: 'rest/rpc/timeslots',
dataType: 'json',
headers: { Prefer: 'params=single-object' },
requestAdaptor:
'return {\r\n ...api,\r\n data: api.data.timeslotcopy\r\n}',
},
},
],
type: 'dialog',
closeOnEsc: false,
showCloseButton: true,
},
},
target: 'location',
},
],
title: '场地时间设置',
closeOnEsc: true,
showCloseButton: true,
size: 'md',
actions: [],
},
},
],
},
},
},
{
type: 'button',
label: '预览',
icon: 'fa fa-eye',
level: 'link',
size: 'md',
iconClassName: '',
visibleOn: '!this.excludeRoomTime',
id: 'u:c8b5eb1749e3',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '场地可用时间',
body: [
{
type: 'tpl',
tpl: '<span class="label" style="background: #e5e7eb;">X</span>\n<span style="vertical-align: middle;">不可用时间</span>\n<span class="label label-info">X</span>\n<span style="vertical-align: middle;">可用时间</span>',
inline: false,
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/periods?select=*,timeslots(*)&timeslots.organization_id=eq.$centre_id&timeslots.date=gte.${semester.since}&timeslots.date=lte.${semester.to}&timeslots.location_id=eq.$id&order=code',
adaptor:
"let items = []\r\nconst week_list = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']\r\nconst oneday = 24 * 60 * 60 * 1000\r\nconst get_week_num = (start, end) => {\r\n let s = new Date(start)\r\n let s_ms = s.getTime()\r\n let s_day = s.getDay() ? s.getDay() : 7\r\n s.setTime(s_ms - (s_day - 1) * oneday)\r\n s_ms = s.getTime()\r\n let e = new Date(end)\r\n let e_ms = e.getTime()\r\n let e_day = e.getDay() ? e.getDay() : 7\r\n return {\r\n sem_start: s,\r\n day: e_day,\r\n week_num: Math.ceil(((e_ms - s_ms) / oneday + 1) / 7)\r\n }\r\n}\r\nlet start = /(?<=\\[date\\]\\[0\\]=gte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nlet end = /(?<=\\[date\\]\\[1\\]=lte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nconst { sem_start, week_num } = get_week_num(start, end)\r\nfor (let i = 0; i < 7; i++) {\r\n let date_temp = new Date(sem_start)\r\n let date_temp_ms = date_temp.getTime()\r\n payload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n }).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n }).forEach(item => {\r\n let week_num_list = []\r\n for (let j = 0; j < week_num; j++) {\r\n date_temp.setTime(date_temp_ms + j * 7 * oneday + i * oneday)\r\n week_num_list.push({\r\n week: j + 1,\r\n date: null,\r\n tpl: \"<span title='\".concat(date_temp.toISOString().split('T')[0], \"' class='label' style='background: #e5e7eb;'>\", j + 1, \"</span>\")\r\n })\r\n }\r\n items.push({\r\n day: {\r\n lab: week_list[i],\r\n val: i + 1\r\n },\r\n period: {\r\n lab: item.name,\r\n val: item.id\r\n },\r\n weeks: week_num_list\r\n })\r\n })\r\n}\r\nconst row_mem = payload.data.items.length\r\npayload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n}).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n}).forEach((item, index) => {\r\n item.timeslots && item.timeslots.forEach(i => {\r\n const { day, week_num } = get_week_num(sem_start, i.date)\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].date = i.date\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].id = i.id\r\n const week = items[(day - 1) * row_mem + index].weeks[week_num - 1].week\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span title='\".concat(i.date, \"' class='label label-primary'>\", week, \"</span>\")\r\n })\r\n})\r\nreturn {\r\n ...payload,\r\n data: {\r\n items: items\r\n }\r\n}\r\n",
requestAdaptor: '',
},
messages: {},
columns: [
{
name: 'day.lab',
label: '',
type: 'text',
inline: true,
className: 'common-padding',
},
{
type: 'text',
label: '',
name: 'period.lab',
placeholder: '-',
className: 'common-padding',
inline: true,
},
{
name: 'weeks',
label: '',
type: 'each',
items: [
{
type: 'tpl',
tpl: '<%= data.tpl %>',
inline: true,
className: 'm-r-xs',
},
],
placeholder: '-',
className: 'common-padding',
},
],
combineNum: 1,
columnsTogglable: false,
syncLocation: false,
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
data: null,
bodyClassName: 'p-t-none',
actions: [],
},
},
],
},
},
},
{
type: 'button',
label: '预览',
icon: 'fa fa-eye',
level: 'link',
size: 'md',
iconClassName: '',
visibleOn: 'this.excludeRoomTime',
id: 'u:fb5fb80671b4',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '场地可用时间',
body: [
{
type: 'tpl',
tpl: '<span class="label" style="background: #e5e7eb;">X</span>\n<span style="vertical-align: middle;">不可用时间</span>\n<span class="label label-info">X</span>\n<span style="vertical-align: middle;">可用时间</span>',
inline: false,
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/periods?select=*,timeslots(*)&timeslots.organization_id=eq.$centre_id&timeslots.date=gte.${semester.since}&timeslots.date=lte.${semester.to}&timeslots.location_id=eq.$id&order=code',
adaptor:
"let items = []\r\nconst week_list = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']\r\nconst oneday = 24 * 60 * 60 * 1000\r\nconst get_week_num = (start, end) => {\r\n let s = new Date(start)\r\n let s_ms = s.getTime()\r\n let s_day = s.getDay() ? s.getDay() : 7\r\n s.setTime(s_ms - (s_day - 1) * oneday)\r\n s_ms = s.getTime()\r\n let e = new Date(end)\r\n let e_ms = e.getTime()\r\n let e_day = e.getDay() ? e.getDay() : 7\r\n return {\r\n sem_start: s,\r\n day: e_day,\r\n week_num: Math.ceil(((e_ms - s_ms) / oneday + 1) / 7)\r\n }\r\n}\r\nlet start = /(?<=\\[date\\]\\[0\\]=gte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nlet end = /(?<=\\[date\\]\\[1\\]=lte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nconst { sem_start, week_num } = get_week_num(start, end)\r\nfor (let i = 0; i < 7; i++) {\r\n let date_temp = new Date(sem_start)\r\n let date_temp_ms = date_temp.getTime()\r\n payload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n }).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n }).forEach(item => {\r\n let week_num_list = []\r\n for (let j = 0; j < week_num; j++) {\r\n date_temp.setTime(date_temp_ms + j * 7 * oneday + i * oneday)\r\n week_num_list.push({\r\n week: j + 1,\r\n date: null,\r\n tpl: \"<span title='\".concat(date_temp.toISOString().split('T')[0], \"' class='label label-info'>\", j + 1, \"</span>\")\r\n })\r\n }\r\n items.push({\r\n day: {\r\n lab: week_list[i],\r\n val: i + 1\r\n },\r\n period: {\r\n lab: item.name,\r\n val: item.id\r\n },\r\n weeks: week_num_list\r\n })\r\n })\r\n}\r\nconst row_mem = payload.data.items.length\r\npayload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n}).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n}).forEach((item, index) => {\r\n item.timeslots && item.timeslots.forEach(i => {\r\n const { day, week_num } = get_week_num(sem_start, i.date)\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].date = i.date\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].id = i.id\r\n const week = items[(day - 1) * row_mem + index].weeks[week_num - 1].week\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span class='label' style='background: #e5e7eb;'>\".concat(week, \"</span>\")\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span title='\".concat(i.date, \"' class='label' style='background: #e5e7eb;'>\", week, \"</span>\")\r\n })\r\n})\r\nreturn {\r\n ...payload,\r\n data: {\r\n items: items\r\n }\r\n}",
requestAdaptor: '',
},
messages: {},
columns: [
{
name: 'day.lab',
label: '',
type: 'text',
inline: true,
className: 'common-padding',
},
{
type: 'text',
label: '',
name: 'period.lab',
placeholder: '-',
className: 'common-padding',
},
{
name: 'weeks',
label: '',
type: 'each',
items: [
{
type: 'tpl',
tpl: '<%= data.tpl %>',
inline: true,
className: 'm-r-xs',
},
],
placeholder: '-',
className: 'common-padding',
},
],
combineNum: 1,
columnsTogglable: false,
syncLocation: false,
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
data: null,
bodyClassName: 'p-t-none',
actions: [],
},
},
],
},
},
},
],
actionsCount: 3,
},
mode: 'cards',
headerToolbar: [],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
columnsCount: 4,
placeholder: '暂无数据',
pageField: 'page',
perPageField: 'perPage',
initApi: '',
initFetch: '',
checkAll: false,
},
],
messages: {},
initApi: {
method: 'get',
url: 'rest/config?key=eq.system&organization_id=eq.${centre_id}',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n excludeRoomTime: payload.data.items[0].value.excludeRoomTime\r\n }\r\n}',
},
title: '',
cssVars: { '--Checkbox-onDisabled-color': 'transparent' },
};
export { schema };

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,373 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
id: 'u:a2be39c9a42e',
className: '',
submitOnChange: true,
reload: 'pro_set?projectSelect=$projectSelect',
wrapWithPanel: false,
body: [
{
type: 'select',
id: 'u:fb8743dd5368',
name: 'projectSelect',
label: '查询',
mode: 'inline',
size: 'md',
clearable: true,
searchable: true,
source: {
method: 'get',
url: 'rest/projects?select=*,course2projects!inner(*)&organization_id=eq.$centre_id&semester_id=eq.$currentSemester&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
},
inputClassName: 'm-l-xs',
checkAll: false,
perPageAvailable: [10],
multiple: false,
},
],
feat: 'Insert',
},
{
type: 'divider',
lineStyle: 'solid',
id: 'u:44b9b52eb026',
},
{
type: 'tpl',
id: 'u:627706c838e0',
className: 'm-b-sm',
tpl: '<span class="label label-warning">* 开头的项目为“自由安排”项目</span>',
inline: false,
},
{
type: 'crud',
syncLocation: false,
api: {
method: 'get',
url: 'rest/projects?select=*,course2projects!inner(*)&semester_id=eq.$currentSemester&organization_id=eq.$centre_id&order=code',
data: {
page: '${page}',
perPage: '${perPage}',
id: 'eq.${projectSelect}',
},
requestAdaptor:
"if (api.query.id === 'eq.') {\r\n api.url = api.url.replace('&id=eq.', '')\r\n}\r\n\r\nreturn {\r\n ...api\r\n}",
adaptor:
"return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n if (item.free_schedule) {\r\n return {\r\n ...item,\r\n name: '* '.concat(item.name),\r\n select_pre: item.pre_list.map(item => {\r\n return ''.concat(item)\r\n }),\r\n select_loc: item.location_list.map(item => {\r\n return ''.concat(item)\r\n }),\r\n select_team: item.team_list.map(item => {\r\n return ''.concat(item)\r\n }),\r\n select_tch: item.teacher_list.map(item => {\r\n return ''.concat(item)\r\n })\r\n }\r\n } else {\r\n return {\r\n ...item,\r\n select_pre: item.pre_list.map(item => {\r\n return ''.concat(item)\r\n }),\r\n select_loc: item.location_list.map(item => {\r\n return ''.concat(item)\r\n }),\r\n select_team: item.team_list.map(item => {\r\n return ''.concat(item)\r\n }),\r\n select_tch: item.teacher_list.map(item => {\r\n return ''.concat(item)\r\n })\r\n }\r\n }\r\n })\r\n }\r\n}\r\n",
},
bulkActions: [],
itemActions: [],
id: 'u:31c2c09e9a0c',
perPageAvailable: [6, 18, 36, 54],
messages: {},
name: 'pro_set',
defaultParams: {
perPage: '6',
},
card: {
type: 'card',
header: {
title: '$name',
subTitle: '$code',
},
body: [
{
label: '',
type: 'tpl',
tpl: '${intro|raw}',
inline: false,
id: 'u:c86359f007f5',
},
{
label: '',
type: 'button',
icon: 'fa fa-pencil text-info',
level: 'link',
size: 'md',
tooltip: '修改项目',
tooltipPlacement: 'top',
id: 'u:6a42142de051',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '修改项目',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/projects/$id',
data: {
code: '$code',
name: '$name',
intro: '$intro',
type: '$type',
free_schedule: '$free_schedule',
},
dataType: 'json',
},
body: [
{
type: 'input-text',
label: '编号',
name: 'code',
required: true,
mode: 'normal',
id: 'u:bcc3e879483c',
},
{
label: '实验',
type: 'input-text',
name: 'name',
required: true,
mode: 'normal',
id: 'u:c2c3f1827843',
},
{
label: '类型',
type: 'select',
name: 'type',
mode: 'normal',
options: [],
required: true,
checkAll: false,
source: {
method: 'get',
url: 'rest/dicts?select=dictkey,dictvalue&typecode=eq.012',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.dictvalue,\r\n value: item.dictkey\r\n }\r\n })\r\n }\r\n}',
},
id: 'u:f01b0c37f8d7',
},
{
type: 'checkbox',
name: 'free_schedule',
mode: 'normal',
option: '学生自主安排时间',
id: 'u:d31e5761e95a',
},
{
label: '描述',
type: 'input-rich-text',
name: 'intro',
mode: 'normal',
options: {
menubar: false,
},
id: 'u:5cb8ddd6777b',
},
],
id: 'u:63f0013c6e86',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:31c2c09e9a0c',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:b7dec6737fba',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:9aeb3899f407',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:2faa3f63df9d',
},
],
},
},
],
},
},
},
{
label: '',
type: 'divider',
id: 'u:f6de922f021a',
},
{
type: 'form',
label: '',
title: '表单',
api: {
method: 'patch',
url: 'rest/projects/$id',
requestAdaptor:
"return {\r\n ...api,\r\n data: {\r\n pre_list: api.data.select_pre[0] === '' ? [] : api.data.select_pre,\r\n location_list: api.data.select_loc[0] === '' ? [] : api.data.select_loc,\r\n team_list: api.data.select_team[0] === '' ? [] : api.data.select_team,\r\n teacher_list: api.data.select_tch[0] === '' ? [] : api.data.select_tch\r\n }\r\n}",
data: {
select_pre: '$select_pre',
select_loc: '$select_loc',
select_team: '$select_team',
select_tch: '$select_tch',
},
adaptor: '',
},
initApi: '',
wrapWithPanel: false,
submitOnChange: true,
resetAfterSubmit: false,
target: '',
reload: 'select_tch?select_team=$select_team',
messages: {},
body: [
{
label: '前置实验',
type: 'select',
name: 'select_pre',
options: [],
checkAll: true,
source: {
method: 'get',
url: 'rest/projects?id=neq.$id&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n\r\n}',
},
clearable: true,
creatable: false,
multiple: true,
joinValues: false,
defaultCheckAll: false,
checkAllLabel: '全选',
extractValue: true,
searchable: true,
id: 'u:e27e10d364a9',
},
{
type: 'select',
label: '可用场地',
options: [],
checkAll: true,
source: {
method: 'get',
url: 'rest/locations?order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n\r\n}',
},
clearable: true,
creatable: false,
multiple: true,
joinValues: false,
defaultCheckAll: false,
checkAllLabel: '全选',
extractValue: true,
name: 'select_loc',
searchable: true,
id: 'u:d2c3a600be4c',
},
{
type: 'select',
label: '教师团队',
options: [],
checkAll: true,
source: {
method: 'get',
url: 'rest/teams?order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n\r\n}',
},
clearable: true,
creatable: false,
multiple: true,
joinValues: false,
defaultCheckAll: false,
checkAllLabel: '全选',
extractValue: true,
name: 'select_team',
searchable: true,
id: 'u:4f1dbbaab280',
},
{
type: 'select',
label: '教师',
options: [],
checkAll: true,
source: {
method: 'get',
url: 'rest/team_members?select=users(id,name)&team_id=in.(${select_team})',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name ? item.name : item.users.name,\r\n value: item.id ? item.id : item.users.id\r\n }\r\n })\r\n}',
requestAdaptor:
"let url = api.url\r\nconst not_use_team = /team_id=in.%28%29/g.test(url)\r\nif (not_use_team) {\r\n url = url.replace(/team_members.+%29/g, 'users?role=eq.teacher')\r\n}\r\nreturn {\r\n ...api,\r\n url: url\r\n}",
},
clearable: true,
creatable: false,
multiple: true,
joinValues: false,
defaultCheckAll: false,
checkAllLabel: '全选',
extractValue: true,
name: 'select_tch',
searchable: true,
id: 'u:dfd2cad019a7',
},
],
id: 'u:eded6c20659c',
feat: 'Edit',
},
],
actions: [],
id: 'u:67ee5a75329d',
},
mode: 'cards',
headerToolbar: [],
footerToolbar: [
{
type: 'pagination',
},
{
type: 'switch-per-page',
},
{
type: 'statistics',
},
],
columnsCount: 3,
},
],
id: 'u:414b97a290cd',
messages: {},
initApi: '',
initFetch: '',
};
export { schema };

View File

@@ -0,0 +1,665 @@
const schema = {
type: 'page',
body: [
{
type: 'form',
submitOnChange: true,
reload: 'tea_set?tchSelect=$tchSelect',
wrapWithPanel: false,
body: [
{
type: 'select',
name: 'tchSelect',
label: '查询',
mode: 'inline',
size: 'md',
clearable: true,
searchable: true,
source: {
method: 'get',
url: 'rest/users_ex?select=id,code,name,project_list,class_list,org_id=eq.$centre_id&role=eq.teacher&order=code',
adaptor:
"return {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name.concat('(', item.code, ')'),\r\n value: item.id\r\n }\r\n })\r\n}",
},
inputClassName: 'm-l-xs',
options: [],
inline: true,
checkAll: false,
id: 'u:0ef31186ce73',
},
],
id: 'u:a09e49581a1e',
feat: 'Insert',
},
{ type: 'divider', lineStyle: 'solid', id: 'u:75cc046fc8ac' },
{
type: 'tpl',
className: 'm-b-sm',
tpl: '<% if (data.excludeTeacherTime) { %>\n <span class="label label-warning">选择不可用时间</span>\n<% } else { %>\n <span class="label label-info">选择可用时间</span>\n<% } %>\n<% if (data.excludeTeacherSubject) { %>\n <span class="label label-warning">选择不可任教实验</span>\n<% } else { %>\n <span class="label label-info">选择可任教实验</span>\n<% } %>\n<% if (data.excludeTeacherStudentGroup) { %>\n <span class="label label-warning">选择不可任教班级</span>\n<% } else { %>\n <span class="label label-info">选择可任教班级</span>\n<% } %>',
inline: false,
id: 'u:83a0a5c3b981',
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/users_ex?select=id,code,name,project_list,class_list,org_id=eq.$centre_id&role=eq.teacher&order=code',
data: { page: '$page', perPage: '$perPage', id: 'eq.$tchSelect' },
adaptor:
"return {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n select_project: item.project_list ? item.project_list.map(item => {\r\n return ''.concat(item)\r\n }) : [],\r\n select_class:item.class_list? item.class_list.map(item => {\r\n return ''.concat(item)\r\n }) : [],\r\n }\r\n })\r\n}",
requestAdaptor:
"if (api.query.id === 'eq.') {\r\n api.url = api.url.replace('&id=eq.', '')\r\n}\r\n\r\nreturn {\r\n ...api\r\n}",
replaceData: false,
},
messages: {},
mode: 'cards',
card: {
type: 'card',
header: { title: '$name', subTitle: '$code' },
body: [
{
type: 'form',
label: '',
api: {
method: 'patch',
url: 'rest/users/${id}',
requestAdaptor:
"return {\r\n ...api,\r\n data: {\r\n project_list: api.data.select_project[0] === '' ? [] : api.data.select_project,\r\n class_list: api.data.select_class[0] === '' ? [] : api.data.select_class\r\n }\r\n}",
data: {
select_project: '$select_project',
select_class: '$select_class',
},
},
title: '表单',
wrapWithPanel: false,
submitOnChange: true,
body: [
{
type: 'select',
label: '实验选择',
name: 'select_project',
options: [],
checkAll: true,
source: {
method: 'get',
url: 'rest/projects?organization_id=eq.${centre_id}&semester_id=eq.${currentSemester}&order=code',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n}',
},
clearable: true,
creatable: false,
multiple: true,
joinValues: false,
defaultCheckAll: false,
checkAllLabel: '全选',
extractValue: true,
visibleOn: '',
disabledOn:
"this.user.role !== 'admin' && this.user.role !== 'centreadmin' ",
searchable: true,
id: 'u:65fb5a416142',
},
{
type: 'tree-select',
label: '班级选择',
name: 'select_class',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/orgs?select=*,course2orgs(*,courses(*))&course2orgs.courses.semester_id=eq.$currentSemester&order=code',
adaptor:
"let school = payload.data.items.find(\r\n item => item.type === 'school'\r\n)\r\nconst recursive = id => {\r\n let nodes = payload.data.items.filter(item => {\r\n return item.parent === id\r\n })\r\n let validNodes = []\r\n nodes.forEach(item => {\r\n let isLeaf = !item.org_children && ((item.type === 'class' || item.type === 'reelectclass' || item.type === 'studentgroup') && item.course2orgs && item.course2orgs.find(item => item.courses !== null))\r\n if (isLeaf) {\r\n validNodes.push({\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`\r\n })\r\n } else {\r\n let childNodes = recursive(item.id)\r\n if (childNodes.length > 0) {\r\n validNodes.push({\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: childNodes\r\n })\r\n }\r\n }\r\n })\r\n return validNodes\r\n}\r\nreturn {\r\n ...payload,\r\n data: recursive(school.id)\r\n}",
sendOn: '',
},
multiple: true,
joinValues: false,
extractValue: true,
mode: 'normal',
required: false,
onlyChildren: true,
searchable: true,
cascade: false,
withChildren: true,
clearable: true,
heightAuto: true,
virtualThreshold: false,
id: 'u:04d5da3d1cd2',
},
],
id: 'u:32d166e0597b',
feat: 'Insert',
},
],
toolbar: [
{
type: 'button',
label: '时间选择',
icon: 'fa fa-group',
iconClassName: '',
reload: '',
visibleOn:
"this.user.role === 'admin' || this.user.role === 'centreadmin' ",
id: 'u:a1c14e8c36bc',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
body: [
{
type: 'timeslot-renderer',
api: {
method: 'get',
url: 'rest/timeslots?select=id,date,organization_id,teacher_id,periods(id,start_time,end_time)&teacher_id=eq.${id}&organization_id=eq.${centre_id}',
adaptor:
'return {\r\n data: {\r\n timeslots: payload.data.items.map(item => {\r\n return {\r\n id: item.id,\r\n date: item.date, semester_id:item.semester_id,organization_id: item.organization_id,teacher_id:item.teacher_id,\r\n period: item.periods,\r\n}\r\n })\r\n }\r\n}',
},
copy: {
type: 'button',
label: '按钮',
actionType: 'dialog',
dialog: {
title: '复制本周设置到其他周',
body: [
{
type: 'form',
title: '表单',
controls: [
{
type: 'timeslot-copy',
name: 'timeslotcopy',
mode: 'normal',
target: 'teacher',
},
],
api: {
method: 'post',
url: 'rest/rpc/timeslots',
dataType: 'json',
headers: { Prefer: 'params=single-object' },
requestAdaptor:
'return {\r\n ...api,\r\n data: api.data.timeslotcopy\r\n}',
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
},
},
target: 'teacher',
},
],
title: '教师时间设置',
closeOnEsc: true,
showCloseButton: true,
size: 'md',
actions: [],
},
},
],
},
},
},
{
type: 'button',
label: '预览',
icon: 'fa fa-eye',
level: 'link',
size: 'md',
iconClassName: '',
visibleOn: '!this.excludeTeacherTime',
id: 'u:6e637dc227f1',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '教师可用时间',
body: [
{
type: 'tpl',
tpl: '<span class="label" style="background: #e5e7eb;">X</span>\n<span style="vertical-align: middle;">不可用时间</span>\n<span class="label label-info">X</span>\n<span style="vertical-align: middle;">可用时间</span>\n<span class="label label-warning">X</span>\n<span style="vertical-align: middle;">尽量不安排时间</span>',
inline: false,
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/periods?select=*,timeslots(*)&timeslots.and=(organization_id.eq.$centre_id,date.gte.${semester.since},date.lte.${semester.to},or(teacher_id.eq.$id,teacher_secondary_id.eq.${id}))&order=start_time',
adaptor:
"let items = []\r\nconst week_list = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']\r\nconst oneday = 24 * 60 * 60 * 1000\r\nconst get_week_num = (start, end) => {\r\n let s = new Date(start)\r\n let s_ms = s.getTime()\r\n let s_day = s.getDay() ? s.getDay() : 7\r\n s.setTime(s_ms - (s_day - 1) * oneday)\r\n s_ms = s.getTime()\r\n let e = new Date(end)\r\n let e_ms = e.getTime()\r\n let e_day = e.getDay() ? e.getDay() : 7\r\n return {\r\n sem_start: s,\r\n day: e_day,\r\n week_num: Math.ceil(((e_ms - s_ms) / oneday + 1) / 7)\r\n }\r\n}\r\nlet start = /(?<=date\\.gte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nlet end = /(?<=date\\.lte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nconst { sem_start, week_num } = get_week_num(start, end)\r\nfor (let i = 0; i < 7; i++) {\r\n let date_temp = new Date(sem_start)\r\n let date_temp_ms = date_temp.getTime()\r\n payload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n }).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n }).forEach(item => {\r\n let week_num_list = []\r\n for (let j = 0; j < week_num; j++) {\r\n date_temp.setTime(date_temp_ms + j * 7 * oneday + i * oneday)\r\n week_num_list.push({\r\n week: j + 1,\r\n date: null,\r\n tpl: \"<span title='\".concat(date_temp.toISOString().split('T')[0], \"' class='label' style='background: #e5e7eb;'>\", j + 1, \"</span>\"),\r\n tip: 'no_id'\r\n })\r\n }\r\n items.push({\r\n day: {\r\n lab: week_list[i],\r\n val: i + 1\r\n },\r\n period: {\r\n lab: item.name,\r\n val: item.id\r\n },\r\n weeks: week_num_list\r\n })\r\n })\r\n}\r\nconst row_mem = payload.data.items.length\r\npayload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n}).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n}).forEach((item, index) => {\r\n item.timeslots && item.timeslots.filter(\r\n f => f.teacher_id !== null\r\n ).forEach(fe => {\r\n const { day, week_num } = get_week_num(sem_start, fe.date)\r\n const week = items[(day - 1) * row_mem + index].weeks[week_num - 1].week\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].id = fe.id\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].date = fe.date\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span title='\".concat(fe.date, \"' class='label label-primary'>\", week, \"</span>\")\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tip = 't_id'\r\n })\r\n item.timeslots && item.timeslots.filter(\r\n f => f.teacher_secondary_id !== null\r\n ).forEach(fe => {\r\n const { day, week_num } = get_week_num(sem_start, fe.date)\r\n const week = items[(day - 1) * row_mem + index].weeks[week_num - 1].week\r\n if (items[(day - 1) * row_mem + index].weeks[week_num - 1].tip === 't_id') {\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].id = fe.id\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].date = fe.date\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span title='\".concat(fe.date, \"' class='label label-warning'>\", week, \"</span>\")\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tip = 't_s_id'\r\n }\r\n })\r\n})\r\nreturn {\r\n ...payload,\r\n data: {\r\n items: items\r\n }\r\n}",
requestAdaptor:
'if (api.headers) {\r\n api.headers["query"] = api.query\r\n} else {\r\n api.headers = {\r\n query: api.query\r\n }\r\n}\r\nreturn api;',
},
messages: {},
columns: [
{
name: 'day.lab',
label: '',
type: 'text',
inline: true,
className: 'common-padding',
},
{
type: 'text',
label: '',
name: 'period.lab',
placeholder: '-',
className: 'common-padding',
},
{
name: 'weeks',
label: '',
type: 'each',
items: [
{
type: 'tpl',
tpl: '<%= data.tpl %>',
inline: true,
className: 'm-r-xs',
},
],
placeholder: '-',
className: 'common-padding',
},
],
combineNum: 1,
columnsTogglable: false,
syncLocation: false,
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
data: null,
bodyClassName: 'p-t-none',
actions: [],
},
},
],
},
},
},
{
type: 'button',
label: '预览',
icon: 'fa fa-eye',
level: 'link',
size: 'md',
iconClassName: '',
visibleOn: 'this.excludeTeacherTime',
id: 'u:872e8943279c',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '教师可用时间',
body: [
{
type: 'tpl',
tpl: '<span class="label" style="background: #e5e7eb;">X</span>\n<span style="vertical-align: middle;">不可用时间</span>\n<span class="label label-info">X</span>\n<span style="vertical-align: middle;">可用时间</span>\n<span class="label label-warning">X</span>\n<span style="vertical-align: middle;">尽量不安排时间</span>',
inline: false,
},
{
type: 'crud',
api: {
method: 'get',
url: 'rest/periods?select=*,timeslots(*)&timeslots.and=(organization_id.eq.$centre_id,date.gte.${semester.since},date.lte.${semester.to},or(teacher_id.eq.$id,teacher_secondary_id.eq.$id))&order=start_time',
adaptor:
"let items = []\r\nconst week_list = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']\r\nconst oneday = 24 * 60 * 60 * 1000\r\nconst get_week_num = (start, end) => {\r\n let s = new Date(start)\r\n let s_ms = s.getTime()\r\n let s_day = s.getDay() ? s.getDay() : 7\r\n s.setTime(s_ms - (s_day - 1) * oneday)\r\n s_ms = s.getTime()\r\n let e = new Date(end)\r\n let e_ms = e.getTime()\r\n let e_day = e.getDay() ? e.getDay() : 7\r\n return {\r\n sem_start: s,\r\n day: e_day,\r\n week_num: Math.ceil(((e_ms - s_ms) / oneday + 1) / 7)\r\n }\r\n}\r\nlet start = /(?<=date\\.gte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nlet end = /(?<=date\\.lte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nconst { sem_start, week_num } = get_week_num(start, end)\r\nfor (let i = 0; i < 7; i++) {\r\n let date_temp = new Date(sem_start)\r\n let date_temp_ms = date_temp.getTime()\r\n payload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n }).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n }).forEach(item => {\r\n let week_num_list = []\r\n for (let j = 0; j < week_num; j++) {\r\n date_temp.setTime(date_temp_ms + j * 7 * oneday + i * oneday)\r\n week_num_list.push({\r\n week: j + 1,\r\n date: null,\r\n tpl: \"<span title='\".concat(date_temp.toISOString().split('T')[0], \"' class='label label-info'>\", j + 1, \"</span>\"),\r\n tip: 'no_id'\r\n })\r\n }\r\n items.push({\r\n day: {\r\n lab: week_list[i],\r\n val: i + 1\r\n },\r\n period: {\r\n lab: item.name,\r\n val: item.id\r\n },\r\n weeks: week_num_list\r\n })\r\n })\r\n}\r\nconst row_mem = payload.data.items.length\r\npayload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n}).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n}).forEach((item, index) => {\r\n item.timeslots && item.timeslots.filter(\r\n f => f.teacher_id !== null\r\n ).forEach(fe => {\r\n const { day, week_num } = get_week_num(sem_start, fe.date)\r\n const week = items[(day - 1) * row_mem + index].weeks[week_num - 1].week\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].id = fe.id\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].date = fe.date\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span title='\".concat(fe.date, \"' class='label' style='background: #e5e7eb;'>\", week, \"</span>\")\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tip = 't_id'\r\n })\r\n item.timeslots && item.timeslots.filter(\r\n f => f.teacher_secondary_id !== null\r\n ).forEach(fe => {\r\n const { day, week_num } = get_week_num(sem_start, fe.date)\r\n const week = items[(day - 1) * row_mem + index].weeks[week_num - 1].week\r\n if (items[(day - 1) * row_mem + index].weeks[week_num - 1].tip === 'no_id') {\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].id = fe.id\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].date = fe.date\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span title='\".concat(fe.date, \"' class='label label-warning'>\", week, \"</span>\")\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tip = 't_s_id'\r\n }\r\n })\r\n})\r\nreturn {\r\n ...payload,\r\n data: {\r\n items: items\r\n }\r\n}",
requestAdaptor:
'if (api.headers) {\r\n api.headers["query"] = api.query\r\n} else {\r\n api.headers = {\r\n query: api.query\r\n }\r\n}\r\nreturn api;',
},
messages: {},
columns: [
{
name: 'day.lab',
label: '',
type: 'text',
inline: true,
className: 'common-padding',
},
{
type: 'text',
label: '',
name: 'period.lab',
placeholder: '-',
className: 'common-padding',
},
{
name: 'weeks',
label: '',
type: 'each',
items: [
{
type: 'tpl',
tpl: '<%= data.tpl %>',
inline: true,
className: 'm-r-xs',
},
],
placeholder: '-',
className: 'common-padding',
},
],
combineNum: 1,
columnsTogglable: false,
syncLocation: false,
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
data: null,
bodyClassName: 'p-t-none',
actions: [],
},
},
],
},
},
},
{
type: 'button',
label: '尽量不安排时间',
icon: 'fa fa-group',
iconClassName: '',
reload: '',
visibleOn:
"this.user.role === 'admin' || this.user.role === 'centreadmin' ",
id: 'u:91324ff3edf8',
editorState: 'default',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '教师时间设置',
body: [
{
type: 'timeslot-renderer',
api: {
method: 'get',
url: 'rest/timeslots?select=id,date,organization_id,teacher_secondary_id,periods(id,start_time,end_time)&teacher_secondary_id=eq.${id}&organization_id=eq.${centre_id}',
adaptor:
'return {\r\n data: {\r\n timeslots: payload.data.items.map(item => {\r\n return {\r\n id: item.id,\r\n date: item.date,\r\n semester_id: item.semester_id,\r\n organization_id: item.organization_id,\r\n teacher_secondary_id: item.teacher_secondary_id,\r\n period: item.periods\r\n }\r\n })\r\n }\r\n}',
},
copy: {
type: 'button',
label: '按钮',
actionType: 'dialog',
dialog: {
title: '复制本周设置到其他周',
body: [
{
type: 'form',
title: '表单',
controls: [
{
type: 'timeslot-copy',
name: 'timeslotcopy',
mode: 'normal',
target: 'teacher_secondary',
},
],
api: {
method: 'post',
url: 'rest/rpc/timeslots',
dataType: 'json',
headers: { Prefer: 'params=single-object' },
requestAdaptor:
'return {\r\n ...api,\r\n data: api.data.timeslotcopy\r\n}',
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
},
},
target: 'teacher_secondary',
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'md',
actions: [],
},
},
],
},
},
},
],
actionsCount: 5,
id: 'u:7b5b16eba035',
},
syncLocation: false,
placeholder: '暂无数据',
columnsCount: 3,
name: 'tea_set',
headerToolbar: [],
footerToolbar: [
{ type: 'pagination' },
{ type: 'switch-per-page' },
{ type: 'statistics' },
],
pageField: 'page',
perPageField: 'perPage',
perPageAvailable: [6, 18, 36, 54],
defaultParams: { perPage: 6 },
title: '',
body: [
{
type: 'panel',
body: [
{
type: 'crud',
api: {
method: 'get',
url: 'rest/locations?organization_id=eq.$centre_id&order=code',
adaptor: '',
data: null,
replaceData: false,
},
messages: {},
mode: 'cards',
headerToolbar: [],
card: {
header: { title: '${name}' },
body: [
{ type: 'tpl', tpl: '${intro|raw}' },
{
type: 'each',
name: 'location_list',
items: [
{
type: 'plain',
className: 'm-l-sm',
tpl: '${users.name}',
inline: true,
},
{
type: 'button',
label: '',
actionType: 'ajax',
level: 'link',
icon: 'fa fa-trash',
iconClassName: 'text-danger',
api: {
method: 'delete',
url: 'rest/team_members?team_id=eq.${id}&teacher_id=eq.${users.id}',
},
},
],
placeholder: '未添加可用时间',
},
],
actions: [
{
type: 'button',
label: '可用时间设置 ',
actionType: 'dialog',
dialog: {
body: [
{
type: 'timeslot-renderer',
api: {
method: 'get',
url: 'rest/timeslots?select=id,date,organization_id,location_id,periods(id,start_time,end_time)&location_id=eq.${id}&organization_id=eq.${centre_id}',
adaptor:
'return {\r\n data: {\r\n timeslots: payload.data.items.map(item => {\r\n return {\r\n id: item.id,\r\n date: item.date,\r\n semester_id: item.semester_id,\r\n organization_id: item.organization_id,\r\n location_id: item.location_id,\r\n period: item.periods\r\n }\r\n })\r\n }\r\n}',
},
copy: {
type: 'button',
label: '按钮',
actionType: 'dialog',
dialog: {
title: '复制本周设置到其他周',
body: [
{
type: 'form',
title: '表单',
controls: [
{
type: 'timeslot-copy',
name: 'timeslotcopy',
mode: 'normal',
target: 'location',
},
],
api: {
method: 'post',
url: 'rest/rpc/timeslots',
dataType: 'json',
headers: { Prefer: 'params=single-object' },
requestAdaptor:
'return {\r\n ...api,\r\n data: api.data.timeslotcopy\r\n}',
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
},
},
target: 'location',
},
],
title: '场地时间设置',
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
size: 'md',
actions: [],
},
icon: 'fa fa-group',
iconClassName: '',
reload: '',
},
{
type: 'button',
label: '预览可用时间 ',
actionType: 'dialog',
icon: 'fa fa-eye',
level: 'link',
size: 'md',
iconClassName: '',
dialog: {
title: '场地可用时间',
body: [
{
type: 'crud',
api: {
method: 'get',
url: 'rest/periods?select=*,timeslots(*)&timeslots.organization_id=eq.$centre_id&timeslots.date=gte.${semester.since}&timeslots.date=lte.${semester.to}&timeslots.location_id=eq.$id&order=start_time',
adaptor:
"let items = []\r\nconst week_list = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']\r\nconst oneday = 24 * 60 * 60 * 1000\r\nconst get_week_num = (start, end) => {\r\n let s = new Date(start)\r\n let s_ms = s.getTime()\r\n let s_day = s.getDay() ? s.getDay() : 7\r\n s.setTime(s_ms - (s_day - 1) * oneday)\r\n s_ms = s.getTime()\r\n let e = new Date(end)\r\n let e_ms = e.getTime()\r\n let e_day = e.getDay() ? e.getDay() : 7\r\n return {\r\n sem_start: s,\r\n day: e_day,\r\n week_num: Math.ceil(((e_ms - s_ms) / oneday + 1) / 7)\r\n }\r\n}\r\nlet start = /(?<=date\\[0\\]=gte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nlet end = /(?<=date\\[1\\]=lte\\.)\\d+-\\d+-\\d+/g.exec(api.url)[0]\r\nconst { sem_start, week_num } = get_week_num(start, end)\r\nfor (let i = 0; i < 7; i++) {\r\n let date_temp = new Date(sem_start)\r\n let date_temp_ms = date_temp.getTime()\r\n payload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n }).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n }).forEach(item => {\r\n let week_num_list = []\r\n for (let j = 0; j < week_num; j++) {\r\n date_temp.setTime(date_temp_ms + j * 7 * oneday + i * oneday)\r\n week_num_list.push({\r\n week: j + 1,\r\n date: null,\r\n tpl: \"<span class='label label-warning'>\".concat(j + 1, \"</span>\"),\r\n tip: date_temp.toISOString().split('T')[0]\r\n })\r\n }\r\n items.push({\r\n day: {\r\n lab: week_list[i],\r\n val: i + 1\r\n },\r\n period: {\r\n lab: item.name,\r\n val: item.id\r\n },\r\n weeks: week_num_list\r\n })\r\n })\r\n}\r\nconst row_mem = payload.data.items.length\r\npayload.data.items.map(x => {\r\n const [h, m, s] = x.start_time.split(':')\r\n return {\r\n ...x,\r\n cmp_time: new Date(2020, 0, 1, h, m, s)\r\n }\r\n}).sort((a, b) => {\r\n return a.cmp_time - b.cmp_time\r\n}).forEach((item, index) => {\r\n item.timeslots && item.timeslots.forEach(i => {\r\n const { day, week_num } = get_week_num(sem_start, i.date)\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].date = i.date\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].id = i.id\r\n const week = items[(day - 1) * row_mem + index].weeks[week_num - 1].week\r\n items[(day - 1) * row_mem + index].weeks[week_num - 1].tpl = \"<span class='label label-success'>\".concat(week, \"</span>\")\r\n })\r\n})\r\nreturn {\r\n ...payload,\r\n data: {\r\n items: items\r\n }\r\n}",
requestAdaptor: '',
},
messages: {},
columns: [
{ name: 'day.lab', label: '', type: 'text' },
{
type: 'text',
label: '',
name: 'period.lab',
placeholder: '-',
},
{
name: 'weeks',
label: '',
type: 'each',
items: [
{
type: 'tpl',
tpl: '<%= data.tpl %>',
inline: true,
className: 'm-r-xs',
},
],
placeholder: '-',
},
],
combineNum: 1,
columnsTogglable: false,
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
data: null,
},
},
],
type: 'card',
actionsCount: 3,
},
columnsCount: 3,
},
],
},
],
masonryLayout: false,
itemClassName: '',
filter: null,
checkAll: false,
id: 'u:2133b3bfaf09',
},
],
messages: {},
title: '',
cssVars: { '--Checkbox-onDisabled-color': 'transparent' },
initApi: {
method: 'get',
url: 'rest/config?key=eq.system&organization_id=eq.${centre_id}',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n excludeTeacherTime: payload.data.items[0].value.excludeTeacherTime,\r\n excludeTeacherStudentGroup: payload.data.items[0].value.excludeTeacherStudentGroup,\r\n excludeTeacherSubject: payload.data.items[0].value.excludeTeacherSubject\r\n }\r\n}',
},
id: 'u:31421ebcd506',
};
export { schema };

View File

@@ -0,0 +1,75 @@
const schema = {
body: [
{
type: 'crud',
columns: [
{ type: 'text', name: 'id', label: '编号' },
{ type: 'text', name: 'name', label: '名称' },
{
type: 'tpl',
label: '是否公共空间',
name: 'public',
tpl: '<% if (data.public) { %>\n <span class="label label-success">是</span>\n<% } %>',
inline: false,
},
],
messages: {},
api: {
method: 'get',
url: 'rest/buckets',
headers: { 'Accept-Profile': 'storage' },
},
headerToolbar: [
{
type: 'button',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加存储空间',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/buckets',
data: null,
dataType: 'json',
headers: { 'Content-Profile': 'storage' },
},
body: [
{
type: 'input-text',
label: '名称',
name: 'name',
required: true,
mode: 'normal',
},
{ type: 'formula', name: 'id', formula: 'data.name' },
{ type: 'hidden', name: 'id' },
{ type: 'hidden', name: 'public', value: 'true' },
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
icon: 'fa fa-plus text-primary',
size: 'lg',
level: 'link',
tooltip: '添加',
tooltipPlacement: 'left',
align: 'right',
label: '',
className: 'p-l-none',
},
],
syncLocation: false,
bulkActions: [],
itemActions: [],
},
],
type: 'page',
};
export { schema };

View File

@@ -0,0 +1,153 @@
const schema = {
type: 'page',
body: [
{
id: 'u:ac8687f8ba94',
type: 'form',
title: '表单',
mode: 'inline',
feat: 'View',
body: [
{
type: 'group',
body: [
{
type: 'select',
label: '数据模型',
name: 'typecode',
mode: 'inline',
clearable: true,
source: {
method: 'get',
url: 'rest/dicts?order=typecode',
adaptor:
'const options = []\r\n\r\npayload.data.items.forEach(item => {\r\n if (!options.some(option => option.typecode === item.typecode))\r\n options.push({typecode: item.typecode, typename: item.typename})\r\n})\r\n\r\nreturn {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: options.map(item => {\r\n return {\r\n label: item.typename,\r\n value: item.typecode\r\n }\r\n })\r\n }\r\n}',
requestAdaptor: '',
messages: {},
},
checkAll: false,
size: 'md',
searchable: true,
inputClassName: 'm-l-xs',
id: 'u:66e672b9a4eb',
multiple: false,
},
],
id: 'u:32358eae378f',
},
{
type: 'divider',
id: 'u:41b4b3321711',
},
],
className: '',
submitOnChange: true,
submitOnInit: true,
reload: 'dict?typecode=$typecode',
wrapWithPanel: false,
name: 'filter',
},
{
type: 'crud',
id: 'u:7226d14e603b',
headerToolbar: [
{
type: 'button',
tpl: '内容',
align: 'right',
label: '',
actionType: 'dialog',
dialog: {
title: '添加页面',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/pages',
},
controls: [
{
type: 'text',
label: '名称',
name: 'name',
required: true,
mode: 'normal',
},
],
},
],
type: 'dialog',
closeOnEsc: true,
},
icon: 'fa fa-plus text-primary',
level: 'link',
visibleOn: 'false',
id: 'u:b509f4d7b6a2',
},
{
type: 'bulk-actions',
},
],
api: {
method: 'get',
url: 'rest/dicts?order=typecode',
messages: {},
requestAdaptor:
"if (api.query.typecode === '')\r\n api.url = api.url.replace('&typecode=', '')\r\nelse \r\n api.url = api.url.replace('&typecode=' + api.query.typecode, '&typecode=eq.' + api.query.typecode)\r\n\r\nreturn api",
adaptor: '',
},
columns: [
{
name: 'id',
label: '编号',
type: 'text',
id: 'u:2e8854f46051',
},
{
name: 'typename',
label: '数据模型',
type: 'text',
placeholder: '-',
sortable: false,
searchable: false,
id: 'u:a86a77369cc6',
},
{
type: 'text',
label: '数据模型编号',
id: 'u:a86a77369cc6',
name: 'typecode',
placeholder: '-',
sortable: false,
searchable: false,
},
{
type: 'text',
label: '字典项',
name: 'dictkey',
id: 'u:b97cdabd49f0',
},
{
type: 'text',
label: '字典值',
name: 'dictvalue',
id: 'u:742d4dd946a7',
},
],
bulkActions: [],
itemActions: [],
messages: {},
perPageAvailable: [10],
filter: null,
syncLocation: false,
name: 'dict',
},
],
messages: {},
title: '',
id: 'u:02a9ce84345c',
};
export { schema };

View File

@@ -0,0 +1,49 @@
const schema = {
body: [
{
type: 'form',
api: {
method: 'post',
url: 'rest/orgs',
requestAdaptor:
"let orgs = [];\r\norgs.push({\r\n id: 1,\r\n code: 1,\r\n name: api.data.school,\r\n type: 'school',\r\n parent: null\r\n});\r\nconsole.log(api.data.centres);\r\napi.data.centres.forEach((x,index)=> {\r\n orgs.push({\r\n id: index+2,\r\n code: `centre${index}`,\r\n name: x,\r\n type: 'centre',\r\n parent: 1\r\n });\r\n})\r\n\r\nreturn {\r\n ...api,\r\n data: orgs\r\n};",
dataType: 'json',
},
title: '初始化',
submitText: '确定',
affixFooter: false,
visibleOn: '!this.items || this.items.length===0',
body: [
{
label: '学校名称',
type: 'input-text',
name: 'school',
required: true,
mode: 'normal',
},
{
type: 'input-array',
label: '教学中心',
name: 'centres',
items: { type: 'input-text', placeholder: '请输入' },
addButtonText: '新增中心',
required: true,
mode: 'normal',
value: '教学中心',
},
],
},
{
type: 'tpl',
tpl: '<p style="text-align: center;"><strong>初始化完成</strong></p>',
inline: false,
__mode: 'rich-text',
visibleOn: 'this.items && this.items.length>0',
},
],
messages: {},
initApi: 'rest/orgs?type=eq.school',
type: 'page',
};
export { schema };

View File

@@ -0,0 +1,273 @@
const schema = {
body: [
{
type: 'crud',
columns: [
{ type: 'text', name: 'id', label: '编号' },
{
type: 'text',
name: 'label',
label: '菜单名称',
placeholder: '-',
},
{
type: 'tpl',
label: '图标',
placeholder: '-',
name: 'icon',
tpl: '<i class="${icon}"></i>',
inline: true,
quickEdit: {
type: 'icon-picker',
label: '图标',
name: 'icon',
labelField: 'text',
mode: 'popOver',
labelTpl: '${value}',
saveImmediately: {
api: {
method: 'patch',
url: 'rest/navs?id=eq.${id}',
data: { icon: '${icon}' },
dataType: 'form',
},
},
},
popOver: false,
},
{
type: 'tpl',
label: '上级菜单',
name: 'parent_name',
placeholder: '-',
tpl: '<span class="label label-info">${parent_name}</span>',
inline: false,
},
{
type: 'tpl',
label: '顺序',
tpl: '${order}',
inline: true,
name: 'order',
placeholder: '-',
quickEdit: {
type: 'input-number',
name: 'order',
label: '数字',
min: '1',
step: 1,
value: 1,
saveImmediately: {
api: {
method: 'patch',
url: 'rest/navs?id=eq.${id}',
data: { order: '$order' },
dataType: 'form',
},
},
mode: 'popOver',
},
},
{
type: 'tpl',
label: '关联页面',
placeholder: '-',
tpl: '<span class="label label-info">${schema_path}</span>',
inline: true,
name: 'schema_path',
className: '',
quickEdit: {
mode: 'popOver',
type: 'input-text',
label: '页面相对存储路径',
name: 'schema_path',
saveImmediately: {
api: {
method: 'patch',
url: 'rest/navs?id=eq.${id}',
data: { schema_path: '${schema_path}' },
dataType: 'form',
},
},
},
style: {},
},
{
type: 'button-group',
buttons: [
{
type: 'button',
label: '',
actionType: 'dialog',
dialog: {
title: '编辑菜单',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/navs/${id}',
data: { '&': '$$' },
dataType: 'form',
requestAdaptor:
'console.log(api)\r\nif(api.body.path === null) {\r\n delete api.body.path; \r\n}\r\n\r\nreturn api;',
},
body: [
{
label: '菜单名称',
type: 'input-text',
name: 'name',
required: true,
mode: 'normal',
},
{
type: 'icon-picker',
label: '图标',
name: 'icon',
mode: 'normal',
labelField: 'text',
},
{
type: 'select',
label: '上级菜单',
name: 'parent',
options: [],
mode: 'normal',
checkAll: false,
source: 'rest/navs?select=label:name,value:id',
},
{
type: 'select',
label: '关联页面',
name: 'path',
options: [],
mode: 'normal',
checkAll: false,
source: 'rest/pages?select=label:name,value:id',
},
],
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
data: { '&': '$$' },
},
level: 'link',
icon: 'fa fa-pencil text-info',
iconClassName: 'pull-left',
size: 'md',
tooltip: '修改',
tooltipPlacement: 'top',
className: 'p-r-none p-l-none',
},
{
type: 'button',
label: '',
actionType: 'ajax',
size: 'md',
icon: 'fa fa-times text-danger',
level: 'link',
iconClassName: 'pull-left',
tooltip: '删除',
tooltipPlacement: 'top',
className: 'p-r-none p-l-none',
api: { method: 'delete', url: 'rest/navs/${id}' },
confirmText: '确认删除 ${label} 吗?',
},
],
label: '操作',
placeholder: '-',
width: 200,
},
],
messages: {},
api: {
method: 'get',
url: 'rest/navs?select=label:name,value:id,*,pages!nav_path_fkey(name)&order=order.asc',
data: {},
adaptor:
'let all_navs = payload.data.items;\r\nconst buildNavs = function(navs, id) {\r\n return navs.filter((x)=>x.parent===id).map((x)=>{\r\n let subNavs = buildNavs(navs, x.id);\r\n return {\r\n ...x,\r\n parent_name: all_navs.find(n=>n.id===x.parent)?.name,\r\n label: x.name,\r\n children: subNavs && subNavs.length > 0 ? subNavs : null\r\n }\r\n });\r\n }\r\n \r\n return {\r\n ...payload,\r\n data: {\r\n items: buildNavs(payload.data.items, null)\r\n }\r\n }',
},
headerToolbar: [
{
type: 'button',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加菜单',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/navs',
data: null,
},
body: [
{
type: 'input-text',
label: '菜单名称',
name: 'name',
required: true,
mode: 'normal',
},
{
type: 'icon-picker',
label: '图标',
name: 'icon',
mode: 'normal',
labelField: 'text',
},
{
type: 'select',
name: 'parent',
label: '上级菜单',
mode: 'normal',
checkAll: false,
source: 'rest/navs?select=label:name,value:id',
options: [],
},
{
type: 'select',
name: 'path',
label: '关联页面',
mode: 'normal',
options: [],
checkAll: false,
source: 'rest/pages?select=label:name,value:id',
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
icon: 'fa fa-plus',
size: 'lg',
level: 'link',
tooltip: '添加',
tooltipPlacement: 'left',
className: 'pull-right p-l-none',
},
],
syncLocation: false,
perPageAvailable: [10],
perPageField: 'perPage',
bulkActions: [],
itemActions: [],
filter: null,
features: ['filter', 'create'],
source: '',
perPage: null,
hideQuickSaveBtn: true,
loadDataOnce: true,
},
],
type: 'page',
};
export { schema };

View File

@@ -0,0 +1,392 @@
const schema = {
body: [
{
type: 'crud',
columns: [
{ type: 'text', name: 'id', label: '编号' },
{ type: 'text', name: 'label', label: '菜单名称', placeholder: '-' },
{
type: 'tpl',
label: '图标',
placeholder: '-',
name: 'icon',
tpl: '<i class="${icon}"></i>',
inline: true,
quickEdit: {
type: 'picker',
name: 'icon',
label: '',
options: [],
source: 'icons.json',
pickerSchema: {
mode: 'cards',
listItem: {
title: '${value}',
body: [
{
type: 'tpl',
tpl: '<i class="${value}"></i>',
inline: false,
label: '',
},
],
},
perPageAvailable: [10],
messages: {},
card: {
type: 'card',
body: [
{
type: 'tpl',
tpl: '<i class="${value}"></i>',
inline: false,
label: '',
},
],
actionsCount: 1,
className: '',
bodyClassName: '',
},
loadDataOnce: true,
alwaysShowPagination: false,
placeholder: '暂无数据',
columnsCount: 8,
perPageField: 'perPage',
perPage: 50,
className: '',
masonryLayout: false,
itemClassName: '',
},
modalMode: 'dialog',
embed: false,
size: 'lg',
mode: 'popOver',
labelTpl: '${value}',
saveImmediately: {
api: {
method: 'patch',
url: 'rest/navs?id=eq.${id}',
data: { icon: '${icon}' },
dataType: 'form',
},
},
},
popOver: false,
},
{
type: 'tpl',
label: '上级菜单',
name: 'parent_name',
placeholder: '-',
tpl: '<span class="label label-info">${parent_name}</span>',
inline: false,
},
{
type: 'tpl',
label: '顺序',
tpl: '${order}',
inline: true,
name: 'order',
placeholder: '-',
quickEdit: {
type: 'input-number',
name: 'order',
label: '数字',
min: '1',
step: 1,
value: 1,
saveImmediately: {
api: {
method: 'patch',
url: 'rest/navs?id=eq.${id}',
data: { order: '$order' },
dataType: 'form',
},
},
mode: 'popOver',
},
},
{
type: 'tpl',
label: '关联页面',
placeholder: '-',
tpl: '<span class="label label-info">${schema_path}</span>',
inline: true,
name: 'schema_path',
className: '',
quickEdit: {
mode: 'popOver',
type: 'input-text',
label: '页面相对存储路径',
name: 'schema_path',
saveImmediately: {
api: {
method: 'patch',
url: 'rest/navs?id=eq.${id}',
data: { schema_path: '${schema_path}' },
dataType: 'form',
},
},
},
style: {},
},
{
type: 'operation',
buttons: [
{
type: 'button',
label: '',
actionType: 'dialog',
dialog: {
title: '编辑菜单',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/navs/${id}',
data: { '&': '$$' },
dataType: 'form',
requestAdaptor:
'console.log(api)\r\nif(api.body.path === null) {\r\n delete api.body.path; \r\n}\r\n\r\nreturn api;',
},
body: [
{
label: '菜单名称',
type: 'input-text',
name: 'name',
required: true,
mode: 'normal',
},
{
type: 'picker',
label: '图标',
name: 'icon',
options: [],
mode: 'normal',
modalMode: 'dialog',
source: 'icons.json',
pickerSchema: {
mode: 'cards',
listItem: {
title: '${value}',
body: [
{
type: 'tpl',
tpl: '<i class="${value}"></i>',
inline: false,
label: '',
},
],
},
perPageAvailable: [10],
messages: {},
card: {
type: 'card',
body: [
{
type: 'tpl',
tpl: '<i class="${value}"></i>',
inline: false,
label: '',
},
],
actionsCount: 1,
className: '',
bodyClassName: '',
},
loadDataOnce: true,
alwaysShowPagination: false,
placeholder: '暂无数据',
columnsCount: 8,
perPageField: 'perPage',
perPage: 50,
className: '',
masonryLayout: false,
itemClassName: '',
},
size: 'lg',
labelTpl: '${value}',
},
{
type: 'select',
label: '上级菜单',
name: 'parent',
options: [],
mode: 'normal',
checkAll: false,
source: 'rest/navs?select=label:name,value:id',
},
{
type: 'select',
label: '关联页面',
name: 'path',
options: [],
mode: 'normal',
checkAll: false,
source: 'rest/pages?select=label:name,value:id',
},
],
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
data: { '&': '$$' },
},
level: 'link',
icon: 'fa fa-pencil text-info',
iconClassName: 'pull-left',
size: 'md',
tooltip: '修改',
tooltipPlacement: 'top',
className: 'm-r-none m-l-none',
},
{
type: 'button',
label: '',
actionType: 'ajax',
size: 'md',
icon: 'fa fa-times text-danger',
level: 'link',
iconClassName: 'pull-left',
tooltip: '删除',
tooltipPlacement: 'top',
className: 'm-r-none m-l-none',
api: { method: 'delete', url: 'rest/navs/${id}' },
confirmText: '确认删除 ${label} 吗?',
},
],
label: '操作',
placeholder: '-',
width: 200,
},
],
messages: {},
api: {
method: 'get',
url: 'rest/navs?select=label:name,value:id,*,pages!nav_path_fkey(name)&order=order.asc',
data: {},
adaptor:
'let all_navs = payload.data.items;\r\nconst buildNavs = function(navs, id) {\r\n return navs.filter((x)=>x.parent===id).map((x)=>{\r\n let subNavs = buildNavs(navs, x.id);\r\n return {\r\n ...x,\r\n parent_name: all_navs.find(n=>n.id===x.parent)?.name,\r\n label: x.name,\r\n children: subNavs && subNavs.length > 0 ? subNavs : null\r\n }\r\n });\r\n }\r\n \r\n return {\r\n ...payload,\r\n data: {\r\n items: buildNavs(payload.data.items, null)\r\n }\r\n }',
},
headerToolbar: [
{
type: 'button',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加菜单',
body: [
{
type: 'form',
title: '表单',
api: { method: 'post', url: 'rest/navs', data: null },
body: [
{
type: 'input-text',
label: '菜单名称',
name: 'name',
required: true,
mode: 'normal',
},
{
type: 'picker',
name: 'icon',
label: '图标',
mode: 'normal',
options: [],
modalMode: 'dialog',
source: 'icons.json',
pickerSchema: {
mode: 'cards',
listItem: {
title: '${value}',
body: [
{
type: 'tpl',
tpl: '<i class="${value}"></i>',
inline: false,
label: '',
},
],
},
perPageAvailable: [10],
messages: {},
card: {
type: 'card',
body: [
{
type: 'tpl',
tpl: '<i class="${value}"></i>',
inline: false,
label: '',
},
],
actionsCount: 1,
className: '',
bodyClassName: '',
},
loadDataOnce: true,
alwaysShowPagination: false,
placeholder: '暂无数据',
columnsCount: 8,
perPageField: 'perPage',
perPage: 50,
className: '',
masonryLayout: false,
itemClassName: '',
},
size: 'lg',
labelTpl: '${value}',
},
{
type: 'select',
name: 'parent',
label: '上级菜单',
mode: 'normal',
checkAll: false,
source: 'rest/navs?select=label:name,value:id',
options: [],
},
{
type: 'select',
name: 'path',
label: '关联页面',
mode: 'normal',
options: [],
checkAll: false,
source: 'rest/pages?select=label:name,value:id',
},
],
},
],
closeOnEsc: true,
showCloseButton: true,
},
icon: 'fa fa-plus',
size: 'lg',
level: 'link',
tooltip: '添加',
tooltipPlacement: 'left',
className: 'pull-right p-l-none',
},
],
syncLocation: false,
perPageAvailable: [10],
perPageField: 'perPage',
bulkActions: [],
itemActions: [],
filter: null,
features: ['filter', 'create'],
source: '',
perPage: null,
hideQuickSaveBtn: true,
loadDataOnce: true,
},
],
type: 'page',
};
export { schema };

View File

@@ -0,0 +1,160 @@
const schema = {
body: [
{
type: 'crud',
api: 'rest/dicts?typecode=eq.001',
columns: [
{ name: 'dictvalue', label: '角色名称', type: 'text' },
{ name: 'dictkey', label: '角色代号', type: 'text' },
{
type: 'operation',
label: '操作',
buttons: [
{
type: 'button',
label: '菜单权限',
actionType: 'drawer',
level: 'primary',
size: 'sm',
drawer: {
type: 'drawer',
title: '菜单权限',
body: [
{
type: 'form',
title: '表单',
initApi: {
method: 'get',
url: 'rest/role2navs?role=eq.${dictkey}',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n navs: payload.data.items.map(x=>x.nav_id)\r\n }\r\n}',
},
wrapWithPanel: false,
mode: 'normal',
className: 'h-full',
body: [
{ type: 'hidden', name: 'role' },
{
type: 'input-tree',
label: '',
name: 'navs',
options: [],
showRadio: true,
multiple: true,
joinValues: false,
mode: 'normal',
source: {
method: 'get',
url: 'rest/navs?select=label:name,value:id,*',
adaptor:
'const buildNavs = function(navs, id) {\r\n return navs.filter((x)=>x.parent===id).map((x)=>{\r\n let subNavs = buildNavs(navs, x.id);\r\n return {\r\n ...x,\r\n label: x.name,\r\n children: subNavs && subNavs.length > 0 ? subNavs : null\r\n }\r\n });\r\n }\r\n \r\n return {\r\n ...payload,\r\n data: {\r\n items: buildNavs(payload.data.items, null)\r\n }\r\n }',
},
hideRoot: true,
extractValue: true,
className: 'h-full',
inputClassName: 'no-border h-full',
showIcon: false,
withChildren: true,
cascade: true,
},
],
},
],
size: 'md',
actions: [
{
type: 'button',
level: 'primary',
label: '保存',
actionType: 'ajax',
api: {
method: 'post',
url: 'rest/rpc/create_role2navs',
requestAdaptor:
'return {\r\n ...api,\r\n data: {\r\n role:api.data.role,\r\n navs: api.data.navs.map(item=>{\r\n return {\r\n role: api.data.role,\r\n nav_id: item\r\n }\r\n})\r\n }\r\n}',
data: { '&': '$$' },
dataType: 'json',
headers: { Prefer: 'params=single-object' },
},
},
],
position: 'right',
data: { '&': '$$', role: '${dictkey}' },
},
},
{
label: '设置默认页面',
type: 'button',
actionType: 'drawer',
level: 'warning',
drawer: {
type: 'drawer',
title: '设置默认页面',
body: [
{
type: 'form',
title: '表单',
initApi: {
method: 'get',
url: 'rest/role2navs?role=eq.${role}',
adaptor:
'let default_nav = payload.data.items.find(x=>x.is_default);\r\nreturn {\r\n ...payload,\r\n data: {\r\n nav_id: default_nav ? default_nav.nav_id : null\r\n }\r\n}',
},
wrapWithPanel: false,
mode: 'normal',
className: 'h-full',
body: [
{
type: 'input-tree',
name: 'nav_id',
label: '',
showRadio: true,
multiple: false,
joinValues: false,
mode: 'normal',
source: {
method: 'get',
url: 'rest/navs?select=label:name,value:id,*',
adaptor:
'const buildNavs = function(navs, id) {\r\n return navs.filter((x)=>x.parent===id).map((x)=>{\r\n let subNavs = buildNavs(navs, x.id);\r\n return {\r\n ...x,\r\n label: x.name,\r\n children: subNavs && subNavs.length > 0 ? subNavs : null\r\n }\r\n });\r\n }\r\n \r\n return {\r\n ...payload,\r\n data: {\r\n items: buildNavs(payload.data.items, null)\r\n }\r\n }',
},
hideRoot: true,
extractValue: true,
className: 'h-full',
inputClassName: 'no-border h-full',
showIcon: false,
withChildren: true,
options: [],
},
],
submitOnChange: true,
api: {
method: 'post',
url: 'rest/rpc/set_default_page',
data: { role: '$role', nav_id: '${nav_id.id}' },
},
},
],
actions: [],
data: { role: '${dictkey}' },
position: 'right',
size: 'md',
},
size: 'sm',
},
],
},
],
bulkActions: [],
itemActions: [],
messages: {},
perPageAvailable: [10],
filter: null,
features: ['create', 'update', 'delete', 'itemDelete'],
},
],
messages: {},
title: '',
};
export { schema };

View File

@@ -0,0 +1,861 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
messages: {},
api: {
method: 'get',
url: 'rest/users_ex?select=*,user2courses!inner(*)&and=(or(code.like.*${keywords}*,name.like.*${keywords}*))&user2courses.course_id=eq.1&org_id=in.($class_ids)&order=org_name,code',
data: {
page: '$page',
perPage: '$perPage',
},
requestAdaptor:
"api.url = api.url.replace('&org_id=in.%28%29', '')\r\n\r\nreturn api",
},
filter: {
title: '',
affixFooter: false,
actions: [],
body: [
{
type: 'input-group',
name: 'keywords',
label: '学号或姓名 ',
body: [
{
type: 'input-text',
placeholder: '',
inputClassName: 'b-r-none p-r-none',
name: 'keywords',
size: 'md',
clearable: true,
value: '',
id: 'u:c5ca01d4b027',
},
{
type: 'control',
label: '',
body: [
{
type: 'submit',
level: 'primary',
actionType: 'submit',
label: '搜索',
id: 'u:455b9e0bc653',
},
],
id: 'u:77ed64056ac1',
},
],
id: 'u:2289c530be59',
},
],
id: 'u:50dafca9d406',
feat: 'Insert',
},
headerToolbar: [
'bulkActions',
{
type: 'button',
align: 'right',
label: '添加',
icon: 'fa fa-plus',
level: 'default',
id: 'u:f303a4872364',
visibleOn:
'this.semesterSelect === this.currentSemester',
name: 'semesterSelect',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n };\r\n }),\r\n};',
sendOn: '',
},
submitOnChange: true,
autoComplete: '',
mode: 'inline',
className: '',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加学生',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'select',
label: '学生',
name: 'users',
id: 'u:3b0736cc7da1',
multiple: true,
joinValues: false,
extractValue: true,
required: true,
searchable: true,
checkAll: false,
defaultCheckAll: false,
checkAllLabel: '全选',
source: {
url: 'rest/users?role=eq.student&order=code,name',
method: 'get',
requestAdaptor: '',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name + "(" + item.code + ")",\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
messages: {},
},
},
],
api: {
method: 'post',
url: 'rest/user2courses?on_conflict=user_id,course_id,semester_id',
headers: {
Prefer:
'resolution=ignore-duplicates',
},
data: {
users: '$users',
course_id: '$id',
semester_id: '$currentSemester',
},
dataType: 'json',
requestAdaptor:
'const data = api.data.users.map(item => {\r\n return {\r\n user_id: item,\r\n course_id: api.data.course_id,\r\n semester_id: api.data.semester_id\r\n }\r\n})\r\n\r\napi.data = data\r\n\r\nreturn api',
},
id: 'u:f305be19cdbe',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:148f700a4963',
ignoreError: false,
actionType: 'reload',
data: {},
dataMergeMode: 'override',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
id: 'u:3fd328b506be',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:851befb0af70',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:916add780f41',
},
],
},
},
],
},
},
},
{
type: 'export-excel',
align: 'right',
label: '导出学生',
icon: 'fa fa-download',
level: 'primary',
api: {
method: 'get',
url: 'rest/users_ex?select=*,user2courses!inner(*)&and=(or(code.like.*${keywords}*,name.like.*${keywords}*))&user2courses.course_id=eq.$id&org_id=in.($class_ids)&order=org_name,code',
requestAdaptor:
"api.url = api.url.replace('&org_id=in.%28%29', '')\r\n\r\nreturn api",
},
filename: '学生',
exportColumns: [
{
label: '*账号',
name: 'code',
},
{
name: 'name',
label: '*姓名',
},
{
name: 'org_name',
label: '班级'
}
],
id: 'u:ab01cbc28399',
},
],
bulkActions: [
{
label: '批量删除',
actionType: 'ajax',
api: {
method: 'delete',
url: 'rest/users?id=in.(${ids|raw})&order=code',
},
confirmText: '确定要批量删除?',
align: 'right',
id: 'u:261e4303f69a',
},
],
syncLocation: false,
footerToolbar: [
{
type: 'pagination',
tpl: '内容',
wrapperComponent: '',
id: 'u:727cc434bcb9',
},
{
type: 'switch-per-page',
tpl: '内容',
wrapperComponent: '',
id: 'u:bba45fa21764',
},
{
type: 'statistics',
tpl: '内容',
wrapperComponent: '',
id: 'u:d80c5e0ac620',
},
],
columns: [
{
type: 'text',
name: 'code',
label: '学号',
id: 'u:9233f140d314',
},
{
type: 'text',
name: 'name',
label: '姓名',
id: 'u:cb913014ed05',
},
{
type: 'text',
label: '班级',
name: 'org_name',
id: 'u:d0de67b68472',
},
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
label: '',
actionType: 'ajax',
size: 'md',
level: 'link',
icon: 'fa fa-times text-danger',
api: {
method: 'delete',
url: 'rest/users/$id',
},
confirmText: '确认删除"${name}(${code})"',
visibleOn: "this.role === 'student'",
tooltip: '删除账号',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:e76d236dfbbc',
}
],
placeholder: '-',
width: 200,
}
],
name: 'group',
bodyClassName: '',
title: '',
initFetch: true,
id: 'u:148f700a4963',
perPageAvailable: [10, 20, 30, 40, 50],
pageField: 'page',
perPageField: 'perPage',
},
{
type: 'crud',
messages: {},
api: {
method: 'get',
url: 'rest/courses?select=*,select_projects:projects,classes,groups,course2projects(*,projects(*,project_type:project_type_fk(dictvalue))),course2orgs(users(*),orgs(*))&organization_id=eq.2&semester_id=eq.$semesterSelect&order=code',
data: {
id: 'eq.$courseSelect',
},
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n ...item,\r\n course2projects: item.course2projects.sort((a, b) => {\r\n return a.projects.name.localeCompare(b.projects.name)\r\n }),\r\n course2orgs: item.course2orgs.filter(item => item.orgs.type !== "studentgroup").sort((a, b) => {\r\n return a.orgs.name.localeCompare(b.orgs.name)\r\n }),\r\n course2groups: item.course2orgs.filter(item => item.orgs.type !== "class" && item.orgs.type !== "reelectclass").sort((a, b) => {\r\n return a.orgs.name.localeCompare(b.orgs.name)\r\n }),\r\n }\r\n })\r\n }\r\n}',
requestAdaptor:
"if (api.body.id === 'eq.') {\r\n api.url = api.url.replace('&id=eq.', '')\r\n}\r\nreturn {\r\n ...api\r\n}",
},
headerToolbar: [
{
type: 'form',
id: 'u:8c0690c8c102',
title: '表单',
wrapWithPanel: false,
target: '',
reload:
'plan?semesterSelect=$semesterSelect&courseSelect=$courseSelect',
submitOnInit: false,
submitOnChange: true,
body: [
{
type: 'select',
label: '学期',
name: 'semesterSelect',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n };\r\n }),\r\n};',
sendOn: '',
},
submitOnChange: true,
visibleOn: '',
autoComplete: '',
mode: 'inline',
inputClassName: 'm-l-xs',
size: 'md',
searchable: true,
id: 'u:9bdb10a2a5c7',
},
{
type: 'select',
label: '查询',
name: 'courseSelect',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/courses?organization_id=eq.2&semester_id=eq.$semesterSelect&order=code',
adaptor:
"return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.name.concat('(', item.code, ')'),\r\n value: item.id,\r\n }\r\n })\r\n}",
sendOn: '',
},
submitOnChange: true,
visibleOn: '',
autoComplete: '',
mode: 'inline',
inputClassName: 'm-l-xs',
clearable: true,
labelClassName: '',
className: 'm-l-sm',
size: 'md',
searchable: true,
id: 'u:12122ee5c0ac',
},
],
feat: 'Insert',
},
{
type: 'button',
align: 'right',
label: '添加',
icon: 'fa fa-plus',
level: 'default',
id: 'u:f303a4872364',
visibleOn: 'this.semesterSelect === this.currentSemester',
name: 'semesterSelect',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n };\r\n }),\r\n};',
sendOn: '',
},
submitOnChange: true,
autoComplete: '',
mode: 'inline',
className: '',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加课程',
body: [
{
type: 'form',
title: '表单',
body: [
{
type: 'control',
label: '',
mode: 'normal',
body: [
{
type: 'grid',
columns: [
{
body: [
{
label: '编号',
name: 'code',
type: 'input-text',
required: true,
mode: 'normal',
id: 'u:373b41b88f85',
},
],
id: 'u:9c7fb175913e',
},
{
body: [
{
label: '名称',
name: 'name',
type: 'input-text',
mode: 'normal',
required: true,
id: 'u:6fb2647e4b89',
},
],
id: 'u:661f33bd71ff',
},
],
id: 'u:5e6d1055e4c9',
},
],
id: 'u:c887113bf328',
},
{
type: 'control',
label: '',
mode: 'normal',
body: [
{
type: 'grid',
columns: [
{
body: [
{
label: '',
name: 'scoring_attendance',
type: 'switch',
mode: 'normal',
value: false,
required: true,
option: '出勤情况是否计入评分',
optionAtLeft: false,
trueValue: true,
falseValue: false,
inputClassName: 'm-t-md m-b-sm',
id: 'u:9efe86e2c743',
},
],
id: 'u:09dd409d8a5f',
},
],
id: 'u:8020115f2a97',
},
],
id: 'u:71c3ed4c9568',
},
{
name: 'item',
type: 'radios',
label: '考勤计分规则',
options: [
{
label: '记零分',
value: 'zero',
},
{
label: '总数取平均',
value: 'average',
},
{
label: '逐个扣分',
value: 'deduction',
},
],
id: 'u:74ff4a000da2',
value: 'average',
hiddenOn: '${!scoring_attendance}',
clearValueOnHidden: true,
mode: 'inline',
},
{
type: 'input-number',
label: '扣分值',
name: 'num',
keyboard: true,
id: 'u:ee239b61f5bb',
step: 1,
mode: 'inline',
value: 10,
min: 0,
hiddenOn: '${item !== "deduction"}',
clearValueOnHidden: true,
},
{
type: 'attscorerule',
name: 'att_score_rule',
label: '考勤评分规则',
api: 'rest/dicts?typecode=eq.014',
visibleOn: 'this.scoring_attendance',
mode: 'normal',
remark: null,
labelRemark: {
icon: 'fa fa-question-circle',
trigger: ['hover', 'focus'],
className: 'Remark--warning',
placement: 'right',
content: '设置每个扣分项系数',
},
description: '',
id: 'u:1a0b6874d2e2',
},
{
label: '描述',
type: 'input-rich-text',
name: 'intro',
mode: 'normal',
options: {
menubar: false,
},
id: 'u:28ff43fab6fc',
},
],
api: {
method: 'post',
url: 'rest/courses',
data: {
'&': '$$',
organization_id: '${centre_id}',
},
dataType: 'json',
},
reload: 'find.courseSelect',
id: 'u:f305be19cdbe',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:e36983b183b3',
ignoreError: false,
actionType: 'reload',
data: {},
dataMergeMode: 'override',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
size: 'lg',
id: 'u:3fd328b506be',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:851befb0af70',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:916add780f41',
},
],
},
},
],
},
},
},
{
type: 'button',
align: 'right',
label: '导入',
icon: 'fa fa-upload',
level: 'warning',
submitOnChange: true,
visibleOn: 'this.semesterSelect === this.currentSemester',
name: 'semesterSelect',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n };\r\n }),\r\n};',
sendOn: '',
},
autoComplete: '',
mode: 'inline',
className: '',
disabledOn: '',
id: 'u:cdfcd31b8af8',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '导入历史数据',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/rpc/create_courses_from_history',
data: null,
dataType: 'json',
headers: {
Prefer: 'params=single-object',
},
},
body: [
{
type: 'select',
label: '历史学期',
mode: 'normal',
name: 'history_semester',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/semesters?is_open=eq.false',
requestAdaptor: '',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
},
required: true,
id: 'u:977dade3d972',
},
{
type: 'select',
label: '历史课程',
mode: 'normal',
name: 'course_list',
required: true,
options: [],
checkAll: true,
defaultCheckAll: false,
checkAllLabel: '全选',
multiple: true,
joinValues: true,
source: {
method: 'get',
url: 'rest/courses?semester_id=eq.${history_semester}&organization_id=eq.2',
sendOn: 'this.history_semester',
adaptor:
'return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id\r\n }\r\n })\r\n }\r\n}',
},
extractValue: false,
id: 'u:64aa5241fbf1',
},
],
id: 'u:b9168b13a3ed',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:34b04fed0264',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:f2fe245af0bf',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:c13e9c8b9584',
},
],
},
},
],
},
},
},
],
syncLocation: false,
perPageAvailable: [10],
footerToolbar: [],
perPageField: '',
pageField: '',
mode: 'cards',
affixHeader: true,
id: 'u:e36983b183b3',
name: 'plan',
card: {
type: 'card',
header: {
title: '$name',
subTitle: '$code',
},
body: [
{
type: 'tabs',
label: '',
tabs: [
{
title: '上课名单',
body: [
{
type: 'form',
className: 'm-b-sm',
id: 'u:90c6fa536042',
mode: 'inline',
name: 'filter',
title: '表单',
submitOnChange: true,
submitOnInit: true,
reload: 'group?class_ids=$class_ids',
wrapWithPanel: false,
body: [
{
type: 'group',
body: [
{
type: 'tree-select',
label: '班级',
name: 'class_ids',
mode: 'horizontal',
multiple: true,
joinValues: false,
extractValue: true,
onlyChildren: true,
clearable: true,
searchable: true,
source: {
method: 'get',
url: 'rest/orgs?select=*,dicts:dicts!organization_type_fkey(*),course2orgs(*)&path=cd.root&course2orgs.course_id=eq.${id}&order=name',
adaptor:
"const recursive = id => {\r\n const nodes = payload.data.items.filter(item => {\r\n return item.parent === id && (\r\n (item.type === 'class' ||\r\n item.type === 'reelectclass') &&\r\n item.course2orgs.length ||\r\n item.type !== 'class' &&\r\n item.type !== 'reelectclass' &&\r\n item.type !== 'studentgroup'\r\n )\r\n })\r\n const result = []\r\n\r\n if (nodes.length) {\r\n nodes.forEach(item => {\r\n const children = recursive(item.id)\r\n\r\n if (children)\r\n result.push({\r\n ...item,\r\n label: item.name,\r\n children,\r\n })\r\n else if (\r\n item.type === 'class' ||\r\n item.type === 'reelectclass'\r\n ) result.push({\r\n ...item,\r\n label: item.name,\r\n value: item.id,\r\n })\r\n })\r\n\r\n if (result.length) return result\r\n } else return null\r\n}\r\n\r\nlet school = payload.data.items.find(x => x.type === 'school')\r\n\r\nreturn {\r\n ...payload,\r\n data: recursive(school.id)\r\n}\r\n",
requestAdaptor:
'if (api.query.course2orgs.course_id === "eq.") {\r\n \n api.url = api.url.replace("&course2orgs[course_id]=eq.", "")\r\n}\r\nif (api.query.course2orgs.course_id === "eq.undefined") {\r\n api.url = api.url.replace("&course2orgs[course_id]=eq.undefined", "")\r\n}',
messages: {},
},
withChildren: true,
initiallyOpen: false,
id: 'u:42ede681efa8',
autoCheckChildren: true,
enableNodePath: false,
showIcon: true,
heightAuto: true,
virtualThreshold: false,
},
],
id: 'u:f2885926e84d',
},
],
feat: 'Insert',
},
{
type: 'divider',
id: 'u:e807ff502b0a',
lineStyle: 'solid',
},
],
id: 'u:f7f436881a45',
},
],
className: 'tab',
id: 'u:1fce1665f076',
},
],
actions: [],
id: 'u:facec9ede01c',
actionsCount: 4,
},
placeholder: '暂无数据',
columnsCount: 1,
loadDataOnce: true,
itemClassName: '',
matchFunc: '',
},
],
id: 'u:08b8e7e7a2e5',
headerToolbar: [
{
type: 'select',
label: '学期',
name: 'semesterSelect',
options: [],
checkAll: false,
source: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since.desc',
adaptor:
'return {\r\n ...payload,\r\n data: payload.data.items.map((item) => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n };\r\n }),\r\n};',
sendOn: '',
},
submitOnChange: true,
visibleOn: '',
autoComplete: '',
mode: 'inline',
},
],
};
export { schema };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,336 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
columns: [
{
name: 'code',
label: '编号',
type: 'text',
id: 'u:15c4fdd31360',
},
{
name: 'name',
label: '节次',
type: 'text',
id: 'u:155ad2b2cd85',
},
{
type: 'text',
label: '开始时间',
id: 'u:90970bb8c69b',
name: 'start_time',
},
{
type: 'text',
label: '结束时间',
name: 'end_time',
id: 'u:c121345b5d56',
},
{
type: 'button-group',
label: '操作',
buttons: [
{
type: 'button',
level: 'link',
icon: 'fa fa-pencil text-info',
size: 'md',
tooltip: '修改',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:d71da5ae8d5e',
onEvent: {
click: {
weight: 0,
actions: [
{
actionType: 'dialog',
dialog: {
title: '修改教学节次',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/periods/$id',
data: {
code: '$code',
name: '$name',
start_time: '$start_time',
end_time: '$end_time',
},
dataType: 'json',
},
id: 'u:51ad7908291f',
body: [
{
label: '编号',
type: 'input-text',
name: 'code',
required: true,
mode: 'normal',
id: 'u:e8f8aaf50ddc',
},
{
label: '节次',
type: 'input-text',
name: 'name',
required: true,
mode: 'normal',
id: 'u:f8e9d0c368f4',
},
{
type: 'input-time',
label: '开始时间',
format: 'HH:mm:ss',
name: 'start_time',
required: true,
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
mode: 'normal',
id: 'u:9b49792274cd',
},
{
type: 'input-time',
label: '结束时间',
name: 'end_time',
format: 'HH:mm:ss',
required: true,
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
mode: 'normal',
id: 'u:f32e3faee4dc',
},
],
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
actionType: 'reload',
componentId: 'u:6da11c92cc84',
args: {
resetPage: false,
},
dataMergeMode: 'merge',
},
],
},
},
},
],
type: 'dialog',
closeOnEsc: true,
showCloseButton: true,
id: 'u:c3be87cc2e31',
},
},
],
},
},
},
{
type: 'button',
confirmText: '确认删除"${name}(${code})"',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
tooltip: '删除',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:c0ec3a95f64c',
onEvent: {
click: {
weight: 0,
actions: [
{
outputVar: 'responseResult',
actionType: 'ajax',
api: {
url: 'rest/periods/$id',
method: 'delete',
},
ignoreError: false,
},
{
ignoreError: false,
actionType: 'reload',
componentId: 'u:6da11c92cc84',
args: {
resetPage: false,
},
},
],
},
},
},
],
placeholder: '-',
width: 200,
id: 'u:8906ce56dc14',
},
],
api: {
method: 'get',
url: 'rest/periods?order=start_time',
data: {
page: '$page',
perPage: '$perPage',
},
requestAdaptor: '',
},
messages: {},
perPageAvailable: [10, 20, 30, 40, 50],
headerToolbar: [
{
type: 'button',
label: '',
align: 'right',
icon: 'fa fa-plus text-primary',
size: 'lg',
level: 'link',
className: 'p-l-none',
tooltip: '添加',
tooltipPlacement: 'left',
id: 'u:af1dabe3755c',
onEvent: {
click: {
weight: 0,
actions: [
{
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加教学节次',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/periods',
data: null,
dataType: 'json',
},
body: [
{
type: 'input-text',
label: '编号',
name: 'code',
required: true,
mode: 'normal',
id: 'u:c526536d8568',
},
{
type: 'input-text',
name: 'name',
label: '节次',
mode: 'normal',
required: true,
id: 'u:fa59fcb75fc9',
},
{
type: 'input-time',
name: 'start_time',
label: '开始时间',
mode: 'normal',
required: true,
format: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
id: 'u:29c058532b80',
},
{
type: 'input-time',
name: 'end_time',
label: '结束时间',
mode: 'normal',
required: true,
format: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
id: 'u:ed17c9f2821a',
},
],
id: 'u:2cac9a30a0f7',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:6da11c92cc84',
actionType: 'reload',
args: {
resetPage: false,
},
dataMergeMode: 'merge',
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:7b13747ea64c',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:6205f65954af',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:212acf2a077a',
},
],
},
},
],
},
},
},
],
syncLocation: false,
footerToolbar: [
{
type: 'pagination',
},
{
type: 'switch-per-page',
},
{
type: 'statistics',
},
],
name: 'per',
bodyClassName: '',
title: '',
initFetch: true,
columnsTogglable: true,
id: 'u:6da11c92cc84',
},
],
title: '',
messages: {},
id: 'u:49951881fbab',
};
export { schema };

View File

@@ -0,0 +1,404 @@
const schema = {
type: 'page',
body: [
{
type: 'crud',
name: 'sem',
id: 'u:038917a084e7',
api: {
method: 'get',
url: 'rest/semesters?order=is_open.desc,since',
data: {
page: '$page',
perPage: '$perPage',
},
requestAdaptor: '',
adaptor: '',
},
columns: [
{
type: 'text',
label: '编号',
name: 'code',
placeholder: '-',
id: 'u:7732470340a9',
},
{
name: 'name',
label: '学期',
type: 'text',
placeholder: '-',
id: 'u:25aaff536205',
},
{
type: 'date',
label: '开始日期',
name: 'since',
placeholder: '-',
id: 'u:09f478f05013',
},
{
type: 'date',
label: '结束日期',
name: 'to',
placeholder: '-',
id: 'u:8eaff00c32c8',
},
{
type: 'tpl',
label: '当前学期',
tpl: '<% if (data.is_open) { %>\n <span class="label label-success">是</span>\n<% } %>',
inline: false,
name: 'is_open',
id: 'u:75d385b2039f',
},
{
buttons: [
{
type: 'button',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '修改学年学期',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'patch',
url: 'rest/semesters/$id',
data: {
code: '$code',
name: '$name',
since: '$since',
to: '$to',
},
},
body: [
{
label: '编号',
type: 'input-text',
name: 'code',
required: true,
mode: 'normal',
id: 'u:6daa52675600',
},
{
label: '学期',
type: 'input-text',
name: 'name',
required: true,
mode: 'normal',
id: 'u:ba2adf16abb3',
},
{
type: 'input-date',
label: '开始日期',
format: 'YYYY-MM-DD',
name: 'since',
required: true,
mode: 'normal',
id: 'u:461905aad6ec',
},
{
type: 'input-date',
label: '结束日期',
name: 'to',
format: 'YYYY-MM-DD',
required: true,
mode: 'normal',
id: 'u:2da01f867a44',
},
],
id: 'u:c1a3d42f5f2c',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:038917a084e7',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:068175fcc455',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:7f3144efc819',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:eb87ba7f98a0',
},
],
},
level: 'link',
icon: 'fa fa-pencil text-info',
size: 'md',
label: '',
tooltip: '修改',
tooltipPlacement: 'top',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:8a3d40a71b13',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
args: {
fromCurrentDialog: true,
},
dialog: {
type: 'dialog',
id: 'u:15384435c118',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:e0d93aab4477',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:712f75a74eac',
},
],
},
},
],
},
},
},
{
type: 'button',
level: 'link',
icon: 'fa fa-times text-danger',
size: 'md',
label: '',
tooltip: '删除',
tooltipPlacement: 'top',
confirmText: '确认删除"${name}(${code})"',
iconClassName: 'pull-left',
className: 'p-r-none p-l-none',
id: 'u:a3c4adaf9dfc',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
outputVar: 'responseResult',
actionType: 'ajax',
options: {},
api: {
url: 'rest/semesters/$id',
method: 'delete',
requestAdaptor: '',
adaptor: '',
messages: {},
},
},
{
componentId: 'u:038917a084e7',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
type: 'button-group',
label: '操作',
placeholder: '-',
width: 200,
id: 'u:ca40a2a8a453',
},
],
messages: {},
headerToolbar: [
{
type: 'button',
label: '',
align: 'right',
icon: 'fa fa-plus text-primary',
size: 'lg',
level: 'link',
className: 'p-l-none',
tooltip: '添加',
tooltipPlacement: 'left',
reload: 'window',
actionType: 'dialog',
dialog: {
type: 'dialog',
title: '添加学年学期',
body: [
{
type: 'form',
title: '表单',
api: {
method: 'post',
url: 'rest/semesters',
data: null,
},
body: [
{
type: 'input-text',
label: '编号',
name: 'code',
required: true,
mode: 'normal',
id: 'u:e91771a2c470',
},
{
type: 'input-text',
name: 'name',
label: '学期',
mode: 'normal',
required: true,
id: 'u:e04aeadb76df',
},
{
type: 'input-date',
name: 'since',
label: '开始日期',
mode: 'normal',
required: true,
format: 'YYYY-MM-DD',
id: 'u:dcd81677a3df',
},
{
type: 'input-date',
name: 'to',
label: '结束日期',
mode: 'normal',
required: true,
format: 'YYYY-MM-DD',
id: 'u:e66fb4a3f528',
},
],
id: 'u:eaa0b0f46535',
actions: [
{
type: 'submit',
label: '提交',
primary: true,
},
],
feat: 'Insert',
dsType: 'api',
onEvent: {
submitSucc: {
weight: 0,
actions: [
{
componentId: 'u:038917a084e7',
ignoreError: false,
actionType: 'reload',
args: {
resetPage: false,
},
},
],
},
},
},
],
closeOnEsc: true,
showCloseButton: true,
id: 'u:ccd3126a083c',
actions: [
{
type: 'button',
actionType: 'cancel',
label: '取消',
id: 'u:5d937c848ec9',
},
{
type: 'button',
actionType: 'confirm',
label: '确定',
primary: true,
id: 'u:1a4a14d90601',
},
],
},
tpl: '内容',
confirmText:
'提示:新建的学期将被设置为新的“当前学期”,之前的学期将被置为“存档状态”,无法更改。请确认学期已经结束,再新建学期。',
id: 'u:7029b79e611d',
onEvent: {
click: {
weight: 0,
actions: [
{
ignoreError: false,
actionType: 'dialog',
args: {
fromCurrentDialog: true,
},
dialog: {
type: 'dialog',
},
},
],
},
},
},
],
syncLocation: false,
perPageAvailable: [10, 20, 30, 40, 50],
footerToolbar: [
{
type: 'pagination',
},
{
type: 'switch-per-page',
},
{
type: 'statistics',
},
],
bodyClassName: '',
},
],
id: 'u:58d186e17b9b',
messages: {},
title: '',
bodyClassName: '',
};
export { schema };

View File

@@ -0,0 +1,196 @@
import {
FormControlProps,
FormItem,
getVariable,
isEffectiveApi,
NumberInput,
Payload,
Switch,
} from 'amis';
import cx from 'classnames';
import React from 'react';
/**
* 考勤评分规则设置
*/
@FormItem({
test: /(^|\/)attscorerule$/,
name: 'attscorerule',
})
export class AttScoreRuleRenderer extends React.Component<
FormControlProps,
{ ruleItems: Array<any> }
> {
constructor(props: FormControlProps) {
super(props);
this.state = {
ruleItems: [],
};
}
componentDidMount() {
const { data, value, env, api } = this.props;
let att_score_rule = getVariable(data, 'att_score_rule');
if (isEffectiveApi(api)) {
env.fetcher(api, data).then((payload: Payload) => {
let items = payload.data.items.map((item: any) => {
let ruleItem: any;
if (att_score_rule !== '')
ruleItem = att_score_rule?.find(
(x: any) => x.item === item.dictkey
);
else ruleItem = null;
return {
item: item.dictkey,
label: item.dictvalue,
enable: ruleItem ? ruleItem.enable : false,
score: ruleItem ? ruleItem.score : 0,
ratio: ruleItem ? ruleItem.ratio : 0,
limit: ruleItem ? ruleItem.limit : 0,
};
});
this.setState({
ruleItems: items,
});
});
}
}
render() {
const { onChange, className } = this.props;
const { ruleItems } = this.state;
return (
<div className={``}>
{ruleItems.map((item, index) => (
<div
key={item.item}
className="cxd-Form-item cxd-Form-item--horizontal"
>
<div
className={cx(
`cxd-NumberControl cxd-Form-control is-inline`,
className
)}
>
<label className="m-l cxd-Form-label cxd-Form-itemColumn--2 w-xs">
<span>{item.label}</span>
</label>
<Switch
className="m-l-sm"
checked={item.enable}
onChange={(value: boolean) => {
ruleItems[index].enable = value;
this.setState({
ruleItems,
});
onChange(
ruleItems.map((x: any) => {
return {
item: x.item,
enable: x.enable,
score: x.score,
ratio: x.ratio,
limit: x.limit,
label: x.label,
};
})
);
}}
/>
<label className="m-l cxd-Form-label cxd-Form-itemColumn--2">
<span></span>
</label>
<NumberInput
className="m-l-sm w-xs"
label="每次扣:"
value={item.score}
required={item.enable}
step={1}
max={100}
onChange={(value: number) => {
ruleItems[index].score = value;
this.setState({
ruleItems,
});
onChange(
ruleItems.map((x: any) => {
return {
item: x.item,
enable: x.enable,
score: x.score,
ratio: x.ratio,
limit: x.limit,
};
})
);
}}
/>
<label className="m-l cxd-Form-label cxd-Form-itemColumn--2">
<span></span>
</label>
<NumberInput
className="m-l-sm w-xs"
label="累次加扣比例:"
value={item.ratio}
required={item.enable}
step={1}
max={100}
onChange={(value: number) => {
ruleItems[index].ratio = value;
this.setState({
ruleItems,
});
onChange(
ruleItems.map((x: any) => {
return {
item: x.item,
enable: x.enable,
score: x.score,
ratio: x.ratio,
limit: x.limit,
};
})
);
}}
/>
<label className="m-l cxd-Form-label cxd-Form-itemColumn--2">
<span></span>
</label>
<NumberInput
className="m-l-sm w-xs"
label="扣分上限:"
value={item.limit}
required={item.enable}
step={1}
max={100}
onChange={(value: number) => {
ruleItems[index].limit = value;
this.setState({
ruleItems,
});
onChange(
ruleItems.map((x: any) => {
return {
item: x.item,
enable: x.enable,
score: x.score,
ratio: x.ratio,
limit: x.limit,
};
})
);
}}
/>
</div>
</div>
))}
</div>
);
}
}

185
src/renderer/BatchGroup.tsx Normal file
View File

@@ -0,0 +1,185 @@
import React from 'react';
import cx from 'classnames';
import { Button, FormItem, FormControlProps, getVariable } from 'amis';
@FormItem({
test: /(^|\/)batch-group$/,
name: 'batch-group',
})
export class Group extends React.Component<
FormControlProps,
{
createdGroups: any;
studentCount: any;
groupsStatus: any;
}
> {
constructor(props: FormControlProps) {
super(props);
this.state = {
createdGroups: [],
studentCount: 0,
groupsStatus: [],
};
}
componentDidMount() {
const { data } = this.props;
const students = getVariable(data, 'selectedItems');
this.setState({ studentCount: students.length });
}
group(payload) {
const { way, students, groups } = payload;
let user2org = [];
let groupsStatus = [];
if (way === 'order') {
const count = Math.ceil(students.length / groups.length);
const remainder = students.length % groups.length;
let i = 0;
for (let j = 0; i < students.length; ++j) {
if (j < remainder || !remainder) {
user2org.push({
user_id: students
.slice(i, i + count)
.map(item => item.student_id),
org: groups[j],
});
i += count;
} else {
user2org.push({
user_id: students
.slice(i, i + count - 1)
.map(item => item.student_id),
org: groups[j],
});
i += count - 1;
}
}
user2org = user2org.flat();
groupsStatus = this.getStatus(user2org);
} else if (way === 'random') {
user2org = groups.map(item => ({
user_id: [],
org: item,
}));
for (let i = 0; i < students.length; ++i) {
user2org[i % groups.length].user_id.push(students[i].student_id);
}
groupsStatus = this.getStatus(user2org);
}
return {
user2org,
groupsStatus
};
}
getStatus(user2org) {
let groupsStatus = user2org.map(item => `分组 ${item.org} 已分配 ${item.user_id.length} 名学生`);
return groupsStatus;
}
check(payload) {
const { env } = this.props;
const { parent, groups, way, students } = payload;
const { groupsStatus } = this.group({ way, students, groups });
env
.fetcher({
url: `rest/orgs?parent=eq.${parent}&type=eq.studentgroup`,
method: 'get',
})
.then((res) => {
this.setState({
createdGroups: res.data.items.map((item) => item.name),
groupsStatus,
});
});
}
create(payload) {
const { env } = this.props;
const { parent, groups, course_id, way, students } = payload;
const newGroups = groups.map(item => ({
name: item,
parent: parseInt(parent),
}));
const { user2org } = this.group({ way, students, groups: newGroups });
const result = { course_id, user2org };
env
.fetcher({
url: 'rest/rpc/group',
method: 'post',
data: result,
headers: {
Prefer: 'params=single-object',
},
})
.then(() => {
env.notify('success', '创建分组成功');
})
.catch(() => {
env.notify('error', '创建分组失败');
});
}
render() {
const { data } = this.props;
return (
<div>
<div>
<div style={{ overflowWrap: 'break-word' }}>{`已建立分组: ${this.state.createdGroups.toString()}`}</div>
<div>{`已选学生人数:${this.state.studentCount}`}</div>
<div>
{this.state.groupsStatus.map((item, index) => (
<div key={`${item}-${index}`}>{item}</div>
))}
</div>
</div>
<div>
<Button
level="primary"
style={{ marginRight: '5px' }}
onClick={() => {
const parent = this.props.store.data.parent;
const groups = this.props.store.data.groups;
const way = this.props.store.data.way;
const students = getVariable(data, 'selectedItems');
this.check({ parent, groups, way, students });
}}
>
</Button>
<Button
level="primary"
onClick={() => {
const parent = this.props.store.data.parent;
const groups = this.props.store.data.groups;
const way = this.props.store.data.way;
const course_id = getVariable(data, 'course_id');
const students = getVariable(data, 'selectedItems');
this.create({ parent, groups, course_id, way, students });
}}
>
</Button>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,687 @@
import React, { useEffect, useState } from 'react';
import { inject, observer } from 'mobx-react';
import { ToolbarProps, View } from 'react-big-calendar';
import { Button, getVariable, Payload, RendererProps, Select } from 'amis';
import {
addDays,
differenceInDays,
format,
parse,
startOfMonth,
startOfWeek,
} from 'date-fns';
export default inject('store')(
observer(function ({
env,
data,
render,
body,
label,
date,
view,
onNavigate,
onView,
course,
project,
location,
teacher,
period,
mode,
events,
copiedEvents,
input,
onOutput,
onImport,
onAction,
onRotation,
onCopy,
onCourseChange,
onProjectChange,
onLocationChange,
onTeacherChange,
onPeriodChange,
onModeChange,
onEventsChange,
onPaste,
onSelectAll,
onEdit,
onDateChange,
onDateCopy,
}: RendererProps & ToolbarProps) {
const [courses, setCourses] = useState([]);
const [projects, setProjects] = useState([]);
const [locations, setLocations] = useState([]);
const [teachers, setTeachers] = useState([]);
const [periods, setPeriods] = useState([]);
let centre_id = getVariable(data, 'centre_id');
let user = getVariable(data, 'user');
let semester_id = getVariable(data, 'currentSemester');
useEffect(() => {
env
.fetcher(
`rest/courses?organization_id=eq.${centre_id}&semester_id=eq.${semester_id}`
)
.then((data: any) => {
setCourses(
data.data.items.map((item: any) => {
return {
label: item.name,
value: item.id,
};
})
);
});
// env
// .fetcher(
// `rest/projects?organization_id=eq.${centre_id}&semester_id=eq.${semester_id}`
// )
// .then((data: any) => {
// setProjects(
// data.data.items.map((item: any) => {
// return {
// label: item.name,
// value: item.id,
// };
// })
// );
// });
env.fetcher(`rest/periods?order=start_time,code`).then((data: any) => {
setPeriods(
data.data.items.map((item: any) => {
return {
label: item.name,
value: item.id,
};
})
);
});
}, []);
useEffect(() => {
if (course) {
env
.fetcher(
`rest/course2projects?select=projects!inner(id,name)&projects.organization_id=eq.${centre_id}&projects.semester_id=eq.${semester_id}&projects.free_schedule=eq.false&course_id=eq.${course}`
)
.then((data: any) => {
setProjects(
data.data.items.map((item: any) => {
return {
label: item.projects.name,
value: item.projects.id,
};
})
);
});
} else {
env
.fetcher(
`rest/projects?organization_id=eq.${centre_id}&semester_id=eq.${semester_id}&free_schedule=eq.false`
)
.then((data: any) => {
setProjects(
data.data.items.map((item: any) => {
return {
label: item.name,
value: item.id,
};
})
);
});
}
}, [course]);
useEffect(() => {
if (project) {
env
.fetcher({
url: `rest/rpc/project_resources`,
method: 'post',
data: {
project_id: project,
type: 'location',
centre_id: centre_id,
},
})
.then((data: any) => {
setLocations(
data.data.items.map((item: any) => {
return {
label: item.label,
value: item.value,
};
})
);
});
env
.fetcher({
method: 'post',
url: `rest/rpc/project_resources`,
data: {
project_id: project,
type: 'teacher',
centre_id: centre_id,
},
})
.then((data: any) => {
setTeachers(
data.data.items.map((item: any) => {
return {
label: item.label,
value: item.value,
};
})
);
});
} else {
env
.fetcher(
`rest/locations?organization_id=eq.${centre_id}&is_valid=eq.true`
)
.then((data: any) => {
setLocations(
data.data.items.map((item: any) => {
return {
label: item.name,
value: item.id,
};
})
);
});
env
.fetcher(`rest/users_ex?org_id=eq.${centre_id}&role=eq.teacher`)
.then((data: any) => {
setTeachers(
data.data.items.map((item: any) => {
return {
label: item.name,
value: item.id,
};
})
);
});
}
}, [project]);
const changeGroup = (events: Array<any>) => {
env
.fetcher({
method: 'patch',
url: `rest/schedules?id=in.(${events.join(',')})`,
data: {
group: Date.now().toString(),
},
})
.then((payload: Payload) => {
onEventsChange();
});
};
const handleDelete = () => {
env.confirm('确认删除选择的场次?', '提示').then((value: boolean) => {
if (value) {
let ids = [];
events.forEach((x) => {
if (x.isSelected) {
ids.push(x.id);
}
});
env
.fetcher({
url: `rest/rpc/delete_schedules`,
method: 'post',
data: {
schedules: ids,
},
dataType: 'json',
headers: {
Prefer: 'params=single-object',
},
})
.then((payload: Payload) => {
onEventsChange();
});
}
});
};
const handleGroup = () => {
let selectedEvents = events.filter((x: any) => x.isSelected);
let group = selectedEvents.filter((x: any) => x.group !== null);
let selectedEventId = selectedEvents.map((x: any) => x.id);
if (selectedEvents.length <= 1) {
env.notify('warning', '请选择多个场次进行分组');
} else {
if (group.length > 0) {
env
.confirm('选择的场次中存在已分组场次,是否合并分组?', '提示')
.then((value: boolean) => {
if (value) {
env
.fetcher({
url: `rest/schedules?group=in.(${group
.map((x: any) => group.id)
.join(',')})`,
})
.then((payload: Payload) => {
let exps = selectedEvents
.map((x: any) => x.project.id)
.concat(
payload.data.items
.filter(
(x: any) =>
selectedEvents.findIndex(
(y: any) => y.id === x.id
) === -1
)
.map((x: any) => x.project_id)
);
let obj: any = {};
for (let i = 0; i < exps.length; i++) {
obj[exps[i]] = i;
}
if (exps.length !== Object.keys(obj).length) {
env.notify('error', '关联场次不能有相同的实验');
return;
}
changeGroup(
selectedEventId.concat(
payload.data.items.map((x: any) => x.id)
)
);
});
} else {
changeGroup(selectedEventId);
}
});
} else {
changeGroup(selectedEventId);
}
}
};
const handleUnGroup = () => {
let selectedEvents = events.filter((x: any) => x.isSelected);
let selectedEventId = selectedEvents.map((x: any) => x.id);
if (selectedEvents.length <= 1) {
env.notify('warning', '未选择要取消分组的场次');
} else {
env
.confirm('是否取消选择的场次中存在的分组?', '提示')
.then((value: boolean) => {
if (value) {
env
.fetcher({
url: `rest/schedules?id=in.(${selectedEventId})`,
method: 'patch',
data: {
group: null,
},
})
.then((payload: Payload) => {
onEventsChange();
});
}
});
}
};
return (
<>
<div className="hbox m-b-xs" style={{ height: 'auto' }}>
<div
className="col wrapper-sm b-r w-xxl "
style={{ verticalAlign: 'middle' }}
>
<Button
className="col"
level="link"
onClick={(event: any) => {
event.preventDefault();
onNavigate('PREV');
}}
>
<i className="fa fa-chevron-left" aria-hidden="true"></i>
</Button>
<span className="m-l m-r inline-block w text-center">{`${label}`}</span>
<Button
className="col"
level="link"
onClick={(event: any) => {
event.preventDefault();
onNavigate('NEXT');
}}
>
<i className="fa fa-chevron-right" aria-hidden="true"></i>
</Button>
</div>
<div className="col wrapper-sm lter">
<div
className=" center-block btn-group text-right "
role="group"
aria-label="..."
>
<Select
className={`w m-l-sm`}
options={courses}
clearable={true}
searchable={true}
placeholder={`请选择课程`}
onChange={onCourseChange}
value={course}
/>
<Select
className={`w m-l-sm`}
options={projects}
clearable={true}
searchable={true}
placeholder={`请选择实验`}
onChange={onProjectChange}
value={project}
resetValue={null}
disabled={false}
/>
<Select
className={`w-sm m-l-sm`}
options={locations}
clearable={true}
searchable={true}
placeholder={`请选择场地`}
onChange={onLocationChange}
value={location}
/>
<Select
className={`w-sm m-l-sm`}
options={teachers}
clearable={true}
searchable={true}
placeholder={`请选择教师`}
onChange={onTeacherChange}
value={teacher}
/>
<Select
className={`w-sm m-l-sm`}
options={periods}
clearable={true}
searchable={true}
placeholder={`请选择节次`}
onChange={onPeriodChange}
value={period}
/>
</div>
<div className="center-block m-t pull-right">
{user?.role === 'admin' ||
user?.role === 'centreadmin' ||
user?.role === 'dev' ||
user?.role === 'teacher' ? (
<div
className="btn-group inline-block m-l-sm "
role="group"
aria-label="..."
>
<Button disabled={mode === 'select'} onClick={onRotation}>
</Button>
<Button
disabled={mode === 'select'}
onClick={() => {
const myEvents = events.filter((item) => item.isMine);
if (!myEvents.length) {
env.notify('warning', '当前视图没有您负责的场次!');
return;
}
let url;
if (user?.role === 'teacher') {
url = `rest/schedules?id=in.(${myEvents
.map((item: any) => item.id)
.join(',')})`;
} else {
url = `rest/schedules?id=in.(${events
.map((item: any) => item.id)
.join(',')})`;
}
env
.confirm('确认发布当前视图下所有您负责的场次?')
.then((value: boolean) => {
if (value) {
env
.fetcher({
url: url,
method: 'patch',
data: {
is_publish: true,
},
})
.then(() => {
onEventsChange();
});
}
});
}}
>
</Button>
<Button
disabled={mode === 'select'}
onClick={() => {
const myEvents = events.filter((item) => item.isMine);
if (!myEvents.length) {
env.notify('warning', '当前视图没有您负责的场次!');
return;
}
let url;
if (user?.role === 'teacher') {
url = `rest/schedules?id=in.(${myEvents
.map((item: any) => item.id)
.join(',')})`;
} else {
url = `rest/schedules?id=in.(${events
.map((item: any) => item.id)
.join(',')})`;
}
env
.confirm('确认将当前视图下所有您负责的场次设置为草稿?')
.then((value: boolean) => {
if (value) {
env
.fetcher({
url: url,
method: 'patch',
data: {
is_publish: false,
},
})
.then(() => {
onEventsChange();
});
}
});
}}
>
稿
</Button>
<Button disabled={mode === 'event'} onClick={onCopy}>
</Button>
<Button
disabled={mode === 'event' || copiedEvents.length === 0}
onClick={() => {
if (copiedEvents.length === 0) {
env.notify('warning', '没有复制场次');
return;
}
env
.confirm('是否保留场次班级?', '粘贴场次')
.then((value: boolean) => {
let schedules = copiedEvents.map((schedule: any) => {
let scheduleDate = parse(
schedule.date,
'yyyy-MM-dd',
new Date(),
{
weekStartsOn: 1,
}
);
let targetDate = schedule.date;
if (view === 'day') {
targetDate = date;
}
if (view === 'week') {
targetDate = addDays(
scheduleDate,
differenceInDays(
startOfWeek(date, {
weekStartsOn: 1,
}),
startOfWeek(scheduleDate, {
weekStartsOn: 1,
})
)
);
}
if (view === 'month') {
targetDate = addDays(
scheduleDate,
differenceInDays(
startOfMonth(date),
startOfMonth(scheduleDate)
)
);
}
return {
is_reserved: schedule.is_reserved,
is_scheduled: schedule.is_scheduled,
date: format(targetDate, 'yyyy-MM-dd'),
period_id: schedule.period_id,
location_id: schedule.location_id,
course_id: schedule.course_id,
project_id: schedule.project_id,
teacher_id: schedule.teacher_id,
organization_id: schedule.organization_id,
class_list: value ? schedule.class_list : [],
max_student_number: schedule.max_student_number,
min_student_number: schedule.min_student_number,
max_group_student_number:
schedule.max_group_student_number,
};
});
if (schedules.length > 0) {
env
.fetcher({
url: `rest/schedules`,
method: 'post',
data: schedules,
})
.then(() => {
onEventsChange();
onPaste();
});
}
});
}}
>
</Button>
<Button
onClick={() => {
if (mode === 'event') {
onModeChange('select');
} else {
onModeChange('event');
}
}}
>
{mode === 'event' ? '选择' : '取消'}
</Button>
<Button disabled={mode === 'event'} onClick={onSelectAll}>
</Button>
<Button disabled={mode === 'event'} onClick={onEdit}>
</Button>
<Button disabled={mode === 'event'} onClick={onDateChange}>
</Button>
<Button disabled={mode === 'event'} onClick={onDateCopy}>
</Button>
<Button disabled={mode === 'event'} onClick={handleGroup}>
</Button>
<Button disabled={mode === 'event'} onClick={handleUnGroup}>
</Button>
<Button
level="danger"
disabled={mode === 'event'}
onClick={handleDelete}
>
</Button>
<Button level="primary" onClick={onOutput}>
</Button>
<Button
level="warning"
icon="fa fa-upload"
onClick={onImport}
>
</Button>
</div>
) : (
<></>
)}
<div
className="btn-group inline-block m-l-sm "
role="group"
aria-label="..."
>
<Button
onClick={() => {
onView('month');
}}
>
</Button>
<Button
onClick={() => {
onView('week');
}}
>
</Button>
<Button
onClick={() => {
onView('day');
}}
>
</Button>
</div>
</div>
</div>
</div>
</>
);
})
);

View File

@@ -0,0 +1,12 @@
export default [
'rgb(0, 153, 102)',
'rgb(51, 153, 51)',
'rgb(255, 204, 204)',
'rgb(102, 102, 153)',
'rgb(102, 204, 204)',
'rgb(204, 51, 51)',
'rgb(255, 153, 102)',
'rgb(102, 51, 102)',
'rgb(204, 153, 51)',
'rgb(255, 204, 51)',
];

View File

@@ -0,0 +1,830 @@
import { getVariable, Renderer, RendererProps, ScopedContext } from 'amis';
import {
endOfMonth,
endOfWeek,
format,
getDay,
isAfter,
isBefore,
parse,
startOfMonth,
startOfWeek,
} from 'date-fns';
import { zhCN } from 'date-fns/locale';
import React from 'react';
import { Calendar, dateFnsLocalizer, View } from 'react-big-calendar';
import 'react-big-calendar/lib/css/react-big-calendar.css';
import XLSX from 'xlsx';
import groupColors from './groupColors';
import Header from './Header';
const locales = { zh: zhCN };
const localizer = dateFnsLocalizer({
format,
parse,
startOfWeek,
getDay,
locales,
});
@Renderer({
test: /(^|\/)calendar\-renderer$/,
name: 'calendar-renderer',
})
export class CalendarRenderer extends React.Component<
RendererProps,
{
min: Date;
max: Date;
date: Date;
start: Date;
end: Date;
mode: string;
view: View;
events: Array<any>;
copiedEvents: Array<any>;
periods: Array<any>;
groups: Array<any>;
output: Array<any>;
course?: number;
project?: number;
location?: number;
teacher?: number;
period?: number;
courses: Array<any>;
defaultLessonStudentMin?: number;
defaultLessonStudentMax?: number;
defaultGroupStudentMax?: number;
showDraftToTeacher: boolean;
}
> {
static defaultProps = {
items: [],
};
static contextType = ScopedContext;
constructor(props: RendererProps) {
super(props);
this.state = {
min: parse('08:00:00', 'kk:mm:ss', new Date()),
max: parse('22:00:00', 'kk:mm:ss', new Date()),
date: new Date(),
start: new Date(),
end: new Date(),
mode: 'event',
view: 'week',
events: [],
copiedEvents: [],
periods: [],
groups: [],
output: [],
course: undefined,
project: undefined,
location: undefined,
teacher: undefined,
period: undefined,
courses: undefined,
defaultLessonStudentMin: 1,
defaultLessonStudentMax: 32,
defaultGroupStudentMax: 5,
showDraftToTeacher: false,
};
}
async fetchSchedules(start: string, end: string) {
const currentSemester = getVariable(this.props.data, 'currentSemester');
const user = getVariable(this.props.data, 'user');
let url = `rest/schedules?select=*,course:courses!schedule_course_id_fkey(id,name,course2teachers(*)),project:projects!schedule_project_id_fkey(id,name), period:schedule_period_id_fkey(id,start_time,end_time),location:locations!schedule_location_id_fkey(id,name), teacher:users!schedule_teacher_id_fkey(id,code,name),reserved_classes,reserved_classes_code&date=gte.${start}&date=lte.${end}&semester_id=eq.${currentSemester}&course.course2teachers.teacher_id=eq.${user.id}`;
if (
user?.role === 'teacher' &&
!this.state.courses.length &&
!this.state.showDraftToTeacher
) {
url += `&is_publish=eq.true`;
}
if (this.state.course) {
url += `&course_id=eq.${this.state.course}`;
}
if (this.state.project) {
url += `&project_id=eq.${this.state.project}`;
}
if (this.state.location) {
url += `&location_id=eq.${this.state.location}`;
}
if (this.state.teacher) {
url += `&teacher_id=eq.${this.state.teacher}`;
}
if (this.state.period) {
url += `&period_id=eq.${this.state.period}`;
}
const { data } = await this.props.env.fetcher(url);
const events = data.items.map((item: any) => {
if (item.is_reserved) {
return {
title: '(保留)'.concat(item.project.name),
start: parse(
`${item.date} ${item.period.start_time}`,
'yyyy-MM-dd kk:mm:ss',
new Date()
),
end: parse(
`${item.date} ${item.period.end_time}`,
'yyyy-MM-dd kk:mm:ss',
new Date()
),
allDay: false,
isSelected: false,
isMine: !!(
(user?.role === 'teacher' &&
item.course &&
item.course.course2teachers.length) ||
user?.role === 'admin' ||
user?.role === 'centreadmin' ||
user?.role === 'dev'
),
...item,
};
} else {
return {
title: item.project.name,
start: parse(
`${item.date} ${item.period.start_time}`,
'yyyy-MM-dd kk:mm:ss',
new Date()
),
end: parse(
`${item.date} ${item.period.end_time}`,
'yyyy-MM-dd kk:mm:ss',
new Date()
),
allDay: false,
isSelected: false,
isMine: !!(
(user?.role === 'teacher' &&
item.course &&
item.course.course2teachers.length) ||
user?.role === 'admin' ||
user?.role === 'centreadmin' ||
user?.role === 'dev'
),
...item,
};
}
});
this.setState({
start: parse(`${start}`, 'yyyy-MM-dd', new Date()),
end: parse(`${end}`, 'yyyy-MM-dd', new Date()),
events: events,
groups: events.map((x: any) => x.group).sort(),
});
}
async fetchCourses(start, end) {
const user = getVariable(this.props.data, 'user');
const currentSemester = getVariable(this.props.data, 'currentSemester');
const centre_id = getVariable(this.props.data, 'centre_id');
let url = `rest/courses?select=*,course2teachers!inner(*)&semester_id=eq.${currentSemester}&organization_id=eq.${centre_id}&course2teachers.in_charge=is.true&course2teachers.teacher_id=eq.${user.id}`;
const { data } = await this.props.env.fetcher(url);
this.setState({ courses: data.items });
if (start) this.fetchSchedules(start, end);
}
async fetchOutput() {
const currentSemester = getVariable(this.props.data, 'currentSemester');
const user = getVariable(this.props.data, 'user');
let url = `rest/schedules?select=*,course:courses!schedule_course_id_fkey(id,code,name),project:projects!schedule_project_id_fkey(id,code,name), period:schedule_period_id_fkey(id,code,name,start_time,end_time),location:locations!schedule_location_id_fkey(id,code,name), teams(id,code,name),teacher:users!schedule_teacher_id_fkey(id,code,name),reserved_classes,reserved_classes_code&semester_id=eq.${currentSemester}&order=date`;
if (
user?.role === 'teacher' &&
!this.state.courses.length &&
!this.state.showDraftToTeacher
) {
url += `&is_publish=eq.true`;
}
if (this.state.course) {
url += `&course_id=eq.${this.state.course}`;
}
if (this.state.project) {
url += `&project_id=eq.${this.state.project}`;
}
if (this.state.location) {
url += `&location_id=eq.${this.state.location}`;
}
if (this.state.teacher) {
url += `&teacher_id=eq.${this.state.teacher}`;
}
if (this.state.period) {
url += `&period_id=eq.${this.state.period}`;
}
const { data } = await this.props.env.fetcher(url);
this.setState({ output: data.items });
let workbook = XLSX.utils.book_new();
let tempSheet: Array<any> = [
[
'*日期',
'*节次编号',
'节次',
'*课程编号',
'课程',
'*实验编号',
'实验',
'*场地编号',
'场地',
'团队编号',
'团队',
'教师账号',
'教师',
'*最大人数',
'*最小人数',
'是否自动排课',
'*是否发布',
'*是否保留场次',
'班级编号',
'班级',
],
];
this.state.output.forEach((item: any) => {
tempSheet.push([
{ t: 's', v: item.date },
{ t: 's', v: item.period.code },
{ t: 's', v: item.period.name },
{ t: 's', v: item.course ? item.course.code : '' },
{ t: 's', v: item.course ? item.course.name : '' },
{ t: 's', v: item.project.code },
{ t: 's', v: item.project.name },
{ t: 's', v: item.location.code },
{ t: 's', v: item.location.name },
{ t: 's', v: item.teams ? item.teams.code : '' },
{ t: 's', v: item.teams ? item.teams.name : '' },
{ t: 's', v: item.teacher ? item.teacher.code : '' },
{ t: 's', v: item.teacher ? item.teacher.name : '' },
{ t: 's', v: item.max_student_number },
{ t: 's', v: item.min_student_number },
{ t: 'b', v: item.is_scheduled },
{ t: 'b', v: item.is_publish },
{ t: 'b', v: item.is_reserved },
{
t: 's',
v: item.reserved_classes_code ? item.reserved_classes_code : '',
},
{ t: 's', v: item.reserved_classes ? item.reserved_classes : '' },
]);
});
let worksheet = XLSX.utils.aoa_to_sheet(tempSheet);
XLSX.utils.book_append_sheet(workbook, worksheet, 'sheet1');
XLSX.writeFile(workbook, '开放选课.xlsx');
}
async handleViewChange(date: Date, view: View) {
this.fetchCourses(
format(this.state.start, 'yyyy-MM-dd'),
format(this.state.end, 'yyyy-MM-dd')
);
}
async componentDidMount() {
const { env, data } = this.props;
const user = getVariable(data, 'user');
const payload = await env.fetcher(`rest/config?key=eq.system`);
const configData =
payload.data.items && payload.data.items.length > 0
? payload.data.items[0].value
: undefined;
const { data: periodsData } = await env.fetcher(
'rest/periods?order=start_time.asc'
);
let start_time =
periodsData && periodsData.items.length > 0
? periodsData.items[0].start_time
: '08:00:00';
let end_time =
periodsData && periodsData.items.length > 0
? periodsData.items[periodsData.items.length - 1].end_time
: '20:00:00';
this.setState({
teacher: user?.role === 'teacher' ? user?.id : undefined,
periods: periodsData.items,
min: parse(start_time, 'kk:mm:ss', new Date()),
max: parse(end_time, 'kk:mm:ss', new Date()),
defaultLessonStudentMin: configData.defaultLessonStudentMin,
defaultLessonStudentMax: configData.defaultLessonStudentMax,
defaultGroupStudentMax: configData.defaultGroupStudentMax,
showDraftToTeacher: configData.showDraftToTeacher,
});
this.fetchCourses(null, null);
}
componentDidUpdate(prevProps: any, prevState: any, snapshot: any) {
let start = this.state.date;
let end = this.state.date;
if (this.state.view === 'week') {
start = startOfWeek(this.state.date, { weekStartsOn: 1 });
end = endOfWeek(this.state.date, { weekStartsOn: 1 });
}
if (this.state.view === 'month') {
start = startOfMonth(this.state.date);
end = endOfMonth(this.state.date);
}
let format_start = format(start, 'yyyy-MM-dd');
let format_end = format(end, 'yyyy-MM-dd');
let prev_start = format(prevState.start, 'yyyy-MM-dd');
let prev_end = format(prevState.end, 'yyyy-MM-dd');
if (format_start !== prev_start || format_end !== prev_end) {
this.fetchCourses(format_start, format_end);
}
}
componentWillMount() {
const scoped = this.context;
scoped.registerComponent(this);
}
componentWillUnmount() {
const scoped = this.context;
scoped.unRegisterComponent(this);
}
// 增删改查后触发
reload() {
this.handleViewChange(this.state.date, this.state.view);
}
render() {
const { env, data } = this.props;
const { date, view, mode, events, copiedEvents, groups } = this.state;
const user = getVariable(data, 'user');
let filterEvents = events;
return (
<>
<Calendar
// className={classes.calendar}
selectable
localizer={localizer}
culture="zh"
events={filterEvents}
defaultView={'week'}
view={view}
date={date}
startAccessor="start"
endAccessor="end"
tooltipAccessor={(event) => {
let tip: any;
if (event.is_reserved) {
tip = `\r
实验:${event.title}\r
地点:${event.location.name}\r
教师:${event.teacher?.name ? event.teacher.name : '未指定'}
保留班级:${event.reserved_classes}`;
} else {
tip = `\r
实验:${event.title}\r
地点:${event.location.name}\r
教师:${event.teacher?.name ? event.teacher.name : '未指定'}`;
}
return tip;
}}
eventPropGetter={({ group, is_publish, isSelected }) => {
let style: any = Object.assign({}, {});
if (group) {
if (groups.indexOf(group) !== -1) {
style.backgroundColor = groupColors[groups.indexOf(group)];
}
}
if (!is_publish) {
style.border = 'dashed';
}
if (isSelected) {
style.opacity = '.5';
}
return {
style: style,
};
}}
step={30}
min={this.state.min}
max={this.state.max}
showMultiDayTimes
components={{
toolbar: (props) => {
return (
<Header
{...props}
{...this.props}
view={this.state.view}
course={this.state.course}
project={this.state.project}
location={this.state.location}
teacher={this.state.teacher}
period={this.state.period}
mode={this.state.mode}
events={filterEvents}
copiedEvents={this.state.copiedEvents}
onOutput={() => {
this.fetchOutput();
}}
onRotation={() => {
const { rotation } = this.props;
if (user?.role === 'teacher')
rotation.dialog.actions[0].dialog.body[0].body[2].body[0].source.url =
'rest/courses?select=*,course2teachers!inner(*)&semester_id=eq.${currentSemester}&organization_id=eq.${centre_id}&course2teachers.in_charge=is.true&course2teachers.teacher_id=eq.${user.id}';
this.props.onAction(
null,
rotation,
{},
false,
this.context
);
}}
onCopy={() => {
this.setState({
copiedEvents: filterEvents.filter((item) => {
return item.isSelected;
}),
});
env.notify('success', '请选择要粘贴到的时间段');
}}
onCourseChange={(item: any) => {
this.setState(
{
course: item?.value,
project: undefined,
location: undefined,
teacher: undefined,
},
() => {
this.handleViewChange(this.state.date, this.state.view);
}
);
}}
onProjectChange={(item: any) => {
this.setState(
{
project: item?.value,
location: undefined,
teacher: undefined,
},
() => {
this.handleViewChange(this.state.date, this.state.view);
}
);
}}
onLocationChange={(item: any) => {
this.setState(
{
location: item?.value,
},
() => {
this.handleViewChange(this.state.date, this.state.view);
}
);
}}
onTeacherChange={(item: any) => {
this.setState(
{
teacher: item?.value,
},
() => {
this.handleViewChange(this.state.date, this.state.view);
}
);
}}
onPeriodChange={(item: any) => {
this.setState(
{
period: item?.value,
},
() => {
this.handleViewChange(this.state.date, this.state.view);
}
);
}}
onModeChange={(mode: string) => {
this.setState({
mode: mode,
copiedEvents: [],
});
this.setState({
events: events.map((item: any) =>
Object.assign(item, {
isSelected: false,
})
),
});
}}
onEventsChange={() => {
this.fetchCourses(
format(this.state.start, 'yyyy-MM-dd'),
format(this.state.end, 'yyyy-MM-dd')
);
}}
onPaste={() => {
this.setState({
copiedEvents: [],
});
}}
onSelectAll={() => {
this.setState({
events: events.map((item: any) => {
if (item.isMine) {
Object.assign(item, {
isSelected: !item.isSelected,
});
}
return item;
}),
});
}}
onImport={() => {
const { input } = this.props;
this.props.onAction(null, input, {}, false, this.context);
}}
onEdit={() => {
const { edit } = this.props;
if (user?.role === 'teacher')
edit.dialog.body[0].tabs[0].body[0].body[2].source.url =
'rest/courses?select=*,course2teachers!inner(*)&semester_id=eq.${currentSemester}&organization_id=eq.${centre_id}&course2teachers.in_charge=is.true&course2teachers.teacher_id=eq.${user.id}';
let selectedEvents = [];
selectedEvents = events.filter((x: any) => x.isSelected);
let selectedEventId = selectedEvents.map((x: any) => x.id);
if (selectedEvents.length === 0) {
env.notify('warning', '未选择要编辑的场次');
return;
}
this.props.onAction(
null,
edit,
{
schedules: selectedEventId,
},
false,
this.context
);
}}
onDateChange={() => {
const { change } = this.props;
let selectedEvents = [];
selectedEvents = events.filter((x: any) => x.isSelected);
let selectedEventId = selectedEvents.map((x: any) => x.id);
if (selectedEvents.length === 0) {
env.notify('warning', '未选择要移动的场次');
return;
}
this.props.onAction(
null,
change,
{
schedules: selectedEventId,
op: 'move',
day: 1,
withReserved: false,
},
false,
this.context
);
}}
onDateCopy={() => {
const { copy } = this.props;
let selectedEvents = [];
selectedEvents = events.filter((x: any) => x.isSelected);
let selectedEventId = selectedEvents.map((x: any) => x.id);
if (selectedEvents.length === 0) {
env.notify('warning', '未选择要复制的场次');
return;
}
let selectedEventItems = selectedEvents.map((x: any) => {
return {
is_reserved: x.is_reserved,
is_scheduled: x.is_scheduled,
date: x.date,
location_id: x.location_id,
course_id: x.course_id,
project_id: x.project_id,
teacher_id: x.teacher_id,
semester_id: x.semester_id,
organization_id: x.organization_id,
class_list: x.class_list,
max_student_number: x.max_student_number,
min_student_number: x.min_student_number,
max_group_student_number: x.max_group_student_number,
};
});
this.props.onAction(
null,
copy,
{
schedules: selectedEventId,
items: selectedEventItems,
op: 'copy',
day: 1,
},
false,
this.context
);
}}
/>
);
},
}}
onNavigate={(date, view) => {
this.setState({
date: date,
});
}}
onView={(view) => {
if (copiedEvents.length > 0 && view != this.state.view) {
env
.confirm('切换视图后请重新复制!', '提示')
.then((value: boolean) => {
if (value) {
this.setState({
copiedEvents: [],
view: view,
});
}
});
} else {
this.setState({
view: view,
});
}
}}
onRangeChange={(range: any) => {}}
onSelectEvent={(event) => {
const { body, add, list } = this.props;
if (event.isMine) {
if (user?.role === 'teacher')
add.dialog.body[0].controls[1].controls[1].source.url =
'rest/courses?select=*,course2teachers!inner(*)&semester_id=eq.${currentSemester}&organization_id=eq.${centre_id}&course2teachers.in_charge=is.true&course2teachers.teacher_id=eq.${user.id}';
if (mode === 'select') {
this.setState({
events: events.map((item: any) => {
return item.id === event.id && item.isMine
? Object.assign(item, {
isSelected: !item.isSelected,
})
: item;
}),
});
} else {
this.props.onAction(
null,
add,
{
id: event.id,
date: event.date,
period_id: event.period?.id,
location_id: event.location?.id,
course_id: event.course?.id,
project_id: event.project?.id,
team_id: event.team_id,
teacher_id: event.teacher?.id,
max_student_number: event.max_student_number,
min_student_number: event.min_student_number,
max_group_student_number: event.max_group_student_number,
is_publish: event.is_publish,
is_reserved: event.is_reserved,
is_scheduled: event.is_scheduled,
organization_id: event.organization_id,
class_list: event.class_list,
},
false,
this.context
);
}
} else {
if (mode === 'select') {
return;
}
this.props.onAction(
null,
list,
{
id: event.id,
date: event.date,
start_time: event.period?.start_time,
project_name: event.project?.name,
location_name: event.location?.name,
teacher_name: event.teacher?.name,
},
false,
this.context
);
}
return;
}}
onSelectSlot={(slotInfo: any) => {
if (
user?.role !== 'admin' &&
user?.role !== 'centreadmin' &&
user?.role !== 'dev' &&
!(user?.role === 'teacher' && this.state.courses.length)
) {
return;
}
const { body, add } = this.props;
if (user?.role === 'teacher' && this.state.courses.length) {
if (
this.state.course &&
!this.state.courses.some((item) => item.id == this.state.course)
) {
env.notify('error', '您不能对所选课程进行排课!');
return;
} else {
add.dialog.body[0].controls[1].controls[1].source.url =
'rest/courses?select=*,course2teachers!inner(*)&semester_id=eq.${currentSemester}&organization_id=eq.${centre_id}&course2teachers.in_charge=is.true&course2teachers.teacher_id=eq.${user.id}';
}
}
const period = this.state.periods.find((item) => {
let start = parse(
`${format(slotInfo.start, 'yyyy-MM-dd')} ${item.start_time}`,
'yyyy-MM-dd kk:mm:ss',
new Date()
);
let end = parse(
`${format(slotInfo.start, 'yyyy-MM-dd')} ${item.end_time}`,
'yyyy-MM-dd kk:mm:ss',
new Date()
);
return (
!isBefore(slotInfo.start, start) &&
!isAfter(slotInfo.start, end)
);
});
if (period && this.state.mode === 'event') {
let data: any = {
date: format(slotInfo.slots[0], 'yyyy-MM-dd'),
period_id: period.id,
max_student_number: this.state.defaultLessonStudentMax,
min_student_number: this.state.defaultLessonStudentMin,
max_group_student_number: this.state.defaultGroupStudentMax,
};
if (this.state.course) data.course_id = this.state.course;
if (this.state.project) data.project_id = this.state.project;
if (this.state.project && this.state.location)
data.location_id = this.state.location;
if (this.state.project && this.state.teacher)
data.teacher_id = this.state.teacher;
this.props.onAction(null, add, data);
}
}}
></Calendar>
</>
);
}
}

367
src/renderer/CourseList.tsx Normal file
View File

@@ -0,0 +1,367 @@
import React from 'react';
import cx from 'classnames';
import { isAfter, isBefore, parse } from 'date-fns';
import { getVariable, isEffectiveApi, OptionsControlProps, Payload, OptionsControl, Select } from 'amis';
@OptionsControl({
test: /(^|\/)course\-list$/,
name: 'course-list',
})
export class CourseListRenderer extends React.Component<
OptionsControlProps,
{
options: Array<any>;
selectedOptions: Array<any>;
selectedProjects: Array<any>;
selectedClasses: Array<any>;
}
> {
constructor(props: OptionsControlProps) {
super(props);
this.state = {
options: [],
selectedOptions: [],
selectedProjects: [],
selectedClasses: [],
};
}
componentDidMount() {
const { data, value, env, source } = this.props;
const dateRange = getVariable(data, 'date');
const startDate = parse(
dateRange.split(',')[0],
'yyyy-MM-dd',
new Date()
);
const endDate = parse(
dateRange.split(',')[1],
'yyyy-MM-dd',
new Date()
);
let selectedOptions: Array<any> = [];
if (value) {
selectedOptions = value.map((item: any) => {
return {
id: item.id,
name: item.name,
value: item.id,
label: item.name,
subjectList: item.subjectList.map((sub: any) => {
return {
value: sub.id,
label: sub.name,
id: sub.id,
name: sub.name,
validRoomList: sub.validRoomList
? sub.validRoomList.map((room: any) => {
return {
id: room.id,
name: room.name,
timeslotList: room.timeslotlist
? room.timeslotlist
.filter(
(t: any) =>
!isBefore(
parse(
t.date,
'yyyy-MM-dd',
new Date()
),
startDate
) &&
!isAfter(
parse(
t.date,
'yyyy-MM-dd',
new Date()
),
endDate
)
)
.map((t: any) => {
return {
id: t.id,
date: t.date,
period: {
id: t.period.id,
name: t.period
.name,
startTime:
t.period
.start_time,
endTime:
t.period
.end_time,
},
};
})
: [],
};
})
: [],
preSubjectList: sub.preSubjectList
? sub.preSubjectList
: [],
};
}),
studentGroupList: item.studentGroupList.map((sub: any) => {
return {
value: sub.id,
label: sub.name,
id: sub.id,
name: sub.name,
validTimeslotList: sub.validTimeslotList
? sub.validTimeslotList.filter((t: any) => {
let tDate = parse(
t.date,
'yyyy-MM-dd',
new Date()
);
return (
!isBefore(tDate, startDate) &&
!isAfter(tDate, endDate)
);
})
: [],
};
}),
};
});
}
if (isEffectiveApi(source)) {
env.fetcher(source, data).then((payload: Payload) => {
this.setState({
options: payload.data.items.map((item: any) => {
return {
id: item.id,
name: item.name,
value: item.id,
label: item.name,
subjectList: item.course2projects.map(
(sub: any) => {
return {
id: sub.projects.id,
name: sub.projects.name,
validRoomList: sub.projects
.validRoomList
? sub.projects.validRoomList.map(
(room: any) => {
return {
id: room.id,
name: room.name,
timeslotList:
room.timeslotlist
? room.timeslotlist
.filter(
(
t: any
) =>
!isBefore(
parse(
t.date,
'yyyy-MM-dd',
new Date()
),
startDate
) &&
!isAfter(
parse(
t.date,
'yyyy-MM-dd',
new Date()
),
endDate
)
)
.map(
(
t: any
) => {
return {
id: t.id,
date: t.date,
period: {
id: t
.period
.id,
name: t
.period
.name,
startTime:
t
.period
.start_time,
endTime:
t
.period
.end_time,
},
};
}
)
: [],
};
}
)
: [],
preSubjectList: sub.projects
.preSubjectList
? sub.projects.preSubjectList
: [],
value: sub.projects.id,
label: sub.projects.name,
};
}
),
studentGroupList: item.course2orgs.map(
(sub: any) => {
return {
id: sub.orgs.id,
name: sub.orgs.name,
validTimeslotList: sub.orgs
.validTimeslotList
? sub.orgs.validTimeslotList.filter(
(t: any) => {
let tDate = parse(
t.date,
'yyyy-MM-dd',
new Date()
);
return (
!isBefore(
tDate,
startDate
) &&
!isAfter(
tDate,
endDate
)
);
}
)
: [],
value: sub.orgs.id,
label: sub.orgs.name,
};
}
),
};
}),
selectedOptions: selectedOptions,
});
});
}
}
handleClick(option: any, e: React.MouseEvent<HTMLElement>) {
if (e.target && (e.target as HTMLElement).closest('a,button')) {
return;
}
const { onChange } = this.props;
let index = this.state.selectedOptions.findIndex(
(item: any) => item.value === option.value
);
let newOptions = Object.assign([], this.state.selectedOptions);
if (index === -1) {
newOptions.push(option);
this.setState({
selectedOptions: newOptions,
});
} else {
newOptions.splice(index, 1);
this.setState({
selectedOptions: newOptions,
});
}
onChange(newOptions);
}
render() {
const { placeholder, className, onChange } = this.props;
const { options, selectedOptions } = this.state;
let body: JSX.Element | null = null;
if (options && options.length) {
body = (
<div>
<div className={cx('a-ListControl-items')}>
{options.map((option, key) => (
<div
key={key}
className={cx(`a-ListControl-item`, 'w-full', {
'is-active':
selectedOptions.findIndex(
(o: any) => o.value === option.value
) !== -1,
'is-disabled': option.disabled,
})}
style={{ maxWidth: '100%' }}
onClick={this.handleClick.bind(this, option)}
>
{option.label}
</div>
))}
</div>
<div className={`m-t-md`}>
{selectedOptions.map((option, key) => {
let optionData = options.find(
(item: any) => item.value === option.value
);
return (
<div
key={key}
className={cx('w-full', 'm-t-xs')}
>
<div>{optionData.label}</div>
<Select
multiple
clearable={false}
joinValues
defaultCheckAll
className={`w-full m-xs`}
options={optionData.subjectList}
onChange={(value: any) => {
option.subjectList = value;
onChange(
this.state.selectedOptions
);
}}
/>
<Select
multiple
clearable={false}
joinValues
defaultCheckAll
className={`w-full m-xs`}
options={optionData.studentGroupList}
onChange={(value: any) => {
option.studentGroupList = value;
onChange(
this.state.selectedOptions
);
}}
/>
</div>
);
})}
</div>
</div>
);
}
return (
<div className={cx('a-ListControl', className)}>
{body ? (
body
) : (
<span className={cx('a-ListControl-placeholder')}>
{placeholder}
</span>
)}
</div>
);
}
}

View File

@@ -0,0 +1,270 @@
import React from 'react';
import cx from 'classnames';
import { FormControlProps, FormItem, LazyComponent } from 'amis';
// // import { noop } from 'amis/lib/utils/helper';
// import { Editor, TinyMCE, } from 'tinymce';
// // @ts-ignore
// import tinymce from 'tinymce/tinymce';
// tinymce.PluginManager.add('fileselector', function (editor: Editor) {
// editor.ui.registry.addButton('fileselector', {
// // text: '选择视频',
// icon: 'embed',
// onAction: () => {
// editor.windowManager.open({
// title: '',
// body: {
// type: 'panel',
// items: [
// {
// name: "source",
// type: "urlinput",
// filetype: "media",
// label: "Source",
// },
// {
// type: "htmlpanel",
// html: '<table><thead></thead><tbody><tr><td>文件x</td></tr></tbody></table>'
// },
// {
// type: 'table', // component type
// header: [ 'Heading 1', 'Heading 2', 'Heading 3' ],
// cells: [
// [ 'Cell 1', 'Cell 2', 'Cell 3' ],
// [ 'Cell 4', 'Cell 5', 'Cell 6' ]
// ]
// },
// {
// type: 'urlinput', // component type
// name: 'url', // identifier
// filetype: 'file', // allow any file types
// label: 'Url', // text for component label
// disabled: true // disabled state
// }
// ]
// },
// buttons: [{
// text: 'Close',
// type: 'cancel',
// },
// {
// text: 'Insert',
// type: 'submit',
// primary: true,
// }]
// })
// },
// });
// });
export interface RichTextProps extends FormControlProps {
options?: any;
vendor?: 'froala' | 'tinymce';
}
function loadRichText(): () => Promise<any> {
return () =>
import('amis-ui/lib/components/Tinymce').then(
(item) => item.default
);
}
export default class CustomImageUpload extends React.Component<
RichTextProps,
any
> {
static defaultProps: Partial<RichTextProps> = {
imageEditable: true,
videoReceiver: '/api/upload/video',
placeholder: 'placeholder.enter',
options: {
toolbarButtonsSM: [
'paragraphFormat',
'quote',
'color',
'|',
'bold',
'italic',
'underline',
'strikeThrough',
'|',
'formatOL',
'formatUL',
'align',
'|',
'insertLink',
'insertImage',
'insertEmotion',
'insertTable',
'|',
'undo',
'redo',
'html',
],
toolbarButtonsMD: [
'paragraphFormat',
'quote',
'color',
'|',
'bold',
'italic',
'underline',
'strikeThrough',
'|',
'formatOL',
'formatUL',
'align',
'|',
'insertLink',
'insertImage',
'insertEmotion',
'insertTable',
'|',
'undo',
'redo',
'html',
],
toolbarButtons: [
'paragraphFormat',
'quote',
'color',
'|',
'bold',
'italic',
'underline',
'strikeThrough',
'|',
'formatOL',
'formatUL',
'align',
'|',
'insertLink',
'insertImage',
'insertEmotion',
'insertTable',
'|',
'undo',
'redo',
'html',
],
},
};
state = {
focused: false,
};
config: any = null;
constructor(props: RichTextProps) {
super(props);
const finnalVendor = props.vendor || 'tinymce';
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
const fetcher = props.env.fetcher;
this.config = {
...props.options,
// plugins: [
// 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker',
// 'searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking',
// 'table emoticons template paste help fileselector',
// ],
// toolbar:
// 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | ' +
// 'bullist numlist outdent indent | link image | print preview fileselector fullpage | ' +
// 'forecolor backcolor emoticons | help',
// document_base_url: `${location.origin}`,
relative_urls: false,
remove_script_host: true,
images_upload_handler: async (
blobInfo: any,
success: (locaiton: string) => void,
failure: (reason: string) => void
) => {
let formData = new FormData();
formData.set('file', blobInfo.blob());
this.props.env
.fetcher({
url: 'storage/v1/object/exam',
method: 'post',
dataType: 'form-data',
data: formData,
})
.then(res => {
success(res.data.data.value);
});
},
};
}
handleFocus() {
this.setState({
focused: true,
});
}
handleBlur() {
this.setState({
focused: false,
});
}
handleChange(
value: any,
submitOnChange?: boolean,
changeImmediately?: boolean
) {
const { onChange, disabled } = this.props;
if (disabled) {
return;
}
onChange?.(value, submitOnChange, changeImmediately);
}
render() {
const {
className,
classPrefix: ns,
value,
onChange,
disabled,
size,
vendor,
env,
locale,
translate,
} = this.props;
const finnalVendor = vendor || (env.richTextToken ? 'froala' : 'tinymce');
return (
<div
className={cx(`${ns}RichTextControl`, className, {
'is-focused': this.state.focused,
'is-disabled': disabled,
})}
>
<LazyComponent
getComponent={loadRichText()}
model={value}
onModelChange={this.handleChange}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
config={this.config}
disabled={disabled}
locale={locale}
translate={translate}
/>
</div>
);
}
}
@FormItem({
test: /(^|\/)image-upload$/,
name: 'image-upload',
})
export class CustomImageUploadRenderer extends CustomImageUpload {}

View File

@@ -0,0 +1,192 @@
import React from 'react';
import { FormItem, FormControlProps } from 'amis';
import cx from 'classnames';
import CustomTinymceEditor from './CustomTiny';
export interface RichTextProps extends FormControlProps {
options?: any;
vendor?: 'froala' | 'tinymce';
}
function loadRichText(): () => Promise<any> {
return () =>
import('./CustomTiny').then((item) => item.default);
}
export default class CustomRichtext extends React.Component<
RichTextProps,
any
> {
static defaultProps: Partial<RichTextProps> = {
imageEditable: true,
videoReceiver: '/api/upload/video',
placeholder: 'placeholder.enter',
options: {
toolbarButtonsSM: [
'paragraphFormat',
'quote',
'color',
'|',
'bold',
'italic',
'underline',
'strikeThrough',
'|',
'formatOL',
'formatUL',
'align',
'|',
'insertLink',
'insertImage',
'insertEmotion',
'insertTable',
'|',
'undo',
'redo',
'html',
],
toolbarButtonsMD: [
'paragraphFormat',
'quote',
'color',
'|',
'bold',
'italic',
'underline',
'strikeThrough',
'|',
'formatOL',
'formatUL',
'align',
'|',
'insertLink',
'insertImage',
'insertEmotion',
'insertTable',
'|',
'undo',
'redo',
'html',
],
toolbarButtons: [
'paragraphFormat',
'quote',
'color',
'|',
'bold',
'italic',
'underline',
'strikeThrough',
'|',
'formatOL',
'formatUL',
'align',
'|',
'insertLink',
'insertImage',
'insertEmotion',
'insertTable',
'|',
'undo',
'redo',
'html',
],
},
};
state = {
focused: false,
};
config: any = null;
constructor(props: RichTextProps) {
super(props);
const finnalVendor = props.vendor || 'tinymce';
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
this.config = {
...props.options,
// plugins: [
// 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker',
// 'searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking',
// 'table emoticons template paste help',
// ],
// toolbar:
// 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | ' +
// 'bullist numlist outdent indent | link image | print preview fullpage | ' +
// 'forecolor backcolor emoticons | help',
relative_urls: false,
remove_script_host: true,
};
}
handleFocus() {
this.setState({
focused: true,
});
}
handleBlur() {
this.setState({
focused: false,
});
}
handleChange(
value: any,
submitOnChange?: boolean,
changeImmediately?: boolean
) {
const { onChange, disabled } = this.props;
if (disabled) {
return;
}
onChange?.(value, submitOnChange, changeImmediately);
}
render() {
const {
className,
classPrefix: ns,
value,
onChange,
disabled,
size,
vendor,
env,
locale,
translate,
} = this.props;
const finnalVendor = vendor || (env.richTextToken ? 'froala' : 'tinymce');
return (
<div
className={cx(`${ns}RichTextControl`, className, {
'is-focused': this.state.focused,
'is-disabled': disabled,
})}
>
<CustomTinymceEditor
model={value}
onModelChange={this.handleChange}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
config={this.config}
disabled={disabled}
fetcher={this.props.env.fetcher}
/>
</div>
);
}
}
@FormItem({
test: /(^|\/)custom-richtext$/,
name: 'custom-richtext',
})
export class CustomRichtextRenderer extends CustomRichtext {}

732
src/renderer/CustomTiny.tsx Normal file
View File

@@ -0,0 +1,732 @@
import React from 'react';
// Import TinyMCE
// @ts-ignore
import tinymce from 'tinymce/tinymce';
// A theme is also required
import 'tinymce/icons/default/index';
import 'tinymce/themes/silver';
import 'tinymce/models/dom/model';
// import 'tinymce/skins/ui/oxide/skin.css';
// Any plugins you want to use has to be imported
import 'tinymce/plugins/advlist';
import 'tinymce/plugins/autolink';
import 'tinymce/plugins/autoresize';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/link';
import 'tinymce/plugins/image';
import 'tinymce/plugins/charmap';
import 'tinymce/plugins/preview';
import 'tinymce/plugins/anchor';
import 'tinymce/plugins/searchreplace';
import 'tinymce/plugins/visualblocks';
import 'tinymce/plugins/code';
import 'tinymce/plugins/fullscreen';
import 'tinymce/plugins/insertdatetime';
import 'tinymce/plugins/media';
import 'tinymce/plugins/table';
import 'tinymce/plugins/help';
import 'tinymce/plugins/wordcount';
import 'tinymce/plugins/pagebreak';
import 'tinymce/plugins/visualchars';
import 'tinymce/plugins/template';
import 'tinymce/plugins/nonbreaking';
import 'tinymce/plugins/emoticons';
import 'tinymce/plugins/emoticons/js/emojis';
import 'tinymce/plugins/quickbars/plugin';
import {LocaleProps} from 'amis-core';
import DestPicker from './DestPicker';
interface TinymceEditorProps extends LocaleProps {
model: string;
onModelChange?: (value: string) => void;
onFocus?: () => void;
onBlur?: () => void;
disabled?: boolean;
config?: any;
outputFormat?: 'html' | 'text';
receiver?: string;
fetcher?: any;
}
export default class CustomTinymceEditor extends React.Component<TinymceEditorProps> {
static defaultProps = {
outputFormat: 'html',
};
config?: any;
editor?: any;
currentContent?: string;
elementRef: React.RefObject<HTMLTextAreaElement> = React.createRef();
constructor(props: TinymceEditorProps) {
super(props);
this.state = {
pickedBreadcrumb: ['根目录'],
showDialog: false,
editor: null,
};
}
componentDidMount() {
const _this = this;
const locale = this.props.locale;
tinymce.PluginManager.add('source', function (editor) {
const openDialog = () => {
_this.setState({
showDialog: true,
editor: editor,
});
// editor.windowManager.open({
// title: 'Source Picker',
// body: {
// type: 'panel',
// items: [
// {
// type: 'htmlpanel',
// html: '<div>hello</div>',
// },
// ],
// },
// buttons: [
// {
// type: 'cancel',
// text: 'Close',
// },
// {
// type: 'submit',
// text: 'Save',
// },
// ],
// onSubmit: (api) => {
// const data = api.getData();
// editor.insertContent('Title: ' + data.title);
// api.close();
// },
// });
};
editor.ui.registry.addButton('source', {
icon: 'more-drawer',
onAction: () => {
openDialog();
},
});
editor.ui.registry.addMenuItem('source', {
icon: 'more-drawer',
text: 'Source Picker',
onAction: () => {
openDialog();
},
});
});
this.config = {
inline: false,
skin: false,
content_css: false,
height: 400,
language: !locale || locale === 'zh-CN' ? 'zh_CN' : 'en',
plugins: [
'advlist',
'autolink',
'autoresize',
'link',
'image',
'source',
'lists',
'charmap',
'preview',
'anchor',
'pagebreak',
'searchreplace',
'wordcount',
'visualblocks',
'visualchars',
'code',
'fullscreen',
'insertdatetime',
'media',
'nonbreaking',
'table',
'emoticons',
'template',
'help',
'quickbars'
],
toolbar:
'undo redo | blocks | bold italic | alignleft aligncenter alignright alignjustify | ' +
'bullist numlist outdent indent | link image source | preview media | ' +
'fontfamily fontsize forecolor backcolor emoticons | print help',
quickbars_selection_toolbar: 'bold italic | link h2 h3 blockquote',
quickbars_insert_toolbar: 'quickimage quicktable',
menu: {
file: {
title: 'File',
items: 'newdocument restoredraft | preview | print ',
},
edit: {
title: 'Edit',
items: 'undo redo | cut copy paste | selectall | searchreplace',
},
view: {
title: 'View',
items:
'code | visualaid visualchars visualblocks | preview fullscreen',
},
insert: {
title: 'Insert',
items:
'image link media source template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor toc | insertdatetime',
},
format: {
title: 'Format',
items:
'bold italic underline strikethrough superscript subscript codeformat | formats blockformats fontformats fontsizes align | forecolor backcolor | removeformat',
},
tools: {
title: 'Tools',
items: 'code wordcount',
},
table: {
title: 'Table',
items: 'inserttable | cell row column | tableprops deletetable',
},
help: { title: 'Help', items: 'help' },
},
paste_data_images: true,
content_style: '[data-mce-bogus] video {display:none;}',
...this.props.config,
target: this.elementRef.current,
readOnly: this.props.disabled,
setup: (editor: any) => {
this.editor = editor;
editor.on('init', (e: Event) => {
this.initEditor(e, editor);
});
editor.on('NodeChange', function(e) {
if (e && e.element.firstChild.nodeName.toLowerCase() == 'video') {
tinymce.DOM.setAttribs(e.element.firstChild, {'width': 640, 'height': 480});
}
});
},
};
tinymce.init(this.config);
}
componentDidUpdate(prevProps: TinymceEditorProps) {
const props = this.props;
if (
props.model !== prevProps.model &&
props.model !== this.currentContent
) {
this.editor?.setContent(props.model || '');
}
}
componentWillUnmount() {
tinymce.remove(this.editor);
}
initEditor(e: any, editor: any) {
const { model, onModelChange, outputFormat, onFocus, onBlur } = this.props;
const value = model || '';
editor.setContent(value);
if (onModelChange) {
editor.on('change keyup setcontent', (e: any) => {
const newContent = editor.getContent({ format: outputFormat });
if (newContent !== this.currentContent) {
this.currentContent = newContent;
onModelChange(newContent);
}
});
}
onFocus && editor.on('focus', onFocus);
onBlur && editor.on('blur', onBlur);
}
onConfirm(html) {
this.state.editor.insertContent(html);
this.setState({ showDialog: false });
}
render() {
return (
<>
<textarea ref={this.elementRef} />
{this.state.showDialog && (
<DestPicker
isRichtext={true}
fetcher={this.props.fetcher}
bucket="mooc"
breadcrumb={this.state.pickedBreadcrumb}
setBreadcrumb={(breadcrumb) =>
this.setState({ pickedBreadcrumb: breadcrumb })
}
onConfirm={(html) => this.onConfirm(html)}
onCancel={() => this.setState({ showDialog: false })}
/>
)}
</>
);
}
}
tinymce.addI18n('zh_CN', {
Redo: '\u91cd\u505a',
Undo: '\u64a4\u9500',
Cut: '\u526a\u5207',
Copy: '\u590d\u5236',
Paste: '\u7c98\u8d34',
'Select all': '\u5168\u9009',
'New document': '\u65b0\u6587\u4ef6',
Ok: '\u786e\u5b9a',
Cancel: '\u53d6\u6d88',
'Visual aids': '\u7f51\u683c\u7ebf',
Bold: '\u7c97\u4f53',
Italic: '\u659c\u4f53',
Underline: '\u4e0b\u5212\u7ebf',
Strikethrough: '\u5220\u9664\u7ebf',
Superscript: '\u4e0a\u6807',
Subscript: '\u4e0b\u6807',
'Clear formatting': '\u6e05\u9664\u683c\u5f0f',
'Align left': '\u5de6\u8fb9\u5bf9\u9f50',
'Align center': '\u4e2d\u95f4\u5bf9\u9f50',
'Align right': '\u53f3\u8fb9\u5bf9\u9f50',
Justify: '\u4e24\u7aef\u5bf9\u9f50',
'Bullet list': '\u9879\u76ee\u7b26\u53f7',
'Numbered list': '\u7f16\u53f7\u5217\u8868',
'Decrease indent': '\u51cf\u5c11\u7f29\u8fdb',
'Increase indent': '\u589e\u52a0\u7f29\u8fdb',
Close: '\u5173\u95ed',
Formats: '\u683c\u5f0f',
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.":
'\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X/C/V\u7b49\u5feb\u6377\u952e\u3002',
Headers: '\u6807\u9898',
'Header 1': '\u6807\u98981',
'Header 2': '\u6807\u98982',
'Header 3': '\u6807\u98983',
'Header 4': '\u6807\u98984',
'Header 5': '\u6807\u98985',
'Header 6': '\u6807\u98986',
Headings: '\u6807\u9898',
'Heading 1': '\u6807\u98981',
'Heading 2': '\u6807\u98982',
'Heading 3': '\u6807\u98983',
'Heading 4': '\u6807\u98984',
'Heading 5': '\u6807\u98985',
'Heading 6': '\u6807\u98986',
Preformatted: '\u9884\u5148\u683c\u5f0f\u5316\u7684',
Div: 'Div',
Pre: 'Pre',
Code: '\u4ee3\u7801',
Paragraph: '\u6bb5\u843d',
Blockquote: '\u5f15\u6587\u533a\u5757',
Inline: '\u6587\u672c',
Blocks: '\u57fa\u5757',
'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.':
'\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002',
Fonts: '\u5b57\u4f53',
'Font Sizes': '\u5b57\u53f7',
Class: '\u7c7b\u578b',
'Browse for an image': '\u6d4f\u89c8\u56fe\u50cf',
OR: '\u6216',
'Drop an image here': '\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64',
Upload: '\u4e0a\u4f20',
Block: '\u5757',
Align: '\u5bf9\u9f50',
Default: '\u9ed8\u8ba4',
Circle: '\u7a7a\u5fc3\u5706',
Disc: '\u5b9e\u5fc3\u5706',
Square: '\u65b9\u5757',
'Lower Alpha': '\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd',
'Lower Greek': '\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd',
'Lower Roman': '\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd',
'Upper Alpha': '\u5927\u5199\u82f1\u6587\u5b57\u6bcd',
'Upper Roman': '\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd',
'Anchor...': '\u951a\u70b9...',
Name: '\u540d\u79f0',
Id: '\u6807\u8bc6\u7b26',
'Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.':
'\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002',
'You have unsaved changes are you sure you want to navigate away?':
'\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f',
'Restore last draft': '\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f',
'Special character...': '\u7279\u6b8a\u5b57\u7b26...',
'Source code': '\u6e90\u4ee3\u7801',
'Insert/Edit code sample':
'\u63d2\u5165/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b',
Language: '\u8bed\u8a00',
'Code sample...': '\u793a\u4f8b\u4ee3\u7801...',
'Color Picker': '\u9009\u8272\u5668',
R: 'R',
G: 'G',
B: 'B',
'Left to right': '\u4ece\u5de6\u5230\u53f3',
'Right to left': '\u4ece\u53f3\u5230\u5de6',
'Emoticons...': '\u8868\u60c5\u7b26\u53f7...',
'Metadata and Document Properties':
'\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027',
Title: '\u6807\u9898',
Keywords: '\u5173\u952e\u8bcd',
Description: '\u63cf\u8ff0',
Robots: '\u673a\u5668\u4eba',
Author: '\u4f5c\u8005',
Encoding: '\u7f16\u7801',
Fullscreen: '\u5168\u5c4f',
Action: '\u64cd\u4f5c',
Shortcut: '\u5feb\u6377\u952e',
Help: '\u5e2e\u52a9',
Address: '\u5730\u5740',
'Focus to menubar': '\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f',
'Focus to toolbar': '\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f',
'Focus to element path':
'\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84',
'Focus to contextual toolbar':
'\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355',
'Insert link (if link plugin activated)':
'\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)',
'Save (if save plugin activated)':
'\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)',
'Find (if searchreplace plugin activated)':
'\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)',
'Plugins installed ({0}):': '\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):',
'Premium plugins:': '\u4f18\u79c0\u63d2\u4ef6\uff1a',
'Learn more...': '\u4e86\u89e3\u66f4\u591a...',
'You are using {0}': '\u4f60\u6b63\u5728\u4f7f\u7528 {0}',
Plugins: '\u63d2\u4ef6',
'Handy Shortcuts': '\u5feb\u6377\u952e',
'Horizontal line': '\u6c34\u5e73\u5206\u5272\u7ebf',
'Insert/edit image': '\u63d2\u5165/\u7f16\u8f91\u56fe\u7247',
'Image description': '\u56fe\u7247\u63cf\u8ff0',
Source: '\u5730\u5740',
Dimensions: '\u5927\u5c0f',
'Constrain proportions': '\u4fdd\u6301\u7eb5\u6a2a\u6bd4',
General: '\u666e\u901a',
Advanced: '\u9ad8\u7ea7',
Style: '\u6837\u5f0f',
'Vertical space': '\u5782\u76f4\u8fb9\u8ddd',
'Horizontal space': '\u6c34\u5e73\u8fb9\u8ddd',
Border: '\u8fb9\u6846',
'Insert image': '\u63d2\u5165\u56fe\u7247',
'Image...': '\u56fe\u7247...',
'Image list': '\u56fe\u7247\u5217\u8868',
'Source Picker': '\u63d2\u5165\u8d44\u6e90\u5e93\u4e2d\u6587\u4ef6',
'Rotate counterclockwise': '\u9006\u65f6\u9488\u65cb\u8f6c',
'Rotate clockwise': '\u987a\u65f6\u9488\u65cb\u8f6c',
'Flip vertically': '\u5782\u76f4\u7ffb\u8f6c',
'Flip horizontally': '\u6c34\u5e73\u7ffb\u8f6c',
'Edit image': '\u7f16\u8f91\u56fe\u7247',
'Image options': '\u56fe\u7247\u9009\u9879',
'Zoom in': '\u653e\u5927',
'Zoom out': '\u7f29\u5c0f',
Crop: '\u88c1\u526a',
Resize: '\u8c03\u6574\u5927\u5c0f',
Orientation: '\u65b9\u5411',
Brightness: '\u4eae\u5ea6',
Sharpen: '\u9510\u5316',
Contrast: '\u5bf9\u6bd4\u5ea6',
'Color levels': '\u989c\u8272\u5c42\u6b21',
Gamma: '\u4f3d\u9a6c\u503c',
Invert: '\u53cd\u8f6c',
Apply: '\u5e94\u7528',
Back: '\u540e\u9000',
'Insert date/time': '\u63d2\u5165\u65e5\u671f/\u65f6\u95f4',
'Date/time': '\u65e5\u671f/\u65f6\u95f4',
'Insert/Edit Link': '\u63d2\u5165/\u7f16\u8f91\u94fe\u63a5',
'Insert/edit link': '\u63d2\u5165/\u7f16\u8f91\u94fe\u63a5',
'Text to display': '\u663e\u793a\u6587\u5b57',
Url: '\u5730\u5740',
'Open link in...': '\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...',
'Current window': '\u5f53\u524d\u7a97\u53e3',
None: '\u65e0',
'New window': '\u5728\u65b0\u7a97\u53e3\u6253\u5f00',
'Remove link': '\u5220\u9664\u94fe\u63a5',
Anchors: '\u951a\u70b9',
'Link...': '\u94fe\u63a5...',
'Paste or type a link': '\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5',
'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?':
'\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f',
'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?':
'\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp://:\u524d\u7f00\u5417\uff1f',
'Link list': '\u94fe\u63a5\u5217\u8868',
'Insert video': '\u63d2\u5165\u89c6\u9891',
'Insert/edit video': '\u63d2\u5165/\u7f16\u8f91\u89c6\u9891',
'Insert/edit media': '\u63d2\u5165/\u7f16\u8f91\u5a92\u4f53',
'Alternative source': '\u955c\u50cf',
'Alternative source URL': '\u66ff\u4ee3\u6765\u6e90\u7f51\u5740',
'Media poster (Image URL)': '\u5c01\u9762(\u56fe\u7247\u5730\u5740)',
'Paste your embed code below:':
'\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:',
Embed: '\u5185\u5d4c',
'Media...': '\u591a\u5a92\u4f53...',
'Nonbreaking space': '\u4e0d\u95f4\u65ad\u7a7a\u683c',
'Page break': '\u5206\u9875\u7b26',
'Paste as text': '\u7c98\u8d34\u4e3a\u6587\u672c',
Preview: '\u9884\u89c8',
'Print...': '\u6253\u5370...',
Save: '\u4fdd\u5b58',
Find: '\u67e5\u627e',
'Replace with': '\u66ff\u6362\u4e3a',
Replace: '\u66ff\u6362',
'Replace all': '\u5168\u90e8\u66ff\u6362',
Previous: '\u4e0a\u4e00\u4e2a',
Next: '\u4e0b\u4e00\u4e2a',
'Find and replace...': '\u67e5\u627e\u5e76\u66ff\u6362...',
'Could not find the specified string.':
'\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.',
'Match case': '\u533a\u5206\u5927\u5c0f\u5199',
'Find whole words only': '\u5168\u5b57\u5339\u914d',
'Spell check': '\u62fc\u5199\u68c0\u67e5',
Ignore: '\u5ffd\u7565',
'Ignore all': '\u5168\u90e8\u5ffd\u7565',
Finish: '\u5b8c\u6210',
'Add to Dictionary': '\u6dfb\u52a0\u5230\u5b57\u5178',
'Insert table': '\u63d2\u5165\u8868\u683c',
'Table properties': '\u8868\u683c\u5c5e\u6027',
'Delete table': '\u5220\u9664\u8868\u683c',
Cell: '\u5355\u5143\u683c',
Row: '\u884c',
Column: '\u5217',
'Cell properties': '\u5355\u5143\u683c\u5c5e\u6027',
'Merge cells': '\u5408\u5e76\u5355\u5143\u683c',
'Split cell': '\u62c6\u5206\u5355\u5143\u683c',
'Insert row before': '\u5728\u4e0a\u65b9\u63d2\u5165',
'Insert row after': '\u5728\u4e0b\u65b9\u63d2\u5165',
'Delete row': '\u5220\u9664\u884c',
'Row properties': '\u884c\u5c5e\u6027',
'Cut row': '\u526a\u5207\u884c',
'Copy row': '\u590d\u5236\u884c',
'Paste row before': '\u7c98\u8d34\u5230\u4e0a\u65b9',
'Paste row after': '\u7c98\u8d34\u5230\u4e0b\u65b9',
'Insert column before': '\u5728\u5de6\u4fa7\u63d2\u5165',
'Insert column after': '\u5728\u53f3\u4fa7\u63d2\u5165',
'Delete column': '\u5220\u9664\u5217',
Cols: '\u5217',
Rows: '\u884c',
Width: '\u5bbd',
Height: '\u9ad8',
'Cell spacing': '\u5355\u5143\u683c\u5916\u95f4\u8ddd',
'Cell padding': '\u5355\u5143\u683c\u5185\u8fb9\u8ddd',
'Show caption': '\u663e\u793a\u6807\u9898',
Left: '\u5de6\u5bf9\u9f50',
Center: '\u5c45\u4e2d',
Right: '\u53f3\u5bf9\u9f50',
'Cell type': '\u5355\u5143\u683c\u7c7b\u578b',
Scope: '\u8303\u56f4',
Alignment: '\u5bf9\u9f50\u65b9\u5f0f',
'H Align': '\u6c34\u5e73\u5bf9\u9f50',
'V Align': '\u5782\u76f4\u5bf9\u9f50',
Top: '\u9876\u90e8\u5bf9\u9f50',
Middle: '\u5782\u76f4\u5c45\u4e2d',
Bottom: '\u5e95\u90e8\u5bf9\u9f50',
'Header cell': '\u8868\u5934\u5355\u5143\u683c',
'Row group': '\u884c\u7ec4',
'Column group': '\u5217\u7ec4',
'Row type': '\u884c\u7c7b\u578b',
Header: '\u8868\u5934',
Body: '\u8868\u4f53',
Footer: '\u8868\u5c3e',
'Border color': '\u8fb9\u6846\u989c\u8272',
'Insert template...': '\u63d2\u5165\u6a21\u677f...',
Templates: '\u6a21\u677f',
Template: '\u6a21\u677f',
'Text color': '\u6587\u5b57\u989c\u8272',
'Background color': '\u80cc\u666f\u8272',
'Custom...': '\u81ea\u5b9a\u4e49...',
'Custom color': '\u81ea\u5b9a\u4e49\u989c\u8272',
'No color': '\u65e0',
'Remove color': '\u79fb\u9664\u989c\u8272',
'Table of Contents': '\u5185\u5bb9\u5217\u8868',
'Show blocks': '\u663e\u793a\u533a\u5757\u8fb9\u6846',
'Show invisible characters': '\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26',
'Word count': '\u5b57\u6570',
Count: '\u8ba1\u6570',
Document: '\u6587\u6863',
Selection: '\u9009\u62e9',
Words: '\u5355\u8bcd',
'Words: {0}': '\u5b57\u6570\uff1a{0}',
'{0} words': '{0} \u5b57',
File: '\u6587\u4ef6',
Edit: '\u7f16\u8f91',
Insert: '\u63d2\u5165',
View: '\u89c6\u56fe',
Format: '\u683c\u5f0f',
Table: '\u8868\u683c',
Tools: '\u5de5\u5177',
'Powered by {0}': '\u7531{0}\u9a71\u52a8',
'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help':
'\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9',
'Image title': '\u56fe\u7247\u6807\u9898',
'Border width': '\u8fb9\u6846\u5bbd\u5ea6',
'Border style': '\u8fb9\u6846\u6837\u5f0f',
Error: '\u9519\u8bef',
Warn: '\u8b66\u544a',
Valid: '\u6709\u6548',
'To open the popup, press Shift+Enter':
'\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846',
'Rich Text Area. Press ALT-0 for help.':
'\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002',
'System Font': '\u7cfb\u7edf\u5b57\u4f53',
'Failed to upload image: {0}': '\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}',
'Failed to load plugin: {0} from url {1}':
'\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}',
'Failed to load plugin url: {0}':
'\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}',
'Failed to initialize plugin: {0}':
'\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}',
example: '\u793a\u4f8b',
Search: '\u641c\u7d22',
All: '\u5168\u90e8',
Currency: '\u8d27\u5e01',
Text: '\u6587\u5b57',
Quotations: '\u5f15\u7528',
Mathematical: '\u6570\u5b66',
'Extended Latin': '\u62c9\u4e01\u8bed\u6269\u5145',
Symbols: '\u7b26\u53f7',
Arrows: '\u7bad\u5934',
'User Defined': '\u81ea\u5b9a\u4e49',
'dollar sign': '\u7f8e\u5143\u7b26\u53f7',
'currency sign': '\u8d27\u5e01\u7b26\u53f7',
'euro-currency sign': '\u6b27\u5143\u7b26\u53f7',
'colon sign': '\u5192\u53f7',
'cruzeiro sign': '\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7',
'french franc sign': '\u6cd5\u90ce\u7b26\u53f7',
'lira sign': '\u91cc\u62c9\u7b26\u53f7',
'mill sign': '\u5bc6\u5c14\u7b26\u53f7',
'naira sign': '\u5948\u62c9\u7b26\u53f7',
'peseta sign': '\u6bd4\u585e\u5854\u7b26\u53f7',
'rupee sign': '\u5362\u6bd4\u7b26\u53f7',
'won sign': '\u97e9\u5143\u7b26\u53f7',
'new sheqel sign': '\u65b0\u8c22\u514b\u5c14\u7b26\u53f7',
'dong sign': '\u8d8a\u5357\u76fe\u7b26\u53f7',
'kip sign': '\u8001\u631d\u57fa\u666e\u7b26\u53f7',
'tugrik sign': '\u56fe\u683c\u91cc\u514b\u7b26\u53f7',
'drachma sign': '\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7',
'german penny symbol': '\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7',
'peso sign': '\u6bd4\u7d22\u7b26\u53f7',
'guarani sign': '\u74dc\u62c9\u5c3c\u7b26\u53f7',
'austral sign': '\u6fb3\u5143\u7b26\u53f7',
'hryvnia sign': '\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7',
'cedi sign': '\u585e\u5730\u7b26\u53f7',
'livre tournois sign': '\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7',
'spesmilo sign': 'spesmilo\u7b26\u53f7',
'tenge sign': '\u575a\u6208\u7b26\u53f7',
'indian rupee sign': '\u5370\u5ea6\u5362\u6bd4',
'turkish lira sign': '\u571f\u8033\u5176\u91cc\u62c9',
'nordic mark sign': '\u5317\u6b27\u9a6c\u514b',
'manat sign': '\u9a6c\u7eb3\u7279\u7b26\u53f7',
'ruble sign': '\u5362\u5e03\u7b26\u53f7',
'yen character': '\u65e5\u5143\u5b57\u6837',
'yuan character': '\u4eba\u6c11\u5e01\u5143\u5b57\u6837',
'yuan character, in hong kong and taiwan':
'\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09',
'yen/yuan character variant one':
'\u5143\u5b57\u6837\uff08\u5927\u5199\uff09',
'Loading emoticons...': '\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...',
'Could not load emoticons':
'\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7',
People: '\u4eba\u7c7b',
'Animals and Nature': '\u52a8\u7269\u548c\u81ea\u7136',
'Food and Drink': '\u98df\u7269\u548c\u996e\u54c1',
Activity: '\u6d3b\u52a8',
'Travel and Places': '\u65c5\u6e38\u548c\u5730\u70b9',
Objects: '\u7269\u4ef6',
Flags: '\u65d7\u5e1c',
Characters: '\u5b57\u7b26',
'Characters (no spaces)': '\u5b57\u7b26(\u65e0\u7a7a\u683c)',
'{0} characters': '{0} \u4e2a\u5b57\u7b26',
'Error: Form submit field collision.':
'\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002',
'Error: No form element found.':
'\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002',
Update: '\u66f4\u65b0',
'Color swatch': '\u989c\u8272\u6837\u672c',
Turquoise: '\u9752\u7eff\u8272',
Green: '\u7eff\u8272',
Blue: '\u84dd\u8272',
Purple: '\u7d2b\u8272',
'Navy Blue': '\u6d77\u519b\u84dd',
'Dark Turquoise': '\u6df1\u84dd\u7eff\u8272',
'Dark Green': '\u6df1\u7eff\u8272',
'Medium Blue': '\u4e2d\u84dd\u8272',
'Medium Purple': '\u4e2d\u7d2b\u8272',
'Midnight Blue': '\u6df1\u84dd\u8272',
Yellow: '\u9ec4\u8272',
Orange: '\u6a59\u8272',
Red: '\u7ea2\u8272',
'Light Gray': '\u6d45\u7070\u8272',
Gray: '\u7070\u8272',
'Dark Yellow': '\u6697\u9ec4\u8272',
'Dark Orange': '\u6df1\u6a59\u8272',
'Dark Red': '\u6df1\u7ea2\u8272',
'Medium Gray': '\u4e2d\u7070\u8272',
'Dark Gray': '\u6df1\u7070\u8272',
'Light Green': '\u6d45\u7eff\u8272',
'Light Yellow': '\u6d45\u9ec4\u8272',
'Light Red': '\u6d45\u7ea2\u8272',
'Light Purple': '\u6d45\u7d2b\u8272',
'Light Blue': '\u6d45\u84dd\u8272',
'Dark Purple': '\u6df1\u7d2b\u8272',
'Dark Blue': '\u6df1\u84dd\u8272',
Black: '\u9ed1\u8272',
White: '\u767d\u8272',
'Switch to or from fullscreen mode': '\u5207\u6362\u5168\u5c4f\u6a21\u5f0f',
'Open help dialog': '\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846',
history: '\u5386\u53f2',
styles: '\u6837\u5f0f',
formatting: '\u683c\u5f0f\u5316',
alignment: '\u5bf9\u9f50',
indentation: '\u7f29\u8fdb',
'permanent pen': '\u8bb0\u53f7\u7b14',
comments: '\u5907\u6ce8',
'Format Painter': '\u683c\u5f0f\u5237',
'Insert/edit iframe': '\u63d2\u5165/\u7f16\u8f91\u6846\u67b6',
Capitalization: '\u5927\u5199',
lowercase: '\u5c0f\u5199',
UPPERCASE: '\u5927\u5199',
'Title Case': '\u9996\u5b57\u6bcd\u5927\u5199',
'Permanent Pen Properties': '\u6c38\u4e45\u7b14\u5c5e\u6027',
'Permanent pen properties...': '\u6c38\u4e45\u7b14\u5c5e\u6027...',
Font: '\u5b57\u4f53',
Size: '\u5b57\u53f7',
'More...': '\u66f4\u591a...',
'Spellcheck Language': '\u62fc\u5199\u68c0\u67e5\u8bed\u8a00',
'Select...': '\u9009\u62e9...',
Preferences: '\u9996\u9009\u9879',
Yes: '\u662f',
No: '\u5426',
'Keyboard Navigation': '\u952e\u76d8\u6307\u5f15',
Version: '\u7248\u672c',
Anchor: '\u951a\u70b9',
'Special character': '\u7279\u6b8a\u7b26\u53f7',
'Code sample': '\u4ee3\u7801\u793a\u4f8b',
Color: '\u989c\u8272',
Emoticons: '\u8868\u60c5',
'Document properties': '\u6587\u6863\u5c5e\u6027',
Image: '\u56fe\u7247',
'Insert link': '\u63d2\u5165\u94fe\u63a5',
Target: '\u6253\u5f00\u65b9\u5f0f',
Link: '\u94fe\u63a5',
Poster: '\u5c01\u9762',
Media: '\u5a92\u4f53',
Print: '\u6253\u5370',
Prev: '\u4e0a\u4e00\u4e2a',
'Find and replace': '\u67e5\u627e\u548c\u66ff\u6362',
'Whole words': '\u5168\u5b57\u5339\u914d',
Spellcheck: '\u62fc\u5199\u68c0\u67e5',
Caption: '\u6807\u9898',
'Insert template': '\u63d2\u5165\u6a21\u677f',
});

View File

@@ -0,0 +1,550 @@
import React, { Fragment } from 'react';
import { FormItem, FormControlProps, getVariable, isEffectiveApi, Payload, render } from 'amis';
import Form from '@rjsf/fluent-ui';
import _ from 'lodash';
@FormItem({
test: /(^|\/)datapreview$/,
name: 'datapreview',
})
export class DataPreviewRenderer extends React.Component<
FormControlProps,
{
data_template: any;
result_template: any;
trial_data: any;
trial_result: any;
student: any;
data: any;
data_id: any;
u2p_id: any;
canNext: boolean;
}
> {
trialInterval: any;
trial_data: any;
trial_result: any;
dataTemplate: any;
resultTemplate: any;
constructor(props: FormControlProps) {
super(props);
this.state = {
data_template: {},
result_template: {},
trial_data: {},
trial_result: {},
student: {},
data: {},
data_id: null,
u2p_id: null,
canNext: true,
};
}
componentDidMount() {
const { data } = this.props;
const { data_template, result_template } = this.state;
this.fetchData(getVariable(data, 'id'));
// this.trialInterval = setInterval(() => {
// if (
// store?.data &&
// (!_.isEqual(this.dataTemplate, store?.data.data) ||
// !_.isEqual(this.resultTemplate, store?.data.result))
// ) {
// this.dataTemplate = store?.data.data;
// this.resultTemplate = store?.data.result;
// this.setState({
// data_template: store?.data.data,
// result_template: store?.data.result,
// });
// }
// }, 1000);
}
componentWillUnmount() {
if (this.trialInterval) {
clearInterval(this.trialInterval);
}
}
fetchData(u2p_id) {
const { env, api, dataApi, data } = this.props;
let project_code = getVariable(data, 'projects.code');
if (isEffectiveApi(api)) {
env
.fetcher(api, { project_code: project_code })
.then((payload: Payload) => {
if (payload.data) {
this.dataTemplate = payload.data.data;
this.resultTemplate = payload.data.result;
this.setState({
data_template: payload.data.data,
result_template: payload.data.result,
});
}
});
env
.fetcher(dataApi, { user2project_id: u2p_id })
.then((payload: Payload) => {
if (payload.data) {
this.setState({
data: payload.data,
trial_data: payload.data.data,
trial_result: payload.data.result,
data_id: payload.data.id,
u2p_id: u2p_id,
student: payload.data.user2projects.users,
});
}
});
}
}
getConst(node: any, index: any) {
let i = index;
if (node.type === 'string') {
let tpl_value = node.default ? node.default : '';
while (
tpl_value &&
tpl_value.match(/{{(.*?)}}/) &&
tpl_value.match(/{{(.*?)}}/).length === 2
) {
try {
tpl_value = tpl_value.replace(
/({{(.*?)}})/,
`${eval(tpl_value.match(/{{(.*?)}}/)[1])}`
);
} catch (error) {
tpl_value = '';
}
}
return tpl_value;
} else if (node.type === 'object') {
if (node.children && Array.isArray(node.children.data)) {
let o: any = {};
node.children.data.forEach((item: any) => {
o[item.name] = this.getConst(item, index);
});
return o;
}
} else if (node.type === 'array') {
let a: Array<any> = [];
if (node.children && Array.isArray(node.children.data)) {
node.children.data.forEach((item: any) => {
if (item.config && item.config.max) {
for (let i = 0; i < item.config.max; i++) {
a.push(this.getConst(item, i));
}
}
});
}
{
if (node.children && this.isValid(node.children.data)) {
a.push(this.getConst(node.children.data, index));
}
}
return a;
}
}
// 通用的处理表达式方法
// 这里打破 JSON Schema 规范
handleExpression(
rootFormData: any,
curNodePath: string,
expression: string,
fallBack: Function
) {
const regExpression = /{{(.*)}}/;
// 未配置
if (undefined === expression) {
return undefined;
}
// 配置了 mustache 表达式
const matchExpression = regExpression.exec(expression);
regExpression.lastIndex = 0; // 重置索引
if (matchExpression) {
try {
const code = matchExpression[1].trim();
// eslint-disable-next-line no-new-func
const fn = new Function(
'parentFormData',
'rootFormData',
`return ${code}`
);
let result = fn(
this.getPathVal(rootFormData, curNodePath, 1),
rootFormData
);
return result ? result : '';
} catch (error) {
return '';
}
}
// 回退
return fallBack();
}
// 获取当前path值
getPathVal(obj: any, path: string, leftDeviation = 0) {
const pathSeparator = '.';
const pathArr = path.split(pathSeparator);
for (let i = 0; i < pathArr.length - leftDeviation; i += 1) {
// 错误路径或者undefined中断查找
if (obj === undefined) return undefined;
obj = pathArr[i] === '' ? obj : obj[pathArr[i]];
}
return obj;
}
buildSchema(node: any): any {
let title = node.title;
// while(title && title.match(/{{(.*?)}}/) && title.match(/{{(.*?)}}/).length === 2) {
// title = title.replace(/({{(.*?)}})/, `${eval(title.match(/{{(.*?)}}/)[1])}`)
// }
if (node.type === 'string') {
let defaultValue = this.getConst(node, 0);
return Object.assign(
{},
{
type: node.type,
title: title,
required: true,
disabled: node.disabled,
computed: node.computed,
template: node.config ? node.config.template : '',
default: defaultValue ? defaultValue : '',
}
);
} else if (node.type === 'object') {
let nodeProperty = Object.assign({
type: node.type,
title: title,
properties: {},
});
if (Array.isArray(node.children.data)) {
node.children.data.forEach((item: any) => {
if (this.isValid(item)) {
nodeProperty.properties[item.name] = this.buildSchema(item);
}
});
} else {
if (this.isValid(node.children.data)) {
nodeProperty.properties[node.children.data.name] = this.buildSchema(
node.children.data
);
}
}
return nodeProperty;
} else if (node.type === 'array') {
let defaultValue: Array<any> = [];
if (node.config && node.config.min === node.config.max) {
for (let i = 0; i < node.config.max; i++) {
defaultValue.push(this.getConst(node.children.data, i));
}
} else if (node.config && node.config.min < node.config.max) {
for (let i = 0; i < node.config.min; i++) {
defaultValue.push(this.getConst(node.children.data, i));
}
}
return Object.assign({
type: node.type,
title: title,
maxItems: node.config && node.config.max ? node.config.max : 100,
minItems: node.config && node.config.min ? node.config.min : 0,
default: defaultValue ? defaultValue : [],
items: this.buildSchema(node.children.data),
});
}
}
buildUISchema(properties: any) {
if (!properties) {
return [];
}
let uiSchema: any = {};
Object.keys(properties).forEach((key) => {
uiSchema[key] = {};
if (properties[key].type === 'string') {
uiSchema[key] = {
'ui:disabled': true,
};
} else if (properties[key].type === 'object') {
uiSchema[key] = this.buildUISchema(properties[key].properties);
} else if (properties[key].type === 'array') {
uiSchema[key] = {
items: this.buildUISchema(properties[key].items.properties),
'ui:options': {
addable: true,
removable: !(properties[key].minItems === properties[key].maxItems),
},
};
}
});
return uiSchema;
}
computeData(properties: any, data: any) {
if (!properties) {
return null;
}
Object.keys(properties).forEach((key: any) => {
if (properties[key] === undefined) {
return data;
}
if (properties[key].type === 'string') {
if (properties[key].computed === true) {
data[key] = this.handleExpression(
data,
'',
properties[key].template,
() => {}
);
}
} else if (properties[key].type === 'object') {
data[key] = this.computeData(properties[key].properties, data[key]);
} else if (properties[key].type === 'array') {
data[key].forEach((subData: any, i: number) => {
data[key][i] = this.computeData(
properties[key].items.properties,
subData
);
});
}
});
return data;
}
isValid(node: any) {
if (_.has(node, 'type')) {
if (node.type === 'string') {
return _.has(node, 'name') && _.has(node, 'title');
}
if (node.type === 'object') {
return (
_.has(node, 'name') &&
_.has(node, 'title') &&
_.has(node, 'children.data') &&
_.isArray(node.children.data)
);
}
if (node.type === 'array') {
return (
_.has(node, 'name') &&
_.has(node, 'title') &&
_.has(node, 'children.data') &&
_.has(node, 'children.data.type')
);
}
}
return false;
}
render() {
const { env, data, dataApi, onChange, className, min, max, step } =
this.props;
const { data_template, result_template, trial_data, trial_result } =
this.state;
let templateSchemaProperties = Object.assign({});
let resultSchemaProperties = Object.assign({});
let students = getVariable(data, 'items');
let curIndex = this.state.student
? students.findIndex((s) => s.student.code === this.state.student.code)
: students.findIndex((s) => s.data && s.data.status === 'data_commit');
if (data_template && _.isArray(data_template.data)) {
data_template.data.forEach((item: any) => {
if (this.isValid(item)) {
templateSchemaProperties[item.name] = this.buildSchema(item);
}
});
}
if (result_template && _.isArray(result_template.data)) {
result_template.data.forEach((item: any) => {
if (this.isValid(item)) {
resultSchemaProperties[item.name] = this.buildSchema(item);
}
});
}
let dataSchema = {
title: '原始数据',
description: data_template && data_template.description,
properties: templateSchemaProperties,
};
// let resultSchema = {
// title: '结果数据',
// description: result_template && result_template.description,
// properties: resultSchemaProperties,
// };
let uiSchema = this.buildUISchema(dataSchema.properties);
// let resultUiSchema = this.buildUISchema(resultSchema.properties);
const style = {
// overflow: 'auto',
// height: '700px',
};
return (
<div>
{/* <div className="">
{`${this.state.student.name}(${this.state.student.code})`}
</div> */}
{/* <Button
level={'success'}
disabled={curIndex === -1}
onClick={() => {
env
.fetcher({
method: 'patch',
url: `rest/data/${this.state.data_id}`,
data: {
status: 'data_verified',
},
dataType: 'json',
})
.then((res) => {
env.notify('success', '数据通过成功');
});
}}
>
通过
</Button>
<Button
level={'danger'}
disabled={curIndex === -1}
onClick={() => {
env
.fetcher({
method: 'patch',
url: `rest/data/${this.state.data_id}`,
data: {
status: 'data_refused',
},
dataType: 'json',
})
.then((res) => {
env.notify('success', '数据退回成功');
});
}}
>
退回
</Button>
<Button
disabled={
curIndex === -1 ||
(this.dataTemplate && !this.dataTemplate.verification_required)
}
onClick={() => {
env.fetcher({
method: 'post',
url: `/report-api/data/${this.state.data_id}/verify`,
data: {
id: this.state.data_id,
},
dataType: 'form',
});
}}
>
自动校验
</Button>
<Button
disabled={curIndex === -1}
onClick={() => {
do {
if (curIndex < students.length) {
curIndex++;
} else {
curIndex = 0;
}
} while (
!students[curIndex].data ||
students[curIndex].data.length === 0 ||
students[curIndex].data.status !== 'data_commit'
);
env
.fetcher(dataApi, { user2project_id: students[curIndex].id })
.then((payload: Payload) => {
if (payload.data) {
this.setState({
trial_data: payload.data.data,
trial_result: payload.data.result,
student: payload.data.user2projects.users,
});
}
});
}}
>
下一个
</Button> */}
<div {...{ style }}>
<Form
schema={dataSchema}
uiSchema={uiSchema}
formData={trial_data}
ArrayFieldTemplate={(props) => {
let columns = Object.keys(
props.items[0].children.props.uiSchema
).map((o, index) => {
return {
name: o,
label: props.schema.items['properties'][o].title,
};
});
return render({
type: 'table',
title: props.title,
items: props.formData,
columns: columns,
});
}}
onSubmit={() => {
console.log('submit');
}}
onError={() => {
console.log('errors');
}}
>
<Fragment />
</Form>
{/* <Form
schema={resultSchema}
uiSchema={resultUiSchema}
formData={trial_result}
ArrayFieldTemplate={(props) => {
return (
<div>
{props.items.map((element) => element.children)}
{props.canAdd && (
<button type="button" onClick={props.onAddClick}></button>
)}
</div>
);
}}
onSubmit={() => {
console.log('submit');
}}
onError={() => {
console.log('errors');
}}
>
<Fragment />
</Form> */}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,479 @@
import React, { Fragment } from 'react';
import { Button, classnames, Modal, FormItem } from 'amis';
import { FormControlProps } from 'amis/lib/renderers/Form/Item';
import { Renderer, RendererProps } from 'amis/lib/factory';
import { getVariable } from 'amis/lib/utils/helper';
import Form from '@rjsf/fluent-ui';
import _ from 'lodash';
import { isEffectiveApi } from 'amis/lib/utils/api';
import { Payload } from 'amis/lib/types';
@FormItem({
test: /(^|\/)datapreview1$/,
name: 'datapreview1',
})
export class DataPreview1Renderer extends React.Component<
FormControlProps,
{
visible: boolean;
data_template: any;
result_template: any;
trial_data: any;
trial_result: any;
}
> {
trialInterval: any;
trial_data: any;
trial_result: any;
dataTemplate: any;
resultTemplate: any;
constructor(props: FormControlProps) {
super(props);
this.state = {
visible: true,
data_template: {},
result_template: {},
trial_data: {},
trial_result: {},
};
}
async componentDidMount() {
const { env, api, dataApi, store, data } = this.props;
const { data_template, result_template } = this.state;
let user2project_id = getVariable(data, 'id');
let project_code = getVariable(data, 'projects.code');
if (isEffectiveApi(api)) {
env
.fetcher(api, { project_code: project_code })
.then((payload: Payload) => {
if (payload.data) {
this.dataTemplate = payload.data.data;
this.resultTemplate = payload.data.result;
this.setState({
data_template: payload.data.data,
result_template: payload.data.result,
});
}
});
env.fetcher(dataApi, { user2project_id }).then((payload: Payload) => {
if (payload.data) {
this.setState({
trial_data: payload.data.data,
trial_result: payload.data.result,
});
}
});
}
}
componentWillUnmount() {
if (this.trialInterval) {
clearInterval(this.trialInterval);
}
}
getConst(node: any, index: any) {
let i = index;
if (node.type === 'string') {
let tpl_value = node.default ? node.default : '';
while (
tpl_value &&
tpl_value.match(/{{(.*?)}}/) &&
tpl_value.match(/{{(.*?)}}/).length === 2
) {
try {
tpl_value = tpl_value.replace(
/({{(.*?)}})/,
`${eval(tpl_value.match(/{{(.*?)}}/)[1])}`
);
} catch (error) {
tpl_value = '';
}
}
return tpl_value;
} else if (node.type === 'object') {
if (node.children && Array.isArray(node.children.data)) {
let o: any = {};
node.children.data.forEach((item: any) => {
o[item.name] = this.getConst(item, index);
});
return o;
}
} else if (node.type === 'array') {
let a: Array<any> = [];
if (node.children && Array.isArray(node.children.data)) {
node.children.data.forEach((item: any) => {
if (item.config && item.config.max) {
for (let i = 0; i < item.config.max; i++) {
a.push(this.getConst(item, i));
}
}
});
}
{
if (node.children && this.isValid(node.children.data)) {
a.push(this.getConst(node.children.data, index));
}
}
return a;
}
}
// 通用的处理表达式方法
// 这里打破 JSON Schema 规范
handleExpression(
rootFormData: any,
curNodePath: string,
expression: string,
fallBack: Function
) {
const regExpression = /{{(.*)}}/;
// 未配置
if (undefined === expression) {
return undefined;
}
// 配置了 mustache 表达式
const matchExpression = regExpression.exec(expression);
regExpression.lastIndex = 0; // 重置索引
if (matchExpression) {
try {
const code = matchExpression[1].trim();
// eslint-disable-next-line no-new-func
const fn = new Function(
'parentFormData',
'rootFormData',
`return ${code}`
);
let result = fn(
this.getPathVal(rootFormData, curNodePath, 1),
rootFormData
);
return result ? result : '';
} catch (error) {
return '';
}
}
// 回退
return fallBack();
}
// 获取当前path值
getPathVal(obj: any, path: string, leftDeviation = 0) {
const pathSeparator = '.';
const pathArr = path.split(pathSeparator);
for (let i = 0; i < pathArr.length - leftDeviation; i += 1) {
// 错误路径或者undefined中断查找
if (obj === undefined) return undefined;
obj = pathArr[i] === '' ? obj : obj[pathArr[i]];
}
return obj;
}
buildSchema(node: any): any {
let title = node.title;
// while(title && title.match(/{{(.*?)}}/) && title.match(/{{(.*?)}}/).length === 2) {
// title = title.replace(/({{(.*?)}})/, `${eval(title.match(/{{(.*?)}}/)[1])}`)
// }
if (node.type === 'string') {
let defaultValue = this.getConst(node, 0);
return Object.assign(
{},
{
type: node.type,
title: title,
required: true,
disabled: node.disabled,
computed: node.computed,
template: node.config ? node.config.template : '',
default: defaultValue ? defaultValue : '',
}
);
} else if (node.type === 'object') {
let nodeProperty = Object.assign({
type: node.type,
title: title,
properties: {},
});
if (Array.isArray(node.children.data)) {
node.children.data.forEach((item: any) => {
if (this.isValid(item)) {
nodeProperty.properties[item.name] = this.buildSchema(item);
}
});
} else {
if (this.isValid(node.children.data)) {
nodeProperty.properties[node.children.data.name] = this.buildSchema(
node.children.data
);
}
}
return nodeProperty;
} else if (node.type === 'array') {
let defaultValue: Array<any> = [];
if (node.config && node.config.min === node.config.max) {
for (let i = 0; i < node.config.max; i++) {
defaultValue.push(this.getConst(node.children.data, i));
}
} else if (node.config && node.config.min < node.config.max) {
for (let i = 0; i < node.config.min; i++) {
defaultValue.push(this.getConst(node.children.data, i));
}
}
return Object.assign({
type: node.type,
title: title,
maxItems: node.config && node.config.max ? node.config.max : 100,
minItems: node.config && node.config.min ? node.config.min : 0,
default: defaultValue ? defaultValue : [],
items: this.buildSchema(node.children.data),
});
}
}
buildUISchema(properties: any) {
if (!properties) {
return [];
}
let uiSchema: any = {};
Object.keys(properties).forEach((key) => {
uiSchema[key] = {};
if (properties[key].type === 'string') {
uiSchema[key] = {
'ui:disabled': true,
};
} else if (properties[key].type === 'object') {
uiSchema[key] = this.buildUISchema(properties[key].properties);
} else if (properties[key].type === 'array') {
uiSchema[key] = {
items: this.buildUISchema(properties[key].items.properties),
'ui:options': {
addable: true,
removable: !(properties[key].minItems === properties[key].maxItems),
},
};
}
});
return uiSchema;
}
computeData(properties: any, data: any) {
if (!properties) {
return null;
}
Object.keys(properties).forEach((key: any) => {
if (properties[key] === undefined) {
return data;
}
if (properties[key].type === 'string') {
if (properties[key].computed === true) {
data[key] = this.handleExpression(
data,
'',
properties[key].template,
() => {}
);
}
} else if (properties[key].type === 'object') {
data[key] = this.computeData(properties[key].properties, data[key]);
} else if (properties[key].type === 'array') {
data[key].forEach((subData: any, i: number) => {
data[key][i] = this.computeData(
properties[key].items.properties,
subData
);
});
}
});
return data;
}
isValid(node: any) {
if (_.has(node, 'type')) {
if (node.type === 'string') {
return _.has(node, 'name') && _.has(node, 'title');
}
if (node.type === 'object') {
return (
_.has(node, 'name') &&
_.has(node, 'title') &&
_.has(node, 'children.data') &&
_.isArray(node.children.data)
);
}
if (node.type === 'array') {
return (
_.has(node, 'name') &&
_.has(node, 'title') &&
_.has(node, 'children.data') &&
_.has(node, 'children.data.type')
);
}
}
return false;
}
render() {
console.log(this.props.visable);
const {
env,
data,
onChange,
className,
min,
max,
step,
label,
level,
size,
block,
overrideClassName,
} = this.props;
const { data_template, result_template, trial_data, trial_result } =
this.state;
let templateSchemaProperties = Object.assign({});
let resultSchemaProperties = Object.assign({});
if (data_template && _.isArray(data_template.data)) {
data_template.data.forEach((item: any) => {
if (this.isValid(item)) {
templateSchemaProperties[item.name] = this.buildSchema(item);
}
});
}
if (result_template && _.isArray(result_template.data)) {
result_template.data.forEach((item: any) => {
if (this.isValid(item)) {
resultSchemaProperties[item.name] = this.buildSchema(item);
}
});
}
let dataSchema = {
title: '原始数据',
description: data_template && data_template.description,
properties: templateSchemaProperties,
};
let resultSchema = {
title: '结果数据',
description: result_template && result_template.description,
properties: resultSchemaProperties,
};
let uiSchema = this.buildUISchema(dataSchema.properties);
let resultUiSchema = this.buildUISchema(resultSchema.properties);
console.log(uiSchema);
return (
<>
{this.props.visable ? (
<Button
level="primary"
className={classnames(
overrideClassName
? ''
: {
Button: true,
[`cxd-Button--${level}`]: level,
[`cxd-Button--xs`]: size,
[`cxd-Button--block`]: block,
},
className
)}
onClick={async () => {
const { env, data } = this.props;
const user2project_id = getVariable(data, 'id');
const reports = getVariable(data, 'reports');
const payload = await env.fetcher(
`report-api/reports/${user2project_id}/url?type=submit`
);
let images = payload.data
.replace('[', '')
.replace(']', '')
.split(',')
.map((item: string) => {
return {
src: item,
alt: '',
};
});
let checkImages = [];
if (
Array.isArray(reports) &&
reports.length > 0 &&
reports[0].status === 'C'
) {
const payload2 = await env.fetcher(
`report-api/reports/${user2project_id}/url?type=check`
);
checkImages = payload2.data
.replace('[', '')
.replace(']', '')
.split(',')
.map((item: string) => {
return {
src: item,
alt: '',
};
});
}
this.setState({
visible: true,
});
this.setState({ visible: true });
}}
>
{label}
</Button>
) : null}
<div className="device device-iphone-14-pro">
<div className="device-frame">
<Form
schema={dataSchema}
uiSchema={uiSchema}
formData={trial_data}
onSubmit={() => {
console.log('submit');
}}
onError={() => {
console.log('errors');
}}
>
<Fragment />
</Form>
<Form
schema={resultSchema}
uiSchema={resultUiSchema}
formData={trial_result}
onSubmit={() => {
console.log('submit');
}}
onError={() => {
console.log('errors');
}}
>
<Fragment />
</Form>
</div>
<div className="device-stripe"></div>
<div className="device-header"></div>
<div className="device-sensors"></div>
<div className="device-btns"></div>
<div className="device-power"></div>
<div className="device-home"></div>
</div>
</>
);
}
}

590
src/renderer/DataSet.tsx Normal file
View File

@@ -0,0 +1,590 @@
import React from 'react';
import { Renderer, ScopedContext, Table, Button } from 'amis';
import { RendererProps } from 'amis/lib/factory';
@Renderer({
test: /(^|\/)dataset\-renderer$/,
name: 'dataset-renderer',
})
export class DataestRenderer extends React.Component<
RendererProps,
{
source: any;
breadcrumb: any;
files: any;
showAddFileDialog: any;
showAddFolderDialog: any;
showRenameDialog: any;
isLoading: any;
renameFile: string;
}
> {
static contextType = ScopedContext;
constructor(props: RendererProps) {
super(props);
this.state = {
source: [],
breadcrumb: ['根目录'],
files: [],
showAddFileDialog: false,
showAddFolderDialog: false,
showRenameDialog: false,
isLoading: false,
renameFile: '',
};
}
fetchSource(prefix) {
return new Promise((resolve, reject) => {
this.props.env
.fetcher({
method: 'post',
url: `/storage/v1/object/list/${this.props.bucket}`,
data: {
prefix: prefix ? prefix : '',
sortBy: {
column: 'name',
order: 'asc',
},
},
})
.then((res) => resolve(res))
.catch((err) => reject(err));
});
}
async setSource(prefix) {
let result: any = await this.fetchSource(prefix);
let source = result.data
.filter((item) => item.name.charAt(0) !== '.')
.map((item) => {
return {
...item,
...item.metadata,
mimetype: item.metadata
? item.metadata.mimetype.split('/')[1]
: '文件夹',
size: item.metadata
? '' + Math.floor(item.metadata.size / 1024) + 'KB'
: null,
updated_at: item.updated_at
? item.updated_at.slice(0, 16).replace('T', ' ')
: null,
};
});
this.setState({ source });
}
getPath(breadcrumb) {
return breadcrumb.slice(1).toString().replaceAll(',', '/');
}
async getContent(path, contents) {
let res: any = await this.fetchSource(path);
let actions = [];
res.data.forEach((item) => {
const subPath = path + '/' + item.name;
if (item.id) {
contents.push(subPath);
} else {
actions.push(this.getContent(subPath, contents));
}
});
await Promise.all(actions);
return contents;
}
componentWillMount() {
const scoped = this.context;
scoped.registerComponent(this);
}
async componentDidMount() {
this.setSource(null);
}
componentDidUpdate(prevProps: any, prevState: any, snapshot: any) {
if (
JSON.stringify(prevState.breadcrumb) !==
JSON.stringify(this.state.breadcrumb)
) {
this.reload();
}
}
componentWillUnmount() {
const scoped = this.context;
scoped.unRegisterComponent(this);
}
reload() {
this.setSource(this.getPath(this.state.breadcrumb));
}
render() {
return (
<>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
{this.state.breadcrumb.map((item, index) => {
if (index) {
if (index === this.state.breadcrumb.length - 1) {
return <span key={`${index}-${item}`}>/{item}</span>;
}
return (
<span key={`${index}-${item}`}>
/
<a
href="#"
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(this.state.breadcrumb)
);
temp_breadcrumb = temp_breadcrumb.slice(0, index + 1);
this.setState({ breadcrumb: temp_breadcrumb });
}}
>
{item}
</a>
</span>
);
} else {
if (index === this.state.breadcrumb.length - 1) {
return <span key={`${index}-${item}`}>{item}</span>;
}
return (
<a
key={`${index}-${item}`}
href="#"
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(this.state.breadcrumb)
);
temp_breadcrumb = temp_breadcrumb.slice(0, index + 1);
this.setState({ breadcrumb: temp_breadcrumb });
}}
>
{item}
</a>
);
}
})}
</span>
<span>
<Button
level="primary"
className={`fa fa-folder`}
onClick={() => {
const { addFolder } = this.props;
const path = this.getPath(this.state.breadcrumb);
this.props.onAction(
null,
addFolder,
{ path },
false,
this.context
);
}}
>
&nbsp;&nbsp;
</Button>
<Button
level="primary"
className={`m-l-xs fa fa-upload`}
onClick={() => this.setState({ showAddFileDialog: true })}
>
&nbsp;&nbsp;
</Button>
{/* <Button
level="danger"
className={`m-l-xs fa fa-trash`}
tooltip="批量删除文件"
onClick={() => {
const { delFiles } = this.props;
const path = this.getPath(this.state.breadcrumb);
this.props.onAction(
null,
delFiles,
{ path },
false,
this.context
);
}}
>
</Button> */}
</span>
</div>
<Table
className={`m-t-xs`}
dataSource={this.state.source}
columns={[
{
title: '名称',
render: (item, rowData, rowIndex, i) => {
return {
props: { rowSpan: 1, colSpan: 1 },
children: (
<div key={`${rowIndex}-${rowData.name}`}>
{rowData.id ? (
<Button
level="link"
size="sm"
className={`m-l-xs`}
onClick={() => {
const path =
this.getPath(this.state.breadcrumb) === ''
? rowData.name
: `${this.getPath(this.state.breadcrumb)}/${
rowData.name
}`;
this.props.env
.fetcher({
method: 'post',
url: `/storage/v1/object/sign/${this.props.bucket}`,
data: {
expiresIn: 60000,
paths: [path],
},
headers: {
post2rest: false,
},
})
.then(({ data }) => {
if (data && data.length > 0) {
let presignedurl = `${location.origin}/api/storage/v1${data[0].signedURL}&download=${path}`;
window.open(
`${
location.origin
}/preview/onlinePreview?url=${encodeURIComponent(
window.btoa(unescape(encodeURIComponent(presignedurl)))
)}`
);
} else {
this.props.env.notify(
'error',
'预览文件失败'
);
}
})
.catch((err) => console.log(err));
}}
>
{rowData.name}
</Button>
) : (
<Button
level="link"
size="sm"
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(this.state.breadcrumb)
);
temp_breadcrumb.push(rowData.name);
this.setState({ breadcrumb: temp_breadcrumb });
}}
>
{rowData.name}
</Button>
)}
</div>
),
};
},
},
{ key: 'mimetype', title: '类型' },
{ key: 'size', title: '大小' },
{ key: 'updated_at', title: '修改日期' },
{
title: '操作',
render: (item, rowData, rowIndex, i) => {
return {
props: { rowSpan: 1, colSpan: 1 },
children: (
<div key={`${rowIndex}-${rowData.name}`}>
<Button
level="warning"
size="xs"
onClick={() => {
const { moveFile } = this.props;
const path = this.getPath(this.state.breadcrumb);
const sourceKey = path
? path + '/' + rowData.name
: rowData.name;
if (rowData.id) {
this.props.onAction(
null,
moveFile,
{ path, sourceKey },
false,
this.context
);
} else {
this.setState({
showRenameDialog: true,
renameFile: rowData.name,
});
}
}}
>
</Button>
<Button
level="link"
size="sm"
className={`m-l-xs fa fa-trash text-danger`}
tooltip="删除文件"
onClick={async () => {
const path = this.getPath(this.state.breadcrumb);
const { delFile } = this.props;
if (rowData.id) {
const prefixes = path
? [path + '/' + rowData.name]
: [rowData.name];
this.props.onAction(
null,
delFile,
{ path, prefixes },
false,
this.context
);
} else {
const prefixes = await this.getContent(
path ? path + '/' + rowData.name : rowData.name,
[]
);
this.props.onAction(
null,
delFile,
{ path, prefixes },
false,
this.context
);
}
}}
></Button>
</div>
),
};
},
},
]}
rowClassName={(item) => !item.id && 'font-bold text-primary'}
/>
{this.state.showAddFileDialog ? (
<div
id="addFileDialog"
className="amis-dialog-widget cxd-Modal cxd-Modal--1th"
>
<div className="cxd-Modal-overlay in"></div>
<div className="cxd-Modal-content in">
<div className="cxd-Modal-header">
<div className="cxd-Modal-title"></div>
</div>
<div className="cxd-Modal-body">
<form method="dialog" className="cxd-Form cxd-Form--horizontal">
<div className="cxd-Form-item cxd-Form-item--horizontal">
<div className="cxd-Form-value">
<div className="cxd-Form-control cxd-TextControl">
<div><strong>500MB</strong></div>
<input
id="addFile"
type="file"
multiple
onChange={() => {
const addFile: any =
document.getElementById('addFile');
if (addFile.files.length)
this.setState({
files: Array.from(addFile.files),
});
}}
/>
{this.state.files.length > 0 && (
<ul>
{this.state.files.map((file, index) => {
return (
<li key={`file-${index}-${file.name}`}>
{file.name}
</li>
);
})}
</ul>
)}
</div>
</div>
</div>
</form>
</div>
<div className="cxd-Modal-footer">
<Button
onClick={() =>
this.setState({ files: [], showAddFileDialog: false })
}
>
</Button>
<Button
level="primary"
onClick={async () => {
const path = this.getPath(this.state.breadcrumb);
this.setState({ isLoading: true });
if (this.state.files.length) {
Promise.all(
this.state.files.map((file: any) => {
let url = `storage/v1/object/${this.props.bucket}/`;
if (path) url += path + '/' + file.name;
else url += file.name;
return this.props.env.fetcher({
method: 'post',
url,
data: { fileBody: file },
});
})
)
.then(() => {
this.reload();
this.props.env.notify('success', '上传文件成功');
this.setState({
files: [],
isLoading: false,
showAddFileDialog: false,
});
})
.catch((err) => {
this.props.env.notify('error', '上传文件失败');
this.setState({
files: [],
isLoading: false,
showAddFileDialog: false,
});
});
} else {
this.props.env.notify('warning', '请上传文件');
}
}}
>
</Button>
</div>
</div>
</div>
) : null}
{this.state.showRenameDialog ? (
<div
id="renameDialog"
className="amis-dialog-widget cxd-Modal cxd-Modal--1th"
>
<div className="cxd-Modal-overlay in"></div>
<div className="cxd-Modal-content in">
<div className="cxd-Modal-header">
<div className="cxd-Modal-title"></div>
</div>
<div className="cxd-Modal-body">
<form method="dialog" className="cxd-Form cxd-Form--horizontal">
<div className="cxd-Form-item cxd-Form-item--horizontal">
<label
htmlFor="rename"
className="cxd-Form-label cxd-Form-itemColumn--normal"
>
<span>
<span className="cxd-TplField">
<span></span>
</span>
</span>
</label>
<div className="cxd-Form-value">
<div className="cxd-Form-control cxd-TextControl">
<div className="cxd-TextControl-input">
<input id="renameFile" type="text" />
</div>
</div>
</div>
</div>
</form>
</div>
<div className="cxd-Modal-footer">
<Button
onClick={() => this.setState({ showRenameDialog: false })}
>
</Button>
<Button
level="primary"
onClick={async () => {
const renameFile: any =
document.getElementById('renameFile');
const path = this.getPath(this.state.breadcrumb);
const prefixes = await this.getContent(
path
? path + '/' + this.state.renameFile
: this.state.renameFile,
[]
);
if (renameFile.value) {
Promise.all(
prefixes.map((item) => {
return new Promise((resolve, reject) => {
this.props.env
.fetcher({
method: 'post',
url: '/storage/v1/object/move',
data: {
bucketId: this.props.bucket,
sourceKey: item,
destinationKey: item.replace(
this.state.renameFile,
renameFile.value
),
},
})
.then((res) => resolve(res))
.catch((err) => reject(err));
});
})
)
.then(() => {
this.setState({ showRenameDialog: false });
this.reload();
})
.catch((err) => console.log(err));
} else {
this.props.env.notify('error', '请输入文件名');
}
}}
>
</Button>
</div>
</div>
</div>
) : null}
{this.state.isLoading ? (
<div
id="isLoading"
className="amis-dialog-widget cxd-Modal cxd-Modal--1th"
>
<div className="cxd-Modal-overlay in"></div>
<div className="cxd-Modal-content in">
<h2>...</h2>
</div>
</div>
) : null}
</>
);
}
}

View File

@@ -0,0 +1,301 @@
import { Button, FormItem, ScopedContext, Table } from 'amis';
import { FormControlProps } from 'amis/lib/renderers/Form/Item';
import React from 'react';
@FormItem({
test: /(^|\/)dataset\-picker$/,
name: 'dataset-picker',
})
export class DataestRenderer extends React.Component<
FormControlProps,
{
source: any;
breadcrumb: any;
hash: any;
showDialog: any;
}
> {
static contextType = ScopedContext;
constructor(props: FormControlProps) {
super(props);
this.state = {
source: [],
breadcrumb: ['根目录'],
hash: '',
showDialog: false,
};
}
fetchSource(prefix) {
if (prefix === '/') {
return new Promise((resolve, reject) => {
this.props.env
.resFetcher({
method: 'get',
url: '/res/api/shares',
})
.then((res) => resolve(res))
.catch((err) => reject(err));
});
} else {
return new Promise((resolve, reject) => {
this.props.env
.resFetcher({
method: 'get',
url: `/res/api/resources/${prefix}`,
})
.then((res) => resolve(res))
.catch((err) => reject(err));
});
}
}
async setSource(prefix) {
const result: any = await this.fetchSource(prefix);
const source =
prefix === '/'
? result.data.data
.filter((item) => !item.password_hash)
.map((item) => {
return {
...item,
name: item.path.replaceAll(/^\/|\/$/g, ''),
mimetype:
item.path[item.path.length - 1] === '/' ? '文件夹' : null,
};
})
: result.data.data.items.map((item) => {
return {
...item,
mimetype: item.isDir ? '文件夹' : item.type,
updated_at: item.modified.slice(0, 16).replace('T', ' '),
};
});
this.setState({ source });
}
getPath(breadcrumb, n) {
return breadcrumb.slice(n).toString().replaceAll(',', '/');
}
componentWillMount() {
const scoped = this.context;
scoped.registerComponent(this);
}
async componentDidMount() {
this.setSource('/');
}
componentDidUpdate(prevProps: any, prevState: any, snapshot: any) {
if (
JSON.stringify(prevState.breadcrumb) !==
JSON.stringify(this.state.breadcrumb)
) {
this.reload();
}
}
componentWillUnmount() {
const scoped = this.context;
scoped.unRegisterComponent(this);
}
reload() {
this.setSource(`${this.getPath(this.state.breadcrumb, 1)}/`);
}
render() {
return (
<>
<Button
level="warning"
size="sm"
onClick={() => this.setState({ showDialog: true })}
>
</Button>
{this.state.showDialog ? (
<div
id="dialog"
className="amis-dialog-widget cxd-Modal cxd-Modal--1th"
>
<div className="cxd-Modal-overlay in"></div>
<div className="cxd-Modal-content in">
<div className="cxd-Modal-header">
<div className="cxd-Modal-title"></div>
</div>
<div className="cxd-Modal-body">
<div
style={{ display: 'flex', justifyContent: 'space-between' }}
>
<span>
{this.state.breadcrumb.map((item, index) => {
if (index) {
if (index === this.state.breadcrumb.length - 1) {
return <span key={`${index}-${item}`}>/{item}</span>;
}
return (
<span key={`${index}-${item}`}>
/
<a
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(this.state.breadcrumb)
);
temp_breadcrumb = temp_breadcrumb.slice(
0,
index + 1
);
this.setState({ breadcrumb: temp_breadcrumb });
}}
>
{item}
</a>
</span>
);
} else {
if (index === this.state.breadcrumb.length - 1) {
return <span key={`${index}-${item}`}>{item}</span>;
}
return (
<a
key={`${index}-${item}`}
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(this.state.breadcrumb)
);
temp_breadcrumb = temp_breadcrumb.slice(
0,
index + 1
);
this.setState({ breadcrumb: temp_breadcrumb });
}}
>
{item}
</a>
);
}
})}
</span>
</div>
<Table
className={`m-t-xs`}
dataSource={this.state.source}
columns={[
{
title: '名称',
render: (item, rowData, rowIndex, i) => {
return {
props: { rowSpan: 1, colSpan: 1 },
children: (
<div key={`${rowIndex}-${rowData.name}`}>
{rowData.isDir ||
rowData.mimetype === '文件夹' ? (
<Button
level="link"
size="sm"
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(this.state.breadcrumb)
);
temp_breadcrumb.push(rowData.name);
this.setState({
breadcrumb: temp_breadcrumb,
});
if (rowData.hash)
this.setState({
hash: rowData.hash,
});
}}
>
{rowData.name}
</Button>
) : (
<Button
level="link"
size="sm"
className={`m-l-xs`}
onClick={() => {
const prefix = this.getPath(
this.state.breadcrumb,
2
);
const path = rowData.hash
? `${rowData.hash}`
: prefix
? `${this.state.hash}/${prefix}/${rowData.name}`
: `${this.state.hash}/${rowData.name}`;
this.props.onChange(path);
this.setState({ showDialog: false });
}}
>
{rowData.name}
</Button>
)}
</div>
),
};
},
},
{
title: '类型',
render: (item, rowData, rowIndex, i) => {
return {
props: { rowSpan: 1, colSpan: 1 },
children: (
<div key={`${rowIndex}-${rowData.mimetype}`}>
{rowData.mimetype}
</div>
),
};
},
},
{
title: '大小',
render: (item, rowData, rowIndex, i) => {
return {
props: { rowSpan: 1, colSpan: 1 },
children: (
<div key={`${rowIndex}-${rowData.size}`}>
{rowData.size}
</div>
),
};
},
},
{
title: '修改日期',
render: (item, rowData, rowIndex, i) => {
return {
props: { rowSpan: 1, colSpan: 1 },
children: (
<div key={`${rowIndex}-${rowData.updated_at}`}>
{rowData.updated_at}
</div>
),
};
},
},
]}
rowClassName={(item) => !item.id && 'font-bold text-primary'}
/>
</div>
<div className="cxd-Modal-footer">
<Button onClick={() => this.setState({ showDialog: false })}>
</Button>
</div>
</div>
</div>
) : null}
</>
);
}
}

View File

@@ -0,0 +1,453 @@
import React, { Fragment } from 'react';
import { Button, FormItem, render, Table } from 'amis';
import { FormControlProps } from 'amis/lib/renderers/Form/Item';
import Form from '@rjsf/fluent-ui';
import _ from 'lodash';
import { isEffectiveApi } from 'amis/lib/utils/api';
import { Payload } from 'amis/lib/types';
import { getVariable, setVariable } from 'amis/lib/utils/helper';
import { Badge } from 'amis/lib/components/Badge';
@FormItem({
test: /(^|\/)datatemplatepreview$/,
name: 'datatemplatepreview',
})
export class DataTemplatePreviewRenderer extends React.Component<
FormControlProps,
{
data_template: any;
result_template: any;
trial_data: any;
trial_result: any;
data: any;
canNext: boolean;
}
> {
trialInterval: any;
trial_data: any;
dataTemplate: any;
resultTemplate: any;
constructor(props: FormControlProps) {
super(props);
this.state = {
data_template: {},
result_template: {},
trial_data: {},
trial_result: {},
data: {},
canNext: true,
};
}
componentDidMount() {
const { store, data, env, onChange } = this.props;
let template_id = getVariable(data, 'template_id');
let sample_data = {};
let sample_result = {};
env
.fetcher({
url: `rest/data_templates/${template_id}`,
})
.then((payload) => {
if (payload.data.sample_data !== null) {
sample_data = payload.data.sample_data;
}
if (payload.data.sample_result !== null) {
sample_result = payload.data.sample_result;
}
this.setState({
trial_data: sample_data,
trial_result: sample_result,
});
onChange({
trial_data: sample_data,
trial_result: sample_result,
});
});
this.trialInterval = setInterval(() => {
if (
store?.data &&
(!_.isEqual(this.dataTemplate, store?.data.data) ||
!_.isEqual(this.resultTemplate, store?.data.result))
) {
this.dataTemplate = store?.data.data;
this.resultTemplate = store?.data.result;
this.setState({
data_template: store?.data.data,
result_template: store?.data.result,
});
}
}, 1000);
}
componentWillUnmount() {
if (this.trialInterval) {
clearInterval(this.trialInterval);
}
}
fetchData() {
const { env, api, dataApi, data } = this.props;
let project_code = getVariable(data, 'projects.code');
if (isEffectiveApi(api)) {
env
.fetcher(api, { project_code: project_code })
.then((payload: Payload) => {
if (payload.data) {
this.dataTemplate = payload.data.data;
this.resultTemplate = payload.data.result;
this.setState({
data_template: payload.data.data,
result_template: payload.data.result,
});
}
});
// env
// .fetcher(dataApi, { user2project_id: })
// .then((payload: Payload) => {
// if (payload.data) {
// this.setState({
// data: payload.data,
// trial_data: payload.data.data,
// });
// }
// });
}
}
getConst(node: any, index: any) {
let i = index;
if (node.type === 'string') {
let tpl_value = node.default ? node.default : '';
while (
tpl_value &&
tpl_value.match(/{{(.*?)}}/) &&
tpl_value.match(/{{(.*?)}}/).length === 2
) {
try {
tpl_value = tpl_value.replace(
/({{(.*?)}})/,
`${eval(tpl_value.match(/{{(.*?)}}/)[1])}`
);
} catch (error) {
tpl_value = '';
}
}
return tpl_value;
} else if (node.type === 'object') {
if (node.children && Array.isArray(node.children.data)) {
let o: any = {};
node.children.data.forEach((item: any) => {
o[item.name] = this.getConst(item, index);
});
return o;
}
} else if (node.type === 'array') {
let a: Array<any> = [];
if (node.children && Array.isArray(node.children.data)) {
node.children.data.forEach((item: any) => {
if (item.config && item.config.max) {
for (let i = 0; i < item.config.max; i++) {
a.push(this.getConst(item, i));
}
}
});
}
{
if (node.children && this.isValid(node.children.data)) {
a.push(this.getConst(node.children.data, index));
}
}
return a;
}
}
// 通用的处理表达式方法
// 这里打破 JSON Schema 规范
handleExpression(
rootFormData: any,
curNodePath: string,
expression: string,
fallBack: Function
) {
const regExpression = /{{(.*)}}/;
// 未配置
if (undefined === expression) {
return undefined;
}
// 配置了 mustache 表达式
const matchExpression = regExpression.exec(expression);
regExpression.lastIndex = 0; // 重置索引
if (matchExpression) {
try {
const code = matchExpression[1].trim();
// eslint-disable-next-line no-new-func
const fn = new Function(
'parentFormData',
'rootFormData',
`return ${code}`
);
let result = fn(
this.getPathVal(rootFormData, curNodePath, 1),
rootFormData
);
return result ? result : '';
} catch (error) {
return '';
}
}
// 回退
return fallBack();
}
// 获取当前path值
getPathVal(obj: any, path: string, leftDeviation = 0) {
const pathSeparator = '.';
const pathArr = path.split(pathSeparator);
for (let i = 0; i < pathArr.length - leftDeviation; i += 1) {
// 错误路径或者undefined中断查找
if (obj === undefined) return undefined;
obj = pathArr[i] === '' ? obj : obj[pathArr[i]];
}
return obj;
}
buildSchema(node: any): any {
let title = node.title;
// while(title && title.match(/{{(.*?)}}/) && title.match(/{{(.*?)}}/).length === 2) {
// title = title.replace(/({{(.*?)}})/, `${eval(title.match(/{{(.*?)}}/)[1])}`)
// }
if (node.type === 'string') {
let defaultValue = this.getConst(node, 0);
return Object.assign(
{},
{
type: node.type,
title: title,
required: true,
disabled: node.disabled,
computed: node.computed,
template: node.config ? node.config.template : '',
default: defaultValue ? defaultValue : '',
}
);
} else if (node.type === 'object') {
let nodeProperty = Object.assign({
type: node.type,
title: title,
properties: {},
});
if (Array.isArray(node.children.data)) {
node.children.data.forEach((item: any) => {
if (this.isValid(item)) {
nodeProperty.properties[item.name] = this.buildSchema(item);
}
});
} else {
if (this.isValid(node.children.data)) {
nodeProperty.properties[node.children.data.name] = this.buildSchema(
node.children.data
);
}
}
return nodeProperty;
} else if (node.type === 'array') {
let defaultValue: Array<any> = [];
if (node.config && node.config.min === node.config.max) {
for (let i = 0; i < node.config.max; i++) {
defaultValue.push(this.getConst(node.children.data, i));
}
} else if (node.config && node.config.min < node.config.max) {
for (let i = 0; i < node.config.min; i++) {
defaultValue.push(this.getConst(node.children.data, i));
}
}
return Object.assign({
type: node.type,
title: title,
maxItems: node.config && node.config.max ? node.config.max : 100,
minItems: node.config && node.config.min ? node.config.min : 0,
default: defaultValue ? defaultValue : [],
items: this.buildSchema(node.children.data),
});
}
}
buildUISchema(properties: any) {
if (!properties) {
return [];
}
let uiSchema: any = {};
Object.keys(properties).forEach((key) => {
uiSchema[key] = {};
if (properties[key].type === 'string') {
uiSchema[key] = {
'ui:disabled': true,
};
} else if (properties[key].type === 'object') {
uiSchema[key] = this.buildUISchema(properties[key].properties);
} else if (properties[key].type === 'array') {
uiSchema[key] = {
items: this.buildUISchema(properties[key].items.properties),
'ui:options': {
addable: true,
removable: !(properties[key].minItems === properties[key].maxItems),
},
};
}
});
return uiSchema;
}
computeData(properties: any, data: any) {
if (!properties) {
return null;
}
Object.keys(properties).forEach((key: any) => {
if (properties[key] === undefined) {
return data;
}
if (properties[key].type === 'string') {
if (properties[key].computed === true) {
data[key] = this.handleExpression(
data,
'',
properties[key].template,
() => {}
);
}
} else if (properties[key].type === 'object') {
data[key] = this.computeData(properties[key].properties, data[key]);
} else if (properties[key].type === 'array') {
data[key].forEach((subData: any, i: number) => {
data[key][i] = this.computeData(
properties[key].items.properties,
subData
);
});
}
});
return data;
}
isValid(node: any) {
if (_.has(node, 'type')) {
if (node.type === 'string') {
return _.has(node, 'name') && _.has(node, 'title');
}
if (node.type === 'object') {
return (
_.has(node, 'name') &&
_.has(node, 'title') &&
_.has(node, 'children.data') &&
_.isArray(node.children.data)
);
}
if (node.type === 'array') {
return (
_.has(node, 'name') &&
_.has(node, 'title') &&
_.has(node, 'children.data') &&
_.has(node, 'children.data.type')
);
}
}
return false;
}
render() {
const { env, data, dataApi, onChange, className, min, max, step } =
this.props;
const { data_template, result_template, trial_data } =
this.state;
let templateSchemaProperties = Object.assign({});
let resultSchemaProperties = Object.assign({});
if (data_template && _.isArray(data_template.data)) {
data_template.data.forEach((item: any) => {
if (this.isValid(item)) {
templateSchemaProperties[item.name] = this.buildSchema(item);
}
});
}
if (result_template && _.isArray(result_template.data)) {
result_template.data.forEach((item: any) => {
if (this.isValid(item)) {
resultSchemaProperties[item.name] = this.buildSchema(item);
}
});
}
let dataSchema = {
title: '原始数据',
description: data_template && data_template.description,
properties: templateSchemaProperties,
};
// let resultSchema = {
// title: '结果数据',
// description: result_template && result_template.description,
// properties: resultSchemaProperties,
// };
let uiSchema = this.buildUISchema(dataSchema.properties);
// let resultUiSchema = this.buildUISchema(resultSchema.properties);
const style = {
// overflow: 'auto',
// height: '700px',
};
return (
<div>
<div {...{ style }}>
<Form
schema={dataSchema}
uiSchema={uiSchema}
formData={trial_data}
ArrayFieldTemplate={(props) => {
let columns = Object.keys(
props.items[0].children.props.uiSchema
).map((o, index) => {
return {
name: o,
label: props.schema.items['properties'][o].title,
};
});
return render({
type: 'table',
title: props.title,
items: props.formData,
columns: columns,
});
}}
onSubmit={() => {
console.log('submit');
}}
onError={() => {
console.log('errors');
}}
>
<Fragment />
</Form>
</div>
</div>
);
}
}

530
src/renderer/DataTrial.tsx Normal file
View File

@@ -0,0 +1,530 @@
import React, { Fragment } from 'react';
import { Button, FormItem, FormControlProps, getVariable } from 'amis';
import Form from '@rjsf/fluent-ui';
import _ from 'lodash';
@FormItem({
test: /(^|\/)datatrial$/,
name: 'datatrial',
})
export class DataTrialRenderer extends React.Component<
FormControlProps,
{
data_template: any;
result_template: any;
trial_data: any;
trial_result: any;
}
> {
trialInterval: any;
trial_data: any;
trial_result: any;
dataTemplate: any;
resultTemplate: any;
constructor(props: FormControlProps) {
super(props);
this.state = {
data_template: {},
result_template: {},
trial_data: {},
trial_result: {},
};
}
componentDidMount() {
const { store, data, env, onChange } = this.props;
let template_id = getVariable(data, 'template_id');
let sample_data = {};
let sample_result = {};
env
.fetcher({
url: `rest/data_templates/${template_id}`,
})
.then((payload) => {
if (payload.data.sample_data !== null) {
sample_data = payload.data.sample_data;
}
if (payload.data.sample_result !== null) {
sample_result = payload.data.sample_result;
}
this.setState({
trial_data: sample_data,
trial_result: sample_result,
});
onChange({
trial_data: sample_data,
trial_result: sample_result,
});
});
this.trialInterval = setInterval(() => {
if (
store?.data &&
(!_.isEqual(this.dataTemplate, store?.data.data) ||
!_.isEqual(this.resultTemplate, store?.data.result))
) {
this.dataTemplate = store?.data.data;
this.resultTemplate = store?.data.result;
this.setState({
data_template: store?.data.data,
result_template: store?.data.result,
});
}
}, 1000);
}
componentWillUnmount() {
if (this.trialInterval) {
clearInterval(this.trialInterval);
}
}
getConst(node: any, index: any) {
let i = index;
if (node.type === 'string') {
let tpl_value = node.default ? node.default : '';
while (
tpl_value &&
tpl_value.match(/{{(.*?)}}/) &&
tpl_value.match(/{{(.*?)}}/).length === 2
) {
try {
tpl_value = tpl_value.replace(
/({{(.*?)}})/,
`${eval(tpl_value.match(/{{(.*?)}}/)[1])}`
);
} catch (error) {
tpl_value = '';
}
}
return tpl_value;
} else if (node.type === 'object') {
if (node.children && Array.isArray(node.children.data)) {
let o: any = {};
node.children.data.forEach((item: any) => {
o[item.name] = this.getConst(item, index);
});
return o;
}
} else if (node.type === 'array') {
let a: Array<any> = [];
if (node.children && Array.isArray(node.children.data)) {
node.children.data.forEach((item: any) => {
if (item.config && item.config.max) {
for (let i = 0; i < item.config.max; i++) {
a.push(this.getConst(item, i));
}
}
});
}
{
if (node.children && this.isValid(node.children.data)) {
a.push(this.getConst(node.children.data, index));
}
}
return a;
}
}
// 通用的处理表达式方法
// 这里打破 JSON Schema 规范
handleExpression(
rootFormData: any,
curNodePath: string,
expression: string,
fallBack: Function
) {
const regExpression = /{{(.*)}}/;
// 未配置
if (undefined === expression) {
return undefined;
}
// 配置了 mustache 表达式
const matchExpression = regExpression.exec(expression);
regExpression.lastIndex = 0; // 重置索引
if (matchExpression) {
try {
const code = matchExpression[1].trim();
// eslint-disable-next-line no-new-func
const fn = new Function(
'parentFormData',
'rootFormData',
`return ${code}`
);
let result = fn(
this.getPathVal(rootFormData, curNodePath, 1),
rootFormData
);
return result ? result : '';
} catch (error) {
return '';
}
}
// 回退
return fallBack();
}
// 获取当前path值
getPathVal(obj: any, path: string, leftDeviation = 0) {
const pathSeparator = '.';
const pathArr = path.split(pathSeparator);
for (let i = 0; i < pathArr.length - leftDeviation; i += 1) {
// 错误路径或者undefined中断查找
if (obj === undefined) return undefined;
obj = pathArr[i] === '' ? obj : obj[pathArr[i]];
}
return obj;
}
buildSchema(node: any): any {
let title = node.title;
// while(title && title.match(/{{(.*?)}}/) && title.match(/{{(.*?)}}/).length === 2) {
// title = title.replace(/({{(.*?)}})/, `${eval(title.match(/{{(.*?)}}/)[1])}`)
// }
if (node.type === 'string') {
let defaultValue = this.getConst(node, 0);
return Object.assign(
{},
{
type: node.type,
title: title,
required: true,
disabled: node.disabled,
computed: node.computed,
template: node.config ? node.config.template : '',
default: defaultValue ? defaultValue : '',
}
);
} else if (node.type === 'object') {
let nodeProperty = Object.assign({
type: node.type,
title: title,
properties: {},
});
if (Array.isArray(node.children.data)) {
node.children.data.forEach((item: any) => {
if (this.isValid(item)) {
nodeProperty.properties[item.name] = this.buildSchema(item);
}
});
} else {
if (this.isValid(node.children.data)) {
nodeProperty.properties[node.children.data.name] = this.buildSchema(
node.children.data
);
}
}
return nodeProperty;
} else if (node.type === 'array') {
let defaultValue: Array<any> = [];
if (node.config && node.config.min === node.config.max) {
for (let i = 0; i < node.config.max; i++) {
defaultValue.push(this.getConst(node.children.data, i));
}
} else if (node.config && node.config.min < node.config.max) {
for (let i = 0; i < node.config.min; i++) {
defaultValue.push(this.getConst(node.children.data, i));
}
}
return Object.assign({
type: node.type,
title: title,
maxItems: node.config && node.config.max ? node.config.max : 100,
minItems: node.config && node.config.min ? node.config.min : 0,
default: defaultValue ? defaultValue : [],
items: this.buildSchema(node.children.data),
});
}
}
buildUISchema(properties: any) {
if (!properties) {
return [];
}
let uiSchema: any = {};
Object.keys(properties).forEach((key) => {
uiSchema[key] = {};
if (properties[key].type === 'string') {
uiSchema[key] = {
'ui:disabled': properties[key].computed === true,
};
} else if (properties[key].type === 'object') {
uiSchema[key] = this.buildUISchema(properties[key].properties);
} else if (properties[key].type === 'array') {
uiSchema[key] = {
items: this.buildUISchema(properties[key].items.properties),
'ui:options': {
addable: true,
removable: !(properties[key].minItems === properties[key].maxItems),
},
};
}
});
return uiSchema;
}
computeData(properties: any, data: any) {
if (!properties) {
return null;
}
Object.keys(properties).forEach((key: any) => {
if (properties[key] === undefined) {
return data;
}
if (properties[key].type === 'string') {
if (properties[key].computed === true) {
data[key] = this.handleExpression(
data,
'',
properties[key].template,
() => {}
);
}
} else if (properties[key].type === 'object') {
data[key] = this.computeData(properties[key].properties, data[key]);
} else if (properties[key].type === 'array') {
data[key].forEach((subData: any, i: number) => {
data[key][i] = this.computeData(
properties[key].items.properties,
subData
);
});
}
});
return data;
}
isValid(node: any) {
if (_.has(node, 'type')) {
if (node.type === 'string') {
return _.has(node, 'name') && _.has(node, 'title');
}
if (node.type === 'object') {
return (
_.has(node, 'name') &&
_.has(node, 'title') &&
_.has(node, 'children.data') &&
_.isArray(node.children.data)
);
}
if (node.type === 'array') {
return (
_.has(node, 'name') &&
_.has(node, 'children.data') &&
_.has(node, 'children.data.type')
);
}
}
return false;
}
// handleTrialData({ formData }: any) {
// console.log(formData)
// let newData = _.cloneDeep(formData);
// let newFormData = this.computeData(this.dataSchema.properties, newData);
// console.log(newFormData)
// this.setState({
// trial_data: newData,
// });
// this.trial_data = newFormData;
// this.props.onChange({
// trial_data: newFormData,
// trial_result: this.trial_result,
// });
// }
// handleTrialResult({ formData }: any) {
// let newData = _.cloneDeep(formData);
// let newFormData = this.computeData(this.resultSchema.properties, newData);
// // this.setState({
// // trial_result: newFormData,
// // });
// this.trial_result = newFormData
// this.props.onChange({
// trial_data: this.trial_data,
// trial_result: newFormData,
// });
// }
render() {
const { env, data, onChange, className, min, max, step } = this.props;
const { data_template, result_template, trial_data, trial_result } =
this.state;
let templateSchemaProperties = Object.assign({});
let resultSchemaProperties = Object.assign({});
if (data_template && _.isArray(data_template.data)) {
data_template.data.forEach((item: any) => {
if (this.isValid(item)) {
templateSchemaProperties[item.name] = this.buildSchema(item);
}
});
}
if (result_template && _.isArray(result_template.data)) {
result_template.data.forEach((item: any) => {
if (this.isValid(item)) {
resultSchemaProperties[item.name] = this.buildSchema(item);
}
});
}
let dataSchema = {
title: '原始数据示例',
description: data_template && data_template.description,
properties: templateSchemaProperties,
};
let resultSchema = {
title: '结果数据示例',
description: result_template && result_template.description,
properties: resultSchemaProperties,
};
let uiSchema = this.buildUISchema(dataSchema.properties);
let calculate_enabled = getVariable(data, 'calculate_enabled');
return (
<div className={``}>
<Form
schema={dataSchema}
uiSchema={uiSchema}
formData={trial_data}
onChange={({ formData }) => {
this.setState({
trial_data: formData,
});
onChange({
trial_data: formData,
trial_result,
});
}}
onSubmit={() => {
console.log('submit');
}}
onError={() => {
console.log('errors');
}}
>
<Fragment />
</Form>
<Button
level="primary"
onClick={async () => {
const { env, data } = this.props;
const template_id = getVariable(data, 'template_id');
const payload = await env.fetcher({
method: 'patch',
url: `rest/data_templates/${template_id}`,
data: {
sample_data: this.state.trial_data,
},
});
if (payload.ok) {
env.notify('success', `测试数据已保存`);
}
}}
>
</Button>
{calculate_enabled ? (
<Button
className="m-l-sm"
level="primary"
onClick={async () => {
const { env, data } = this.props;
const calculate_rule = getVariable(data, 'calculate_rule');
const orderNumber = getVariable(data, 'orderNumber');
const payload = await env.fetcher(
{
method: 'post',
url: `/report-api/data/rule/test`,
dataType: 'form',
},
{
data: JSON.stringify(this.state.trial_data),
rule: calculate_rule,
ref: JSON.stringify({ _orderNumber_: orderNumber }),
}
);
if (payload.data.result) {
this.setState({
trial_data: payload.data.data,
});
onChange({
trial_data: payload.data.data,
trial_result,
});
} else {
env.notify(
'error',
`${payload.data.result}: ${payload.data.message}`
);
}
}}
>
</Button>
) : (
<></>
)}
<Form
schema={resultSchema}
formData={trial_result}
onChange={({ formData }) => {
let newData = _.cloneDeep(formData);
let newFormData = this.computeData(
resultSchema.properties,
newData
);
this.setState({
trial_result: newFormData,
});
onChange({
trial_data,
trial_result: newFormData,
});
}}
onSubmit={() => {
console.log('submit');
}}
onError={() => {
console.log('errors');
}}
>
<Fragment />
</Form>
<Button
level="primary"
onClick={async () => {
const { env, data } = this.props;
const template_id = getVariable(data, 'template_id');
const payload = await env.fetcher({
method: 'patch',
url: `rest/data_templates/${template_id}`,
data: {
sample_result: this.state.trial_result,
},
});
if (payload.ok) {
env.notify('success', `测试结果已保存`);
}
}}
>
</Button>
</div>
);
}
}

238
src/renderer/DestPicker.tsx Normal file
View File

@@ -0,0 +1,238 @@
import React from 'react'
import { useEffect, useState } from 'react';
import { Table, Button } from 'amis';
export default function DestPicker({
isRichtext,
fetcher,
breadcrumb,
setBreadcrumb,
bucket,
onConfirm,
onCancel,
}) {
const [source, setSource] = useState([]);
useEffect(() => {
fetchSource(null);
}, []);
useEffect(() => {
reload();
}, [breadcrumb]);
async function fetchSource(prefix) {
const result = await fetcher({
method: 'post',
url: `/storage/v1/object/list/${bucket}`,
data: {
prefix: prefix ? prefix : '',
sortBy: {
column: 'name',
order: 'asc',
},
},
});
let source = result.data
.filter((item) => item.name.charAt(0) !== '.')
.map((item) => {
return {
...item,
...item.metadata,
mimetype: item.metadata
? item.metadata.mimetype.split('/')[1]
: '文件夹',
size: item.metadata
? '' + Math.floor(item.metadata.size / 1024) + 'KB'
: null,
updated_at: item.updated_at
? item.updated_at.slice(0, 16).replace('T', ' ')
: null,
};
});
setSource(source);
}
function getPath(breadcrumb) {
return breadcrumb.slice(1).toString().replaceAll(',', '/');
}
function reload() {
fetchSource(getPath(breadcrumb));
}
return (
<>
<div
id="dialog"
className="amis-dialog-widget cxd-Modal cxd-Modal--xl cxd-Modal--1th"
>
<div className="cxd-Modal-overlay in"></div>
<div className="cxd-Modal-content in">
<div className="cxd-Modal-header">
<div className="cxd-Modal-title"></div>
</div>
<div className="cxd-Modal-body">
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
{breadcrumb.map((item, index) => {
if (index) {
if (index === breadcrumb.length - 1) {
return <span key={`${index}-${item}`}>/{item}</span>;
}
return (
<span key={`${index}-${item}`}>
/
<a
href="#"
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(breadcrumb)
);
temp_breadcrumb = temp_breadcrumb.slice(
0,
index + 1
);
setBreadcrumb(temp_breadcrumb);
}}
>
{item}
</a>
</span>
);
} else {
if (index === breadcrumb.length - 1) {
return <span key={`${index}-${item}`}>{item}</span>;
}
return (
<a
href="#"
key={`${index}-${item}`}
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(breadcrumb)
);
temp_breadcrumb = temp_breadcrumb.slice(0, index + 1);
setBreadcrumb(temp_breadcrumb);
}}
>
{item}
</a>
);
}
})}
</span>
</div>
<Table
className={`m-t-xs`}
dataSource={source}
columns={[
{
title: '名称',
render: (item, rowData, rowIndex, i) => {
return {
props: { rowSpan: 1, colSpan: 1 },
children: (
<div key={`${rowIndex}-${rowData.name}`}>
{rowData.id ? (
isRichtext ? (
<Button
level="link"
size="sm"
className={`m-l-xs`}
onClick={() => {
const path = getPath(breadcrumb)
const filePath = path ? `${path}/${rowData.name}` : `${rowData.name}`
const src = path ? `/api/storage/v1/object/public/mooc/${path}/${rowData.name}` : `/api/storage/v1/object/public/mooc/${rowData.name}`
const type =
rowData.metadata.mimetype.split('/')[0];
console.log(src)
switch (type) {
case 'image':
onConfirm(`<img src="${src}" />`);
break;
case 'video':
onConfirm(
`<video controls src="${src}" />`
);
break;
default:
fetcher({
method: 'post',
url: `/storage/v1/object/sign/${bucket}`,
data: {
expiresIn: 60000,
paths: [filePath],
},
headers: {
post2rest: false,
},
})
.then(({ data }) => {
if (data && data.length > 0) {
const presignedurl = `${location.origin}/api/storage/v1${data[0].signedURL}&download=${filePath}`;
const href = `${
location.origin
}/preview/onlinePreview?url=${encodeURIComponent(
window.btoa(unescape(encodeURIComponent(presignedurl)))
)}`;
onConfirm(`<a href=${href}>${rowData.name}</a>`);
} else {
this.props.env.notify(
'error',
'选取文件失败'
);
}
})
.catch((err) => console.log(err));
}
}}
>
{rowData.name}
</Button>
) : (
<span>{rowData.name}</span>
)
) : (
<Button
level="link"
size="sm"
onClick={() => {
let temp_breadcrumb = JSON.parse(
JSON.stringify(breadcrumb)
);
temp_breadcrumb.push(rowData.name);
setBreadcrumb(temp_breadcrumb);
}}
>
{rowData.name}
</Button>
)}
</div>
),
};
},
},
{ key: 'mimetype', title: '类型' },
{ key: 'size', title: '大小' },
{ key: 'updated_at', title: '修改日期' },
]}
rowClassName={(item) => !item.id && 'font-bold text-primary'}
/>
</div>
<div className="cxd-Modal-footer">
<Button onClick={onCancel}></Button>
{!isRichtext && (
<Button level="primary" onClick={onConfirm}>
</Button>
)}
</div>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,78 @@
import { Button, Renderer } from 'amis';
import { RendererProps } from 'amis/lib/factory';
import React from 'react';
@Renderer({
test: /(^|\/)download-report$/,
name: 'download-report',
})
export class DownloadReport extends React.Component<RendererProps> {
constructor(props: RendererProps) {
super(props);
}
render() {
const { data, env, way } = this.props;
let selectedU2cs = [];
let selectedU2ps = [];
let selectedSchedules = [];
if (way === 'stu') {
data.items.forEach((item) => {
if (item.user2courses.length)
selectedU2cs = selectedU2cs.concat(
item.user2courses.map((item) => item.id)
);
});
} else if (way === 'report') {
data.items.forEach((item) => {
if (
item.reports.length &&
(item.reports[0].status === 'C' || item.reports[0].status === 'Y')
)
selectedU2ps.push(item);
});
} else if (way === 'schedule') {
selectedSchedules = data.items.map((item) => item.id);
}
return (
<Button
level="primary"
onClick={() => {
if (data.items.length) {
if (selectedU2cs.length) {
window.open(
`/api/report-api/reports/zip/${selectedU2cs}/personal`
);
} else if (selectedU2ps.length) {
const json = JSON.stringify([
{
name:
selectedU2ps[0].user_code + '_' + selectedU2ps[0].user_name,
report: selectedU2ps.map((item) => ({
id: item.id.toString(),
name: item.courses.name + '_' + item.projects.name,
})),
},
]);
window.open(`/api/report-api/reports/zip?json=${json}`);
} else if (selectedSchedules.length) {
window.open(
`/api/report-api/reports/zip/${selectedSchedules}/schedule`
);
} else {
env.notify('error', '没有可以导出的报告!');
}
} else {
if (way === 'stu') env.notify('error', '请选择学生!');
else if (way === 'report' || way === 'schedule')
env.notify('error', '请选择场次!');
}
}}
>
{this.props.label}
</Button>
);
}
}

View File

@@ -0,0 +1,47 @@
import { Button, Renderer, RendererProps, getVariable } from 'amis';
import React from 'react';
@Renderer({
test: /(^|\/)download-report-schedule$/,
name: 'download-report-schedule',
})
export class DownloadReportSchedule extends React.Component<RendererProps> {
constructor(props: RendererProps) {
super(props);
}
async fetchIDs() {
const { data } = this.props;
const semester_id = getVariable(data, 'semesterSelect');
const id = getVariable(data, 'id');
const remote_u2p = await this.props.env.fetcher(
`rest/user2projects?schedule_id=eq.${id}&semester_id=eq.${semester_id}`
);
return {
u2p: remote_u2p.data.items,
};
}
render() {
return (
<Button
level="primary"
size="xs"
onClick={async () => {
const { u2p } = await this.fetchIDs();
if (u2p.length) {
window.open(
`/api/report-api/report_paper/zip/${u2p
.map((item) => item.id)
.toString()}`
);
}
}}
>
{this.props.label}
</Button>
);
}
}

269
src/renderer/ExamImport.tsx Normal file
View File

@@ -0,0 +1,269 @@
import { FormControlProps, FormItem, getVariable } from 'amis';
import mammoth from 'mammoth/mammoth.browser';
import React from 'react';
@FormItem({
test: /(^|\/)examimport$/,
name: 'examimport',
})
export class ExamImportRenderer extends React.Component<
FormControlProps,
{
exam: Array<any>;
file: any;
}
> {
constructor(props: FormControlProps) {
super(props);
this.state = {
exam: [],
file: '',
};
}
// word 文件解析方法
parseWord(event: any, organization_id: any, category: any, onChange: any) {
const reader = new FileReader();
reader.onload = (e: any) => {
const arrayBuffer = e.target.result;
const options = {
convertImage: mammoth.images.imgElement((image: any) => {
return image.read().then((res: any) => {
let file = new Blob([res], {
type: image.contentType,
});
let formData = new FormData();
formData.set('file', file);
return this.props.env
.fetcher({
url: 'storage/v1/object/exam',
method: 'post',
dataType: 'form-data',
data: formData,
})
.then((res) => {
return {
src: res.data.data.value,
};
});
});
}),
ignoreEmptyParagraphs: false,
};
mammoth
.convertToHtml({ arrayBuffer: arrayBuffer }, options)
.then((resultObject: any) => {
let temp: any[] = [];
let local: any[] = [];
// 清除粗体、软回车,按空行或题号分割题目,并做 forEach 循环
resultObject.value
.replace(/<strong>|<\/strong>/g, '')
.replace(/<br \/><\/p>/g, '</p>')
.replace(/<br \/>/g, '</p><p>')
.split(/<p>\s*<\/p>|(?<!^|<p>\s*<\/p>)<p>\d+/g)
.forEach((item: any, q_index: any) => {
if (item) {
// 清除开头的 . 或 、
if (!item.match(/^<p>/g)) {
item = item.replace(/^(\.|、)/g, '<p>');
}
// 建立当前题目的分割序列
let split_series: any[] = [];
if (item.search(/<p>答案:|<p>答案:/g) > 0) {
split_series.push({
pattern: /(<p>答案:|<p>答案:)/g,
num: item.search(/<p>答案:|<p>答案:/g),
dict: 'answer',
});
}
if (item.search(/<p>答案解析:|<p>答案解析:/g) > 0) {
split_series.push({
pattern: /(<p>答案解析:|<p>答案解析:)/g,
num: item.search(/<p>答案解析:|<p>答案解析:/g),
dict: 'analysis',
});
}
if (item.search(/<p>难易程度:|<p>难易程度:/g) > 0) {
split_series.push({
pattern: /(<p>难易程度:|<p>难易程度:)/g,
num: item.search(/<p>难易程度:|<p>难易程度:/g),
dict: 'difficulty',
});
}
if (item.search(/<p>题型:|<p>题型:/g) > 0) {
split_series.push({
pattern: /(<p>题型:|<p>题型:)/g,
num: item.search(/<p>题型:|<p>题型:/g),
dict: 'type',
});
}
// 按分割序列各项在题目中的位置排序,从前到后
split_series = split_series.sort((a, b) => a.num - b.num);
let exam = item;
let data_item: any = {};
let last_split_item: any = {};
// 按分割序列逐项分割当前题目
split_series.forEach((split_item, index) => {
let result = exam.split(split_item.pattern);
// 如果是第一项,必定是题干和选项(如有),其他项则为其他信息
if (!index) {
// 提取选项
let options = result[0].match(
/<p>[A-Za-z](\.|、).+?<\/p>(?=<p>[A-Za-z](\.|、)|$)/g
);
// 如果有选项则继续分割题干和选项,否则即为题干
if (options) {
let title = result[0]
.replace(/\n/g, '')
.match(/^.+?(?=<p>[A-Za-z](\.|、))/g)[0]
.replace(
/(<p>\d+(\.|、)\{<\/p>)|(?<=<p>)\d+(\.|、)|(<p>}<\/p>)/g,
''
)
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
options = options.map((item: any) => {
return {
key: item.match(/(?<=<p>)[A-Za-z]/g)[0],
label: '<p>'
.concat(
item
.match(/(?<=\.|、).+<\/p>/g)[0]
.replace(/(<p>{<\/p>)|(<p>}<\/p>)/g, '')
)
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, ''),
is_fixed: false,
};
});
// 判断是否有相同的选项
let key_set = new Set();
let key_count = 0;
options.forEach((item: any) => {
key_set.add(item.key);
key_count++;
});
if (key_set.size !== key_count) local.push(q_index + 1);
data_item.options = options;
data_item.title = title;
} else {
let title = result[0]
.replace(
/(<p>\d+(\.|、)\{<\/p>)|(?<=<p>)\d+(\.|、)|(<p>}<\/p>)/g,
''
)
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
data_item.title = title;
}
} else {
// 清除 { 、} 和 分割项
data_item[last_split_item.dict] = result[0]
.replace(/(<p>{<\/p>)|(<p>}<\/p>)/g, '')
.replace(last_split_item.pattern, '<p>')
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
// 如果是答案、题型、难易程度,则清除 DOM 标签,并清除空格
if (
last_split_item.dict === 'answer' ||
last_split_item.dict === 'type' ||
last_split_item.dict === 'difficulty'
) {
data_item[last_split_item.dict] = data_item[
last_split_item.dict
]
.replace(/<p>|<\/p>/g, '')
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
}
}
// 最后一项
if (index + 1 === split_series.length) {
data_item[split_item.dict] = result[1]
.concat(result[2])
.replace(/(<p>{<\/p>)|(<p>}<\/p>)/g, '')
.replace(split_item.pattern, '<p>')
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
// 如果是答案、题型、难易程度,则清除 DOM 标签,并清除空格
if (
split_item.dict === 'answer' ||
split_item.dict === 'type' ||
split_item.dict === 'difficulty'
) {
data_item[split_item.dict] = data_item[split_item.dict]
.replace(/<p>|<\/p>/g, '')
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
}
}
// 重组分割项和剩余项
exam = result[1].concat(result[2]);
// 保留上一次分割的分割项
last_split_item = split_item;
});
if (data_item.type === '判断题') {
data_item.options = [
{ key: '正确', label: '正确', is_fixed: false },
{ key: '错误', label: '错误', is_fixed: false },
];
}
data_item.organization_id = organization_id;
data_item.category = category;
temp.push(data_item);
}
});
this.setState({ exam: temp, file: '' });
if (!local.length) {
onChange(this.state.exam);
} else {
this.props.env.notify(
'error',
`${local.toString()} 题中有重复选项,请修改后重新上传`
);
}
});
};
if (event.target.files.length !== 0) {
reader.readAsArrayBuffer(event.target.files[0]);
}
}
updateValue(event: any) {
this.setState({ file: event.target.value });
}
render() {
const { data, onChange } = this.props;
const { exam } = this.state;
const organization_id = getVariable(data, 'centre_id');
const category = getVariable(data, 'tree');
return (
<form action="">
<input
type="file"
accept=".docx"
value={this.state.file}
onChange={(event) => {
this.parseWord(event, organization_id, category, onChange);
this.updateValue(event);
}}
/>
</form>
);
}
}

View File

@@ -0,0 +1,298 @@
import React from 'react';
import { FormItem, FormControlProps, getVariable, isEffectiveApi, Payload, NumberInput } from 'amis';
import cx from 'classnames';
import { isNumber } from 'lodash';
@FormItem({
test: /(^|\/)custom\-exampaperrule$/,
name: 'custom-exampaperrule',
})
export class ExamPaperRuleRenderer extends React.Component<
FormControlProps,
{ scopeData: Array<any>; scopes: Array<any>; questionTypes: Array<any> }
> {
constructor(props: FormControlProps) {
super(props);
this.state = {
scopeData: [],
scopes: [],
questionTypes: [],
};
}
componentDidMount() {
const { env, api, scopeApi, onChange } = this.props;
if (isEffectiveApi(api)) {
env.fetcher(api).then((payload: Payload) => {
let items = payload.data.items.map((item: any) => {
return {
type: item.value,
name: item.label,
number: 0,
};
});
this.setState({
questionTypes: items,
});
});
}
if (isEffectiveApi(scopeApi)) {
env.fetcher(scopeApi).then((payload: Payload) => {
this.setState({
scopeData: payload.data.items,
});
});
}
}
render() {
const { data, onChange, className } = this.props;
const { scopeData, questionTypes } = this.state;
let inputScope = getVariable(data, 'scope');
let range = getVariable(data, 'rules.range');
if (range && range.length > 0) {
range = range.filter((x: any) => x !== null && x !== undefined);
}
if (inputScope && inputScope.length) {
inputScope = inputScope.filter(
(x: any) => x !== null && x !== undefined
);
}
let scopes: Array<any> = [];
if (scopeData.length > 0) {
if (range && range.length > 0) {
// 未设置题目数量
if (isNumber(range[0])) {
range = range.map((x: any, index: number) => {
let category = scopeData.find(
(s) => s.id.toString() === x.toString()
);
return {
id: x,
name: category?.name,
orderNumber: index + 1,
category_questions: category?.exam_questions,
questions: questionTypes.map(
(x: any, qindex: number) => {
return {
name: x.name,
type: x.type,
number: 0,
};
}
),
};
});
}
scopes = range.map((x: any) => {
let category = scopeData.find(
(s) => s.id.toString() === x?.id.toString()
);
return {
...x,
category_questions: category?.exam_questions,
};
});
} else {
scopes = inputScope
? inputScope.map((id: any, index: number) => {
let q = range?.find((x: any) => x.id === id);
let category = scopeData.find(
(x) => x.id.toString() === id.toString()
);
return {
id: id,
name: category?.name,
orderNumber: index + 1,
category_questions: category?.exam_questions,
questions: questionTypes.map(
(x: any, qindex: number) => {
let scope = this.state.scopes.find(
(x: any) => x.id === id
);
return {
name: x.name,
type: x.type,
number: q
? q.questions[qindex].number
: scope
? scope.questions[qindex].number
: 0,
};
}
),
};
})
: [];
}
}
return (
<div className={``}>
<h2>
{scopes.reduce(
(acc, cur) =>
acc +
cur.questions.reduce(
(acc: any, cur: any) => acc + cur.number,
0
),
0
)}
{scopes.reduce(
(acc, cur) =>
acc +
cur.questions
.filter((x: any) => isNumber(x.score))
.reduce(
(acc: any, cur: any) =>
acc + cur.number * cur.score,
0
),
0
)}
</h2>
{scopes.map((scope: any, scopeIndex: number) => (
<div className="cxd-Form-item cxd-Form-item-normal mt-2">
<label className="cxd-Form-label cxd-Form-item">
<span>{`${scope.name}`}</span>
</label>
<div
className={cx(
`cxd-NumberControl cxd-Form-control w-full`,
className
)}
>
{questionTypes.map((item, index) => {
let type_number = scope.category_questions
? scope.category_questions.filter(
(x: any) => x.type === item.type
).length
: 0;
let question_number = scope.questions[index]
? scope.questions[index].number
: 0;
if (
type_number === 0 &&
question_number === 0
) {
return <></>;
}
return (
<div className="inline mt-2">
<label className="m-l cxd-Form-label cxd-Form-itemColumn--2">
<span>{`${item.name}`}</span>
</label>
({type_number}), &nbsp;
<NumberInput
className="w-xs is-inline"
value={question_number}
step={1}
max={type_number}
min={0}
onChange={(value: number) => {
scopes.find(
(x: any) =>
x.id === scope.id
).questions[index].number =
value;
this.setState({
scopes: scopes,
questionTypes,
});
onChange(
scopes.map(
(
x: any,
index: number
) => {
return {
id: x.id,
name: x.name,
orderNumber:
index + 1,
questions:
x.questions,
};
}
)
);
}}
/>
{type_number > 0 ? (
<>
,&nbsp;&nbsp;
<NumberInput
className="w-xs is-inline"
value={
scope.questions[index]
? scope.questions[
index
].score
: 0
}
step={1}
max={
type_number === 0
? 0
: 100
}
min={0}
onChange={(
value: number
) => {
scopes.find(
(x: any) =>
x.id ===
scope.id
).questions[
index
].score = value;
this.setState({
scopes: scopes,
questionTypes,
});
onChange(
scopes.map(
(
x: any,
index: number
) => {
return {
id: x.id,
name: x.name,
orderNumber:
index +
1,
questions:
x.questions,
};
}
)
);
}}
/>
</>
) : (
<></>
)}
<br />
</div>
);
})}
</div>
</div>
))}
</div>
);
}
}

Some files were not shown because too many files have changed in this diff Show More