`渲染在页面上。
95 |
96 | * 填写完题目以后再根据``组件渲染答案。
97 |
--------------------------------------------------------------------------------
/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/assets/.DS_Store
--------------------------------------------------------------------------------
/assets/iconfonts/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/assets/iconfonts/.DS_Store
--------------------------------------------------------------------------------
/assets/iconfonts/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1528702461354'); /* IE9*/
4 | src: url('iconfont.eot?t=1528702461354#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'),
6 | url('iconfont.ttf?t=1528702461354') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1528702461354#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-daisuifang-icon-green:before { content: "\e601"; }
19 |
20 | .icon-yisuifang-icon-green:before { content: "\e602"; }
21 |
22 | .icon-zhankai:before { content: "\e604"; }
23 |
24 | .icon-xiaoxi:before { content: "\e605"; }
25 |
26 | .icon-shouqi:before { content: "\e606"; }
27 |
28 | .icon-suifangguanliicon:before { content: "\e607"; }
29 |
30 | .icon-huanzheguanliicon:before { content: "\e608"; }
31 |
32 | .icon-suifangmobanicon:before { content: "\e609"; }
33 |
34 | .icon-dengpao:before { content: "\e60a"; }
35 |
36 | .icon-chachaicon:before { content: "\e60b"; cursor: pointer; margin-left: 10px; color: #979797;}
37 |
38 | .icon-fangdajingicon:before { content: "\e60c"; }
39 |
40 | .icon-jinggaotanhaoicon:before { content: "\e60d"; }
41 |
42 | .icon-shanchuicon:before { content: "\e60e"; }
43 |
44 | .icon-fuzhiicon:before { content: "\e60f"; }
45 |
46 | .icon-tianjiaicon:before { content: "\e610"; }
47 |
48 | .icon-information:before { content: "\e612"; }
49 |
50 | .icon-baocunchenggong:before { content: "\e613"; }
51 |
52 | .icon-danxuanicon:before { content: "\e614"; }
53 |
54 | .icon-duohangicon:before { content: "\e615"; }
55 |
56 | .icon-bi:before { content: "\e616"; }
57 |
58 | .icon-baocunzhong:before { content: "\e617"; }
59 |
60 | .icon-hongselajixiang:before { content: "\e618"; }
61 |
62 | .icon-lansezantingshiyong:before { content: "\e619"; }
63 |
64 | .icon-duoxuan-icon:before { content: "\e61a"; }
65 |
66 | .icon-guaduan_icon:before { content: "\e61b"; }
67 |
68 | .icon-jinggaochacha:before { content: "\e61c"; }
69 |
70 | .icon-querenbodaicon:before { content: "\e61d"; }
71 |
72 | .icon-sanjiaoxingjinggao:before { content: "\e61e"; }
73 |
74 | .icon-shanchuwenzichacha:before { content: "\e61f"; }
75 |
76 | .icon-suifangjihuaicon:before { content: "\e620"; }
77 |
78 | .icon-rili:before { content: "\e621"; }
79 |
80 | .icon-tiankongtiicon:before { content: "\e622"; }
81 |
82 | .icon-xialaicon:before { content: "\e623"; }
83 |
84 | .icon-tianjialiebiao_icon:before { content: "\e625"; }
85 |
86 | .icon-green_guanbiyulan:before { content: "\e626"; }
87 |
88 | .icon-green_phone:before { content: "\e629"; }
89 |
90 | .icon-grey_bianji:before { content: "\e62c"; }
91 |
92 | .icon-grey_shanchu:before { content: "\e62d"; }
93 |
94 | .icon-grey_fuzhi:before { content: "\e62e"; }
95 |
96 | .icon-Q-icon:before { content: "\e62f"; color: #06AEA6;}
97 |
98 | .icon-grey_yanjing:before { content: "\e630"; }
99 |
100 | .icon-white_jinggao:before { content: "\e631"; }
101 |
102 | .icon-red_phone:before { content: "\e632"; }
103 |
104 | .icon-suifangyuqi-icon-color:before { content: "\e634"; }
105 |
106 | .icon-yisuifang-icon-color:before { content: "\e638"; }
107 |
108 | .icon-daisuifangicon-color:before { content: "\e633"; }
109 |
110 | .icon-shijianweidao-icon-color:before { content: "\e636"; }
111 |
112 | .icon-tongyongbiaotiicon:before { content: "\e611"; }
113 |
114 | .icon-xuanxiangicon:before { content: "\e63b"; color: #cdcdcd;}
115 |
116 | .icon-zhongxinfabuicon:before { content: "\e63c"; }
117 |
118 | .icon-xinjianxuanxiangicon:before { content: "\e63d"; margin: 0 10px;}
119 |
120 | .icon-jieshu:before { content: "\e600"; }
121 |
122 | .icon-danhangicon:before { content: "\e603"; }
123 |
124 |
--------------------------------------------------------------------------------
/assets/iconfonts/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/assets/iconfonts/iconfont.eot
--------------------------------------------------------------------------------
/assets/iconfonts/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
193 |
--------------------------------------------------------------------------------
/assets/iconfonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/assets/iconfonts/iconfont.ttf
--------------------------------------------------------------------------------
/assets/iconfonts/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/assets/iconfonts/iconfont.woff
--------------------------------------------------------------------------------
/assets/scale_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/assets/scale_default.png
--------------------------------------------------------------------------------
/bin/dev-server.js:
--------------------------------------------------------------------------------
1 | /*
2 | * webpack-dev-server是一个小型的静态文件服务器,为webpack打包的资源文件提供Web服务
3 | */
4 | const WebpackDevServer = require('webpack-dev-server');
5 | const config = require('../webpack/webpack.config');
6 | const webpack = require('webpack');
7 | const path = require('path');
8 | const compiler = webpack(config);
9 | const port = 9090;
10 |
11 | const server = new WebpackDevServer(compiler, {
12 | contentBase: path.resolve(__dirname, '../dist'), //默认会以根文件夹提供本地服务器,这里指定文件夹
13 | historyApiFallback: true, //在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html
14 | port: port, //如果省略,默认8080
15 | publicPath: "/",
16 | inline: true, // 自动刷新
17 | hot: true, // 开启热模块替换
18 | });
19 |
20 | console.log('> Starting dev server...')
21 | server.listen(port, 'localhost', function(err) {
22 | if (err) throw err
23 | })
--------------------------------------------------------------------------------
/dist/29933c03dca9629dd8bfef50bec5005f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/dist/29933c03dca9629dd8bfef50bec5005f.png
--------------------------------------------------------------------------------
/dist/3f8467b01369f58b0a67ac2ed08c8f3a.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/dist/3f8467b01369f58b0a67ac2ed08c8f3a.eot
--------------------------------------------------------------------------------
/dist/ab85a97f51f177206f0635614657e685.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/dist/ab85a97f51f177206f0635614657e685.ttf
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | react-questionnair
6 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | react-questionnair
6 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/libs/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/libs/.DS_Store
--------------------------------------------------------------------------------
/libs/Button/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.less';
3 |
4 | class Button extends React.PureComponent {
5 | static defaultProps = {
6 | disabled: false,
7 | type: '',
8 | size: '',
9 | }
10 |
11 | handleClick = (e) => {
12 | const {
13 | onClick,
14 | } = this.props;
15 | if (onClick) {
16 | onClick();
17 | };
18 | }
19 |
20 | render() {
21 | const {
22 | type,
23 | size,
24 | disabled,
25 | children,
26 | onClick,
27 | ...otherProps,
28 | } = this.props;
29 | const buttonType = type ? `wowjoy-button__${type}` : '';
30 | const buttonSize = size ? `wowjoy-button__${size}` : '';
31 | return (
32 |
38 | );
39 | }
40 | }
41 |
42 | export default Button;
--------------------------------------------------------------------------------
/libs/Button/index.less:
--------------------------------------------------------------------------------
1 | :global{
2 | .wowjoy-button {
3 | padding: 6px 20px;
4 | border: 1px solid #06aea6;
5 | margin: 0 10px;
6 | outline: none;
7 | cursor: pointer;
8 | font-size: 14px
9 | }
10 | .wowjoy-button__primary {
11 | background: #06aea6;
12 | color: #fff;
13 | }
14 | .wowjoy-button__normal {
15 | background: #fff;
16 | color: #06aea6;
17 | }
18 | .wowjoy-button__mini {
19 | padding: 4px;
20 | font-size: 12px;
21 | }
22 | .wowjoy-button__small {
23 | padding: 7px 9px;
24 | font-size: 12px;
25 | }
26 | .wowjoy-button__large {
27 | padding: 11px 19px;
28 | font-size: 16px;
29 | }
30 | };
--------------------------------------------------------------------------------
/libs/Checkbox/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.less';
3 |
4 | class Checkbox extends React.PureComponent {
5 | static defaultProps = {
6 | value: '',
7 | name: '',
8 | }
9 |
10 | handleChange = (e) => {
11 | const {
12 | onChange,
13 | index,
14 | } = this.props;
15 | if (onChange) {
16 | onChange(e, index);
17 | };
18 | }
19 |
20 | render() {
21 | const {
22 | defaultChecked,
23 | value,
24 | name,
25 | index,
26 | label,
27 | style,
28 | } = this.props;
29 | return (
30 |
42 | );
43 | }
44 | }
45 |
46 | export default Checkbox;
--------------------------------------------------------------------------------
/libs/Checkbox/index.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .wowjoy-checkbox {
3 | cursor: pointer;
4 | display: inline-block;
5 | }
6 | input[type='checkbox']:checked {
7 | &+.wowjoy-checkbox__inner {
8 | border-color: #06aea6;
9 | &:before {
10 | content: '\2713';
11 | color: #06aea6;
12 | position: absolute;
13 | top: 50%;
14 | left: 50%;
15 | transform: translate(-50%, -50%);
16 | }
17 | }
18 | }
19 | .wowjoy-checkbox__inner {
20 | position: relative;
21 | display: inline-block;
22 | width: 16px;
23 | height: 16px;
24 | background: #fff;
25 | border: 1px solid #DBDBDB;
26 | vertical-align: sub;
27 | margin-right: 5px;
28 | }
29 | .wowjoy-checkbox__text {
30 | display: inline-block;
31 | max-width: 80%;
32 | vertical-align: top;
33 | word-break: break-all;
34 | }
35 | }
--------------------------------------------------------------------------------
/libs/ContentEditable/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.less';
3 |
4 | const stripNbsp = str => str.replace(/ |\u202F|\u00A0/g, ' ');
5 |
6 | export default class ContentEditable extends React.Component {
7 | shouldComponentUpdate(nextProps) {
8 | let {
9 | props,
10 | htmlEl
11 | } = this;
12 | if (JSON.stringify(this.props.style) === JSON.stringify(nextProps.style)) {
13 | return false;
14 | };
15 | // We need not rerender if the change of props simply reflects the user's edits.
16 | // Rerendering in this case would make the cursor/caret jump
17 |
18 | // Rerender if there is no element yet... (somehow?)
19 | if (!htmlEl) {
20 | return true;
21 | };
22 | // ...or if html really changed... (programmatically, not by user edit)
23 | if (
24 | stripNbsp(nextProps.html) !== stripNbsp(htmlEl.innerHTML) &&
25 | nextProps.html !== props.html
26 | ) {
27 | return true;
28 | };
29 | let optional = ['style', 'className', 'disabled', 'tagName'];
30 | // Handle additional properties
31 | return optional.some(name => props[name] !== nextProps[name]);
32 | }
33 |
34 | componentDidUpdate() {
35 | if (this.htmlEl && this.props.html !== this.htmlEl.innerHTML) {
36 | // Perhaps React (whose VDOM gets outdated because we often prevent
37 | // rerendering) did not update the DOM. So we update it manually now.
38 | this.htmlEl.innerHTML = this.props.html;
39 | };
40 | }
41 |
42 | emitChange = (evt) => {
43 | if (!this.htmlEl) return;
44 | var name = evt.target.dataset.name;
45 | var html = this.htmlEl.innerHTML;
46 | if (this.props.onChange && html !== this.lastHtml) {
47 | // Clone event with Object.assign to avoid
48 | // "Cannot assign to read only property 'target' of object"
49 | var evt = Object.assign({}, evt, {
50 | target: {
51 | value: html,
52 | name: name,
53 | },
54 | });
55 | this.props.onChange(evt);
56 | }
57 | this.lastHtml = html;
58 | }
59 |
60 | render() {
61 | var {
62 | tagName,
63 | name,
64 | html,
65 | style,
66 | onKeyPress,
67 | ...otherProps,
68 | } = this.props;
69 |
70 | return (
71 | // React.createElement(
72 | // tagName || 'div',
73 | // {
74 | // ...props,
75 | // ref: (e) => this.htmlEl = e,
76 | // onInput: this.emitChange,
77 | // onBlur: this.props.onBlur || this.emitChange,
78 | // contentEditable: !this.props.disabled,
79 | // dangerouslySetInnerHTML: {__html: html}
80 | // },
81 | // this.props.children);
82 | this.htmlEl = e}
87 | onInput={this.emitChange}
88 | onKeyPress={onKeyPress}
89 | //onBlur={this.props.onBlur || this.emitChange}
90 | contentEditable={!this.props.disabled}
91 | dangerouslySetInnerHTML={{__html: html}}>
92 | {this.props.children}
93 |
94 | );
95 | }
96 | }
--------------------------------------------------------------------------------
/libs/ContentEditable/index.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .contentEditable {
3 | outline: none;
4 | line-height: 36px;
5 | }
6 | }
--------------------------------------------------------------------------------
/libs/Dialog/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from 'libs/Button';
3 | import './index.less'
4 |
5 | class Dialog extends React.PureComponent {
6 | state = {
7 | visible: this.props.visible,
8 | }
9 |
10 | componentWillReceiveProps(nextProps) {
11 | if (nextProps.visible !== this.props.visible) {
12 | this.setState({
13 | visible: nextProps.visible,
14 | });
15 | };
16 | }
17 |
18 | confirm = () => {
19 | const {
20 | onConfirm,
21 | } = this.props;
22 | if (onConfirm) {
23 | onConfirm();
24 | };
25 | }
26 |
27 | cancel = () => {
28 | const {
29 | onCancel,
30 | } = this.props;
31 | if (onCancel) {
32 | onCancel();
33 | };
34 | }
35 |
36 | render() {
37 | const {
38 | title,
39 | children,
40 | onCancel,
41 | onConfirm,
42 | } = this.props;
43 | const {
44 | visible,
45 | } = this.state;
46 | const fade = visible ? 'wowjoy-dialog__fadeIn' : '';
47 | return (
48 |
49 |
50 |
51 | {title}
52 |
53 |
54 | {children}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | );
63 | }
64 | }
65 |
66 | export default Dialog;
--------------------------------------------------------------------------------
/libs/Dialog/index.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .wowjoy-dialog {
3 | position: absolute;
4 | top: 0;
5 | left: 0;
6 | right: 0;
7 | bottom: 0;
8 | background: rgba(0,0,0,0.30);
9 | //visibility: hidden;
10 | display: none;
11 | //transition: all .3s;
12 | z-index: 10;
13 | }
14 | .wowjoy-dialog__fadeIn {
15 | display: block;
16 | }
17 | .wowjoy-dialog__inner {
18 | background: #FFFFFF;
19 | box-shadow: 0 0 4px 0 rgba(0,0,0,0.20);
20 | border-radius: 3px;
21 | padding: 10px 20px;
22 | width: 40%;
23 | max-width: 500px;
24 | min-width: 300px;
25 | position: absolute;
26 | top: 50%;
27 | left: 50%;
28 | transform: translate(-50%, -50%);
29 | //opacity: 0;
30 | //transition: all .3s;
31 | }
32 | .wowjoy-dialog__slideDown {
33 | opacity: 1;
34 | transform: translateY(-50%);
35 | }
36 | .wowjoy-dialog__header {
37 | margin-bottom: 15px;
38 | font-family: PingFangSC-Medium;
39 | font-size: 16px;
40 | color: #333333;
41 | }
42 | .wowjoy-dialog__footer {
43 | height: 40px;
44 | width: 100%;
45 | display: flex;
46 | align-items: center;
47 | justify-content: center;
48 | }
49 | };
--------------------------------------------------------------------------------
/libs/DragSort/example.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DragSort from './index.js';
3 |
4 | export default class DragSortExample extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | list: [{
9 | name: 'title'
10 | }, {
11 | name: 'name'
12 | }, {
13 | name: 'code'
14 | }, {
15 | name: 'email'
16 | }],
17 | curMoveItem: null,
18 | index: '',
19 | dragged: false,
20 | }
21 | }
22 |
23 | handleDragMove = (data, from, to) => {
24 | this.setState({
25 | curMoveItem: to,
26 | list: data,
27 | index: null,
28 | });
29 | }
30 |
31 | handleDragEnd = (index) => {
32 | this.setState({
33 | curMoveItem: null,
34 | dragged: false,
35 | index,
36 | });
37 | }
38 |
39 | enter = (index) => {
40 | if (this.state.index !== null) {
41 | this.setState({
42 | index,
43 | });
44 | };
45 | }
46 |
47 | leave = () => {
48 | if (this.state.index !== null) {
49 | this.setState({
50 | index: '',
51 | });
52 | };
53 | }
54 |
55 | render() {
56 | const {
57 | dragged,
58 | } = this.state;
59 | const el = this.state.list.map((item, index) => {
60 | return (
61 |
71 | );
72 | });
73 | return (
74 |
85 | );
86 | }
87 | }
--------------------------------------------------------------------------------
/libs/DragSort/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | let curDragIndex = null;
4 |
5 | export default function DragSort(props) {
6 | let container = props.children;
7 | let draggable = props.draggable;
8 |
9 | function onChange(from, to) {
10 | let curValue = props.data;
11 | let newValue = arrMove(curValue, from, to);
12 | if (typeof props.onDragMove === 'function') {
13 | return props.onDragMove(newValue, from, to);
14 | }
15 | }
16 | return (
17 |
18 | {container.map((item, index)=>{
19 | if(React.isValidElement(item)){
20 | return React.cloneElement(item, {
21 | draggable,
22 | //开始拖动元素时触发此事件
23 | onDragStart(){
24 | curDragIndex = index;
25 | },
26 | /*
27 | * 当被拖动的对象进入其容器范围内时触发此事件
28 | * 在自身拖动时也会触发该事件
29 | */
30 | onDragEnter() {
31 | onChange(curDragIndex, index);
32 | curDragIndex = index;
33 | },
34 | /*
35 | * 当被拖动的对象在另一对象容器范围内拖动时触发此事件
36 | * 在拖动元素时,每隔350毫秒会触发onDragOver事件
37 | */
38 | onDragOver(e) {
39 | /*
40 | * 默认情况下,数据/元素不能放置到其他元素中。如果要实现该功能,我们需要
41 | * 防止元素的默认处理方法,我们可以通过调用event.preventDefault()方法来实现onDragOver事件
42 | */
43 | e.preventDefault();
44 | },
45 | //完成元素拖动后触发
46 | onDragEnd(){
47 | curDragIndex = null;
48 | if(typeof props.onDragEnd === 'function'){
49 | props.onDragEnd(index);
50 | };
51 | },
52 | })
53 | }
54 | return item;
55 | })}
56 |
57 | );
58 | }
59 |
60 | function arrMove(arr, fromIndex, toIndex) {
61 | if (fromIndex !== toIndex) {
62 | arr = arr.concat();
63 | let item = arr.splice(fromIndex, 1)[0];
64 | arr.splice(toIndex, 0, item);
65 | };
66 | return arr;
67 | }
--------------------------------------------------------------------------------
/libs/DragSort/index.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .item{
3 | height: 35px;
4 | margin:10px;
5 | // background-color: #eee;
6 | // &:hover {
7 | // background-color: red;
8 | // }
9 | }
10 | .item-notdrag {
11 | height: 35px;
12 | margin:10px;
13 | background-color: #eee;
14 |
15 | }
16 | .inner {
17 | height: 100%;
18 | }
19 | .item.active{
20 | //height: 35px;
21 | //margin:10px;
22 | opacity: 0;
23 | //background-color: #eee !important;
24 | }
25 | };
--------------------------------------------------------------------------------
/libs/Dropdown/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.less';
3 |
4 | class Select extends React.PureComponent {
5 |
6 | handleChange = (e) => {
7 | const {
8 | onChange,
9 | } = this.props;
10 | if (onChange) {
11 | onChange(e);
12 | };
13 | }
14 |
15 | render() {
16 | const {
17 | name,
18 | value,
19 | options,
20 | } = this.props;
21 | return (
22 |
23 |
33 |
34 | );
35 | }
36 | }
37 |
38 | export default Select;
--------------------------------------------------------------------------------
/libs/Dropdown/index.less:
--------------------------------------------------------------------------------
1 | :global{
2 | .wowjoy-select {
3 | display: inline-block;
4 | }
5 | };
--------------------------------------------------------------------------------
/libs/Input/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './index.less'
3 |
4 | class Input extends React.PureComponent {
5 | static defaultProps = {
6 | disabled: false,
7 | type: 'text',
8 | }
9 |
10 | handleChange = (e) => {
11 | const {
12 | onChange,
13 | index,
14 | } = this.props;
15 | if (onChange) {
16 | onChange(e, index);
17 | };
18 | }
19 |
20 | handleBlur = (e) => {
21 | const {
22 | onBlur,
23 | index,
24 | } = this.props;
25 | if (onBlur) {
26 | onBlur(e, index);
27 | };
28 | }
29 |
30 | fixControlledValue = (value) => {
31 | if (typeof value === 'undefined' || value === null) {
32 | return '';
33 | };
34 | return value;
35 | }
36 |
37 | render() {
38 | const {
39 | type,
40 | name,
41 | width,
42 | margin,
43 | style,
44 | maxLength,
45 | rows,
46 | disabled,
47 | ...otherProps
48 | } = this.props;
49 | /*
50 | * defaultValue只会在第一次渲染有效
51 | * defaultValue和value尽量不共存,如果共存的话value将会覆盖defaultValue
52 | * 在共存的情况下,如果value值为undefined或者null,会被defaultValue覆盖
53 | */
54 | if ('value' in otherProps) {
55 | otherProps.value = this.fixControlledValue(otherProps.value);
56 | delete otherProps.defaultValue;
57 | };
58 | return (
59 |
62 | {type === 'textarea' ? (
63 |
71 | ) : (
72 |
82 | )}
83 |
84 | );
85 | }
86 | }
87 |
88 | export default Input;
--------------------------------------------------------------------------------
/libs/Input/index.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .wowjoy-input {
3 | overflow: hidden;
4 | width: 100%;
5 | height: 100%;
6 | flex: 1;
7 | z-index: 5;
8 | display: inline-block;
9 | }
10 | .wowjoy-input__inner {
11 | width: 100%;
12 | height: 36px;
13 | border: 1px solid #ddd;
14 | outline: none;
15 | transition: all .3s;
16 | padding-left: 11px;
17 | font-size: 14px;
18 | color: rgb(102, 102, 102);
19 | &:focus {
20 | border-color: #06AEA6;
21 | }
22 | }
23 | .wowjoy-textarea__inner {
24 | width: 100%;
25 | border: 1px solid #ddd;
26 | outline: none;
27 | transition: all .3s;
28 | padding: 0 11px;
29 | font-size: 14px;
30 | color: rgb(102, 102, 102);
31 | &:focus {
32 | border-color: #06AEA6;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/libs/Radio/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.less';
3 |
4 | class Radio extends React.PureComponent {
5 |
6 | handleChange = (e) => {
7 | const {
8 | onChange,
9 | } = this.props;
10 | if (onChange) {
11 | onChange(e);
12 | };
13 | }
14 |
15 | render() {
16 | const {
17 | defaultChecked,
18 | value,
19 | name,
20 | label,
21 | style,
22 | } = this.props;
23 | return (
24 |
35 | );
36 | }
37 | }
38 |
39 | export default Radio;
--------------------------------------------------------------------------------
/libs/Radio/index.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .wowjoy-radio {
3 | cursor: pointer;
4 | display: inline-block;
5 | }
6 | input[type='radio']:checked {
7 | &+.wowjoy-radio__inner {
8 | border-color: #06aea6;
9 | &:before {
10 | content: '';
11 | background: #06aea6;
12 | width: 8px;
13 | height: 8px;
14 | border-radius: 50%;
15 | position: absolute;
16 | top: 50%;
17 | left: 50%;
18 | transform: translate(-50%, -50%);
19 | }
20 | }
21 | }
22 | .wowjoy-radio__inner {
23 | position: relative;
24 | display: inline-block;
25 | width: 16px;
26 | height: 16px;
27 | border-radius: 50%;
28 | background: #fff;
29 | border: 1px solid #DBDBDB;
30 | vertical-align: sub;
31 | margin-right: 5px;
32 | }
33 | .wowjoy-radio__text {
34 | display: inline-block;
35 | max-width: 80%;
36 | vertical-align: top;
37 | word-break: break-all;
38 | }
39 | }
--------------------------------------------------------------------------------
/libs/Shake/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.less';
3 |
4 | class ShakeTransition extends React.PureComponent {
5 | state = {
6 | shake: false,
7 | }
8 |
9 | componentWillReceiveProps(nextProps) {
10 | if (nextProps.shake !== this.props.shake) {
11 | this.setState({
12 | shake: true,
13 | });
14 | this.timerID = setTimeout(() => this.triggerShake(), 1000);
15 | };
16 | }
17 |
18 | triggerShake = () => {
19 | this.setState({
20 | shake: false,
21 | });
22 | }
23 |
24 | componentWillUnmount() {
25 | clearTimeout(this.timerID);
26 | }
27 |
28 | render() {
29 | const {
30 | children,
31 | } = this.props;
32 | const {
33 | shake,
34 | } = this.state;
35 | return (
36 |
37 | {this.props.children}
38 |
39 | );
40 | }
41 | }
42 |
43 | export default ShakeTransition;
--------------------------------------------------------------------------------
/libs/Shake/index.less:
--------------------------------------------------------------------------------
1 | //animate
2 | @keyframes shake {
3 | from, to {
4 | transform: translate3d(0, 0, 0);
5 | }
6 |
7 | 10%, 30%, 50%, 70%, 90% {
8 | transform: translate3d(-8px, 0, 0);
9 | }
10 |
11 | 20%, 40%, 60%, 80% {
12 | transform: translate3d(8px, 0, 0);
13 | }
14 | }
15 | .shake-transition {
16 | height: 100%;
17 | width: 100%;
18 | }
19 | .shaked {
20 | animation-name: shake;
21 | animation-duration: 1s;
22 | animation-fill-mode: both;
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-questionnair",
3 | "version": "v1.0.1",
4 | "description": "",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "dev": "node bin/dev-server",
8 | "build": "webpack --config webpack/webpack.config.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "babel-core": "^6.26.3",
14 | "babel-loader": "^7.1.4",
15 | "babel-preset-es2015": "^6.24.1",
16 | "babel-preset-react": "^6.24.1",
17 | "babel-preset-stage-1": "^6.24.1",
18 | "react": "^16.4.1",
19 | "react-dom": "^16.4.1"
20 | },
21 | "devDependencies": {
22 | "babel-eslint": "^8.2.6",
23 | "css-loader": "^0.28.11",
24 | "file-loader": "^1.1.11",
25 | "html-webpack-plugin": "^3.2.0",
26 | "less": "^3.0.4",
27 | "less-loader": "^4.1.0",
28 | "style-loader": "^0.21.0",
29 | "url-loader": "^1.0.1",
30 | "webpack": "^4.15.0",
31 | "webpack-cli": "^3.0.8",
32 | "webpack-dev-server": "^3.1.4"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/react-questionnair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/react-questionnair.png
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/src/.DS_Store
--------------------------------------------------------------------------------
/src/Questionnair/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import QuestionnairContent from '../QuestionnairContent';
3 | import QuestionnairEditor from '../QuestionnairEditor';
4 | import QuestionnairSiderbar from '../QuestionnairSiderbar';
5 | import DragSort from 'libs/DragSort/index.js';
6 | import ShakeTransition from 'libs/Shake';
7 | import Input from 'libs/Input';
8 | import uuid from 'utils/utils';
9 | import './index.less';
10 |
11 | class Questionnair extends React.PureComponent {
12 | constructor(props) {
13 | super(props);
14 | this.editorsEl = [];
15 | this.scaleId = '';
16 | this.sign = false;
17 | }
18 |
19 | state = {
20 | editors: [],
21 | questionnairTitle: '问卷标题',
22 | curMoveItem: null,
23 | drag: false,
24 | scrollTo: 0,
25 | newEditor: true,
26 | }
27 |
28 | updateEditors = (callback) => {
29 | this.state.editors.some((data, index) => {
30 | if (data.isFirst && data.isEditor) {
31 | this.state.editors.splice(index, 1)
32 | return true;
33 | } else if (!data.isFirst && data.isEditor) {
34 | data.isEditor = false;
35 | return true;
36 | };
37 | });
38 | callback(this.state.editors);
39 | }
40 | /*
41 | * 判断是否有处于编辑状态的题目, activeEditorIndex // -1,没有处于编辑状态的题目
42 | * 如果有处于编辑状态的题目,则激活该编辑器抖动
43 | */
44 | isThereEditor = () => {
45 | const activeEditorIndex = this.state.editors.findIndex(data => data.isEditor === true);
46 | if (activeEditorIndex !== -1) {
47 | let editors = JSON.parse(JSON.stringify(this.state.editors));
48 | editors[activeEditorIndex].editorShake = uuid();
49 | this.setState({
50 | editors,
51 | });
52 | return true;
53 | } else {
54 | return false;
55 | };
56 | }
57 |
58 | createEditor = (type) => {
59 | if (this.isThereEditor()) {
60 | return;
61 | }
62 | const editor = {
63 | questionId: uuid(), //id
64 | type: type, //类型
65 | title: '', //题目
66 | required: false, //是否必填
67 | remark: false, //是否有备注
68 | remarkText: '', //备注内容
69 | options: ['选项', '选项'], //选项(只有radio,checkbox,select有,其余尽量给个空数组)
70 | rows: 1, //选项占的行数
71 | textareaHeight: 3, //多行文本高度
72 | maxLength: 50, //单行文本限制的字数
73 | otherOption: false, //是否有其他选项
74 | otherOptionForwards: '其他', //”其他“项文本(前)
75 | otherOptionBackwards: '', //”其他“项文本(后)
76 | completionForwards: '题目:', //填空题文本(前)
77 | completionBackwards: '', //填空题文本(后)
78 | isEditor: true, //编辑状态还是已编辑状态
79 | isFirst: true, //是否是新创建的
80 | editorShake: '',
81 | };
82 | this.setState(prevState => ({
83 | editors: [...prevState.editors, editor],
84 | }));
85 | }
86 |
87 | dragEditorByOutline = (editors) => {
88 | const {
89 | onDrag,
90 | } = this.props;
91 | this.setState({
92 | editors,
93 | }, () => {
94 | if (onDrag) {
95 | this.updateEditors(onDrag);
96 | };
97 | });
98 | }
99 |
100 | locateEditor = (index) => {
101 | this.setState({
102 | scrollTo: this.editorsEl[index].offsetTop,
103 | });
104 | }
105 |
106 | cancelEdit = (index) => {
107 | let editors = JSON.parse(JSON.stringify(this.state.editors));
108 | editors[index].isFirst ? editors.splice(index, 1) : editors[index].isEditor = false;
109 | this.setState({
110 | editors,
111 | });
112 | }
113 |
114 | confirmEdit = (index, newEditor) => {
115 | const {
116 | onConfirm,
117 | } = this.props;
118 | let editors = JSON.parse(JSON.stringify(this.state.editors));
119 | editors.splice(index, 1, newEditor);
120 | this.setState({
121 | editors,
122 | }, () => {
123 | if (onConfirm) {
124 | this.updateEditors(onConfirm);
125 | };
126 | });
127 | }
128 |
129 | againEdit = (index) => {
130 | if (this.isThereEditor()) {
131 | return;
132 | }
133 | let editors = JSON.parse(JSON.stringify(this.state.editors));
134 | editors[index].isEditor = true;
135 | this.setState({
136 | editors,
137 | });
138 | }
139 |
140 | copyEdit = (index) => {
141 | const {
142 | onCopy,
143 | } = this.props;
144 | let editors = JSON.parse(JSON.stringify(this.state.editors));
145 | const copyEditor = {
146 | ...this.state.editors[index],
147 | questionId: uuid(),
148 | };
149 | editors.splice(index + 1, 0, copyEditor);
150 | this.setState({
151 | editors,
152 | }, () => {
153 | if (onCopy) {
154 | this.updateEditors(onCopy);
155 | };
156 | });
157 | }
158 |
159 | removeEdit = (index) => {
160 | const {
161 | onRemove,
162 | } = this.props;
163 | let editors = JSON.parse(JSON.stringify(this.state.editors));
164 | editors.splice(index, 1);
165 | this.setState({
166 | editors,
167 | }, () => {
168 | if (onRemove) {
169 | this.updateEditors(onRemove);
170 | };
171 | });
172 | }
173 |
174 | handleDragMove = (editors, from, to) => {
175 | this.setState({
176 | curMoveItem: to,
177 | editors,
178 | drag: true,
179 | });
180 | }
181 |
182 | handleDragEnd = () => {
183 | const {
184 | onDrag,
185 | } = this.props;
186 | this.setState({
187 | curMoveItem: null,
188 | drag: false,
189 | }, () => {
190 | if (onDrag) {
191 | this.updateEditors(onDrag);
192 | };
193 | });
194 | }
195 | //标记事件
196 | handleSgin = (sign) => {
197 | const {
198 | onSign,
199 | } = this.props;
200 | if (onSign) {
201 | onSign(sign);
202 | };
203 | }
204 | //问卷标题失焦事件
205 | blurTitle = (title) => {
206 | const {
207 | onSaveTitle,
208 | } = this.props;
209 | if (onSaveTitle) {
210 | onSaveTitle(title);
211 | };
212 | }
213 |
214 | render() {
215 | const {
216 | editors,
217 | drag,
218 | editorShake,
219 | scrollTo,
220 | questionnairTitle,
221 | } = this.state;
222 | //如果有编辑状态的题目则禁止拖动
223 | const hasEditor = editors.some(data => data.isEditor === true);
224 | const canDrag = hasEditor ? false : true;
225 | const isFirst = editors.length !== 0 && editors[editors.length - 1].isFirst;
226 | const editorsEl = editors.map((editor, index) => {
227 | return (
228 | this.editorsEl[index] = el}
231 | key={editor.questionId}>
232 |
243 |
244 | );
245 | });
246 | return (
247 |
248 |
254 |
261 | {editorsEl.length !== 0 && (
262 |
267 | {editorsEl}
268 |
269 | )}
270 |
271 |
272 | );
273 | }
274 | }
275 |
276 | export default Questionnair;
--------------------------------------------------------------------------------
/src/Questionnair/index.less:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | user-select: none;
4 | font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
5 | }
6 | .questionnair {
7 | display: flex;
8 | width: 100%;
9 | height: 100%;
10 | background: #f0f0f0;
11 | }
12 | :global {
13 | .title-inner {
14 | width: 700px;
15 | height: 45px;
16 | margin: 0 auto;
17 | background: #fff;
18 | display: flex;
19 | justify-content: center;
20 | align-items: center;
21 | border: 1px solid transparent;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/QuestionnairAnswer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const QuestionnairAnswer = ({
4 | answer
5 | }) => {
6 | return (
7 |
8 |
{`${index + 1}.${answer.type === 'input' ? answer.completionForwards : answer.title + ':'}`}
9 |
{typeof answer.answer[answer.type] !== 'string' ? (
10 | typeof answer.answer[answer.type].optionValue !== 'string' ? (
11 | answer.answer[answer.type].optionValue.map((item, index) => {
12 | return (
13 | item && {item}
14 | )
15 | })
16 | ) : answer.answer[answer.type].optionValue
17 | ) : answer.answer[answer.type]}
18 |
19 | );
20 | }
21 |
22 | export default QuestionnairAnswer;
--------------------------------------------------------------------------------
/src/QuestionnairAnswer/index.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pandly/react-questionnair/6a7f3c7a563826b59fcb6f7b55ac7b9971a2c0f7/src/QuestionnairAnswer/index.less
--------------------------------------------------------------------------------
/src/QuestionnairContent/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './index.less'
3 | import Input from 'libs/Input'
4 | import Default from 'assets/scale_default.png'
5 |
6 | class QuestionnairContent extends React.PureComponent {
7 | state = {
8 | questionnairSign: false,
9 | questionnairTitle: '问卷标题',
10 | }
11 | //新增题目时内容页滚动到底部
12 | componentDidUpdate() {
13 | if (this.scrollBottom) {
14 | const scrollHeight = this.content.scrollHeight;
15 | this.page.scrollTo(0, scrollHeight);
16 | };
17 | if (this.scrollTo) {
18 | this.page.scrollTo(0, this.scrollTo);
19 | };
20 | }
21 |
22 | componentWillReceiveProps(nextProps) {
23 | if (nextProps.isFirst) {
24 | this.scrollBottom = true;
25 | } else {
26 | this.scrollBottom = false;
27 | };
28 | if (nextProps.scrollTo !== this.props.scrollTo) {
29 | this.scrollTo = nextProps.scrollTo;
30 | } else {
31 | this.scrollTo = false;
32 | };
33 | this.setState({
34 | questionnairSign: nextProps.questionnairSign,
35 | questionnairTitle: nextProps.questionnairTitle,
36 | });
37 | }
38 |
39 | handleSign = () => {
40 | const {
41 | onChangeSign
42 | } = this.props;
43 | this.setState(prevState => ({
44 | questionnairSign: !prevState.questionnairSign,
45 | }), () => {
46 | onChangeSign(this.state.questionnairSign);
47 | });
48 | }
49 |
50 | handleChange = (e) => {
51 | this.setState({
52 | questionnairTitle: e.target.value,
53 | });
54 | }
55 |
56 | handleBlur = () => {
57 | const {
58 | onBlurTitle
59 | } = this.props;
60 | if (onBlurTitle) {
61 | onBlurTitle(this.state.questionnairTitle);
62 | };
63 | }
64 |
65 | render() {
66 | const {
67 | questionnairSign,
68 | questionnairTitle,
69 | } = this.state;
70 | const questionnairtitleEl = (
71 |
72 |
85 |
86 | );
87 | return (
88 | this.page = el}>
89 |
90 |
91 |
92 | {questionnairSign ? '取消标记' : '标记'}
93 |
94 |
95 |
96 | {questionnairtitleEl}
97 |
98 |
this.content = el}>
99 | {/*如果组件没有子节点,this.props.children返回false*/}
100 | {this.props.children || (
101 |
102 |

103 |
您还没有添加题目哦,请点击左侧控件开始出题吧
104 |
105 | )}
106 |
107 |
108 | );
109 | }
110 | }
111 |
112 | export default QuestionnairContent;
--------------------------------------------------------------------------------
/src/QuestionnairContent/index.less:
--------------------------------------------------------------------------------
1 | .questionnair-page {
2 | flex: 1;
3 | background: #fff;
4 | box-shadow: 0 3px 4px 0 rgba(202,202,202,0.50);
5 | margin-left: 20px;
6 | overflow: auto;
7 | position: absolute;
8 | left: 170px;
9 | right: 20px;
10 | top: 20px;
11 | bottom: 20px;
12 | width: 1000px;
13 | margin: auto;
14 | }
15 | .questionnair-page-banner {
16 | height: 46px;
17 | width: 700px;
18 | margin: 0 auto;
19 | border-bottom: 1px solid #DBDBDB;
20 | display: flex;
21 | align-items: center;
22 | justify-content: flex-end;
23 | }
24 | .banner-text {
25 | cursor: pointer;
26 | display: inline-block;
27 | color: #999;
28 | }
29 | .questionnair-page-title {
30 | margin: 10px 0;
31 | width: 100%;
32 | padding: 10px 0;
33 | border-top: 1px solid transparent;
34 | border-bottom: 1px solid transparent;
35 | &:hover {
36 | border-color: #DBDBDB;
37 | background: #FAFAFA;
38 | :global {
39 | .title-inner {
40 | border-color: #DBDBDB;
41 | }
42 | };
43 | }
44 | }
45 | .questionnair-page-content {
46 | width: 100%;
47 | }
48 | .questionnair-page-default {
49 | display: flex;
50 | align-items: center;
51 | justify-content: center;
52 | flex-direction: column;
53 | position: absolute;
54 | top: 45%;
55 | left: 50%;
56 | transform: translate(-50%, -50%);
57 | }
58 | .page-default-text {
59 | font-size: 16px;
60 | color: #999;
61 | margin-top: 20px;
62 | }
--------------------------------------------------------------------------------
/src/QuestionnairEditor/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Input from 'libs/Input';
3 | import Checkbox from 'libs/Checkbox';
4 | import Radio from 'libs/Radio';
5 | import Dropdown from 'libs/Dropdown';
6 | import Button from 'libs/Button';
7 | import ContentEditable from 'libs/ContentEditable';
8 | import ShakeTransition from 'libs/Shake';
9 | import Dialog from 'libs/Dialog';
10 | import uuid from 'utils/utils';
11 | import './index.less';
12 |
13 | const rowOptions = [1, 2, 3, 4];
14 |
15 | class QuestionnairEditor extends React.PureComponent {
16 | constructor(props) {
17 | super(props);
18 | this.temp = '';
19 | this.otherOptionInput = '';
20 | }
21 |
22 | static defaultProps = {
23 | acitveAnswer: false,
24 | }
25 |
26 | state = {
27 | toggleMutiOption: false,
28 | editor: {
29 | ...this.props.editor,
30 | },
31 | hover: false,
32 | inputShake: false,
33 | optionShake: [],
34 | hasOption: [],
35 | hasTitle: true,
36 | dialogVisible: false,
37 | mutiOption: '',
38 | otherOptionInput: '',
39 | otherOptionForwards: '',
40 | }
41 | /* 首先有点乱。。。
42 | * 由于每次每个题目的操作实际上都是在操作整个题目数组,题目数组交给了题目组件的父组件来管理
43 | * 所以每次题目操作完以后更新父组件的题目数组,父组件更新会触发题目组件的componentWillReceiveProps生命周期函数
44 | */
45 | componentWillReceiveProps(nextProps) {
46 | /*
47 | * 每次只能编辑一个题目,所以触发再次添加题目的操作,将会引起当前题目的抖动
48 | * 连续点击产生连续抖动的效果,所以创建一个变量editorShake,来标志表格需要抖动
49 | * 由于是更新数组,为了在抖动的效果下保留之前填写的数据,所以比较之前和新的editorShake是否相同
50 | * 如果不同则说明只是要产生抖动,只更新editorShake变量
51 | */
52 | if (nextProps.editor.editorShake !== this.props.editor.editorShake) {
53 | this.setState({
54 | editor: {
55 | ...this.state.editor,
56 | editorShake: nextProps.editor.editorShake,
57 | },
58 | });
59 | } else {
60 | this.setState({
61 | editor: {
62 | ...this.state.editor,
63 | ...nextProps.editor,
64 | },
65 | });
66 | };
67 | }
68 |
69 | switchEditor = (type) => {
70 | switch (type) {
71 | case 'radio':
72 | return '单选题';
73 | case 'dropdown':
74 | return '下拉题';
75 | case 'checkbox':
76 | return '多选题';
77 | case 'textarea':
78 | return '多行文本题';
79 | case 'text':
80 | return '单行文本题';
81 | case 'input':
82 | return '填空题';
83 | default:
84 | return '错误';
85 | };
86 | }
87 |
88 | handleChange = (e, index) => {
89 | let value = e.target ? e.target.value : e;
90 | let key = e.target.name;
91 | let checked = e.target.checked;
92 | if (key === 'title' && value) {
93 | //控制题目input边框颜色
94 | this.setState({
95 | hasTitle: true,
96 | });
97 | };
98 | if (key === 'options') {
99 | let {
100 | options,
101 | } = this.state.editor;
102 | let {
103 | hasOption,
104 | } = this.state;
105 | hasOption[index] = true;
106 | this.setState({
107 | hasOption: [...hasOption],
108 | })
109 | let optionsTemp = options.concat();
110 | optionsTemp[index] = value;
111 | value = optionsTemp;
112 | };
113 | if (key === 'required' || key === 'remark') {
114 | value = checked;
115 | };
116 | if (key === 'maxLength') {
117 | value = parseInt(value);
118 | };
119 | this.setState(prevState => ({
120 | editor: {
121 | ...prevState.editor,
122 | [key]: value,
123 | },
124 | }));
125 | }
126 | //填写答案时触发的事件
127 | handleAnswerChange = (e, index) => {
128 | let {
129 | type,
130 | } = this.state.editor;
131 | let value = e.target.value;
132 | this.optionIndex = e.target.dataset.index;
133 | if (value === 'undefined') {
134 | value = this.otherOptionValue;
135 | };
136 | if (type === 'checkbox') {
137 | let valueIn = this.answer.checkbox.optionValue.includes(value);
138 | let indexIn = this.answer.checkbox.optionIndex.includes(this.optionIndex);
139 | this.answer.checkbox.optionValue[this.optionIndex] = valueIn ? null : value;
140 | this.answer.checkbox.optionIndex[this.optionIndex] = indexIn ? null : this.optionIndex;
141 | this.answer.checkbox.otherOptionValue = this.otherOptionValue;
142 | } else if (type === 'radio') {
143 | this.answer[type] = {
144 | optionValue: value,
145 | optionIndex: this.optionIndex,
146 | otherOptionValue: this.otherOptionValue,
147 | };
148 | } else {
149 | this.answer[type] = value;
150 | };
151 | const answerEditor = {
152 | ...this.state.editor,
153 | answer: this.answer,
154 | };
155 | this.props.onAnswer(answerEditor, this.props.index);
156 | }
157 | //填写radio、checkbox'其他'选项时触发的方法
158 | handleOtherOptionInputChange = (e) => {
159 | const {
160 | type,
161 | otherOptionForwards,
162 | otherOptionBackwards,
163 | } = this.state.editor;
164 | this.otherOptionValue = e.target.innerHTML;
165 | this.allValue = otherOptionForwards + this.otherOptionValue + otherOptionBackwards;
166 | this.optionIndex = e.target.dataset.index;
167 | if (type === 'checkbox') {
168 | const length = this.answer.checkbox.optionValue.length;
169 | this.answer.checkbox.optionValue[length - 1] = this.answer.checkbox.optionValue[length - 1] === null ? null : this.allValue;
170 | this.answer.checkbox.otherOptionValue = this.allValue;
171 | } else if (type === 'radio') {
172 | this.answer[type] = {
173 | optionValue: this.allValue,
174 | optionIndex: this.optionIndex,
175 | otherOptionValue: this.allValue,
176 | };
177 | } else {
178 | this.answer[type] = this.allValue;
179 | };
180 | const answerEditor = {
181 | ...this.state.editor,
182 | answer: this.answer,
183 | };
184 | this.props.onAnswer(answerEditor, this.props.index);
185 | }
186 | //新增选项
187 | createOption = () => {
188 | this.setState(prevState => ({
189 | editor: {
190 | ...prevState.editor,
191 | 'options': [...prevState.editor.options, ''],
192 | },
193 | }));
194 | }
195 | //删除选项
196 | deleteOption = (index) => {
197 | let options = [...this.state.editor.options];
198 | options.splice(index, 1);
199 | this.setState(prevState => ({
200 | editor: {
201 | ...prevState.editor,
202 | options,
203 | },
204 | }));
205 | }
206 | /*
207 | * 以下都是有关于批量编辑的事件
208 | */
209 | //点击打开批量编辑的弹窗
210 | handleMutiOption = () => {
211 | this.setState({
212 | dialogVisible: true,
213 | mutiOption: this.state.editor.options.join('\n'),
214 | });
215 | }
216 | //批量编辑textarea中的change
217 | handleMutiTextarea = (e) => {
218 | this.mutiTextareaValue = e.target.value;
219 | this.setState({
220 | mutiOption: e.target.value,
221 | });
222 | }
223 | //关闭批量编辑的弹窗
224 | closeDialog = () => {
225 | this.setState({
226 | dialogVisible: false,
227 | });
228 | }
229 | //打开批量编辑的弹窗
230 | confirmDialog = () => {
231 | const options = this.mutiTextareaValue.split('\n');
232 | this.setState(prevState => ({
233 | dialogVisible: false,
234 | editor: {
235 | ...prevState.editor,
236 | options,
237 | },
238 | }));
239 | }
240 | //确认
241 | confirm = () => {
242 | const {
243 | index,
244 | handleConfirm,
245 | } = this.props;
246 | const {
247 | editor,
248 | inputShake,
249 | } = this.state;
250 | if (!editor.title && editor.type !== 'input') {
251 | this.setState(prevState => ({
252 | inputShake: !prevState.inputShake,
253 | hasTitle: false,
254 | }));
255 | return;
256 | };
257 | //判断选项是否为空
258 | if (['radio', 'checkbox', 'dropdown'].includes(editor.type)) {
259 | let empty = editor.options.some((item, index) => {
260 | if (item === '') {
261 | this.setState(prevState => {
262 | prevState.optionShake[index] = !prevState.optionShake[index];
263 | prevState.hasOption[index] = false;
264 | return {
265 | optionShake: [...prevState.optionShake],
266 | hasOption: [...prevState.hasOption],
267 | }
268 | })
269 | return true;
270 | }
271 | })
272 | if (empty) {
273 | return;
274 | }
275 | };
276 | const newEditor = {
277 | ...editor,
278 | isEditor: false,
279 | isFirst: false,
280 | };
281 | if (handleConfirm) {
282 | handleConfirm(index, newEditor);
283 | };
284 | this.isFirst = false;
285 | this.temp = JSON.parse(JSON.stringify(this.state.editor));
286 | }
287 | //取消
288 | cancel = () => {
289 | const {
290 | index,
291 | handleCancel,
292 | } = this.props;
293 | if (handleCancel) {
294 | handleCancel(index);
295 | };
296 | this.setState({
297 | editor: this.temp,
298 | });
299 | }
300 | //编辑
301 | edit = () => {
302 | const {
303 | index,
304 | handleEdit,
305 | } = this.props;
306 | if (handleEdit) {
307 | handleEdit(index);
308 | };
309 | }
310 | //复制
311 | copy = () => {
312 | const {
313 | index,
314 | handleCopy,
315 | } = this.props;
316 | if (handleCopy) {
317 | handleCopy(index);
318 | };
319 | }
320 | //删除
321 | remove = () => {
322 | const {
323 | index,
324 | handleRemove,
325 | } = this.props;
326 | if (handleRemove) {
327 | handleRemove(index);
328 | };
329 | }
330 | //鼠标进入
331 | mouseEnter = () => {
332 | if (!this.props.drag && !this.props.acitveAnswer) {
333 | this.setState({
334 | hover: true,
335 | });
336 | };
337 | }
338 | //鼠标离开
339 | mouseLeave = () => {
340 | if (!this.props.drag && !this.props.acitveAnswer) {
341 | this.setState({
342 | hover: false,
343 | });
344 | };
345 | }
346 | disableEnter = (event) => {
347 | if (event.which == 13) {
348 | event.cancelBubble = true;
349 | event.preventDefault();
350 | event.stopPropagation();
351 | };
352 | }
353 | render() {
354 | const {
355 | index,
356 | curMoveItem,
357 | drag,
358 | acitveAnswer,
359 | } = this.props;
360 | const {
361 | toggleMutiOption,
362 | editor,
363 | hover,
364 | inputShake,
365 | optionShake,
366 | hasOption,
367 | hasTitle,
368 | dialogVisible,
369 | mutiOption,
370 | } = this.state;
371 | let {
372 | type,
373 | isEditor,
374 | title,
375 | required,
376 | remark,
377 | remarkText,
378 | options,
379 | rows,
380 | textareaHeight,
381 | maxLength,
382 | otherOption,
383 | otherOptionForwards,
384 | otherOptionBackwards,
385 | completionForwards,
386 | completionBackwards,
387 | editorShake,
388 | answer,
389 | } = editor;
390 | this.answer = answer && JSON.parse(JSON.stringify(answer));
391 | this.otherOptionValue = answer && this.answer[type].otherOptionValue;
392 | /*
393 | *
394 | * 以下元素为编辑状态下的元素
395 | *
396 | */
397 | //编辑状态下的题目
398 | const ediTitleEl = (
399 |
400 |
401 |
402 |
403 |
411 |
412 |
413 |
414 | );
415 | //编辑状态下的选项框
416 | const optionsArr = options.map((option, index) => {
417 | return (
418 |
419 |
422 |
423 |
424 |
433 |
434 |
435 |
this.deleteOption(index)}>
436 |
437 | )
438 | });
439 | //编辑状态下的选项框和新建框
440 | const ediOptionsEl = (
441 |
442 | { optionsArr }
443 |
444 |
445 |
446 |
447 | 新建选项
448 |
449 |
450 |
451 |
452 |
453 | );
454 | //编辑状态下的”其他“选项
455 | const ediOtherOptionsEl = (
456 |
457 |
477 |
this.handleChange({target: {value: false, name: 'otherOption'}})}>
478 |
479 | );
480 | //编辑状态下的填空题
481 | const ediCompletionEl = (
482 |
505 | );
506 | //添加"其他"选项 | 批量编辑
507 | const ediCtrlOptionsEl = (
508 |
509 |
520 | |
521 |
532 |
533 | );
534 | /*
535 | *
536 | * 以下元素为填写状态下的元素
537 | *
538 | */
539 | //填写状态下的填空
540 | const subCompletionEl = (
541 |
542 |
{completionForwards}
543 |
549 |
550 |
{completionBackwards}
551 |
552 | );
553 | //填写状态下的单选、多选其他选项
554 | const subOtherOptionsEl = (
555 |
556 |
{otherOptionForwards}
557 |
563 |
564 |
{otherOptionBackwards}
565 |
566 | );
567 | //填写状态下的单选、多选
568 | const optionsCom = otherOption ? options.concat('undefined') : options;
569 | const subRadioEl = (
570 |
571 | {optionsCom.map((data, index) => {
572 | return (
573 |
603 | )
604 | })}
605 |
606 | );
607 | const subCheckboxEl = (
608 |
609 | {optionsCom.map((data, index) => {
610 | return (
611 |
641 | )
642 | })}
643 |
644 | );
645 | //填写状态下的下拉框
646 | const subDropdownEl = (
647 |
654 | );
655 | const optionsEl = type === 'dropdown' ? subDropdownEl : (type === 'radio' ? subRadioEl : subCheckboxEl);
656 | //填写状态下的单行文本、多行文本
657 | const subTextEl = (
658 |
664 | );
665 | const subTextareaEl = (
666 |
672 | );
673 | return (
674 | /*
675 | * 想了很多交互,最终认为还是将编辑模块和题目模块放在一起实现起来相对方便点,虽然这样造成的后果是代码很臃肿。。。
676 | * 如果不这样做,组件之间的传值问题将会变得错综复杂
677 | * 后期有时间再仔细想想看看能不能有更优的办法
678 | */
679 |
680 | {isEditor ? (
681 |
682 |
683 |
684 |
685 |
686 | {this.switchEditor(type)}
687 |
688 | {'input' === type ? ediCompletionEl : ediTitleEl}
689 |
690 |
691 |
700 |
709 | {remark && (
710 |
715 | )}
716 |
717 |
718 | {['radio', 'dropdown', 'checkbox'].includes(type) && ediOptionsEl}
719 | {otherOption && ediOtherOptionsEl}
720 | {['radio', 'checkbox'].includes(type) && ediCtrlOptionsEl}
721 | {['radio', 'checkbox'].includes(type) && (
722 |
723 |
724 | 每行显示
725 |
730 | 个选项
731 |
732 |
733 | )}
734 | {'text' === type && (
735 |
736 |
737 | 最多填写
738 |
745 | 字
746 |
747 |
748 | )}
749 | {'textarea' === type && (
750 |
751 |
752 | 文本框高度
753 |
760 | 行
761 |
762 |
763 | )}
764 |
765 |
766 |
767 |
768 |
769 |
780 |
781 |
782 | ) : (
783 |
793 |
794 |
795 | {index + 1}.
796 | {'input' === type ? subCompletionEl : ({title})}
797 | {required && *}
798 |
799 | {remark &&
{remarkText}
}
800 |
801 | {['radio', 'dropdown', 'checkbox'].includes(type) && optionsEl}
802 | {type === 'text' && subTextEl}
803 | {type === 'textarea' && subTextareaEl}
804 |
805 |
806 | {!acitveAnswer && (
807 |
812 |
813 | )}
814 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 | )}
831 |
832 | );
833 | }
834 | }
835 |
836 | export default QuestionnairEditor;
--------------------------------------------------------------------------------
/src/QuestionnairEditor/index.less:
--------------------------------------------------------------------------------
1 | :global{
2 | //编辑框样式
3 | .questionnair-item {
4 | width: 100%;
5 | }
6 | .questionnair-editor {
7 | width: 100%;
8 | background: #f5f5f5;
9 | position: relative;
10 | font-size: 14px;
11 | }
12 | .questionnair-editor-inner {
13 | width: 700px;
14 | margin: 0 auto;
15 | padding: 15px 0;
16 | color: #666;
17 | margin-bottom: 20px;
18 | }
19 | .editor-type-text {
20 | font-family: PingFangSC-Medium;
21 | font-size: 16px;
22 | margin-left: 10px;
23 | }
24 | .editor-row {
25 | display: flex;
26 | align-items: center;
27 | margin: 15px 0;
28 | position: relative;
29 | }
30 | .editor-row-title {
31 | position: absolute;
32 | left: 0;
33 | text-align: right;
34 | width: 32px;
35 | }
36 | .editor-row-content {
37 | flex: 1;
38 | display: flex;
39 | align-items: center;
40 | margin-left: 40px;
41 | height: 36px;
42 | }
43 | .other-option-wrapper {
44 | width: 100%;
45 | background: #fff;
46 | padding: 0 11px;
47 | border: 1px solid #ddd;
48 | display: flex;
49 | transition: all .3s;
50 | &:focus-within {
51 | border-color: #06aea6;
52 | }
53 | }
54 | .other-option {
55 | outline: none;
56 | height: 36px;
57 | line-height: 36px;
58 | }
59 | .other-fill {
60 | width: 30px;
61 | height: 36px;
62 | padding-top: 5px;
63 | }
64 | .other-fill-inner {
65 | height: 25px;
66 | padding-top: 3px;
67 | border: 1px solid transparent;
68 | cursor: pointer;
69 | transition: all .3s;
70 | &:hover{
71 | border-color: #45A8E6;
72 | }
73 | }
74 | .other-option-input {
75 | border-bottom: 1px solid;
76 | min-width: 80px;
77 | display: inline-block;
78 | padding: 0 5px;
79 | outline: none;
80 | cursor: text;
81 | }
82 | .editor-create-option {
83 | width: 100%;
84 | height: 36px;
85 | border: 1px dashed #ddd;
86 | display: flex;
87 | align-items: center;
88 | cursor: pointer;
89 | }
90 | .options-control {
91 | margin-left: 38px;
92 | margin-bottom: 15px;
93 | }
94 | .control-button {
95 | border: none;
96 | background: #F5F5F5;
97 | padding: 0;
98 | cursor: pointer;
99 | outline: none;
100 | }
101 | .editor-button {
102 | width: 100%;
103 | text-align: center;
104 | }
105 | .editor-adv {
106 | width: 100%;
107 | border-top: 1px solid #DBDBDB;
108 | padding: 20px 0;
109 | display: flex;
110 | }
111 | .adv-option {
112 | display: flex;
113 | align-items: center;
114 | margin-right: 60px;
115 | font-size: 12px;
116 | }
117 | .editor-input-number {
118 | width: 54px;
119 | height: 36px;
120 | border: 1px solid #ddd;
121 | outline: none;
122 | margin: 0 10px;
123 | padding-left: 11px;
124 | }
125 | //题目样式
126 | .questionnair-subject {
127 | position: relative;
128 | width: 100%;
129 | padding: 20px 0;
130 | overflow: hidden;
131 | min-height: 144px;
132 | border: 1px dashed transparent;
133 | }
134 | .questionnair-subject-inner {
135 | width: 700px;
136 | }
137 | .subject-row {
138 | margin-bottom: 8px;
139 | width: 100%;
140 | }
141 | .subject-title-require {
142 | color: red;
143 | vertical-align: middle;
144 | margin-left: 5px;
145 | }
146 | .subject-remarks {
147 | font-size: 12px;
148 | color: #999;
149 | }
150 | .subject-control-mask {
151 | position: absolute;
152 | top: 0;
153 | bottom: 0;
154 | left: 0;
155 | right: 0;
156 | z-index: 2;
157 | }
158 | .subject-control-bar {
159 | width: 48px;
160 | background: #ededed;
161 | position: absolute;
162 | top: 0;
163 | bottom: 0;
164 | right: 0;
165 | transform: translateX(100%);
166 | transition: transform .2s;
167 | z-index: 3;
168 | display: flex;
169 | align-items: center;
170 | }
171 | .control-bar-inner {
172 | width: 100%;
173 | height: 144px;
174 | display: flex;
175 | flex-direction: column;
176 | }
177 | .control-bar-button {
178 | width: 100%;
179 | cursor: pointer;
180 | flex: 1;
181 | display: flex;
182 | justify-content: center;
183 | align-items: center;
184 | &:hover {
185 | i {
186 | color: #06aea6;
187 | }
188 | }
189 | }
190 | .subject-other-option {
191 | display: inline-block;
192 | }
193 | .subject-input {
194 | outline: none;
195 | border: 1px solid #DDDDDD;
196 | width: 100%;
197 | resize: none;
198 | padding: 0 11px;
199 | }
200 | .wowjoy-radio {
201 | cursor: pointer;
202 | display: inline-block;
203 | }
204 | input[type='radio']:checked {
205 | &+.wowjoy-radio__inner {
206 | border-color: #06aea6;
207 | &:before {
208 | content: '';
209 | background: #06aea6;
210 | width: 8px;
211 | height: 8px;
212 | border-radius: 50%;
213 | position: absolute;
214 | top: 50%;
215 | left: 50%;
216 | transform: translate(-50%, -50%);
217 | }
218 | }
219 | }
220 | .wowjoy-radio__inner {
221 | position: relative;
222 | display: inline-block;
223 | width: 16px;
224 | height: 16px;
225 | border-radius: 50%;
226 | background: #fff;
227 | border: 1px solid #DBDBDB;
228 | vertical-align: sub;
229 | margin-right: 5px;
230 | }
231 | .wowjoy-radio__text {
232 | display: inline-block;
233 | max-width: 80%;
234 | vertical-align: top;
235 | word-break: break-all;
236 | }
237 | .wowjoy-checkbox {
238 | cursor: pointer;
239 | display: inline-block;
240 | }
241 | input[type='checkbox']:checked {
242 | &+.wowjoy-checkbox__inner {
243 | border-color: #06aea6;
244 | &:before {
245 | content: '\2713';
246 | color: #06aea6;
247 | position: absolute;
248 | top: 50%;
249 | left: 50%;
250 | transform: translate(-50%, -50%);
251 | }
252 | }
253 | }
254 | .wowjoy-checkbox__inner {
255 | position: relative;
256 | display: inline-block;
257 | width: 16px;
258 | height: 16px;
259 | background: #fff;
260 | border: 1px solid #DBDBDB;
261 | vertical-align: sub;
262 | margin-right: 5px;
263 | }
264 | .wowjoy-checkbox__text {
265 | display: inline-block;
266 | max-width: 80%;
267 | vertical-align: top;
268 | word-break: break-all;
269 | }
270 | };
271 |
--------------------------------------------------------------------------------
/src/QuestionnairSiderbar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DragSort from 'libs/DragSort';
3 | import './index.less';
4 |
5 | const tabs = [{
6 | name: '题目控件',
7 | }, {
8 | name: '问题大纲',
9 | }, ];
10 |
11 | const subjects = [{
12 | name: '单选题',
13 | type: 'radio',
14 | icon: 'icon-danxuanicon',
15 | }, {
16 | name: '下拉题',
17 | type: 'dropdown',
18 | icon: 'icon-xialaicon',
19 | }, {
20 | name: '多选题',
21 | type: 'checkbox',
22 | icon: 'icon-duoxuan-icon',
23 | }, {
24 | name: '单行文本题',
25 | type: 'text',
26 | icon: 'icon-danhangicon',
27 | }, {
28 | name: '多行文本题',
29 | type: 'textarea',
30 | icon: 'icon-duohangicon',
31 | }, {
32 | name: '填空题',
33 | type: 'input',
34 | icon: 'icon-tiankongtiicon',
35 | }, ];
36 |
37 | class QuestionnairSiderbar extends React.PureComponent {
38 | state = {
39 | tabIndex: 0,
40 | curMoveItem: null,
41 | editors: [],
42 | };
43 |
44 | componentWillReceiveProps(nextProps) {
45 | this.setState({
46 | editors: nextProps.editors,
47 | })
48 | }
49 |
50 | toggleTab = (index) => {
51 | this.setState({
52 | tabIndex: index,
53 | })
54 | }
55 |
56 | switchIcon = (type) => {
57 | switch (type) {
58 | case 'radio':
59 | return 'icon-danxuanicon';
60 | case 'dropdown':
61 | return 'icon-xialaicon';
62 | case 'checkbox':
63 | return 'icon-duoxuan-icon';
64 | case 'textarea':
65 | return 'icon-duohangicon';
66 | case 'text':
67 | return 'icon-danhangicon';
68 | case 'input':
69 | return 'icon-tiankongtiicon';
70 | }
71 | }
72 |
73 | handleDragMove = (editors, from, to) => {
74 | const {
75 | onDragOutline,
76 | } = this.props;
77 | if (onDragOutline) {
78 | onDragOutline(editors)
79 | }
80 | this.setState({
81 | curMoveItem: to,
82 | editors,
83 | })
84 | }
85 |
86 | handleDragEnd = () => {
87 | this.setState({
88 | curMoveItem: null,
89 | })
90 | }
91 |
92 | clickOutline = (index) => {
93 | const {
94 | onClickOutline,
95 | } = this.props;
96 | if (onClickOutline) {
97 | onClickOutline(index)
98 | }
99 | }
100 |
101 | render() {
102 | this.tabs = [];
103 | const {
104 | onSelectEditor,
105 | } = this.props;
106 | const {
107 | tabIndex,
108 | editors,
109 | curMoveItem,
110 | } = this.state;
111 | return (
112 |
113 |
114 | {tabs.map((data, index) => {
115 | return (
116 |
this.toggleTab(index)}
120 | ref={(tab) => tab && this.tabs.push(tab)}>
121 | {data.name}
122 |
123 | )
124 | })}
125 |
126 |
127 |
128 | {subjects.map((data, index) => {
129 | return (
130 |
onSelectEditor(data.type)}>
134 |
135 | {data.name}
136 |
137 | )
138 | })}
139 |
140 |
141 |
146 | {editors.map((data, index) => {
147 | return (
148 | this.clickOutline(index)}
151 | style={{
152 | border: 'none',
153 | cursor: 'move'
154 | }}
155 | key={data.questionId}>
156 |
157 | {data.type === 'input' ? (
158 |
{data.completionForwards}____{data.completionBackwards}
159 | ) : (
160 |
{data.title}
161 | )}
162 |
167 |
168 |
169 | )
170 | })}
171 |
172 |
173 |
174 |
175 | )
176 | }
177 | }
178 |
179 | export default QuestionnairSiderbar;
--------------------------------------------------------------------------------
/src/QuestionnairSiderbar/index.less:
--------------------------------------------------------------------------------
1 | .questionnair-siderbar {
2 | position: absolute;
3 | bottom: 20px;
4 | top: 20px;
5 | left: 20px;
6 | width: 210px;
7 | display: flex;
8 | flex-direction: column;
9 | }
10 | .siderbar-tab {
11 | width: 100%;
12 | height: 30px;
13 | display: flex;
14 | position: relative;
15 | z-index: 1;
16 | }
17 | .tab-item {
18 | flex: 1;
19 | text-align: center;
20 | height: 30px;
21 | line-height: 30px;
22 | color: #666;
23 | background: #F7F7F7;
24 | border: 1px solid #e8e8e8;
25 | cursor: pointer;
26 | }
27 | .tab-item-active {
28 | box-shadow: 0 0 4px 0 rgba(202,202,202,0.50);
29 | border: 1px solid transparent;
30 | background: #fff;
31 | position: relative;
32 | &:before {
33 | content: '';
34 | position: absolute;
35 | top: -1px;
36 | left: -1px;
37 | right: -1px;
38 | height: 2px;
39 | background-color: #06AEA6;
40 | z-index: 1;
41 | }
42 | }
43 | .siderbar-menu {
44 | flex: 1;
45 | box-shadow: 0 3px 4px 0 rgba(202,202,202,0.50);
46 | background: #fff;
47 | z-index: 2;
48 | }
49 | .siderbar-menu-content {
50 | width: 100%;
51 | }
52 | .siderbar-menu-icon {
53 | margin-right: 10px;
54 | }
55 | .siderbar-menu-editors {
56 | width: 168px;
57 | height: 36px;
58 | border: 1px solid #06AEA6;
59 | color: #06AEA6;
60 | padding-left: 8px;
61 | margin: 15px auto;
62 | cursor: pointer;
63 | display: flex;
64 | align-items: center;
65 | position: relative;
66 | }
67 | .siderbar-menu-summary {
68 | width: 100%;
69 | height: 36px;
70 | color: #06AEA6;
71 | padding-left: 30px;
72 | padding-right: 10px;
73 | margin: 0 auto;
74 | cursor: pointer;
75 | display: flex;
76 | align-items: center;
77 | position: relative;
78 | }
79 | .summary-text {
80 | overflow: hidden;
81 | text-overflow: ellipsis;
82 | white-space: nowrap;
83 | }
84 | .summary-drag-mask {
85 | position: absolute;
86 | top: 0;
87 | left: 0;
88 | right: 0;
89 | bottom: 0;
90 | }
91 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Questionnair from './Questionnair';
2 | import Editor from './QuestionnairEditor';
3 | import Answer from './QuestionnairAnswer';
4 | import 'assets/iconfonts/iconfont.css';
5 |
6 | Questionnair.Editor = Editor;
7 | Questionnair.Answer = Answer;
8 |
9 | export default Questionnair;
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | render,
4 | } from 'react-dom';
5 | import Questionnair from './index.js';
6 |
7 | const index = require('./index');
8 |
9 | const renderDom = (Component) => {
10 | render(
11 | ,
12 | document.getElementById('app')
13 | );
14 | };
15 | renderDom(Questionnair);
16 | if (module.hot) {
17 | module.hot.accept('./index', () => {
18 | const QuestionnairEl = index.default;
19 | renderDom(QuestionnairEl);
20 | });
21 | }
--------------------------------------------------------------------------------
/utils/utils.js:
--------------------------------------------------------------------------------
1 | export default function uuid() {
2 | const s = [];
3 | const hexDigits = '0123456789abcdef';
4 | for (let i = 0; i < 36; i += 1) {
5 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
6 | }
7 | s[14] = '4';
8 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
9 | const id = s.join('');
10 | return id;
11 | }
--------------------------------------------------------------------------------
/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: [
7 | 'webpack-dev-server/client?http://localhost:9090',
8 | 'webpack/hot/only-dev-server',
9 | path.resolve(__dirname, '../src/main.js'),
10 | ],
11 | //指定入口文件,程序从这里开始编译,__dirname当前所在目录
12 | output: {
13 | path: path.resolve(__dirname, '../dist'),
14 | filename: 'bundle.js'
15 | },
16 | resolve: {
17 | extensions: ['.js', '.jsx'],
18 | alias: {
19 | 'libs': path.resolve(__dirname, '../libs'),
20 | 'assets': path.resolve(__dirname, '../assets'),
21 | 'utils': path.resolve(__dirname, '../utils'),
22 | }
23 | },
24 | module: {
25 | rules: [{
26 | test: /\.(js|jsx)$/,
27 | loader: ['babel-loader'],
28 | exclude: /node_modules/
29 | }, {
30 | test: /\.less$/,
31 | use: [
32 | 'style-loader', {
33 | loader: 'css-loader?modules',
34 | options: {
35 | importLoaders: 1
36 | }
37 | },
38 | 'less-loader'
39 | ]
40 | }, {
41 | test: /\.css$/,
42 | use: [
43 | 'style-loader', //模块运行时,使用