Selaa lähdekoodia

feat(admin): 长难句列表页

Go 6 vuotta sitten
vanhempi
commit
34e7f08bdf

+ 3 - 1
front/project/admin/routes/subject/index.js

@@ -5,5 +5,7 @@ import textbook from './textbook';
 import sentence from './sentence';
 import preview from './preview';
 import previewDetail from './previewDetail';
+import sentenceArticle from './sentenceArticle';
+import sentenceQuestion from './sentenceQuestion';
 
-export default [question, exercise, examination, textbook, sentence, preview, previewDetail];
+export default [question, exercise, examination, textbook, sentence, preview, previewDetail, sentenceArticle, sentenceQuestion];

+ 11 - 1
front/project/admin/routes/subject/preview/page.js

@@ -9,7 +9,7 @@ import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
 import { PreviewStatus } from '@src/services/Constant';
 import { getMap, formatDate } from '@src/services/Tools';
-import { asyncSMessage } from '@src/services/AsyncTools';
+import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
 import { Exercise } from '../../../stores/exercise';
 import { Preview } from '../../../stores/preview';
 import { System } from '../../../stores/system';
@@ -137,6 +137,16 @@ export default class extends Page {
     });
   }
 
+  deleteAction() {
+    const { selectedKeys } = this.state;
+    asyncDelConfirm('删除确认', '是否删除选中作业?', () => {
+      return Promise.all(selectedKeys.map(row => Preview.delStruct({ id: row })));
+    }).then(() => {
+      asyncSMessage('删除成功!');
+      this.refresh();
+    });
+  }
+
   renderView() {
     return <Block flex>
       <FilterLayout

+ 2 - 3
front/project/admin/routes/subject/previewDetail/page.js

@@ -9,7 +9,7 @@ import { formatFormError, generateSearch } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import { Preview } from '../../../stores/preview';
 import { Exercise } from '../../../stores/exercise';
-import { Users } from '../../../stores/users';
+import { User } from '../../../stores/user';
 import config from './index';
 
 export default class extends Page {
@@ -21,7 +21,6 @@ export default class extends Page {
   }
 
   initData() {
-    console.log(Users);
     const { id } = this.params;
     const { form } = this.props;
     let handler;
@@ -36,7 +35,7 @@ export default class extends Page {
       .then(result => {
         form.setFieldsValue(result);
         generateSearch('userIds', { mode: 'multiple' }, this, (search) => {
-          return Users.list(search);
+          return User.list(search);
         }, (row) => {
           return {
             title: `${row.nickname}(${row.mobile})`,

+ 5 - 1
front/project/admin/routes/subject/sentence/index.less

@@ -1,3 +1,7 @@
 @charset "utf-8";
 
-#subject-sentence {}
+#subject-sentence {}
+
+.block.ant-radio-group {
+  display: block;
+}

+ 215 - 1
front/project/admin/routes/subject/sentence/page.js

@@ -1,10 +1,224 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
+import { Button, Modal, Checkbox } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
+import EditTableCell from '@src/components/EditTableCell';
+import FilterLayout from '@src/layouts/FilterLayout';
+import ActionLayout from '@src/layouts/ActionLayout';
+import TableLayout from '@src/layouts/TableLayout';
+// import { PreviewStatus } from '@src/services/Constant';
+import { getMap } from '@src/services/Tools';
+import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
+import { Sentence } from '../../../stores/sentence';
 
+const filterForm = [
+  {
+    key: 'chapter',
+    type: 'select',
+    allowClear: true,
+    name: 'chapter',
+    placeholder: '请选择',
+    number: true,
+  },
+  // {
+  //   key: 'part',
+  //   type: 'select',
+  //   allowClear: true,
+  //   name: '状态',
+  //   number: true,
+  //   placeholder: '请选择',
+  // },
+];
 export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.structMap = {};
+    this.actionList = [{
+      key: 'struct',
+      name: '编辑章节',
+    }, {
+      key: 'article',
+      name: '新建文章',
+      render: (item) => {
+        return <Link to='/subject/sentence/article'><Button>{item.name}</Button></Link>;
+      },
+    }, {
+      key: 'question',
+      name: '新建题目',
+      render: (item) => {
+        return <Link to='/subject/sentence/question'><Button>{item.name}</Button></Link>;
+      },
+    }, {
+      key: 'delete',
+      name: '批量删除',
+      needSelect: 1,
+    }];
+    this.columns = [{
+      title: 'chapter',
+      dataIndex: 'chapter',
+    }, {
+      title: 'part',
+      dataIndex: 'part',
+    }, {
+      title: 'title',
+      dataIndex: 'title',
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        const item = this.structMap[this.state.search.chapter];
+        return <div className="table-button">
+          {item.exercise && (<Link to={`/sentence/question/${record.id}`}>编辑</Link>)}
+          {!item.exercise && (<Link to={`/sentence/article/${record.id}`}>编辑</Link>)}
+        </div>;
+      },
+    }];
+    this.structColumns = [{
+      title: 'chapter',
+      dataIndex: 'chapter',
+      render: (text, record, index) => {
+        return index + 1;
+      },
+    }, {
+      title: '短名称',
+      dataIndex: 'short',
+      render: (text, record, index) => {
+        return <EditTableCell value={text} onChange={(v) => {
+          this.changeStruct(index, 'short', v);
+        }} />;
+      },
+    }, {
+      title: '长名称',
+      dataIndex: 'title',
+      render: (text, record, index) => {
+        return <EditTableCell value={text} onChange={(v) => {
+          this.changeStruct(index, 'title', v);
+        }} />;
+      },
+    }, {
+      title: '练习章',
+      dataIndex: 'exercise',
+      render: (text, record, index) => {
+        return <Checkbox onChange={(e) => {
+          if (e.target.checked) this.changeStruct(index, 'exercise', 1, 0);
+        }} checked={!!text} />;
+      },
+    }];
+  }
+
+  init() {
+    Sentence.getStruct().then(result => {
+      return this.refreshStruct(result);
+    });
+  }
+
+  initData() {
+    const item = this.structMap[this.state.search.chapter];
+    if (!item) return;
+    if (item.exercise) {
+      Sentence.listQuestion(this.state.search).then(result => {
+        this.setTableData(result.list, result.total);
+      });
+    } else {
+      Sentence.listArticle(this.state.search).then(result => {
+        this.setTableData(result.list, result.total);
+      });
+    }
+  }
+
+  refreshStruct(result) {
+    result = result || {};
+    result.chapters = result.chapters || [];
+    filterForm[0].select = result.chapters.map((row, index) => { row.value = index + 1; return row; });
+    this.structMap = getMap(filterForm[0].select, 'value');
+    this.setState({ struct: result });
+  }
+
+  structAction() {
+    const { struct = {} } = this.state;
+    this.open(struct);
+  }
+
+  changeStruct(index, field, value, other) {
+    const { detail } = this.state;
+    if (other !== undefined) {
+      detail.chapters.forEach((row) => {
+        row[field] = other;
+      });
+    }
+    detail.chapters[index][field] = value;
+    this.setState({ detail });
+  }
+
+  submitStruct() {
+    const { detail } = this.state;
+    Sentence.setStruct(detail).then(() => {
+      asyncSMessage('保存成功');
+      this.close(false, 'detail');
+      return this.refreshStruct(detail);
+    }).then(() => {
+      return this.initData();
+    });
+  }
+
+  deleteAction() {
+    const { selectedKeys } = this.state;
+
+    const item = this.structMap[this.state.search.chapter];
+    if (!item) return;
+    asyncDelConfirm('删除确认', '是否删除选中?', () => {
+      if (item.exercise) {
+        return Promise.all(selectedKeys.map(row => Sentence.delQuestion({ id: row })));
+      }
+      return Promise.all(selectedKeys.map(row => Sentence.delArticle({ id: row })));
+    }).then(() => {
+      asyncSMessage('删除成功!');
+      this.refresh();
+    });
+  }
+
   renderView() {
-    return <Block flex />;
+    return <Block flex>
+      <FilterLayout
+        show
+        itemList={filterForm}
+        data={this.state.search}
+        onChange={data => {
+          this.search(data);
+        }} />
+      <ActionLayout
+        itemList={this.actionList}
+        selectedKeys={this.state.selectedKeys}
+        onAction={key => this.onAction(key)}
+      />
+      <TableLayout
+        select
+        columns={this.columns}
+        list={this.state.list}
+        pagination={this.state.page}
+        loading={this.props.core.loading}
+        onChange={(pagination, filters, sorter) => this.tableChange(pagination, filters, sorter)}
+        onSelect={(keys, rows) => this.tableSelect(keys, rows)}
+        selectedKeys={this.state.selectedKeys}
+      />
+      {this.state.detail && <Modal visible closable title='编辑章节' onCancel={() => {
+        this.close(false, 'detail');
+      }} onOk={() => {
+        this.submitStruct();
+      }}>
+        <TableLayout
+          rowKey={'title'}
+          columns={this.structColumns}
+          list={this.state.detail.chapters}
+          pagination={false}
+        />
+        <Button type='link' onClick={() => {
+          const { detail } = this.state;
+          detail.chapters.push({});
+          this.setState({ detail });
+        }}>增加chapter</Button></Modal>}
+    </Block>;
   }
 }

+ 15 - 0
front/project/admin/routes/subject/sentenceArticle/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/subject/sentence/article/:id?',
+  key: 'subject-sentence-article',
+  title: '新建长难句文章',
+  needLogin: true,
+  module,
+  group,
+  showKey: 'subject-sentence',
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/admin/routes/subject/sentenceArticle/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#subject-sentence-article {}

+ 158 - 0
front/project/admin/routes/subject/sentenceArticle/page.js

@@ -0,0 +1,158 @@
+import React from 'react';
+import { Form, Input, Button, Row, Col, DatePicker, List, Icon } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import Select from '@src/components/Select';
+// import FileUpload from '@src/components/FileUpload';
+import { formatFormError, generateSearch } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { Preview } from '../../../stores/preview';
+import { Exercise } from '../../../stores/exercise';
+import { User } from '../../../stores/user';
+import config from './index';
+
+export default class extends Page {
+  init() {
+    Exercise.allStruct().then(result => {
+      result = result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; });
+      this.setState({ exercise: result });
+    });
+  }
+
+  initData() {
+    const { id } = this.params;
+    const { form } = this.props;
+    let handler;
+    if (id) {
+      config.title = '编辑预习作业';
+      handler = Preview.get({ id });
+    } else {
+      config.title = '添加预习作业';
+      handler = Promise.resolve({});
+    }
+    handler
+      .then(result => {
+        form.setFieldsValue(result);
+        generateSearch('userIds', { mode: 'multiple' }, this, (search) => {
+          return User.list(search);
+        }, (row) => {
+          return {
+            title: `${row.nickname}(${row.mobile})`,
+            value: row.id,
+          };
+        }, result.userIds || [], null);
+      });
+  }
+
+  submit() {
+    const { form } = this.props;
+    form.validateFields((err) => {
+      if (!err) {
+        const data = form.getFieldsValue();
+        let handler;
+        if (data.id) {
+          handler = Preview.add(data);
+        } else {
+          handler = Preview.edit(data);
+        }
+        handler.then(() => {
+          asyncSMessage('保存成功');
+        }).catch((e) => {
+          if (e.result) form.setFields(formatFormError(data, e.result));
+        });
+      }
+    });
+  }
+
+  searchQuestion(values) {
+    console.log(values);
+  }
+
+  renderBase() {
+    const { getFieldDecorator } = this.props.form;
+    return <Block>
+      <Form>
+        {getFieldDecorator('id')(<input hidden />)}
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择课程'>
+          {getFieldDecorator('category', {
+            rules: [
+              { required: true, message: '请选择课程' },
+            ],
+          })(
+            <Select select={this.state.exercise} placeholder='请选择课程' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='起止时间'>
+          {getFieldDecorator('time', {
+            rules: [
+              { required: true, message: '请输入起止时间' },
+            ],
+          })(
+            <DatePicker.RangePicker />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作业标题'>
+          {getFieldDecorator('title', {
+            rules: [
+              { required: true, message: '请输入作业标题' },
+            ],
+          })(
+            <Input placeholder='请输入作业标题' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='指定做题人'>
+          {getFieldDecorator('userIds', {
+            rules: [
+              { required: true, message: '请指定做题人' },
+            ],
+          })(
+            <Select {...this.state.userIds} placeholder='请指定做题人' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择作业题'>
+          {getFieldDecorator('questionIds', {
+            rules: [
+              { required: true, message: '请选择作业题' },
+            ],
+          })(
+            <Select select={[]} mode='tags' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} onSelect={(values) => {
+              this.searchQuestion(values);
+            }} />,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  renderQuestionList() {
+    return <List
+      header={<h1>题目预览</h1>}
+      loading={this.props.core.loading}
+      itemLayout="horizontal"
+      dataSource={this.state.questionList || [{}]}
+      renderItem={item => (
+        <List.Item actions={[<Button type='link' onClick={() => {
+          console.log(item);
+        }}><Icon type='delete' /></Button>, <a>more</a>]}>
+          123123
+        </List.Item>
+      )}
+    />;
+  }
+
+  renderView() {
+    return <Block flex>
+      {this.renderBase()}
+      {this.renderQuestionList()}
+
+      <Row type="flex" justify="center">
+        <Col>
+          <Button type="primary" onClick={() => {
+            this.submit();
+          }}>保存</Button>
+        </Col>
+      </Row>
+    </Block>;
+  }
+}

+ 15 - 0
front/project/admin/routes/subject/sentenceQuestion/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/subject/sentence/question/:id?',
+  key: 'subject-sentence-question',
+  title: '新建长难句题目',
+  needLogin: true,
+  module,
+  group,
+  showKey: 'subject-sentence',
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/admin/routes/subject/sentenceQuestion/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#subject-sentence-question {}

+ 158 - 0
front/project/admin/routes/subject/sentenceQuestion/page.js

@@ -0,0 +1,158 @@
+import React from 'react';
+import { Form, Input, Button, Row, Col, DatePicker, List, Icon } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import Select from '@src/components/Select';
+// import FileUpload from '@src/components/FileUpload';
+import { formatFormError, generateSearch } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { Preview } from '../../../stores/preview';
+import { Exercise } from '../../../stores/exercise';
+import { User } from '../../../stores/user';
+import config from './index';
+
+export default class extends Page {
+  init() {
+    Exercise.allStruct().then(result => {
+      result = result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; });
+      this.setState({ exercise: result });
+    });
+  }
+
+  initData() {
+    const { id } = this.params;
+    const { form } = this.props;
+    let handler;
+    if (id) {
+      config.title = '编辑预习作业';
+      handler = Preview.get({ id });
+    } else {
+      config.title = '添加预习作业';
+      handler = Promise.resolve({});
+    }
+    handler
+      .then(result => {
+        form.setFieldsValue(result);
+        generateSearch('userIds', { mode: 'multiple' }, this, (search) => {
+          return User.list(search);
+        }, (row) => {
+          return {
+            title: `${row.nickname}(${row.mobile})`,
+            value: row.id,
+          };
+        }, result.userIds || [], null);
+      });
+  }
+
+  submit() {
+    const { form } = this.props;
+    form.validateFields((err) => {
+      if (!err) {
+        const data = form.getFieldsValue();
+        let handler;
+        if (data.id) {
+          handler = Preview.add(data);
+        } else {
+          handler = Preview.edit(data);
+        }
+        handler.then(() => {
+          asyncSMessage('保存成功');
+        }).catch((e) => {
+          if (e.result) form.setFields(formatFormError(data, e.result));
+        });
+      }
+    });
+  }
+
+  searchQuestion(values) {
+    console.log(values);
+  }
+
+  renderBase() {
+    const { getFieldDecorator } = this.props.form;
+    return <Block>
+      <Form>
+        {getFieldDecorator('id')(<input hidden />)}
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择课程'>
+          {getFieldDecorator('category', {
+            rules: [
+              { required: true, message: '请选择课程' },
+            ],
+          })(
+            <Select select={this.state.exercise} placeholder='请选择课程' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='起止时间'>
+          {getFieldDecorator('time', {
+            rules: [
+              { required: true, message: '请输入起止时间' },
+            ],
+          })(
+            <DatePicker.RangePicker />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作业标题'>
+          {getFieldDecorator('title', {
+            rules: [
+              { required: true, message: '请输入作业标题' },
+            ],
+          })(
+            <Input placeholder='请输入作业标题' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='指定做题人'>
+          {getFieldDecorator('userIds', {
+            rules: [
+              { required: true, message: '请指定做题人' },
+            ],
+          })(
+            <Select {...this.state.userIds} placeholder='请指定做题人' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择作业题'>
+          {getFieldDecorator('questionIds', {
+            rules: [
+              { required: true, message: '请选择作业题' },
+            ],
+          })(
+            <Select select={[]} mode='tags' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} onSelect={(values) => {
+              this.searchQuestion(values);
+            }} />,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  renderQuestionList() {
+    return <List
+      header={<h1>题目预览</h1>}
+      loading={this.props.core.loading}
+      itemLayout="horizontal"
+      dataSource={this.state.questionList || [{}]}
+      renderItem={item => (
+        <List.Item actions={[<Button type='link' onClick={() => {
+          console.log(item);
+        }}><Icon type='delete' /></Button>, <a>more</a>]}>
+          123123
+        </List.Item>
+      )}
+    />;
+  }
+
+  renderView() {
+    return <Block flex>
+      {this.renderBase()}
+      {this.renderQuestionList()}
+
+      <Row type="flex" justify="center">
+        <Col>
+          <Button type="primary" onClick={() => {
+            this.submit();
+          }}>保存</Button>
+        </Col>
+      </Row>
+    </Block>;
+  }
+}

+ 3 - 2
front/project/admin/stores/index.js

@@ -2,6 +2,7 @@ import { System } from './system';
 import { Examination } from './examination';
 import { Exercise } from './exercise';
 import { Preview } from './preview';
-import { Users } from './users';
+import { User } from './user';
+import { Sentence } from './sentence';
 
-export default [System, Examination, Exercise, Preview, Users];
+export default [System, Examination, Exercise, Preview, User, Sentence];

+ 45 - 0
front/project/admin/stores/sentence.js

@@ -0,0 +1,45 @@
+import BaseStore from '@src/stores/base';
+
+export default class SentenceStore extends BaseStore {
+  getStruct() {
+    return this.apiGet('/setting/sentence');
+  }
+
+  setStruct(params) {
+    return this.apiPut('/setting/sentence', params);
+  }
+
+  listArticle(params) {
+    return this.apiGet('/sentence/article/list', params);
+  }
+
+  addArticle(params) {
+    return this.apiPost('/sentence/article/add', params);
+  }
+
+  editArticle(params) {
+    return this.apiPut('/sentence/article/edit', params);
+  }
+
+  delArticle(params) {
+    return this.apiDel('/sentence/article/delete', params);
+  }
+
+  listQuestion(params) {
+    return this.apiGet('/sentence/question/list', params);
+  }
+
+  addQuestion(params) {
+    return this.apiPost('/sentence/question/add', params);
+  }
+
+  editQuestion(params) {
+    return this.apiPut('/sentence/question/edit', params);
+  }
+
+  delQuestion(params) {
+    return this.apiDel('/sentence/question/delete', params);
+  }
+}
+
+export const Sentence = new SentenceStore({ key: 'sentence' });

+ 2 - 2
front/project/admin/stores/users.js

@@ -1,6 +1,6 @@
 import BaseStore from '@src/stores/base';
 
-export default class UsersStore extends BaseStore {
+export default class UserStore extends BaseStore {
   list(params) {
     return this.apiGet('/user/list', params);
   }
@@ -22,4 +22,4 @@ export default class UsersStore extends BaseStore {
   }
 }
 
-export const Users = new UsersStore({ key: 'users' });
+export const User = new UserStore({ key: 'users' });

+ 13 - 0
front/src/containers/Page.js

@@ -156,4 +156,17 @@ export default class extends Component {
   renderView() {
     return <div />;
   }
+
+  open(item = {}, modal = 'detail') {
+    this.setState({
+      [modal]: item,
+    });
+  }
+
+  close(refresh, modal = 'detail') {
+    this.setState({
+      [modal]: null,
+    });
+    if (refresh) this.refresh();
+  }
 }