Commit 7544023f by chenshu

Merge branch 'master' into feature/chenshu/college

parents 2d7537f4 2f055add
......@@ -663,9 +663,9 @@ module.exports = function (webpackEnv) {
// The formatter is invoked directly in WebpackDevServerUtils during development
formatter: isEnvProduction ? typescriptFormatter : undefined,
}),
new vConsolePlugin({
enable: (process.env.DEPLOY_ENV === 'prod' || process.env.DEPLOY_ENV === 'beta') ? false : true
})
// new vConsolePlugin({
// enable: (process.env.DEPLOY_ENV === 'prod' || process.env.DEPLOY_ENV === 'beta') ? false : true
// })
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.
// Tell webpack to provide empty mocks for them so importing them works.
......
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"header": {
"reportVersion": 2,
"event": "Allocation failed - JavaScript heap out of memory",
"trigger": "FatalError",
"filename": "report.20210401.105323.55612.0.001.json",
"dumpEventTime": "2021-04-01T10:53:23Z",
"dumpEventTimeStamp": "1617245603487",
"processId": 55612,
"threadId": null,
"cwd": "C:\\Users\\admin\\xiaomai\\xiaomai-cloud-class-web",
"commandLine": [
"node",
"scripts/start.js"
],
"nodejsVersion": "v12.16.3",
"wordSize": 64,
"arch": "x64",
"platform": "win32",
"componentVersions": {
"node": "12.16.3",
"v8": "7.8.279.23-node.35",
"uv": "1.34.2",
"zlib": "1.2.11",
"brotli": "1.0.7",
"ares": "1.16.0",
"modules": "72",
"nghttp2": "1.40.0",
"napi": "5",
"llhttp": "2.0.4",
"http_parser": "2.9.3",
"openssl": "1.1.1g",
"cldr": "36.0",
"icu": "65.1",
"tz": "2019c",
"unicode": "12.1"
},
"release": {
"name": "node",
"lts": "Erbium",
"headersUrl": "https://nodejs.org/download/release/v12.16.3/node-v12.16.3-headers.tar.gz",
"sourceUrl": "https://nodejs.org/download/release/v12.16.3/node-v12.16.3.tar.gz",
"libUrl": "https://nodejs.org/download/release/v12.16.3/win-x64/node.lib"
},
"osName": "Windows_NT",
"osRelease": "10.0.18363",
"osVersion": "Windows 10 Pro",
"osMachine": "x86_64",
"cpus": [
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 5435843,
"nice": 0,
"sys": 5018328,
"idle": 215171078,
"irq": 1418281
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 2509578,
"nice": 0,
"sys": 1841718,
"idle": 221273656,
"irq": 38343
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 8413968,
"nice": 0,
"sys": 5013765,
"idle": 212197218,
"irq": 93453
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 3652062,
"nice": 0,
"sys": 2196062,
"idle": 219776828,
"irq": 43203
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 4915125,
"nice": 0,
"sys": 2809156,
"idle": 217900671,
"irq": 78109
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 2340328,
"nice": 0,
"sys": 1177750,
"idle": 222106875,
"irq": 24656
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 4430078,
"nice": 0,
"sys": 2128781,
"idle": 219066093,
"irq": 51734
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 3844562,
"nice": 0,
"sys": 1357375,
"idle": 220423015,
"irq": 26500
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 11182953,
"nice": 0,
"sys": 2309000,
"idle": 212132984,
"irq": 76109
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 3822796,
"nice": 0,
"sys": 1150843,
"idle": 220651296,
"irq": 80671
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 5035984,
"nice": 0,
"sys": 8298203,
"idle": 212290750,
"irq": 63218
},
{
"model": "Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz",
"speed": 3192,
"user": 5453875,
"nice": 0,
"sys": 1530250,
"idle": 218640812,
"irq": 47968
}
],
"networkInterfaces": [
{
"name": "以太网",
"internal": false,
"mac": "a8:5e:45:b4:05:40",
"address": "fe80::c4af:8af2:8435:4f1",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 14
},
{
"name": "以太网",
"internal": false,
"mac": "a8:5e:45:b4:05:40",
"address": "10.0.1.151",
"netmask": "255.255.254.0",
"family": "IPv4"
},
{
"name": "Loopback Pseudo-Interface 1",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "::1",
"netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
"family": "IPv6",
"scopeid": 0
},
{
"name": "Loopback Pseudo-Interface 1",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "127.0.0.1",
"netmask": "255.0.0.0",
"family": "IPv4"
}
],
"host": "DESKTOP-C38ISPA"
},
"javascriptStack": {
"message": "No stack.",
"stack": [
"Unavailable."
]
},
"nativeStack": [
{
"pc": "0x00007ff6d7722449",
"symbol": "std::basic_ostream<char,std::char_traits<char> >::operator<<+11577"
},
{
"pc": "0x00007ff6d77269a9",
"symbol": "std::basic_ostream<char,std::char_traits<char> >::operator<<+29337"
},
{
"pc": "0x00007ff6d7725828",
"symbol": "std::basic_ostream<char,std::char_traits<char> >::operator<<+24856"
},
{
"pc": "0x00007ff6d785d8c2",
"symbol": "v8::base::CPU::has_sse+68658"
},
{
"pc": "0x00007ff6d8079bbe",
"symbol": "v8::Isolate::ReportExternalAllocationLimitReached+94"
},
{
"pc": "0x00007ff6d8061c91",
"symbol": "v8::SharedArrayBuffer::Externalize+833"
},
{
"pc": "0x00007ff6d7f2e1ec",
"symbol": "v8::internal::Heap::EphemeronKeyWriteBarrierFromCode+1436"
},
{
"pc": "0x00007ff6d7f39420",
"symbol": "v8::internal::Heap::ProtectUnprotectedMemoryChunks+1312"
},
{
"pc": "0x00007ff6d7f35f44",
"symbol": "v8::internal::Heap::PageFlagsAreConsistent+3204"
},
{
"pc": "0x00007ff6d7f2b743",
"symbol": "v8::internal::Heap::CollectGarbage+1283"
},
{
"pc": "0x00007ff6d7f29db4",
"symbol": "v8::internal::Heap::AddRetainedMap+2452"
},
{
"pc": "0x00007ff6d7f4afbd",
"symbol": "v8::internal::Factory::NewFillerObject+61"
},
{
"pc": "0x00007ff6d7cb1871",
"symbol": "v8::internal::interpreter::JumpTableTargetOffsets::iterator::operator=+1665"
},
{
"pc": "0x00007ff6d84c6c4d",
"symbol": "v8::internal::SetupIsolateDelegate::SetupHeap+546637"
},
{
"pc": "0x00007ff6d84c7b7d",
"symbol": "v8::internal::SetupIsolateDelegate::SetupHeap+550525"
},
{
"pc": "0x000001a4f68ed9d9",
"symbol": ""
}
],
"javascriptHeap": {
"totalMemory": 2179272704,
"totalCommittedMemory": 2179272704,
"usedMemory": 2103855312,
"availableMemory": 48911208,
"memoryLimit": 2197815296,
"heapSpaces": {
"read_only_space": {
"memorySize": 262144,
"committedMemory": 262144,
"capacity": 32808,
"used": 32808,
"available": 0
},
"new_space": {
"memorySize": 33554432,
"committedMemory": 33554432,
"capacity": 16759296,
"used": 3562272,
"available": 13197024
},
"old_space": {
"memorySize": 1969369088,
"committedMemory": 1969369088,
"capacity": 1928135560,
"used": 1927723264,
"available": 412296
},
"code_space": {
"memorySize": 3837952,
"committedMemory": 3837952,
"capacity": 3253792,
"used": 3253792,
"available": 0
},
"map_space": {
"memorySize": 7081984,
"committedMemory": 7081984,
"capacity": 4403680,
"used": 4403680,
"available": 0
},
"large_object_space": {
"memorySize": 164544512,
"committedMemory": 164544512,
"capacity": 164335208,
"used": 164335208,
"available": 0
},
"code_large_object_space": {
"memorySize": 622592,
"committedMemory": 622592,
"capacity": 544288,
"used": 544288,
"available": 0
},
"new_large_object_space": {
"memorySize": 0,
"committedMemory": 0,
"capacity": 16759296,
"used": 0,
"available": 16759296
}
}
},
"resourceUsage": {
"userCpuSeconds": 294.906,
"kernelCpuSeconds": 222.421,
"cpuConsumptionPercent": 269.441,
"maxRss": 2946191360,
"pageFaults": {
"IORequired": 2303672,
"IONotRequired": 0
},
"fsActivity": {
"reads": 15688,
"writes": 268
}
},
"libuv": [
],
"workers": [
],
"environmentVariables": {
"=C:": "C:\\Users\\admin\\xiaomai\\xiaomai-cloud-class-web",
"ALLUSERSPROFILE": "C:\\ProgramData",
"APPDATA": "C:\\Users\\admin\\AppData\\Roaming",
"BABEL_ENV": "development",
"ChocolateyInstall": "C:\\ProgramData\\chocolatey",
"ChocolateyLastPathUpdate": "132489353938825301",
"CommonProgramFiles": "C:\\Program Files\\Common Files",
"CommonProgramFiles(x86)": "C:\\Program Files (x86)\\Common Files",
"CommonProgramW6432": "C:\\Program Files\\Common Files",
"COMPUTERNAME": "DESKTOP-C38ISPA",
"ComSpec": "C:\\Windows\\system32\\cmd.exe",
"DriverData": "C:\\Windows\\System32\\Drivers\\DriverData",
"FPS_BROWSER_APP_PROFILE_STRING": "Internet Explorer",
"FPS_BROWSER_USER_PROFILE_STRING": "Default",
"HOME": "C:\\Users\\admin",
"HOMEDRIVE": "C:",
"HOMEPATH": "\\Users\\admin",
"INIT_CWD": "C:\\Users\\admin\\xiaomai\\xiaomai-cloud-class-web",
"JAVA_HOME": "C:\\Program Files\\Java\\jdk1.8.0_281",
"LOCALAPPDATA": "C:\\Users\\admin\\AppData\\Local",
"LOGONSERVER": "\\\\DESKTOP-C38ISPA",
"NODE": "C:\\Program Files\\nodejs\\node.exe",
"NODE_ENV": "development",
"NODE_EXE": "C:\\Program Files\\nodejs\\\\node.exe",
"NODE_PATH": "",
"NPM_CLI_JS": "C:\\Program Files\\nodejs\\\\node_modules\\npm\\bin\\npm-cli.js",
"npm_config_access": "",
"npm_config_allow_same_version": "",
"npm_config_also": "",
"npm_config_always_auth": "",
"npm_config_argv": "{\"remain\":[],\"cooked\":[\"run\",\"start\"],\"original\":[\"run\",\"start\"]}",
"npm_config_audit": "true",
"npm_config_audit_level": "low",
"npm_config_auth_type": "legacy",
"npm_config_before": "",
"npm_config_bin_links": "true",
"npm_config_browser": "",
"npm_config_ca": "",
"npm_config_cache": "C:\\Users\\admin\\AppData\\Roaming\\npm-cache",
"npm_config_cache_lock_retries": "10",
"npm_config_cache_lock_stale": "60000",
"npm_config_cache_lock_wait": "10000",
"npm_config_cache_max": "Infinity",
"npm_config_cache_min": "10",
"npm_config_cafile": "",
"npm_config_cert": "",
"npm_config_cidr": "",
"npm_config_color": "true",
"npm_config_commit_hooks": "true",
"npm_config_depth": "Infinity",
"npm_config_description": "true",
"npm_config_dev": "",
"npm_config_dry_run": "",
"npm_config_editor": "notepad.exe",
"npm_config_engine_strict": "",
"npm_config_fetch_retries": "2",
"npm_config_fetch_retry_factor": "10",
"npm_config_fetch_retry_maxtimeout": "60000",
"npm_config_fetch_retry_mintimeout": "10000",
"npm_config_force": "",
"npm_config_format_package_lock": "true",
"npm_config_fund": "true",
"npm_config_git": "git",
"npm_config_git_tag_version": "true",
"npm_config_global": "",
"npm_config_globalconfig": "C:\\Users\\admin\\AppData\\Roaming\\npm\\etc\\npmrc",
"npm_config_globalignorefile": "C:\\Users\\admin\\AppData\\Roaming\\npm\\etc\\npmignore",
"npm_config_global_style": "",
"npm_config_group": "",
"npm_config_ham_it_up": "",
"npm_config_heading": "npm",
"npm_config_https_proxy": "",
"npm_config_if_present": "",
"npm_config_ignore_prepublish": "",
"npm_config_ignore_scripts": "",
"npm_config_init_author_email": "",
"npm_config_init_author_name": "",
"npm_config_init_author_url": "",
"npm_config_init_license": "ISC",
"npm_config_init_module": "C:\\Users\\admin\\.npm-init.js",
"npm_config_init_version": "1.0.0",
"npm_config_json": "",
"npm_config_key": "",
"npm_config_legacy_bundling": "",
"npm_config_link": "",
"npm_config_local_address": "",
"npm_config_loglevel": "notice",
"npm_config_logs_max": "10",
"npm_config_long": "",
"npm_config_maxsockets": "50",
"npm_config_message": "%s",
"npm_config_metrics_registry": "https://registry.npmjs.org/",
"npm_config_node_gyp": "C:\\Program Files\\nodejs\\node_modules\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js",
"npm_config_node_options": "",
"npm_config_node_version": "12.16.3",
"npm_config_noproxy": "",
"npm_config_offline": "",
"npm_config_onload_script": "",
"npm_config_only": "",
"npm_config_optional": "true",
"npm_config_otp": "",
"npm_config_package_lock": "true",
"npm_config_package_lock_only": "",
"npm_config_parseable": "",
"npm_config_prefer_offline": "",
"npm_config_prefer_online": "",
"npm_config_prefix": "C:\\Users\\admin\\AppData\\Roaming\\npm",
"npm_config_preid": "",
"npm_config_production": "",
"npm_config_progress": "true",
"npm_config_proxy": "",
"npm_config_read_only": "",
"npm_config_rebuild_bundle": "true",
"npm_config_registry": "https://registry.npmjs.org/",
"npm_config_rollback": "true",
"npm_config_save": "true",
"npm_config_save_bundle": "",
"npm_config_save_dev": "",
"npm_config_save_exact": "",
"npm_config_save_optional": "",
"npm_config_save_prefix": "^",
"npm_config_save_prod": "",
"npm_config_scope": "",
"npm_config_scripts_prepend_node_path": "warn-only",
"npm_config_script_shell": "",
"npm_config_searchexclude": "",
"npm_config_searchlimit": "20",
"npm_config_searchopts": "",
"npm_config_searchstaleness": "900",
"npm_config_send_metrics": "",
"npm_config_shell": "C:\\Windows\\system32\\cmd.exe",
"npm_config_shrinkwrap": "true",
"npm_config_sign_git_commit": "",
"npm_config_sign_git_tag": "",
"npm_config_sso_poll_frequency": "500",
"npm_config_sso_type": "oauth",
"npm_config_strict_ssl": "true",
"npm_config_tag": "latest",
"npm_config_tag_version_prefix": "v",
"npm_config_timing": "",
"npm_config_tmp": "C:\\Users\\admin\\AppData\\Local\\Temp",
"npm_config_umask": "0000",
"npm_config_unicode": "",
"npm_config_unsafe_perm": "true",
"npm_config_update_notifier": "true",
"npm_config_usage": "",
"npm_config_user": "",
"npm_config_userconfig": "C:\\Users\\admin\\.npmrc",
"npm_config_user_agent": "npm/6.14.4 node/v12.16.3 win32 x64",
"npm_config_version": "",
"npm_config_versions": "",
"npm_config_viewer": "browser",
"npm_execpath": "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js",
"npm_lifecycle_event": "start",
"npm_lifecycle_script": "node scripts/start.js",
"npm_node_execpath": "C:\\Program Files\\nodejs\\node.exe",
"npm_package_babel_plugins_0": "jsx-control-statements",
"npm_package_babel_presets_0": "react-app",
"npm_package_browserslist_development_0": "last 1 chrome version",
"npm_package_browserslist_development_1": "last 1 firefox version",
"npm_package_browserslist_development_2": "last 1 safari version",
"npm_package_browserslist_production_0": ">0.2%",
"npm_package_browserslist_production_1": "not dead",
"npm_package_browserslist_production_2": "not op_mini all",
"npm_package_dependencies_ali_oss": "^6.12.0",
"npm_package_dependencies_antd": "^4.9.4",
"npm_package_dependencies_array_move": "^3.0.1",
"npm_package_dependencies_axios": "^0.20.0",
"npm_package_dependencies_babel_eslint": "10.1.0",
"npm_package_dependencies_babel_jest": "^24.9.0",
"npm_package_dependencies_babel_loader": "8.1.0",
"npm_package_dependencies_babel_plugin_jsx_control_statements": "^4.1.0",
"npm_package_dependencies_babel_plugin_named_asset_import": "^0.3.6",
"npm_package_dependencies_babel_preset_react_app": "^9.1.2",
"npm_package_dependencies_bizcharts": "^4.1.7",
"npm_package_dependencies_camelcase": "^5.3.1",
"npm_package_dependencies_case_sensitive_paths_webpack_plugin": "2.3.0",
"npm_package_dependencies_classnames": "^2.2.6",
"npm_package_dependencies_cropper": "^3.1.4",
"npm_package_dependencies_cross_env": "^7.0.2",
"npm_package_dependencies_css_loader": "3.4.2",
"npm_package_dependencies_dom_to_image": "^2.6.0",
"npm_package_dependencies_dotenv": "8.2.0",
"npm_package_dependencies_dotenv_expand": "5.1.0",
"npm_package_dependencies_eslint": "^6.6.0",
"npm_package_dependencies_eslint_config_react_app": "^5.2.1",
"npm_package_dependencies_eslint_loader": "3.0.3",
"npm_package_dependencies_eslint_plugin_flowtype": "4.6.0",
"npm_package_dependencies_eslint_plugin_import": "2.20.1",
"npm_package_dependencies_eslint_plugin_jsx_a11y": "6.2.3",
"npm_package_dependencies_eslint_plugin_jsx_control_statements": "^2.2.1",
"npm_package_dependencies_eslint_plugin_react": "7.19.0",
"npm_package_dependencies_eslint_plugin_react_hooks": "^1.6.1",
"npm_package_dependencies_file_loader": "4.3.0",
"npm_package_dependencies_fs_extra": "^8.1.0",
"npm_package_dependencies_html2canvas": "^1.0.0-rc.7",
"npm_package_dependencies_html_webpack_plugin": "4.0.0-beta.11",
"npm_package_dependencies_husky": "^4.2.5",
"npm_package_dependencies_identity_obj_proxy": "3.0.0",
"npm_package_dependencies_jest": "24.9.0",
"npm_package_dependencies_jest_environment_jsdom_fourteen": "1.0.1",
"npm_package_dependencies_jest_resolve": "24.9.0",
"npm_package_dependencies_jest_watch_typeahead": "0.4.2",
"npm_package_dependencies_jquery": "^3.4.1",
"npm_package_dependencies_less": "^3.12.2",
"npm_package_dependencies_less_loader": "^6.2.0",
"npm_package_dependencies_lottie_web": "^5.7.6",
"npm_package_dependencies_microevent": "^1.0.0",
"npm_package_dependencies_mini_css_extract_plugin": "0.9.0",
"npm_package_dependencies_moment": "^2.27.0",
"npm_package_dependencies_optimize_css_assets_webpack_plugin": "5.0.3",
"npm_package_dependencies_pnp_webpack_plugin": "1.6.4",
"npm_package_dependencies_postcss_flexbugs_fixes": "4.1.0",
"npm_package_dependencies_postcss_loader": "3.0.0",
"npm_package_dependencies_postcss_normalize": "8.0.1",
"npm_package_dependencies_postcss_preset_env": "6.7.0",
"npm_package_dependencies_postcss_safe_parser": "4.0.1",
"npm_package_dependencies_prop_types": "^15.7.2",
"npm_package_dependencies_qs": "^6.9.4",
"npm_package_dependencies_react": "^16.13.1",
"npm_package_dependencies_react_app_polyfill": "^1.0.6",
"npm_package_dependencies_react_async_component": "^2.0.0",
"npm_package_dependencies_react_dev_utils": "^10.2.1",
"npm_package_dependencies_react_dom": "^16.13.1",
"npm_package_dependencies_react_infinite_scroller": "^1.2.4",
"npm_package_dependencies_react_lottie": "^1.2.3",
"npm_package_dependencies_react_router_dom": "^5.2.0",
"npm_package_dependencies_react_sortable_hoc": "^1.11.0",
"npm_package_dependencies_resolve": "1.15.0",
"npm_package_dependencies_resolve_url_loader": "3.1.1",
"npm_package_dependencies_semver": "6.3.0",
"npm_package_dependencies_style_loader": "0.23.1",
"npm_package_dependencies_terser_webpack_plugin": "2.3.8",
"npm_package_dependencies_ts_pnp": "1.1.6",
"npm_package_dependencies_typescript": "^4.0.2",
"npm_package_dependencies_underscore": "^1.10.2",
"npm_package_dependencies_url_loader": "2.3.0",
"npm_package_dependencies_video_react": "0.14.1",
"npm_package_dependencies_wangeditor": "^4.6.9",
"npm_package_dependencies_webpack": "4.42.0",
"npm_package_dependencies_webpack_dev_server": "3.11.0",
"npm_package_dependencies_webpack_manifest_plugin": "2.2.0",
"npm_package_dependencies_workbox_webpack_plugin": "4.3.1",
"npm_package_dependencies_xiaomai_b_components": "1.4.4",
"npm_package_dependencies__antv_data_set": "^0.11.8",
"npm_package_dependencies__antv_g2": "^3.5.13",
"npm_package_dependencies__babel_core": "7.9.0",
"npm_package_dependencies__babel_plugin_transform_typescript": "^7.11.0",
"npm_package_dependencies__babel_preset_typescript": "^7.10.4",
"npm_package_dependencies__svgr_webpack": "4.3.3",
"npm_package_dependencies__testing_library_jest_dom": "^4.2.4",
"npm_package_dependencies__testing_library_react": "^9.3.2",
"npm_package_dependencies__testing_library_user_event": "^7.1.2",
"npm_package_dependencies__typescript_eslint_eslint_plugin": "^2.10.0",
"npm_package_dependencies__typescript_eslint_parser": "^2.10.0",
"npm_package_dependencies__types_ali_oss": "^6.0.5",
"npm_package_dependencies__types_qs": "^6.9.5",
"npm_package_dependencies__types_react": "^16.9.46",
"npm_package_dependencies__types_react_dom": "^16.9.8",
"npm_package_dependencies__types_react_router_dom": "^5.1.5",
"npm_package_dependencies__types_underscore": "^1.10.22",
"npm_package_description": "This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).",
"npm_package_devDependencies_ali_oss": "^6.12.0",
"npm_package_devDependencies_react_sortable_hoc": "^1.11.0",
"npm_package_devDependencies_vconsole_webpack_plugin": "^1.5.2",
"npm_package_devDependencies__types_ali_oss": "^6.0.5",
"npm_package_devDependencies__types_jquery": "^3.5.4",
"npm_package_eslintConfig_extends": "react-app",
"npm_package_gitHead": "4898b7f545fa2037fac5e8ed81077e8e3806fb7c",
"npm_package_homepage": "http://./",
"npm_package_name": "cloudclass",
"npm_package_private": "true",
"npm_package_readmeFilename": "README.md",
"npm_package_scripts_build_dev": "cross-env DEPLOY_ENV=dev node scripts/build.js",
"npm_package_scripts_build_dev1": "cross-env DEPLOY_ENV=dev node scripts/build.js",
"npm_package_scripts_build_gray": "cross-env DEPLOY_ENV=gray node scripts/build.js",
"npm_package_scripts_build_prod": "cross-env DEPLOY_ENV=prod node scripts/build.js",
"npm_package_scripts_build_rc": "cross-env DEPLOY_ENV=rc node scripts/build.js",
"npm_package_scripts_start": "node scripts/start.js",
"npm_package_version": "0.1.0",
"NPM_PREFIX_NPM_CLI_JS": "C:\\Users\\admin\\AppData\\Roaming\\npm\\node_modules\\npm\\bin\\npm-cli.js",
"NUMBER_OF_PROCESSORS": "12",
"OneDrive": "C:\\Users\\admin\\OneDrive",
"OS": "Windows_NT",
"Path": "C:\\Program Files\\nodejs\\node_modules\\npm\\node_modules\\npm-lifecycle\\node-gyp-bin;C:\\Users\\admin\\xiaomai\\xiaomai-cloud-class-web\\node_modules\\.bin;C:\\Users\\admin\\AppData\\Roaming\\npm;C:\\Program Files\\nodejs\\;C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Windows\\System32\\OpenSSH\\;C:\\应用\\Git\\cmd;C:\\Program Files\\nodejs\\;C:\\ProgramData\\chocolatey\\bin;\\;C:\\Program Files (x86)\\Yozosoft\\Yozo_Office\\Lib\\xuggler\\bin;C:\\Program Files\\Java\\jdk1.8.0_281\\bin;C:\\Users\\admin\\.cargo\\bin;C:\\Users\\admin\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Users\\admin\\AppData\\Roaming\\npm;C:\\Users\\admin\\AppData\\Local\\Programs\\Microsoft VS Code\\bin;C:\\Users\\admin\\AppData\\Local\\GitHubDesktop\\bin",
"PATHEXT": ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC",
"PROCESSOR_ARCHITECTURE": "AMD64",
"PROCESSOR_IDENTIFIER": "Intel64 Family 6 Model 158 Stepping 10, GenuineIntel",
"PROCESSOR_LEVEL": "6",
"PROCESSOR_REVISION": "9e0a",
"ProgramData": "C:\\ProgramData",
"ProgramFiles": "C:\\Program Files",
"ProgramFiles(x86)": "C:\\Program Files (x86)",
"ProgramW6432": "C:\\Program Files",
"PROMPT": "$P$G",
"PSModulePath": "C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules",
"PUBLIC": "C:\\Users\\Public",
"SESSIONNAME": "Console",
"SystemDrive": "C:",
"SystemRoot": "C:\\Windows",
"TEMP": "C:\\Users\\admin\\AppData\\Local\\Temp",
"TMP": "C:\\Users\\admin\\AppData\\Local\\Temp",
"USERDOMAIN": "DESKTOP-C38ISPA",
"USERDOMAIN_ROAMINGPROFILE": "DESKTOP-C38ISPA",
"USERNAME": "admin",
"USERPROFILE": "C:\\Users\\admin",
"WEBPACK_DEV_SERVER": "true",
"windir": "C:\\Windows",
"WXDRIVE_START_ARGS": "--wxdrive-setting=0 --disable-gpu --disable-software-rasterizer --enable-features=NetworkServiceInProcess",
"YOZO_HOME": "C:\\Program Files (x86)\\Yozosoft\\Yozo_Office",
"ZES_ENABLE_SYSMAN": "1"
},
"sharedObjects": [
"C:\\Program Files\\nodejs\\node.exe",
"C:\\Windows\\SYSTEM32\\ntdll.dll",
"C:\\Windows\\System32\\KERNEL32.DLL",
"C:\\Windows\\System32\\KERNELBASE.dll",
"C:\\Windows\\System32\\WS2_32.dll",
"C:\\Windows\\System32\\RPCRT4.dll",
"C:\\Windows\\System32\\PSAPI.DLL",
"C:\\Windows\\System32\\ADVAPI32.dll",
"C:\\Windows\\SYSTEM32\\dbghelp.dll",
"C:\\Windows\\System32\\msvcrt.dll",
"C:\\Windows\\System32\\ucrtbase.dll",
"C:\\Windows\\System32\\sechost.dll",
"C:\\Windows\\System32\\USER32.dll",
"C:\\Windows\\SYSTEM32\\IPHLPAPI.DLL",
"C:\\Windows\\SYSTEM32\\USERENV.dll",
"C:\\Windows\\System32\\win32u.dll",
"C:\\Windows\\System32\\GDI32.dll",
"C:\\Windows\\System32\\profapi.dll",
"C:\\Windows\\System32\\gdi32full.dll",
"C:\\Windows\\System32\\msvcp_win.dll",
"C:\\Windows\\System32\\CRYPT32.dll",
"C:\\Windows\\System32\\MSASN1.dll",
"C:\\Windows\\System32\\bcrypt.dll",
"C:\\Windows\\SYSTEM32\\WINMM.dll",
"C:\\Windows\\SYSTEM32\\winmmbase.dll",
"C:\\Windows\\System32\\cfgmgr32.dll",
"C:\\Windows\\System32\\bcryptPrimitives.dll",
"C:\\Windows\\System32\\IMM32.DLL",
"C:\\Windows\\System32\\powrprof.dll",
"C:\\Windows\\System32\\UMPDC.dll",
"C:\\Windows\\SYSTEM32\\CRYPTBASE.DLL",
"C:\\Windows\\system32\\uxtheme.dll",
"C:\\Windows\\System32\\combase.dll",
"C:\\Windows\\system32\\mswsock.dll",
"C:\\Windows\\System32\\kernel.appcore.dll",
"C:\\Windows\\System32\\NSI.dll",
"C:\\Windows\\SYSTEM32\\dhcpcsvc6.DLL",
"C:\\Windows\\SYSTEM32\\dhcpcsvc.DLL",
"C:\\Windows\\SYSTEM32\\DNSAPI.dll",
"C:\\Windows\\system32\\napinsp.dll",
"C:\\Windows\\system32\\pnrpnsp.dll",
"C:\\Windows\\System32\\winrnr.dll",
"C:\\Windows\\system32\\NLAapi.dll",
"C:\\Windows\\system32\\wshbth.dll",
"C:\\Program Files\\Bonjour\\mdnsNSP.dll",
"C:\\Windows\\System32\\rasadhlp.dll",
"C:\\Windows\\System32\\fwpuclnt.dll"
]
}
\ No newline at end of file
......@@ -67,3 +67,5 @@ declare module '*.module.less' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '@/components/GraphicsEditor'
\ No newline at end of file
......@@ -16,6 +16,25 @@ class Permission {
return this.permissionCodes.includes('ShopStaff');
}
CreateNewExam() {
return this.permissionCodes.includes('AddExam');
}
export default Permission;
// AddExam() { //创建考试
// return this.permissionCodes.includes('AddExam');
// }
SeeExamData() { // 查看考试数据
return this.permissionCodes.includes('SeeExamData');
}
DelExam() { // 删除考试
return this.permissionCodes.includes('SeeExamData');
}
}
Permission.prototype.AddExam = function () { //创建考试
return this.permissionCodes.includes('AddExam');
}
export default Permission;
......@@ -11,6 +11,7 @@ import CheckBox from './CheckBox.tsx';
import CropperModal from './CropperModal.tsx';
import ImgCutModalNew from './ImgCutModalNew';
export {
SearchBar,
PageControl,
......
.xm-search-filter {
position: relative;
.ant-input-search-button{
border-left:none;
}
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 12px;
display: flex;
.search-name{
vertical-align: middle;
display:inline-block;
height:32px;
line-height:32px;
margin-right: 4px;
}
.search-input{
flex:1;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
}
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left:4px;
}
}
}
\ No newline at end of file
......@@ -33,7 +33,7 @@ if (!Number.prototype.toFixedCorrect) {
window.processMonthCourseDate = courseList => {
if (Array.isArray(courseList) && courseList.length > 0) {
// 因为所购买的时间不可能有重叠,先排序
courseList = courseList.sort(function(now, next) {
courseList = courseList.sort(function (now, next) {
return parseInt(now.startDate) - parseInt(next.startDate)
})
......@@ -41,7 +41,7 @@ window.processMonthCourseDate = courseList => {
let sumDays = 0,
finishDays = 0,
givingDays = 0
courseList.forEach(function(item) {
courseList.forEach(function (item) {
if (item.endDate != item.purchaseEndDate) {
givingDays += moment(parseInt(item.endDate)).diff(
parseInt(item.purchaseEndDate),
......@@ -327,7 +327,7 @@ window.getRequest = () => {
return theRequest
}
window.getParameterByName = function(name) {
window.getParameterByName = function (name) {
name = name.replace(/[\\[]/, '\\[').replace(/[\]]/, '\\]')
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)')
const results = regex.exec(window.location.href)
......@@ -344,7 +344,7 @@ window.getUrlParam = function () {
return isNaN(param) ? '' : str;
}
window.getStringParamete = function(string, name) {
window.getStringParamete = function (string, name) {
const theRequest = {}
if (string.indexOf('?') != -1) {
const str = string.substr(1)
......@@ -530,6 +530,14 @@ window.getMonthLastDate = timestamp => {
return time
}
window.formatHourTime = (timestr) => {
const h = Math.floor(timestr / 1000 / 60 / 60);
const m = Math.floor((timestr - h * 1000 * 60 * 60) / 1000 / 60);
const s = Math.floor((timestr - h * 1000 * 60 * 60 - m * 1000 * 60) / 1000);
return `${h > 9 ? h : ('0' + h)}:${m > 9 ? m : ('0' + m)}:${s > 9 ? s : ('0' + s)}`
}
/*
获取日期相差多少天
* */
......@@ -832,7 +840,7 @@ window.getNumberInput = (str, ret = 1) => {
return str * ret
}
window.removeNull = function(param) {
window.removeNull = function (param) {
if (!param) {
return
}
......@@ -853,7 +861,7 @@ window.removeNull = function(param) {
}
}
window.convertBase64ToBlob = function(base64) {
window.convertBase64ToBlob = function (base64) {
var base64Arr = base64.split(',')
var imgtype = ''
var base64String = ''
......@@ -881,7 +889,7 @@ window.convertBase64ToBlob = function(base64) {
return new Blob([bytesCode], { type: imgtype })
}
window.formatMsgCreateTime = function(createTime) {
window.formatMsgCreateTime = function (createTime) {
const newTime =
moment().year() > moment(parseInt(createTime)).year()
? formatDate('YYYY-MM-DD H:i', parseInt(createTime))
......@@ -1070,7 +1078,7 @@ window.getMoneyFormatYuan = (price) => {
// 处理定价标准 仅订单详情,打印收据预览可用
window.formatPriceStandard = (info) => {
let { quantityUnit, specName, commoditySpecs, purchaseItemType, unitPriceAfterDiscount,specCurrentPrice } = info
let { quantityUnit, specName, commoditySpecs, purchaseItemType, unitPriceAfterDiscount, specCurrentPrice } = info
let newCommoditySpecs = JSON.parse(commoditySpecs || '[]') || {}
let { containQuantity, unitPrice, measurementUnits } = newCommoditySpecs
let content = ''
......@@ -1145,9 +1153,9 @@ window.convertBase64UrlToBlob = (urlData) => {
}
// 小麦秀 new 标签显示
window.XMShowClassName = (date,itemName) => {
window.XMShowClassName = (date, itemName) => {
// 超过时间不显示
if (new Date().getTime() > date ) {
if (new Date().getTime() > date) {
return '';
}
// 用户点击相应区域后不显示
......
......@@ -1665,3 +1665,38 @@ input:focus {
.row-dragging .drag-visible {
visibility: visible;
}
.oneLineText{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table_operate {
display: flex;
align-items: center;
flex-wrap: wrap;
.operate-text {
color: #5289FA;
cursor: pointer;
}
.operate__item {
color: #5289FA;
cursor: pointer;
&.split {
margin: 0 8px;
color: #BFBFBF;
}
}
.more{
color: #5289FA;
cursor: pointer;
cursor: pointer;
}
}
.ant-tag-blue{
background: transparent !important;
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-03-03 15:13:12
* @LastEditors: yuananting
* @LastEditTime: 2021-03-17 11:40:41
* @LastEditTime: 2021-04-13 13:58:40
* @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -29,10 +29,6 @@ export function editCategoryTree(params: object) {
return Service.Hades("public/hades/editCategoryTree", params);
}
export function queryQuestionCategoryTree(params: object) {
return Service.Hades("public/hades/queryQuestionCategoryTree", params);
}
export function queryQuestionPageList(params: object) {
return Service.Hades("public/hades/queryQuestionPageList", params);
}
......@@ -56,3 +52,35 @@ export function editQuestion(params: object) {
export function batchImport(params: object) {
return Service.Hades("public/hades/batchImport", params);
}
export function createPaper(params: object) {
return Service.Hades("public/hades/createPaper", params);
}
export function queryPaperPageList(params: object) {
return Service.Hades("public/hades/queryPaperPageList", params);
}
export function deletePaper(params: object) {
return Service.Hades("public/hades/deletePaper", params);
}
export function queryPaperDetail(params: object) {
return Service.Hades("public/hades/queryPaperDetail", params);
}
export function viewPaper(params: object) {
return Service.Hades("public/hades/viewPaper", params);
}
export function editPaper(params: object) {
return Service.Hades("public/hades/editPaper", params);
}
export function batchQueryQuestionDetails(params: object) {
return Service.Hades("public/hades/batchQueryQuestionDetails", params);
}
export function queryQuestionPageListWithContent(params: object) {
return Service.Hades("public/hades/queryQuestionPageListWithContent", params);
}
\ No newline at end of file
......@@ -2,12 +2,32 @@
* @Author: yuananting
* @Date: 2021-03-11 11:34:37
* @LastEditors: yuananting
* @LastEditTime: 2021-03-16 15:12:09
* @Description: 描述一下咯
* @LastEditTime: 2021-04-13 13:58:11
* @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import { queryCategoryTree, addCategory, delCategory, editCategory, editCategoryTree, queryQuestionCategoryTree, addQuestion, queryQuestionPageList, deleteQuestion, queryQuestionDetails, editQuestion, batchImport } from '@/data-source/questionBank/request-apis';
export default class QuestionBankService {
import {
queryCategoryTree,
addCategory,
delCategory,
editCategory,
editCategoryTree,
addQuestion,
queryQuestionPageList,
deleteQuestion,
queryQuestionDetails,
editQuestion,
batchImport,
createPaper,
queryPaperPageList,
deletePaper,
queryPaperDetail,
viewPaper,
editPaper,
batchQueryQuestionDetails,
queryQuestionPageListWithContent,
} from '@/data-source/aidTool/request-apis';
export default class AidToolService {
// 获取题目分类树
static queryCategoryTree(params: any) {
return queryCategoryTree(params);
......@@ -33,11 +53,6 @@ export default class QuestionBankService {
return editCategoryTree(params);
}
// 查询分类树列表
static queryQuestionCategoryTree(params: any) {
return queryQuestionCategoryTree(params);
}
// 查询题目列表
static queryQuestionPageList(params: any) {
return queryQuestionPageList(params);
......@@ -67,4 +82,44 @@ export default class QuestionBankService {
static batchImport(params: any) {
return batchImport(params);
}
// 创建试卷
static createPaper(params: any) {
return createPaper(params);
}
// 查询试卷列表
static queryPaperPageList(params: any) {
return queryPaperPageList(params);
}
// 删除试卷
static deletePaper(params: any) {
return deletePaper(params);
}
// 编辑前查询试卷信息
static queryPaperDetail(params: any) {
return queryPaperDetail(params);
}
// 预览试卷
static viewPaper(params: any) {
return viewPaper(params);
}
// 编辑试卷
static editPaper(params: any) {
return editPaper(params);
}
// 操作试卷-预览查询多题目信息
static batchQueryQuestionDetails(params: any) {
return batchQueryQuestionDetails(params);
}
// 操作试卷-选择题目列表带题目详情
static queryQuestionPageListWithContent(params: any) {
return queryQuestionPageListWithContent(params);
}
}
\ No newline at end of file
/*
* @Author: 陈剑宇
* @Date: 2020-05-07 14:43:01
* @LastEditTime: 2021-03-26 14:05:25
* @LastEditTime: 2021-04-19 16:42:24
* @LastEditors: zhangleyuan
* @Description:
* @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts
......@@ -22,8 +22,8 @@ const PATH_MAP: MapInterface = {
dev: 'https://dev.xiaomai5.com/xiaomai-cloud-class-web/h5.html',
dev1: 'https://dev.xiaomai5.com/xiaomai-cloud-class-web/h5.html',
rc: 'https://rc.xiaomai5.com/xiaomai-cloud-class-web/h5.html',
gray: 'https://gray.xiaomai5.com/xiaomai-cloud-class-web/h5.html',
prod: 'https://b.xiaomai5.com/xiaomai-cloud-class-web/h5.html'
gray: 'https://res.xiaomai0.com/xiaomai-cloud-class-web/gray/h5.html',
prod: 'https://res.xiaomai0.com/xiaomai-cloud-class-web/h5.html'
};
// axios headers config
......
declare module 'jquery'
declare module 'cropper'
declare var this: any
\ No newline at end of file
declare module 'ExamShareModal'
// declare var this: any
\ No newline at end of file
......@@ -78,11 +78,6 @@ function EmployeeManage() {
avatar: "",
});
const [roleIds, setRoleIds] = useState<Array<RoleItemType>>([]);
const roleMap = {
CloudManager: "管理员",
CloudLecturer: "讲师",
StoreManager: "学院管理员",
};
const storeId = User.getStoreId();
const StoreType = User.getStoreType();
const isWorkWechat = !!(StoreType === "WE_CHAT_STORE");
......
import React, { useState, useRef, useEffect } from 'react'
import { Select } from 'antd';
import StoreService from "@/domains/store-domain/storeService";
export default function ExaminationManager(props: any) {
const queryInit: any = {
planName: ''
}
const teacherQueryInit: any = {};
const [teacherQuery, setTeacherQuery] = useState({
current:1,
size: 10,
roleCodes:props.roleCodes
});
const [teacherList, setTeacherList] = useState([]);
const [hasNext, setHasNext] = useState(true);
const teacherQueryRef = useRef({})
useEffect(() => {
teacherQueryRef.current = teacherQuery;
getTeacherList()
}, [teacherQuery])
function getTeacherList() {
const _query: any = {
...teacherQueryRef.current,
};
StoreService.getStoreUserBasicPage(_query).then((res) => {
const { result = {} } = res;
const { records = [], total = 0, hasNext } = result;
const list = _query.current > 1 ? teacherList.concat(records) : records;
setTeacherList(list);
setHasNext(hasNext)
});
}
// 滑动加载更多讲师列表
function handleScrollTeacherList(e: any) {
const container = e.target;
const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop;
if (scrollToBottom && hasNext) {
let _teacherQuery: any = { ...teacherQueryRef.current };
_teacherQuery.current = _teacherQuery.current + 1;
setTeacherQuery(_teacherQuery)
}
}
return <div className="search-condition__item">
<span className="search-name">创建人:</span>
<Select
placeholder="请选择创建人"
className='search-input'
showSearch
allowClear
onPopupScroll={handleScrollTeacherList}
suffixIcon={<span className="icon iconfont" style={{ fontSize: '12px', color: '#BFBFBF' }}>&#xe835;</span>}
value={props.val || null}
onChange={(value) => {
props.onChange(value)
}}
onSearch={(value) => {
let _teacherQuery: any = { ...teacherQueryRef.current };
_teacherQuery.nickName = value;
setTeacherQuery(_teacherQuery)
}}
onClear={() => {
props.onChange(null)
setTeacherQuery({
roleCodes:props.roleCodes,
current:1,
size: 10
})
}}
>
{teacherList.map((item: any) => {
return (
<Select.Option value={item.id} key={item.id}>{item.nickName}</Select.Option>
);
})}
</Select>
</div>
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-07-17 15:49:11
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-24 14:13:04
* @Last Modified time: 2021-04-06 16:43:23
* @Description: 大班互动-添加/编辑直播课
*/
......@@ -20,7 +20,6 @@ import AddLiveIntro from './components/AddLiveIntro';
import { randomString } from '@/domains/basic-domain/utils';
import Upload from '@/core/upload';
import PreviewCourseModal from './modal/PreviewCourseModal';
import SelectPrepareFileModal from '../prepare-lesson/modal/SelectPrepareFileModal';
import CourseService from "@/domains/course-domain/CourseService";
import moment from 'moment';
import User from '@/common/js/user';
......@@ -113,23 +112,6 @@ class AddLive extends React.Component {
if (type === 'edit') {
this.getCourseDetail();
}
this.initBus();
}
componentWillUnmount() {
this.removeBus();
}
initBus = () => {
Bus.bind('graphicsEditorImage', this.uploadImage)
}
removeBus = () => {
Bus.unbind('graphicsEditorImage', this.uploadImage)
}
uploadImage = () => {
this.setState({ showSelectImageModal: true })
}
getCourseDetail = () => {
......@@ -543,20 +525,6 @@ handleChangeBasicInfo = (field, value) => {
}
}
handleSelectImage = (file) => {
this.setState({
showSelectImageModal: false
})
const { ossUrl } = file;
const { addLiveIntroInfo } = this.state;
this.setState({
addLiveIntroInfo: {
...addLiveIntroInfo,
introduce: `${addLiveIntroInfo.introduce}<p><img style="max-width: 100%;" src="${ossUrl}" /><br/><p>`
}
});
}
render() {
const {
......@@ -567,9 +535,7 @@ handleChangeBasicInfo = (field, value) => {
addLiveIntroInfo,
isEdit,
loadintroduce,
showSelectImageModal,
} = this.state;
console.log(loadintroduce, addLiveIntroInfo, 888888)
return (
<div className="page add-live-page">
<Breadcrumbs
......@@ -617,22 +583,6 @@ handleChangeBasicInfo = (field, value) => {
<Button onClick={this.handleShowPreviewModal}>预览</Button>
<Button type="primary" onClick={_.debounce(() => this.handleSubmit(), 3000, true)}>保存</Button>
</div>
{showSelectImageModal &&
<SelectPrepareFileModal
key="basic"
operateType="select"
multiple={false}
accept="image/jpeg,image/png,image/jpg"
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectImageModal}
onClose={() => {
this.setState({ showSelectImageModal: false })
}}
onSelect={this.handleSelectImage}
/>
}
{ this.state.previewLiveCourseModal }
{ this.state.lackConsumeStudentModal }
</div>
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-07-16 11:05:17
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-23 17:39:50
* @Last Modified time: 2021-04-06 16:40:24
* @Description: 添加直播-简介
*/
......@@ -83,61 +83,6 @@ class AddLiveIntro extends React.Component {
}
// 删除简介
handleDeleteIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests.splice(index, 1);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
// 上移简介
handleMoveUpIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
const prevItem = liveCourseMediaRequests[index];
const nextItem = liveCourseMediaRequests[index + 1];
liveCourseMediaRequests.splice(index, 2, nextItem, prevItem);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
// 下移简介
handleMoveDownIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
const prevItem = liveCourseMediaRequests[index - 1];
const nextItem = liveCourseMediaRequests[index];
liveCourseMediaRequests.splice(index - 1, 2, nextItem, prevItem);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
renderLittleIcon = (index) => {
const { liveCourseMediaRequests } = this.props.data;
return (
<div className="little-icon">
<span
className="icon iconfont close"
onClick={() => { this.handleDeleteIntro(index); }}
></span>
{
index > 0 &&
<span
className="icon iconfont"
onClick={() => { this.handleMoveDownIntro(index); }}
>&#xe6d1;</span>
}
{
index !== liveCourseMediaRequests.length - 1 &&
<span
className="icon iconfont"
onClick={() => { this.handleMoveUpIntro(index); }}
>&#xe6cf;</span>
}
</div>
)
}
changeIntro = (value) => {
this.props.onChange('introduce', value);
}
......@@ -149,9 +94,6 @@ class AddLiveIntro extends React.Component {
this.props.onChange('whetherVisitorsJoin','NO')
}
}
componentWillMount() {
}
render() {
const {liveType, isXiaomai, isEdit, data: { id, introduce, needRecord,whetherVisitorsJoin, loadintroduce, liveCourseWarmMedia = {} } } = this.props;
......
import { message } from 'antd';
import React from 'react';
import E from 'wangeditor';
import Bus from '../../../core/bus';
import SelectPrepareFileModal from '../../prepare-lesson/modal/SelectPrepareFileModal';
import './GraphicsEditor.less';
const { BtnMenu } = E;
class ImageMenu extends BtnMenu {
class GraphicsEditor extends React.Component {
constructor(props) {
super(props)
this.state = {
editorId: window.random_string(16),
textLength: 0,
showSelectImageModal: false,
showSelectVideoModal: false,
}
this.editorInt = null;
this.isContent = true;
}
componentDidMount() {
this.renderEditor()
this.resetIndex(true);
this.initBus();
this.bindClick();
}
componentWillUnmount() {
this.resetIndex();
this.removeBus();
this.removeClick();
}
bindClick = () => {
window.addEventListener('click', this.clickEditor)
}
removeClick = () => {
window.removeEventListener('click', this.clickEditor)
}
clickEditor = (e) => {
if (e && e.target.closest('.content-editor')) {
this.isContent = true
} else if (e && e.target.closest('.introduce-editor')) {
this.isContent = false
}
}
resetIndex = (bool) => {
const topDom = document.querySelector('.top-container');
const leftDom = document.querySelector('.left-container');
topDom.style.zIndex = bool ? 'auto' : 112;
leftDom.style.zIndex = bool ? 'auto' : 2;
}
renderEditor() {
const { editorId } = this.state;
const { detail, onChange, isIntro, maxLimit } = this.props;
class ImageMenu extends BtnMenu {
constructor(editor) {
// data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
const $elem = E.$(
......@@ -17,15 +73,15 @@ class ImageMenu extends BtnMenu {
}
// 菜单点击事件
clickHandler() {
Bus.trigger('graphicsEditorImage')
Bus.trigger(`graphicsEditorImage${isIntro ? '' : 'Content'}`)
}
tryChangeActive() {
}
}
}
class VideoMenu extends BtnMenu {
class VideoMenu extends BtnMenu {
constructor(editor) {
// data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
const $elem = E.$(
......@@ -43,49 +99,8 @@ class VideoMenu extends BtnMenu {
tryChangeActive() {
}
}
class GraphicsEditor extends React.Component {
constructor(props) {
super(props)
this.state = {
editorId: window.random_string(16),
textLength: 0,
}
this.editorInt = null;
}
componentDidMount() {
this.renderEditor()
this.resetIndex(true);
}
componentWillReceiveProps(nextProps) {
const { content } = this.props.detail;
const { content: nextContent } = nextProps.detail;
const videoCount = ((content || '').match(/<iframe/g) || []).length;
const nextVideoCount = ((nextContent || '').match(/<iframe/g) || []).length;
const imageCount = ((content || '').match(/<img/g) || []).length;
const nextImageCount = ((nextContent || '').match(/<img/g) || []).length;
if ((videoCount !== nextVideoCount) || (imageCount !== nextImageCount)) {
this.editorInt && this.editorInt.txt.html(nextProps.detail.content);
}
}
componentWillUnmount() {
this.resetIndex();
}
resetIndex = (bool) => {
const topDom = document.querySelector('.top-container');
const leftDom = document.querySelector('.left-container');
topDom.style.zIndex = bool ? 'auto' : 112;
leftDom.style.zIndex = bool ? 'auto' : 2;
}
renderEditor() {
const { editorId } = this.state;
const { detail, onChange, isIntro } = this.props;
this.editorInt = new E(`#editor${editorId}`);
this.editorInt.config.showFullScreen = !isIntro
this.editorInt.menus.extend('xmimage', ImageMenu);
......@@ -157,6 +172,9 @@ class GraphicsEditor extends React.Component {
const imageCount = ((html || '').match(/<img/g) || []).length;
const textLength = this.editorInt.txt.text().replace(/\&nbsp\;/ig, ' ').length + videoCount + imageCount;
this.setState({ textLength }, () => {
if (textLength > maxLimit) {
// message.warning('超过字数限定');
}
onChange(html, this.state.textLength);
})
}
......@@ -164,12 +182,85 @@ class GraphicsEditor extends React.Component {
this.editorInt.txt.html(detail.content);
}
// 选择图文
handleSelectVideo = (file) => {
this.setState({
showSelectVideoModal: false
})
const { ossUrl } = file;
const { detail } = this.props;
this.editorInt && this.editorInt.txt.html(`${detail.content}<p style="width: 100%;padding-top: 56.25%;position: relative;"><iframe style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;" src="${ossUrl}"></iframe><br/></p><p><br/></p>`)
}
handleSelectImage = (file) => {
this.setState({
showSelectImageModal: false
})
const { ossUrl } = file;
const { detail } = this.props;
this.editorInt && this.editorInt.txt.html(`${detail.content}<p><img style="max-width: 100%;" src="${ossUrl}" /><br/><p>`)
}
initBus = () => {
const { isIntro } = this.props;
Bus.bind(`graphicsEditorImage${isIntro ? '' : 'Content'}`, this.uploadImage)
Bus.bind('graphicsEditorVideo', this.uploadVideo)
}
removeBus = () => {
const { isIntro } = this.props;
Bus.unbind(`graphicsEditorImage${isIntro ? '' : 'Content'}`, this.uploadImage)
Bus.unbind('graphicsEditorVideo', this.uploadVideo)
}
uploadImage = () => {
this.setState({ showSelectImageModal: true })
}
uploadVideo = () => {
this.setState({ showSelectVideoModal: true })
}
render() {
const { editorId, textLength } = this.state;
const { limitLength = 1000, isIntro } = this.props;
return <div className={`graphics-editor-container${isIntro ? ' introduce' : ''}`}>
<div className="editor-box" id={`editor${editorId}`}></div>
<div className="editor-tips">({textLength}/100000)</div>
const { editorId, textLength, showSelectImageModal, showSelectVideoModal } = this.state;
const { limitLength = 1000, isIntro, maxLimit } = this.props;
return <div className={`graphics-editor-container${isIntro ? ' introduce' : ''} ${(textLength > maxLimit)&& 'warning'}`}>
<div className="editor-box" id={`editor${editorId}`} ></div>
<div className="editor-tips">( {(textLength > maxLimit) ? <span style={{ color: 'red' }} >{textLength}</span> : textLength}/{maxLimit || 100000})</div>
{showSelectVideoModal &&
<SelectPrepareFileModal
operateType="select"
selectTypeList={['MP4']}
accept="video/mp4"
confirm={{
title: '文件过大,无法上传',
content: '为保障学员的观看体验,上传的图文大小不能超过2G',
}}
tooltip={'格式支持mp4,大小不超过2G'}
isOpen={showSelectVideoModal}
diskList={diskList}
addVideo={true}
onClose={() => {
this.setState({ showSelectVideoModal: false })
}}
onSelect={this.handleSelectVideo}
/>
}
{showSelectImageModal &&
<SelectPrepareFileModal
key="basic"
operateType="select"
multiple={false}
accept="image/jpeg,image/png,image/jpg"
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectImageModal}
onClose={() => {
this.setState({ showSelectImageModal: false })
}}
onSelect={this.handleSelectImage}
/>
}
</div>
}
}
......
......@@ -49,4 +49,8 @@
height: ~'calc(100% - 69px)' !important;
}
}
&.warning{
border-color: red;
}
}
\ No newline at end of file
......@@ -15,7 +15,6 @@ import { DISK_MAP, FileTypeIcon, FileVerifyMap } from '@/common/constants/academ
import { ImgCutModalNew } from '@/components';
import ShowTips from "@/components/ShowTips";
import Breadcrumbs from "@/components/Breadcrumbs";
import Bus from '../../../core/bus'
import AddGraphicsIntro from './components/AddGraphicsIntro';
import SelectStudent from '../modal/select-student';
import SelectPrepareFileModal from '../../prepare-lesson/modal/SelectPrepareFileModal';
......@@ -70,10 +69,6 @@ class AddGraphicsCourse extends React.Component {
}
}
componentDidMount() {
this.initBus()
}
componentWillMount() {
const { id, pageType } = this.state;
this.getCourseCatalogList();
......@@ -82,10 +77,6 @@ class AddGraphicsCourse extends React.Component {
}
}
componentWillUnmount() {
this.removeBus();
}
initBus = () => {
Bus.bind('graphicsEditorImage', this.uploadImage)
Bus.bind('graphicsEditorVideo', this.uploadVideo)
......@@ -304,29 +295,6 @@ class AddGraphicsCourse extends React.Component {
this.setState({ previewGraphicsModal });
}
// 选择图文
handleSelectVideo = (file) => {
this.setState({
showSelectVideoModal: false
})
const { ossUrl } = file;
const { courseMedia } = this.state;
this.setState({
courseMedia: `${courseMedia}<p style="width: 100%;padding-top: 56.25%;position: relative;"><iframe style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;" src="${ossUrl}"></iframe><br/></p><p><br/></p>`
});
}
handleSelectImage = (file) => {
this.setState({
showSelectImageModal: false
})
const { ossUrl } = file;
const { courseMedia, introduce, isContent } = this.state;
this.setState({
[isContent ? 'courseMedia' : 'introduce']: `${isContent ? courseMedia : introduce}<p><img style="max-width: 100%;" src="${ossUrl}" /><br/><p>`
});
}
handleSelectCover = (file)=> {
this.uploadCoverImage(file);
}
......@@ -546,8 +514,6 @@ class AddGraphicsCourse extends React.Component {
courseMedia,
introduce,
showCutModal,
showSelectVideoModal,
showSelectImageModal,
diskList,
imageFile,
videoType,
......@@ -640,41 +606,6 @@ class AddGraphicsCourse extends React.Component {
<Button type="primary" onClick={_.debounce(() => this.handleSubmit(), 3000, true)}>保存</Button>
</div>
{/* 选择备课文件弹窗 */}
{ showSelectVideoModal &&
<SelectPrepareFileModal
operateType="select"
selectTypeList={['MP4']}
accept="video/mp4"
confirm={{
title: '文件过大,无法上传',
content: '为保障学员的观看体验,上传的图文大小不能超过2G',
}}
tooltip={'格式支持mp4,大小不超过2G'}
isOpen={showSelectVideoModal}
diskList={diskList}
addVideo={true}
onClose={() => {
this.setState({ showSelectVideoModal: false })
}}
onSelect={this.handleSelectVideo}
/>
}
{showSelectImageModal &&
<SelectPrepareFileModal
key="basic"
operateType="select"
multiple={false}
accept="image/jpeg,image/png,image/jpg"
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectImageModal}
onClose={() => {
this.setState({ showSelectImageModal: false })
}}
onSelect={this.handleSelectImage}
/>
}
{showSelectCoverModal &&
<SelectPrepareFileModal
key="basic"
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-07-16 11:05:17
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-25 11:25:02
* @Last Modified time: 2021-04-06 16:17:57
* @Description: 添加直播-简介
*/
......@@ -28,33 +28,6 @@ class AddGraphicsIntro extends React.Component {
diskList: [],
selectType: null,
}
this.isContent = true;
}
componentDidMount() {
this.bindClick();
}
componentWillUnmount() {
this.removeClick();
}
bindClick = () => {
window.addEventListener('click', this.clickEditor)
}
removeClick = () => {
window.removeEventListener('click', this.clickEditor)
}
clickEditor = (e) => {
if (e && e.target.closest('.content-editor')) {
this.isContent = true
console.log(11111111)
} else if (e && e.target.closest('.introduce-editor')) {
this.isContent = false
console.log(222222222)
}
}
// 上传封面图
......@@ -109,17 +82,11 @@ class AddGraphicsIntro extends React.Component {
}
changeDetail = (value) => {
this.props.onChange('isContent', !!this.isContent);
setTimeout(() => {
this.props.onChange('courseMedia', value);
}, 0)
}
changeIntro = (value) => {
this.props.onChange('isContent', !!this.isContent);
setTimeout(() => {
this.props.onChange('introduce', value);
}, 0)
}
whetherVisitorsJoinChange = ()=>{
......
......@@ -33,9 +33,11 @@ class GraphicsCourseFilter extends React.Component {
expandFilter:false
}
}
componentDidMount() {
this.getTeacherList();
}
getTeacherList(current = 1, selectList){
const { teacherQuery,teacherList} = this.state;
const _query = {
......@@ -53,6 +55,7 @@ class GraphicsCourseFilter extends React.Component {
})
});
}
// 滑动加载更多讲师列表
handleScrollTeacherList = (e) => {
const { hasNext } = this.state;
......
......@@ -27,7 +27,6 @@ import _ from "underscore";
import Upload from '@/core/upload';
import { randomString } from '@/domains/basic-domain/utils';
import $ from 'jquery';
import Bus from '../../../core/bus'
// import PhotoClip from 'photoclip';
import './AddVideoCourse.less';
......@@ -90,19 +89,6 @@ class AddVideoCourse extends React.Component {
if (pageType === 'edit') {
this.handleFetchScheudleDetail(id);
}
this.initBus()
}
initBus = () => {
Bus.bind('graphicsEditorImage', this.uploadIntroImage)
}
removeBus = () => {
Bus.unbind('graphicsEditorImage', this.uploadIntroImage)
}
uploadIntroImage = () => {
this.setState({ showSelectImageModal: true })
}
//获取分类列表
......@@ -448,17 +434,6 @@ class AddVideoCourse extends React.Component {
this.uploadImage(file);
}
handleSelectImage = (file) => {
this.setState({
showSelectImageModal: false
})
const { ossUrl } = file;
const { introduce } = this.state;
this.setState({
introduce: `${introduce}<p><img style="max-width: 100%;" src="${ossUrl}" /><br/><p>`
});
}
//上传图片
uploadImage = (imageFile) => {
const { folderName } = imageFile;
......@@ -574,7 +549,6 @@ class AddVideoCourse extends React.Component {
introduce,
loadintroduce,
id,
showSelectImageModal,
} = this.state;
// 已选择的上课学员数量
......@@ -702,21 +676,6 @@ class AddVideoCourse extends React.Component {
onSelect={this.handleSelectVideo}
/>
}
{showSelectImageModal &&
<SelectPrepareFileModal
key="basic"
operateType="select"
multiple={false}
accept="image/jpeg,image/png,image/jpg"
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectImageModal}
onClose={() => {
this.setState({ showSelectImageModal: false })
}}
onSelect={this.handleSelectImage}
/>
}
{showSelectCoverModal &&
<SelectPrepareFileModal
key="basic"
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-07-16 11:05:17
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-23 18:12:05
* @Last Modified time: 2021-04-06 16:44:09
* @Description: 添加直播-简介
*/
......@@ -83,83 +83,10 @@ class AddVideoIntro extends React.Component {
}
// 删除简介
handleDeleteIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests.splice(index, 1);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
// 上移简介
handleMoveUpIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
const prevItem = liveCourseMediaRequests[index];
const nextItem = liveCourseMediaRequests[index + 1];
liveCourseMediaRequests.splice(index, 2, nextItem, prevItem);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
// 下移简介
handleMoveDownIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
const prevItem = liveCourseMediaRequests[index - 1];
const nextItem = liveCourseMediaRequests[index];
liveCourseMediaRequests.splice(index - 1, 2, nextItem, prevItem);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
renderLittleIcon = (index) => {
const { liveCourseMediaRequests } = this.props.data;
return (
<div className="little-icon">
<span
className="icon iconfont close"
onClick={() => { this.handleDeleteIntro(index); }}
></span>
{
index > 0 &&
<span
className="icon iconfont"
onClick={() => { this.handleMoveDownIntro(index); }}
>&#xe6d1;</span>
}
{
index !== liveCourseMediaRequests.length - 1 &&
<span
className="icon iconfont"
onClick={() => { this.handleMoveUpIntro(index); }}
>&#xe6cf;</span>
}
</div>
)
}
handleChangeIntro = (index, value, length) => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests[index].mediaContent = value;
liveCourseMediaRequests[index].mediaContentLength = length
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
changeIntro = (value) => {
this.props.onChange('introduce', value);
}
handleAddIntroText = () => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests.push({
contentType:"INTRO",
mediaType: 'TEXT',
mediaContent: '',
key: Math.random()
});
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
handleUpload = (Blob) => {
this.setState({
showSelectFileModal: true,
......@@ -180,9 +107,7 @@ class AddVideoIntro extends React.Component {
this.props.onChange('shelfState','NO')
}
}
componentWillMount() {
}
render() {
const {data: { whetherVisitorsJoin,liveCourseMediaRequests = [], shelfState, id, introduce, loadintroduce } } = this.props;
const {showSelectFileModal,selectType} = this.state
......
......@@ -96,7 +96,9 @@ class Home extends React.Component {
Service.Hades('public/hades/storePanelInfo', { storeId: User.getStoreId() }).then((res) => {
if (res.success) {
this.setState({
examNum: res.result.examNum,
incCustomerNum: res.result.incCustomerNum,
incExamNum: res.result.incExamNum,
incLiveCourseNum: res.result.incLiveCourseNum,
incVideoCourseNum: res.result.incVideoCourseNum,
incPictureCourseNum: res.result.incPictureCourseNum,
......@@ -210,6 +212,8 @@ class Home extends React.Component {
render() {
const {
examNum,
incExamNum,
list,
incCustomerNum,
incLiveCourseNum,
......@@ -340,18 +344,19 @@ class Home extends React.Component {
<span className="footer-number">{incTrainingPlanNum}</span>
</div>
</div>
<div className="data-item">
<div className="header">
<img className="header-icon" src="https://image.xiaomaiketang.com/xm/3CfrPs23Re.png" />
<span className="header-word">考试总数 (个)</span>
</div>
<div className="data-number">0</div>
<div className="data-number">{examNum}</div>
<div className="data-footer">
<span className="footer-word">本月新增</span>
{false &&
{incExamNum > 0 &&
<span className="icon iconfont">&#xe635;</span>
}
<span className="footer-number">0</span>
<span className="footer-number">{incExamNum}</span>
</div>
</div>
</div>
......
......@@ -2,8 +2,8 @@
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-19 18:05:23
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-08 11:29:12
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 21:46:41
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -202,7 +202,7 @@ class Classification extends Component {
<Button
onClick={() => {
window.RCHistory.push({
pathname: "/question-category-manage?from=knowledge",
pathname: "/course-category-manage?from=knowledge",
});
}}
>
......
......@@ -7,7 +7,7 @@
*/
import React, { useContext, useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import { ConfigProvider } from 'antd';
import { ConfigProvider, message } from 'antd';
import Header from './Header'
import Menu from './Menu'
import Main from './Main'
......@@ -81,12 +81,17 @@ const App: React.FC = (props: any) => {
BaseService.getUserStore({ userId }).then((res) => {
const { storeGroupVOS = [], storeVOS = [] } = res.result;
if (storeVOS.length < 1) {
message.warning('店铺不存在');
window.RCHistory.replace('/login');
return
}
const { id, storeUserId, storeName, userRole, storeType } = storeVOS[0];
User.setStoreId(id);
User.setStoreUserId(storeUserId);
User.setStoreName(storeName);
Bus.trigger('storeNameChange',storeName);
Bus.trigger('storeNameChange', storeName);
User.setUserRole(userRole);
User.setStoreType(storeType);
ctx.dispatch(setStoreGroupList(storeGroupVOS))
......
......@@ -11,6 +11,7 @@ const roleMap = {
CloudManager: "管理员",
CloudLecturer: "讲师",
StoreManager: "学院管理员",
CloudOperator: '运营师',
};
export default class CollegeManagePage extends React.Component {
......@@ -97,7 +98,7 @@ export default class CollegeManagePage extends React.Component {
User.removeToken();
User.removeEnterpriseId();
User.clearUserInfo();
const htmlUrl = `${LIVE_SHARE}store/index?id=${User.getStoreId()}&userId=${User.getUserId()}&from=work_weixin`;
const htmlUrl = `${LIVE_SHARE}store/index?id=${User.getCustomerStoreId()||User.getStoreId()}&userId=${User.getUserId()}&from=work_weixin`;
window.location.href = htmlUrl;
});
}
......
......@@ -2,6 +2,7 @@
@import '../../core/page.less';
@import '../../core/table.less';
@import '../../core/global.less';
@import '../../core/filter.less';
@top-height: 50px;
......
......@@ -80,11 +80,6 @@ function EmployeesManagePage() {
avatar: "",
});
const [roleIds, setRoleIds] = useState<Array<RoleItemType>>([]);
const roleMap = {
CloudManager: "管理员",
CloudLecturer: "讲师",
StoreManager: "学院管理员",
};
const storeId = User.getStoreId();
const StoreType = User.getStoreType();
const isWorkWechat = !!(StoreType === "WE_CHAT_STORE");
......
/*
* @Author: yuananting
* @Date: 2021-02-23 18:28:50
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-29 16:14:02
* @Description: 助学工具-题库-主页面分类管理
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 21:53:13
* @Description: 助学工具-课程分类
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import Breadcrumbs from "@/components/Breadcrumbs";
import "./QuestionCategoryManage.less";
import NewEditQuestionBankCategory from "./modal/NewEditQuestionBankCategory";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import "./CourseCategoryManage.less";
import OpearteCourseCategoryModal from "../modal/OpearteCourseCategoryModal";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import User from "@/common/js/user";
import {
Tree,
......@@ -26,39 +26,70 @@ import ShowTips from "@/components/ShowTips";
const { DirectoryTree } = Tree;
const { Search } = Input;
const { confirm } = Modal;
class QuestionCategoryManage extends Component {
class CourseCategoryManage extends Component {
constructor(props) {
super(props);
this.state = {
NewEditQuestionBankCategory: null, //新增或编辑分类模态框
operateCourseCategoryModal: null, //新增或编辑分类模态框
treeData: [],
originTreeData: [],
treeMap: {},
selectedKeys: ["0"],
selectedKeys: ["null"],
autoExpandParent: true,
};
}
componentDidMount() {
this.queryCategoryTree("search");
this.queryCategoryTree("init");
}
getWholeTree = () => {
let query = {
bizType: "QUESTION",
count: false,
source: 0,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
AidToolService.queryCategoryTree(query).then((res) => {
const { categoryList = [] } = res.result;
this.setState({ originTreeData: categoryList }, () => {
let map = {};
let topItem = [];
categoryList.forEach((item) => {
topItem.push(item);
});
this.setState({
treeMap: Object.assign(this.getTreeMap(categoryList, map), {
0: {
sonCategoryList: topItem,
},
}),
});
});
});
};
// 查询分类树
queryCategoryTree = (operateType, categoryName) => {
this.getWholeTree();
this.setState({ categoryName });
let query = {
bizType: "QUESTION",
count: false,
source: 0,
categoryName,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryCategoryTree(query).then((res) => {
const { result = [] } = res;
AidToolService.queryCategoryTree(query).then((res) => {
const { categoryList = [] } = res.result;
let str = "未分类";
if (categoryName) {
this.setState({ autoExpandParent: true });
if (str.indexOf(categoryName) < 0) {
this.setState({
treeData: this.renderTreeNodes(result, categoryName),
treeData: this.renderTreeNodes(categoryList, categoryName),
});
let nodeId = [];
Object.keys(this.state.treeMap).forEach((item) => {
......@@ -67,74 +98,184 @@ class QuestionCategoryManage extends Component {
this.setState({ expandedKeys: nodeId });
} else {
const defaultNode = {
id: "0",
id: "null",
categoryName: "未分类",
categoryCount: 0,
parentId: "0",
categoryLevel: 0,
};
result.unshift(defaultNode);
categoryList.unshift(defaultNode);
this.setState({
treeData: this.renderTreeNodes(result, categoryName),
treeData: this.renderTreeNodes(categoryList, categoryName),
});
let nodeId = [];
Object.keys(this.state.treeMap).forEach((item) => {
nodeId.push(item);
});
if (operateType === "search") {
if (operateType === "init") {
this.setState({ expandedKeys: nodeId });
}
}
} else {
this.setState({ autoExpandParent: false });
const defaultNode = {
id: "0",
id: "null",
categoryName: "未分类",
categoryCount: 0,
parentId: "0",
categoryLevel: 0,
};
result.unshift(defaultNode);
this.setState({
treeData: this.renderTreeNodes(result, categoryName),
});
if (operateType === "search") {
categoryList.unshift(defaultNode);
this.setState({ treeData: this.renderTreeNodes(categoryList, categoryName) });
if (operateType === "init") {
this.setState({ expandedKeys: [] });
}
}
});
};
// 删除分类
delCategory = (item) => {
return confirm({
title: "确认删除该分类吗?",
content: "删除后,分类下的所有内容将自动转入“未分类”中。",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
okType: "danger",
cancelText: "取消",
onOk: () => {
let params = {
categoryId: item.id,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
QuestionBankService.delCategory(params).then((res) => {
if (res.success) {
message.success("删除分类成功");
this.queryCategoryTree("change");
// 树节点渲染-内容处理
renderTreeNodes = (data, value) => {
let newTreeData = data.map((item) => {
item.title = item.categoryName;
item.key = item.id;
item.title = (
<div
style={{
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
className="node-title-div"
onMouseOver={(e) => {
let mouseNodeOpts = e.currentTarget.getElementsByTagName("div")[0];
if (mouseNodeOpts) {
mouseNodeOpts.style.visibility = "visible";
}
}}
onMouseOut={(e) => {
let mouseNodeOpts = e.currentTarget.getElementsByTagName("div")[0];
if (mouseNodeOpts) {
mouseNodeOpts.style.visibility = "hidden";
}
}}
>
<span>{item.categoryName}</span>
{item.categoryName !== "未分类" && (
<Space className="title-opts" size={16}>
<span
onClick={() => {
let nodesCount = 0;
const { originTreeData } = this.state;
console.log("orororo", originTreeData);
if (
(item.categoryLevel === 0 && originTreeData.length >= 29) ||
(item.categoryLevel > 0 &&
this.getRelatedNodes(item.parentId).length >= 30)
) {
return message.info("最多只能添加30个分类");
}
this.newEditCourseCategory(
"newEqualLevelCategory",
"equal",
"new",
item
);
}}
>
<span className="icon iconfont" style={{ color: "#BFBFBF" }}>
&#xe7f5;{" "}
</span>
<span>同级</span>
</span>
{item.categoryLevel < 4 && (
<span
onClick={() => {
if (
this.getRelatedNodes(item.id) &&
this.getRelatedNodes(item.id).length >= 30
) {
message.info("最多只能添加30个子分类");
return;
}
this.newEditCourseCategory(
"newChildLevelCategory",
"child",
"new",
item
);
}}
>
<span className="icon iconfont" style={{ color: "#BFBFBF" }}>
&#xe7f8;{" "}
</span>
<span>子级</span>
</span>
)}
<Dropdown overlay={this.initDropMenu(item)}>
<span>
<span className="icon iconfont" style={{ color: "#BFBFBF" }}>
&#xe7f7;{" "}
</span>
<span>更多</span>
</span>
</Dropdown>
</Space>
)}
</div>
);
item.icon =
item.categoryName === "未分类" ? (
<img
style={{
width: "24px",
height: "24px",
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
src="https://image.xiaomaiketang.com/xm/defaultCategory.png"
alt=""
/>
) : (
<img
style={{
width: "24px",
height: "24px",
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
src="https://image.xiaomaiketang.com/xm/hasCategory.png"
alt=""
/>
);
if (item.sonCategoryList) {
item.children = this.renderTreeNodes(item.sonCategoryList, value);
}
return item;
});
},
return newTreeData;
};
// 树结构平铺
getTreeMap = (data, map) => {
data.forEach((item) => {
map[item.id] = item;
if (item.sonCategoryList && item.sonCategoryList.length > 0) {
this.getTreeMap(item.sonCategoryList, map);
}
});
return map;
};
// 新增或编辑分类
newEditQuestionCategory = (categoryType, addLevelType, type, node) => {
newEditCourseCategory = (categoryType, addLevelType, type, node) => {
let title = "";
let label = "";
switch (categoryType) {
......@@ -156,24 +297,52 @@ class QuestionCategoryManage extends Component {
break;
}
const m = (
<NewEditQuestionBankCategory
<OpearteCourseCategoryModal
node={node}
addLevelType={addLevelType}
type={type}
treeData={this.state.treeData}
title={title}
label={label}
close={() => {
this.queryCategoryTree("change");
this.queryCategoryTree("remain", this.state.categoryName);
this.setState({
NewEditQuestionBankCategory: null,
operateCourseCategoryModal: null,
});
}}
/>
);
this.setState({ NewEditQuestionBankCategory: m });
this.setState({ operateCourseCategoryModal: m });
};
// 删除分类
delCategory = (item) => {
return confirm({
title: "确认删除该分类吗?",
content: "删除后,分类下的所有内容将自动转入“未分类”中。",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
okType: "danger",
cancelText: "取消",
onOk: () => {
let params = {
categoryId: item.id,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
AidToolService.delCategory(params).then((res) => {
if (res.success) {
message.success("删除分类成功");
this.queryCategoryTree("remain", this.state.categoryName);
}
});
},
});
};
// 更多操作-【重命名 删除】
initDropMenu = (item) => {
return (
<Menu>
......@@ -184,7 +353,7 @@ class QuestionCategoryManage extends Component {
item.categoryLevel === 0
? "editEqualLevelCategory"
: "editChildLevelCategory";
this.newEditQuestionCategory(categoryType, "equal", "edit", item);
this.newEditCourseCategory(categoryType, "equal", "edit", item);
}}
>
重命名
......@@ -203,12 +372,14 @@ class QuestionCategoryManage extends Component {
);
};
// 获取相关节点
getRelatedNodes = (parentId) => {
return this.state.treeMap[parentId]
? this.state.treeMap[parentId].sonCategoryList
: [];
};
// 获取拖拽目标父节点层级
getParentDragNodesLevel = (dragNode) => {
if (!dragNode) {
return [];
......@@ -224,6 +395,7 @@ class QuestionCategoryManage extends Component {
return dragNodes;
};
// 获取拖拽节点层级
getDragNodesLevel = (dragNode) => {
let dragNodes = [];
if (dragNode.sonCategoryList && dragNode.sonCategoryList.length > 0) {
......@@ -237,6 +409,7 @@ class QuestionCategoryManage extends Component {
return [...new Set(dragNodes)];
};
// 拖拽
onDrop = (info) => {
if (this.state.categoryName) {
return;
......@@ -257,6 +430,13 @@ class QuestionCategoryManage extends Component {
return;
let targetParentId = info.dropToGap ? info.node.parentId : info.node.id;
let relatedNodes = this.getRelatedNodes(targetParentId);
if (
!(
(info.dropToGap && info.node.parentId === info.dragNode.parentId) ||
(!info.dropToGap && info.node.id === info.dragNode.parentId)
)
) {
if (this.state.treeMap[targetParentId].categoryLevel === 4) {
return message.info("最多支持5级分类");
} else {
......@@ -267,13 +447,13 @@ class QuestionCategoryManage extends Component {
this.state.treeMap[targetParentId]
);
if (nodesArr.length + parentArr.length > 4) {
console.log(nodesArr.length, parentArr.length);
return message.info("最多支持5级分类");
}
}
let relatedNodes = this.getRelatedNodes(targetParentId);
if (relatedNodes && relatedNodes.length === 30) {
return message.info("最多只能添加30个子分类");
if (relatedNodes && relatedNodes.length >= 30) {
return message.info("最多只能添加30个分类");
}
}
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
......@@ -371,8 +551,8 @@ class QuestionCategoryManage extends Component {
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
QuestionBankService.editCategoryTree(params).then((res) => {
this.queryCategoryTree("change");
AidToolService.editCategoryTree(params).then((res) => {
this.queryCategoryTree("remain");
});
};
......@@ -389,186 +569,12 @@ class QuestionCategoryManage extends Component {
return data;
};
/** 获取树状第一级key 设置默认展开第一项 */
getFirstLevelKeys = (data) => {
let firstLevelKeys = [];
data.forEach((item) => {
if (item.categoryLevel === 0) {
firstLevelKeys.push(item.key);
}
});
return firstLevelKeys;
};
/** 树状展开事件 */
// 树状展开事件
onExpand = (expandedKeys) => {
this.setState({ expandedKeys });
};
renderTreeNodes = (data, value) => {
let newTreeData = data.map((item) => {
item.title = item.categoryName;
item.key = item.id;
item.title = (
<div
style={{
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
className="node-title-div"
onMouseOver={(e) => {
let mouseNodeOpts = e.currentTarget.getElementsByTagName("div")[0];
if (mouseNodeOpts) {
mouseNodeOpts.style.visibility = "visible";
}
}}
onMouseOut={(e) => {
let mouseNodeOpts = e.currentTarget.getElementsByTagName("div")[0];
if (mouseNodeOpts) {
mouseNodeOpts.style.visibility = "hidden";
}
}}
>
<span>{item.categoryName}</span>
{item.categoryName !== "未分类" && (
<Space className="title-opts" size={16}>
<span
onClick={() => {
let nodesCount = 0;
const { treeData } = this.state;
if (item.categoryLevel === 0) {
// 第一层级
nodesCount = treeData.length;
} else {
let parentNodes = this.getRelatedNodes(item.parentId);
if (
parentNodes.length > 0 &&
parentNodes[0].sonCategoryList
) {
nodesCount = parentNodes[0].sonCategoryList.length;
} else {
nodesCount = 0;
}
}
if (nodesCount >= 30) {
message.info("最多只能添加30个分类");
return;
}
this.newEditQuestionCategory(
"newEqualLevelCategory",
"equal",
"new",
item
);
}}
>
<span className="icon iconfont" style={{ color: "#BFBFBF" }}>
&#xe7f5;{" "}
</span>
<span>同级</span>
</span>
{item.categoryLevel < 4 && (
<span
onClick={() => {
if (
item.sonCategoryList &&
item.sonCategoryList.length >= 30
) {
message.info("最多只能添加30个子分类");
return;
}
this.newEditQuestionCategory(
"newChildLevelCategory",
"child",
"new",
item
);
}}
>
<span className="icon iconfont" style={{ color: "#BFBFBF" }}>
&#xe7f8;{" "}
</span>
<span>子级</span>
</span>
)}
<Dropdown overlay={this.initDropMenu(item)}>
<span>
<span className="icon iconfont" style={{ color: "#BFBFBF" }}>
&#xe7f7;{" "}
</span>
<span>更多</span>
</span>
</Dropdown>
</Space>
)}
</div>
);
item.icon =
item.categoryName === "未分类" ? (
<img
style={{
width: "24px",
height: "24px",
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
src="https://image.xiaomaiketang.com/xm/defaultCategory.png"
alt=""
/>
) : (
<img
style={{
width: "24px",
height: "24px",
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
src="https://image.xiaomaiketang.com/xm/hasCategory.png"
alt=""
/>
);
if (item.sonCategoryList) {
item.children = this.renderTreeNodes(item.sonCategoryList, value);
}
return item;
});
let map = {};
let topItem = [];
data.forEach((item) => {
topItem.push(item);
});
this.setState({
treeMap: Object.assign(this.getTreeMap(data, map), {
0: {
sonCategoryList: topItem,
},
}),
});
return newTreeData;
};
getTreeMap = (data, map) => {
data.forEach((item) => {
map[item.id] = item;
if (item.sonCategoryList && item.sonCategoryList.length > 0) {
this.getTreeMap(item.sonCategoryList, map);
}
});
return map;
};
/** 树状选中事件 */
// 树状选中事件
onSelect = (selectedKeys) => {
this.setState({ selectedKeys });
};
......@@ -576,18 +582,20 @@ class QuestionCategoryManage extends Component {
render() {
const {
treeData,
originTreeData,
expandedKeys,
selectedKeys,
autoExpandParent,
operateCourseCategoryModal,
} = this.state;
return (
<div className="page question-category-manage">
{getParameterByName("from") ? (
<div className="page course-category-manage">
{["aid", "knowledge"].includes(getParameterByName("from")) ? (
<Breadcrumbs
navList="课程分类"
// goBack={() =>
// window.RCHistory.goBack()
// }
goBack={() =>
window.RCHistory.goBack()
}
/>
) : (
<div className="content-header">课程分类</div>
......@@ -598,7 +606,7 @@ class QuestionCategoryManage extends Component {
<Search
placeholder="请输入名称"
style={{ width: "300px" }}
onSearch={(value) => this.queryCategoryTree("search", value)}
onSearch={(value) => this.queryCategoryTree("init", value)}
className="search-input"
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
......@@ -606,11 +614,11 @@ class QuestionCategoryManage extends Component {
<Button
type="primary"
onClick={() => {
if (treeData.length >= 30) {
if (originTreeData.length >= 29) {
message.info("最多只能添加30个分类");
return;
}
this.newEditQuestionCategory(
this.newEditCourseCategory(
"newEqualLevelCategory",
"equal",
"new"
......@@ -639,10 +647,10 @@ class QuestionCategoryManage extends Component {
></DirectoryTree>
</div>
</div>
{this.state.NewEditQuestionBankCategory}
{operateCourseCategoryModal}
</div>
);
}
}
export default QuestionCategoryManage;
export default CourseCategoryManage;
/*
* @Author: yuananting
* @Date: 2021-02-23 19:41:42
* @LastEditors: yuananting
* @LastEditTime: 2021-03-24 17:16:43
* @Description: 助学工具-题库-题目分类管理样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-category-manage {
.course-category-manage {
position: relative;
.search-condition {
width: 30%;
margin-bottom: 16px;
.search-label {
vertical-align: middle;
......@@ -22,7 +13,7 @@
position: relative;
margin-top: 16px;
width: 900px;
border: 1px solid #E8E8E8;
border: 1px solid #e8e8e8;
.ant-tree.ant-tree-directory {
font-size: 14px;
font-weight: 400;
......@@ -75,6 +66,6 @@
}
.ant-tree.ant-tree-directory .ant-tree-treenode:hover::before {
background-color: #F3F6FA;
background-color: #f3f6fa;
}
}
......@@ -2,88 +2,56 @@
* @Author: yuananting
* @Date: 2021-02-22 10:59:43
* @LastEditors: yuananting
* @LastEditTime: 2021-03-24 14:50:08
* @Description: 助学工具-题库-题库主页面侧边栏
* @LastEditTime: 2021-04-13 13:55:37
* @Description: 助学工具-侧边课程分类树
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Input, Button, Tree } from "antd";
import "./QuestionBankSider.less";
import "./CourseCategorySiderTree.less";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import Bus from "@/core/bus";
const { Search } = Input;
const { DirectoryTree } = Tree;
class QuestionBankSider extends Component {
class CourseCategorySiderTree extends Component {
constructor(props) {
super(props);
const categoryId = getParameterByName("categoryId");
this.state = {
selectedKeys: categoryId
? categoryId === "null"
? ["0"]
: [categoryId]
: ["0"],
searchValue: null,
NewEditQuestionBankCategory: null, //新增或编辑分类模态框
ImportCourseCategory: null, // 引用课程分类模态框
treeData: this.props.treeData || [],
selectedKeys: ["QUESTION_INDEX", "PAPER_INDEX"].includes(props.fromModule)
? [getParameterByName("categoryId") || "null"]
: ["null"],
treeData: props.treeData || [],
autoExpandParent: false,
};
}
componentDidMount() {
this.queryCategoryTree("change");
this.props.getSelectedCategoryId(
getParameterByName("categoryId")
? [getParameterByName("categoryId")]
: ["0"]
);
}
shouldComponentUpdate(nextProps, nextState) {
const { currentTotal, updatedCategoryId } = nextProps;
if (
this.props.currentTotal !== currentTotal &&
this.props.updatedCategoryId === updatedCategoryId
) {
this.queryCategoryTree("remain");
}
return true;
this.queryCategoryTree("init");
Bus.bind("queryCategoryTree", this.queryCategoryTree);
}
/** 获取树状第一级key 设置默认展开第一项 */
getFirstLevelKeys = (data) => {
let firstLevelKeys = [];
data.forEach((item) => {
if (item.categoryLevel === 0) {
firstLevelKeys.push(item.key);
componentWillUnmount() {
Bus.unbind("queryCategoryTree", this.queryCategoryTree);
}
});
return firstLevelKeys;
};
/** 树状展开事件 */
onExpand = (expandedKeys) => {
this.setState({ expandedKeys });
};
/** 树状选中事件 */
onSelect = (selectedKeys) => {
this.setState({ selectedKeys });
this.props.getSelectedCategoryId(selectedKeys);
};
// 查询分类树
queryCategoryTree = (type, categoryName) => {
queryCategoryTree = (type = "init", categoryName) => {
let query = {
source: 0,
bizType: ["QUESTION_INDEX", "QUESTION_MODAL"].includes(
this.props.fromModule
)
? "QUESTION"
: "PAPER",
categoryName,
count: true,
source: 0,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryQuestionCategoryTree(query).then((res) => {
AidToolService.queryCategoryTree(query).then((res) => {
const { categoryList = [], noCategoryCnt = 0 } = res.result;
let str = "未分类";
if (categoryName) {
......@@ -96,12 +64,12 @@ class QuestionBankSider extends Component {
Object.keys(this.state.treeMap).forEach((item) => {
nodeId.push(item);
});
if (type === "change") {
if (type === "init") {
this.setState({ expandedKeys: nodeId });
}
} else {
const defaultNode = {
id: "0",
id: "null",
categoryName: "未分类",
categoryCount: noCategoryCnt,
parentId: "0",
......@@ -115,14 +83,14 @@ class QuestionBankSider extends Component {
Object.keys(this.state.treeMap).forEach((item) => {
nodeId.push(item);
});
if (type === "change") {
if (type === "init") {
this.setState({ expandedKeys: nodeId });
}
}
} else {
this.setState({ autoExpandParent: false });
const defaultNode = {
id: "0",
id: "null",
categoryName: "未分类",
categoryCount: noCategoryCnt,
parentId: "0",
......@@ -132,13 +100,14 @@ class QuestionBankSider extends Component {
this.setState({
treeData: this.renderTreeNodes(categoryList, categoryName),
});
if (type === "change") {
if (type === "init") {
this.setState({ expandedKeys: [] });
}
}
});
};
// 树结构平铺
getTreeMap = (data, map) => {
data.forEach((item) => {
map[item.id] = item;
......@@ -149,6 +118,25 @@ class QuestionBankSider extends Component {
return map;
};
// 树状展开事件
onExpand = (expandedKeys) => {
this.setState({ expandedKeys });
};
// 树状选中事件
onSelect = (selectedKeys) => {
this.setState({ selectedKeys }, () => {
if (this.props.fromModule === "QUESTION_INDEX") {
Bus.trigger("queryQuestionPageList", selectedKeys[0]);
} else if (this.props.fromModule === "QUESTION_MODAL") {
Bus.trigger("queryQuestionPageListWithContent", selectedKeys[0]);
} else {
Bus.trigger("queryPaperPageList", selectedKeys[0], 0);
}
});
};
// 树节点渲染-内容处理
renderTreeNodes = (data, value) => {
let newTreeData = data.map((item) => {
item.title = item.categoryName;
......@@ -166,13 +154,27 @@ class QuestionBankSider extends Component {
item.icon =
item.categoryName === "未分类" ? (
<img
style={{ width: "24px", height: "24px", opacity: !value || (value && item.categoryName.indexOf(value) > -1) ? 1 : 0.5 }}
style={{
width: "24px",
height: "24px",
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
src="https://image.xiaomaiketang.com/xm/defaultCategory.png"
alt=""
/>
) : (
<img
style={{ width: "24px", height: "24px", opacity: !value || (value && item.categoryName.indexOf(value) > -1) ? 1 : 0.5 }}
style={{
width: "24px",
height: "24px",
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
src="https://image.xiaomaiketang.com/xm/hasCategory.png"
alt=""
/>
......@@ -194,23 +196,32 @@ class QuestionBankSider extends Component {
selectedKeys,
autoExpandParent,
} = this.state;
return (
<div className="question-bank-sider">
<div className="sider-title">题目分类</div>
<div className="category-tree-sider">
{["QUESTION_INDEX", "PAPER_INDEX"].includes(this.props.fromModule) && (
<div className="sider-title">
{this.props.fromModule === "QUESTION_INDEX"
? "题目分类"
: "试卷分类"}
</div>
)}
<Search
className="sider-search"
placeholder="搜索名称分类"
onSearch={(value) => {
this.queryCategoryTree("change", value);
this.queryCategoryTree("init", value);
}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
{User.getUserRole() !== "CloudLecturer" && (
{["QUESTION_INDEX", "PAPER_INDEX"].includes(this.props.fromModule) &&
User.getUserRole() !== "CloudLecturer" &&
this.props.type !== "modal-select" && (
<div className="sider-btn">
<Button
onClick={() => {
window.RCHistory.push({
pathname: "/question-category-manage?from=aid",
pathname: "/course-category-manage?from=aid",
});
}}
>
......@@ -229,11 +240,9 @@ class QuestionBankSider extends Component {
treeData={treeData}
/>
</div>
{this.state.NewEditQuestionBankCategory}
{this.state.ImportCourseCategory}
</div>
);
}
}
export default QuestionBankSider;
export default CourseCategorySiderTree;
/*
* @Author: yuananting
* @Date: 2021-02-22 12:02:34
* @LastEditors: yuananting
* @LastEditTime: 2021-03-24 15:57:12
* @Description: 助学工具-题库-题库主页面侧边栏样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-bank-sider {
.category-tree-sider {
position: relative;
.sider-title {
height: 22px;
......@@ -68,6 +60,6 @@
}
.ant-tree.ant-tree-directory .ant-tree-treenode:hover::before {
background-color: #F3F6FA;
background-color: #f3f6fa;
}
}
......@@ -8,16 +8,6 @@
import React, { useState, useEffect, useRef } from "react";
import "./XMAudio.less";
import VideoUpload from "@/core/upload";
import { string } from "prop-types";
// interface XMAudioProps {
// style?: any;
// index?: any;
// size?: number | string;
// url?: any;
// forbidParse?:boolean;
// getDuration?: (value: number) => void;
// }
let timerInterval;
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-03-18 10:01:28
* @LastEditors: yuananting
* @LastEditTime: 2021-03-22 17:24:38
* @LastEditTime: 2021-03-27 14:44:31
* @Description: 录音组件
*/
......@@ -11,7 +11,7 @@ import { Button, Modal } from "antd";
import UploadOss from "@/core/upload";
import { RECORD_ERROR } from "@/common/constants/academic";
import AudioRecorder from "./audioRecord";
import AudioRecorder from "../components/audioRecord";
import "./XMRecord.less";
......
/*
* @Author: chenjianyu
* @Date: 2020-09-12 17:00:44
* @LastEditTime: 2021-03-18 17:23:09
* @LastEditors: sunbingqing
* @LastEditTime: 2021-03-27 14:02:17
* @LastEditors: yuananting
* @Description: 答题模式模板
* @Copyright © 2020 杭州杰竞科技有限公司 版权所有
*/
......
.examPage{
padding-bottom: 62px;
.ant-alert-info{
background: rgba(255, 244, 221, 1);
border: none;
}
.ant-form-item{
&:last-child{
margin-bottom: 0px !important;
}
}
.form{
margin-top: 12px;
width: 1000px;
.title{
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: bold;
color: #333333;
line-height: 22px;
margin-bottom: 8px;
}
}
.paperTitle{
background-color:rgba(247, 248, 249, 1) ;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 40px;
border: 2px;
margin: 8px 0;
padding-left: 12px;
img{
width: 20px;
height: 20px;
margin-right: 4px;
position: relative;
top: -2px;
}
}
.table{
border: 1px solid #e8e8e8;
width: 600px;
.header,.body-list{
background-color: #fafafa;
display: flex;
.item{
width: 16%;
box-sizing: border-box;
line-height: 32px;
text-align: center;
&.long{
width: 20%;
}
}
}
.body-list{
background-color: transparent;
}
}
.footer {
position: fixed;
bottom: 0;
height: 58px;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 252px;
background: #fff;
border-top: 1px solid #E8E8E8;
z-index: 9999;
.ant-btn {
margin-left: 10px;
}
}
}
\ No newline at end of file
import React, { useState, useRef, useEffect, useContext } from 'react'
import Breadcrumbs from "@/components/Breadcrumbs";
import { Form, Alert, Input, Button, InputNumber, DatePicker, Switch, Radio, message, Modal } from 'antd';
import { Route, withRouter } from 'react-router-dom';
import User from "@/common/js/user";
import moment from 'moment'
import Service from "@/common/js/service";
import _ from 'underscore'
import GraphicsEditor from '../../course-manage/components/GraphicsEditor';
import SelectPaperModal from './SelectPaperModal'
import PreviewModal from './PreviewModal'
import './AddExam.less';
const { RangePicker } = DatePicker;
function AddExam(props: any) {
const paperInfoInit: any = { passScore: 60 };
const [showModal, setShowModal] = useState(false);
const [paperInfo, setPaperInfo] = useState(paperInfoInit);
const [paperId, setPaperId] = useState('');
const [passRate, setPassRate] = useState(60);//及格线
const [examStartTime, setStartTime] = useState('');
const [examEndTime, setExamEndTime] = useState('');
const [examName, setExamName] = useState('');
const [needPhone, setNeedPhone] = useState('DO_NOT_NEED_PHONE_VERIFY');
const [needOptionDisorder, setNeedOptionDisorder] = useState('OPTION_SORT');
const [resultContent, setResultContent] = useState('PASS_AND_SCORE');
const [answerAnalysis, setAnswerAnalysis] = useState('RIGHT_OR_WRONG');
const [resultShow, setResultShow] = useState('IMMEDIATELY');
const [examDesc, setExamDesc] = useState('');
const [passScore, setPassScore] = useState(100);
const [desclen, setDescLen] = useState(0);
const [check, setCheck] = useState(false);
const [getData, setGetData] = useState(false);
const [preview, setPreview] = useState(false);
const [examTotal, setExamTotal] = useState(0);
const timer = useRef({});
const request = useRef(false);
const { match } = props;
const [examDuration, setExamDuration] = useState(undefined);
useEffect(() => {
if (props.type === 'edit') {
queryExamDetail()
}
}, [])
useEffect(() => {
console.log(paperInfo)
setPaperId(paperInfo.paperId)
setPassRate(paperInfo.passRate)
}, [paperInfo.paperId])
useEffect(() => {
setPassScore(parseInt((paperInfo.totalScore || 0) * (passRate || 0) as any / 100 + ''))
setExamTotal(paperInfo.singleChoiceCnt + paperInfo.multiChoiceCnt + paperInfo.judgeCnt + paperInfo.gapFillingCnt + paperInfo.indefiniteChoiceCnt || 0)
}, [paperInfo.paperId, passRate])
function disabledDate(current: any) {
// Can not select days before today and today
return current && current < moment().startOf('day');
}
function queryExamDetail() {
Service.Hades("public/hades/queryExamDetail", {
examId: match.params.id,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
const { result } = res
setPaperInfo(result.examPaper)
setPaperId(result.examPaper.paperId)
setStartTime(result.examStartTime)
setExamEndTime(result.examEndTime)
setExamName(result.examName)
setPassRate(result.passRate * 100)
setNeedPhone(result.needPhone)
setExamDesc(result.examDesc)
setExamDuration(result.examDuration / 60 / 1000 as any)
setAnswerAnalysis(result.answerAnalysis)
setNeedOptionDisorder(result.needOptionDisorder)
setPassScore(result.passScore)
setResultContent(result.resultContent)
setResultShow(result.resultShow)
setGetData(true)
})
}
function handleSave() {
if (request.current) {
return
}
setCheck(true);
const param = {
paperId,
startTime: examStartTime,
endTime: examEndTime,
examName,
passRate: passRate / 100,
examStartTime,
examEndTime,
examDesc,
needPhone,
needOptionDisorder,
resultContent,
answerAnalysis,
resultShow,
examDuration: (examDuration || 0) * 60 * 1000,
passScore,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
examId: ''
}
if (!param.examName) {
message.warning('请输入考试名称');
return
}
if (!paperId) {
message.warning('请选择试卷');
return
}
if (!passRate) {
message.warning('请输入及格线');
return
}
if (!examStartTime || !examEndTime) {
message.warning('请选择考试起止时间');
return
}
if (Number(examStartTime) < moment().valueOf()) {
message.warning('开始时间不能早于现在');
return
}
if (!examDuration) {
message.warning('请输入考试时长');
return
}
if (examStartTime + (examDuration as any) * 60 * 1000 > examEndTime) {
message.warning('考试时长不得超过考试有效期时长');
return
}
if (desclen > 1000) {
message.warning('内容过长,不能超过1000字');
return
}
request.current = true;
setTimeout(() => {
request.current = false
}, 2000)
if (props.type === 'edit') {
param.examId = match.params.id;
}
Service.Hades(props.type === 'edit' ? 'public/hades/editExam' : "public/hades/createExam", param).then((res) => {
message.success(props.type === 'edit' ? '编辑成功' : '创建成功');
props.freshList()
props.history.goBack();
})
}
function disabledRangeTime(date: any, type: any) {
if (moment(date).isSame(moment(), 'day')) {
return {
disabledHours: () => {
const hours = [];
for (let i = 0; i < moment().hour(); i++) {
hours.push(i);
}
return hours;
},
disabledMinutes: () => {
const currentMinute = moment().minute();
const currentHour = moment(date).hour();
const minutes = [];
if (currentHour === moment().hour()) {
for (let i = 0; i < currentMinute; i++) {
minutes.push(i);
}
}
return minutes;
},
};
}
return {
disabledHours: () => [],
disabledMinutes: () => [],
disabledSeconds: () => [],
};
}
function handleGoBack() {
Modal.confirm({
title: '确定要返回吗?',
content: '返回后,本次编辑的内容将不被保存',
okText: '确认返回',
cancelText: '留在本页',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
props.history.goBack();
}
})
}
return <div className="page examPage">
<Breadcrumbs navList={props.type === 'edit' ? "编辑考试" : "新建考试"} goBack={handleGoBack} />
<div className="box">
<Alert message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企培保有依据国家规定及平台规则进行处理的权利" type="info" showIcon />
<div className="form">
<div className="title">基本信息</div>
<Form
labelCol={{ span: 3 }}
wrapperCol={{ span: 14 }}
layout="horizontal"
>
<Form.Item label="考试名称"
validateStatus={(check && !examName) ? 'error' : ''}
help={check && !examName && '请选择课程'}
required>
<Input placeholder='请输入试卷名称(40字以内)' maxLength={40} value={examName} onChange={(e) => {
setExamName(e.target.value)
}} style={{ width: 320 }} />
</Form.Item>
<Form.Item label="选择试卷"
validateStatus={(check && !paperId) ? 'error' : ''}
help={check && !paperId && '请选择试卷'}
required>
<Button onClick={() => { setShowModal(true) }} >{paperInfo.paperId ? '重新选择' : '选择试卷'}</Button>
{
paperInfo.paperId && <div className="paperTitle"><img src="https://image.xiaomaiketang.com/xm/pY5imEhjzw.png" alt="" /> {paperInfo.paperName}</div>
}
{
paperInfo.paperId && <div className="table">
<div className="header">
<div className="item">单选题</div>
<div className="item">多选题</div>
<div className="item">判断题</div>
<div className="item">填空题</div>
<div className="item long">不定项选择题</div>
<div className="item">合计</div>
</div>
<div className="body-list">
<div className="item">{paperInfo.singleChoiceCnt || 0}</div>
<div className="item">{paperInfo.multiChoiceCnt || 0}</div>
<div className="item">{paperInfo.judgeCnt || 0}</div>
<div className="item">{paperInfo.gapFillingCnt || 0}</div>
<div className="item long">{paperInfo.indefiniteChoiceCnt || 0}</div>
<div className="item">{examTotal}</div>
</div>
<div className="body-list">
<div className="item">{paperInfo.singleChoiceScore || 0}</div>
<div className="item">{paperInfo.multiChoiceScore || 0}</div>
<div className="item">{paperInfo.judgeScore || 0}</div>
<div className="item">{paperInfo.gapFillingScore || 0}</div>
<div className="item long">{paperInfo.indefiniteChoiceScore || 0}</div>
<div className="item">{paperInfo.totalScore || 0}</div>
</div>
</div>
}
</Form.Item>
<Form.Item label="及格线"
style={{ marginTop: 24 }}
validateStatus={(check && !passRate) ? 'error' : ''}
help={check && !passRate && '请输入及格线'}
required>
<InputNumber value={passRate} min={0} max={100} onChange={(value: any) => { setPassRate(parseInt(value)) }} style={{ width: 100 }} />
<span style={{ marginLeft: 4 }}>%
</span>
<span style={{ marginLeft: 16, color: "#999" }}>
{` 总分(${paperInfo.totalScore || 0})*及格线(${passRate || 0}%)=及格分数(${passScore})`}</span>
</Form.Item>
<Form.Item label="考试有效期"
validateStatus={(check && !examStartTime) ? 'error' : ''}
help={check && !examStartTime && '请选择考试起止时间'}
required>
<RangePicker
style={{ width: 320 }}
ranges={{
'近七天': [moment(), moment().add(6, 'day').endOf('day')],
'近1个月': [moment(), moment().add(1, 'month').endOf('day')],
'近3个月': [moment(), moment().add(3, 'month').endOf('day')],
}}
disabledDate={disabledDate}
value={[
examStartTime ? moment(Number(examStartTime)) : null,
examEndTime ? moment(Number(examEndTime)) : null
]}
disabledTime={disabledRangeTime}
showTime
format="YYYY/MM/DD HH:mm"
onChange={(date: any) => {
setStartTime(date && date[0]?.valueOf());
setExamEndTime(date && date[1]?.valueOf());
}}
/>
</Form.Item>
<Form.Item label="考试时长"
validateStatus={(check && !examDuration) ? 'error' : ''}
help={check && !examDuration && '请输入考试时长'}
required>
<InputNumber value={examDuration} max={1440} min={1} onChange={(value: any) => { setExamDuration(parseInt(value) as any) }} style={{ width: 100 }} />
<span style={{ marginLeft: 4 }}>分钟
</span>
<span style={{ marginLeft: 16, color: "#999" }}>
{` 时长不能超过1440分钟(24小时)`}</span>
</Form.Item>
<Form.Item label="考试说明"
validateStatus={(check && (desclen > 1000)) ? 'error' : ''}
help={check && (desclen > 1000) && '最多只能输入1000个字'}
>
{
(getData || (props.type !== 'edit')) && <GraphicsEditor
maxLimit={1000}
isIntro={true}
detail={{
content: examDesc
}}
onChange={(val: any, len: any) => { setExamDesc(val); setDescLen(len) }}
/>
}
</Form.Item>
<div className="title" style={{ marginTop: 40 }}>考试设置</div>
<Form.Item label="身份验证" required>
<div style={{ display: 'flex', marginLeft: 4, }}>
<Switch style={{ position: 'relative', top: 6 }}
checked={needPhone == 'NEED_PHONE_VERIFY'}
onChange={(val) => { setNeedPhone(val ? 'NEED_PHONE_VERIFY' : 'DO_NOT_NEED_PHONE_VERIFY') }}
></Switch>
<div style={{ position: 'relative', top: 3, left: 8, color: "#999" }}><p>开启:需要绑定手机号的用户才能参加考试</p>
<p>关闭:微信/企业微信登陆直接参加考试</p></div>
</div>
</Form.Item>
<Form.Item label="选项乱序" required>
<div style={{ display: 'flex', marginLeft: 4, }}>
<Switch style={{ position: 'relative', top: 6 }}
checked={needOptionDisorder == 'OPTION_RANDOM'}
onChange={(val) => { setNeedOptionDisorder(val ? 'OPTION_RANDOM' : 'OPTION_SORT') }}
></Switch>
<div style={{ position: 'relative', top: 3, left: 8, color: "#999" }}><p>开启:选择题的选项随机排序</p>
<p>关闭:选择题按题目原有顺序展示</p></div>
</div>
</Form.Item>
<Form.Item label="考试结果查看" required>
<Radio.Group onChange={(e: any) => { setResultShow(e.target.value) }} value={resultShow}>
<Radio value={'IMMEDIATELY'}>交卷后立即显示考试结果</Radio>
<Radio value={'AFTER_EXAM_END'}>到达考试截止日期才显示结果</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label=" 考试结果内容" required>
<Radio.Group onChange={(e: any) => { setResultContent(e.target.value) }} value={resultContent}>
<Radio value={'PASS_AND_SCORE'}>显示考试分数和是否及格</Radio>
<Radio value={'ONLY_SCORE'}>仅显示考试分数</Radio>
<Radio value={'ONLY_PASS'}>仅显示是否及格</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="答案与解析" required>
<Radio.Group onChange={(e: any) => { setAnswerAnalysis(e.target.value) }} value={answerAnalysis}>
<Radio value={'ANALYSE_AND_RIGHT_OR_WRONG'}>显示对错与解析</Radio>
<Radio value={'RIGHT_OR_WRONG'}>仅显示对错</Radio>
<Radio value={'CAN_NOT_CHECK'}>都不显示</Radio>
</Radio.Group>
</Form.Item>
</Form>
</div>
</div>
{
showModal && <SelectPaperModal onSelect={(info: any) => {
setPaperInfo(info)
}} paperInfo={paperInfo} close={() => { setShowModal(false) }}></SelectPaperModal>
}
<div className="footer">
<Button onClick={handleGoBack}>取消</Button>
<Button onClick={() => { setPreview(true) }}>预览</Button>
<Button type="primary" onClick={handleSave}>保存</Button>
</div>
{
preview && <PreviewModal
info={{
paperId,
startTime: examStartTime,
endTime: examEndTime,
examName,
passRate: passRate / 100,
examStartTime,
examEndTime,
examDesc,
needPhone,
needOptionDisorder,
resultContent,
answerAnalysis,
resultShow,
examDuration,
passScore,
examTotal,
totalScore: paperInfo.totalScore
}}
onClose={() => { setPreview(false) }}></PreviewModal>
}
</div>
}
export default withRouter(AddExam);
\ No newline at end of file
import React, { useState, useRef, useEffect, useContext } from 'react'
import { Route, withRouter } from 'react-router-dom';
import Breadcrumbs from "@/components/Breadcrumbs";
import UserData from './UserData';
import ExamData from './ExamData'
import Service from "@/common/js/service";
import { Tabs } from 'antd';
import User from "@/common/js/user";
import './dataAnalysic.less'
const { TabPane } = Tabs;
function DataAnalysic(props: any) {
const examDetailInit: any = {};
const [selectKey, setSelectKey] = useState('user')
const [examDetail, setExamDetail] = useState(examDetailInit);
const { match } = props;
const examId =match.params.id;
useEffect(() => {
queryExamDetail();
}, [])
function queryExamDetail() {
Service.Hades("public/hades/queryExamDetail", {
examId:examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
const { result } = res
setExamDetail(result)
})
}
return <div className="page dataAnalysic">
<Breadcrumbs navList={"考试数据"} goBack={props.history.goBack} />
<div className="box">
<div className="titleBox ">
<span className='tips'></span>
考试名称:{examDetail.examName}
</div>
</div>
<div className="box">
<Tabs activeKey={selectKey} onChange={(key: any) => {
setSelectKey(key)
}}>
<TabPane tab="考试人员数据" key="user">
<UserData examDetail ={examDetail } examId={examId} />
</TabPane>
<TabPane tab="题目数据" key="exam">
<ExamData examDetail ={examDetail } examId={examId}></ExamData>
</TabPane>
</Tabs>
</div>
</div>
}
export default withRouter(DataAnalysic);
\ No newline at end of file
import React, { useState, useRef, useEffect, useContext } from 'react'
import Service from "@/common/js/service";
import { PageControl } from "@/components";
import { Input, Select, Tooltip, Button, Table } from 'antd';
import User from "@/common/js/user";
import moment from 'moment';
import './userData.less'
const { Search } = Input;
const { Option } = Select;
interface sortType {
type: "ascend" | "descend" | null | undefined
}
function ExamData(props: any) {
const sortStatus: sortType = {
type: undefined
}
const examDataInit: any = {};
const queryInit: any = { current: 1, size: 10, order: 'SORT_ASC' };
const [examData, setUserData] = useState(examDataInit);
const [list, setList] = useState([]);
const [query, setQuery] = useState(queryInit);
const [total, setTotal] = useState(0);
const [field, setfield] = useState('');
const [allData, setAllData] = useState(0);
const [order, setOrder] = useState(sortStatus.type);
const questionTypeList = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
};
const userTypeEnum = {
WORK_WE_CHAT: '企业微信',
WE_CHAT: '微信'
}
const userExamStateEnum = {
EXAM: '进行中',
LACK_EXAM: '缺考',
FINISH_EXAM: '已考试'
}
const orderEnum = {
currentAccuracy: {
ascend: 'ACCURACY_ASC',
descend: 'ACCURACY_DESC'
},
}
const queryRef = useRef({});
useEffect(() => {
queryExamUserData();
}, [])
useEffect(() => {
queryRef.current = query;
queryExamUserDataList();
}, [query])
function queryExamUserData() {
Service.Hades('public/hades/queryExamQuestionData', {
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
setUserData(res.result)
})
}
function queryExamUserDataList() {
Service.Hades('public/hades/queryExamQuestionDataList', {
...query,
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
setList(res.result.records);
setTotal(parseInt(res.result.total))
if (!allData) {
setAllData(parseInt(res.result.total))
}
})
}
const columns = [
{
title: "序号",
dataIndex: "sort",
width: 60,
render: (text: any) => <span>{text + 1}</span>,
},
{
title: "题目",
dataIndex: "questionStem",
ellipsis: true,
width: 350,
render: (val: any) => {
var handleVal = val;
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
return (
<Tooltip
overlayClassName="aid-tool-list"
title={
<div style={{ maxWidth: 700, width: "auto" }}>{handleVal}</div>
}
placement="topLeft"
overlayStyle={{ maxWidth: 700 }}
>
{handleVal}
</Tooltip>
);
},
},
{
title: "题型",
dataIndex: "questionType",
render: (text: any) => <span>{(questionTypeList as any)[text]}</span>,
filters: Object.keys(questionTypeList).map((key) => {
return {
text: (questionTypeList as any)[key],
value: key
}
}),
},
{
title: "本次正确率",
dataIndex: "currentAccuracy",
sorter: true,
sortOrder: field === "currentAccuracy" ? order : sortStatus.type,
render: (text: any) => <span>{parseInt(text * 100 as any)}%</span>,
},
{
title: <div>历史正确率 <Tooltip
overlayClassName="tool-list"
title='包含本次考试正确率'
placement="top"
overlayStyle={{ maxWidth: 700 }}
> <span style={{ color: 'rgba(191, 191, 191, 1)' }} className="icon iconfont">&#xe61d;</span>
</Tooltip>
</div>,
dataIndex: "totalAccuracy",
render: (text: any) => <span>{parseInt(text * 100 as any)}%</span>,
},
];
function onChange(pagination: any, filters: any, sorter: any, extra: any) {
console.log(filters, sorter);
setfield(sorter.field);
setOrder(sorter.order)
console.log(sorter.field, sorter.order, (orderEnum as any)[sorter.field])
let _query: any = { ...queryRef.current };
console.log(filters.questionType)
if (filters.questionType) {
console.log(233232)
_query.questionType = filters.questionType;
_query.current = 1;
} else {
delete _query.questionType
}
_query.order = (orderEnum as any)[sorter.field][sorter.order]
setQuery(_query)
}
function download() {
Service.Hades('public/hades/exportExamData', {
// ...query,
examId: props.examId,
exportDataType: 'EXAM_QUESTION_DATA',
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
const dom = (document as any).getElementById("load-play-back-excel")
dom.setAttribute('href', res.result);
dom.click();
})
}
return <div className="rr">
<a
target="_blank"
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
></a>
<div className="dataPanal">
{
!!examData.singleChoiceCnt && <div className="item">
<div className="num">{Math.round((examData.singleChoiceAccuracy || 0) * 100)}%</div>
<div className="percent">正确率</div>
<div className="subTitle"><div className="type"><span className="icon iconfont">&#xe7fa;</span>单选题 <span>(共{examData.singleChoiceCnt}题)</span></div></div>
</div>
}
{
!!examData.multiChoiceCnt && <div className="item">
<div className="num">{Math.round((examData.multiChoiceAccuracy || 0) * 100)}%</div>
<div className="percent">正确率</div>
<div className="subTitle"><div className="type"><span className="icon iconfont">&#xe7fb;</span>多选题<span>(共{examData.multiChoiceCnt}题)</span></div></div>
</div>
}
{
!!examData.judgeCnt && <div className="item">
<div className="num">{Math.round((examData.judgeAccuracy || 0) * 100)}%</div>
<div className="percent">正确率</div>
<div className="subTitle"><div className="type"><span className="icon iconfont">&#xe7fc;</span>判断题<span>(共{examData.judgeCnt}题)</span></div></div>
</div>
}
{
!!examData.gapFillingCnt && <div className="item">
<div className="num">{Math.round((examData.gapFillingAccuracy || 0) * 100)}%</div>
<div className="percent">正确率</div>
<div className="subTitle"><div className="type"><span className="icon iconfont">&#xe7fd;</span>填空题<span>(共{examData.gapFillingCnt}题)</span></div></div>
</div>
}
{
!!examData.indefiniteChoiceCnt && <div className="item">
<div className="num">{Math.round((examData.indefiniteChoiceAccuracy || 0) * 100)}%</div>
<div className="percent">正确率</div>
<div className="subTitle"><div className="type"><span className="icon iconfont">&#xe7fe;</span>不定项选择题 <span>(共{examData.indefiniteChoiceCnt}题)</span></div></div>
</div>
}
</div>
{
!!allData && <Button style={{ marginBottom: 12, marginTop: 12 }} onClick={download} >导出</Button>
}
<div className="content">
<Table
bordered
size="small"
columns={columns}
dataSource={list}
onChange={onChange}
pagination={false}
>
</Table>
{total > 0 &&
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
toPage={(page: any) => {
console.log(page)
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query)
}}
/>
}
</div>
</div>
}
export default ExamData;
\ No newline at end of file
import React from 'react';
import { Modal, Button, message } from 'antd';
import html2canvas from 'html2canvas';
import User from "../../../common/js/user";
import QRCode from '../../../libs/qrcode/qrcode';
import { LIVE_SHARE } from '../../../domains/course-domain/constants';
import Service from '../../../common/js/service';
import { copyText } from '../../../domains/basic-domain/utils';
import './ExamShareModal.less';
class ExamShareModal extends React.Component {
constructor(props) {
super(props);
this.state = {
shareUrl: '',
storeName: User.getStoreName()
}
}
componentDidMount() {
// 获取短链接
this.handleConvertShortUrl();
}
handleConvertShortUrl = () => {
const longUrl = `${LIVE_SHARE}test_detail/${this.props.data.examId}?id=${User.getStoreId()}`
console.log(longUrl)
// 发请求
Service.Sales('public/businessShow/convertShortUrls', {
urls: [longUrl]
}).then((res) => {
const { result = [] } = res;
this.setState({
shareUrl: result[0].shortUrl
}, () => {
const qrcodeWrapDom = document.querySelector('#qrcodeWrap');
const qrcodeNode = new QRCode({
text: this.state.shareUrl,
size: 98,
})
qrcodeWrapDom.appendChild(qrcodeNode);
});
})
}
// 下载海报
handleDownloadPoster = () => {
const { data } = this.props;
const dom = document.querySelector('#poster');
html2canvas(dom, {
useCORS: true,
}).then(canvas => {
const downloadDOM = document.createElement('a');
const { courseName } = this.props.data;
const dataUrl = canvas.toDataURL('image/png');
downloadDOM.href = dataUrl;
downloadDOM.download = `${data.examName}.png`;
downloadDOM.click();
});
}
// 复制分享链接
handleCopy = () => {
const textContent = document.getElementById('shareUrl').innerText;
copyText(textContent);
message.success('复制成功!');
}
render() {
const { data } = this.props;
const { shareUrl, storeName } = this.state;
return (
<Modal
title="分享考试"
width={680}
visible={true}
footer={null}
maskClosable={false}
className="ExamShareModal"
onCancel={this.props.close}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
>
<div className="left" id="poster">
<div
className="inst-name oneLineText"
>
{storeName}
</div>
<div style={{ color: '#333' }}>邀请你参与考试:</div>
<div className="examName">{data.examName}</div>
<img
src={'https://image.xiaomaiketang.com/xm/HQXwFsyf3e.png'}
className="course-cover"
alt="course-cover"
/>
<div className="qrcode-wrap">
<div className="qrcode-wrap__left">
<div className="text">长按识别二维码进入考试</div>
<img
className="finger"
alt="finger"
src="https://image.xiaomaiketang.com/xm/thpkWDwJsC.png"
/>
</div>
<div className="qrcode-wrap__right" id="qrcodeWrap">
</div>
</div>
</div>
<div className="right">
<div className="share-poster right__item">
<div className="title">① 海报分享</div>
<div className="sub-title">用户可通过微信扫描海报二维码,查看考试</div>
<div className="content" onClick={this.handleDownloadPoster}>下载海报</div>
</div>
{/* <div className="share-url right__item" style={{ marginTop: 40 }}>
<div className="title">② 链接分享</div>
<div className="sub-title">用户可通过微信或浏览器打开以下链接,查看考试(建议使用谷歌浏览器)</div>
<div className="content">
<div className="share-url" id="shareUrl">{shareUrl}</div>
<Button
type="primary"
onClick={this.handleCopy}
>复制</Button>
</div>
</div> */}
</div>
</Modal>
)
}
}
export default ExamShareModal;
.ExamShareModal{
.ant-modal-body {
display: flex;
.left {
width: 303px;
padding: 20px;
margin: 0 32px 0 16px;
box-shadow:0px 2px 10px 0px rgba(0,0,0,0.05);
border-radius: 12px;
.course-cover {
width: 263px;
height: 143px;
border-radius: 6px;
margin-top: 8px;
}
.examName{
font-size: 16px;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #333333;
line-height: 22px;
max-height: 45px;
overflow: hidden;
// text-overflow: -o-ellipsis-lastline;
// overflow: hidden;
// text-overflow: ellipsis;
// display: -webkit-box;
// -webkit-line-clamp: 2;
// line-clamp: 2;
// -webkit-box-orient: vertical;
}
.qrcode-wrap {
padding: 0 16px;
display: flex;
align-items: center;
margin: 24px 0 16px 0;
&__left {
width: 98px;
text-align: center;
margin-right: 22px;
.text {
line-height: 20px;
}
.finger {
width: 40px;
height: 40px;
margin-top: 8px;
}
}
&__right {
width: 110px;
height: 110px;
padding: 6px
}
}
.inst-name {
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 20px;
.iconfont {
color: #999;
margin-right: 4px;
}
.text {
font-size: 12px;
color: #999;
}
}
}
.right {
.title {
color: #333;
font-weight: 500;
}
.sub-title {
color: #999;
margin-top: 16px;
}
.content {
display: flex;
align-items: center;
margin-top: 8px;
.share-url {
width: 212px;
overflow: hidden;
height: 29px;
line-height: 32px;
border-radius: 4px 0 0 4px;
padding-left: 12px;
white-space: nowrap;
color: #999999;
background: #EFEFEF;
}
.ant-btn {
margin-left: -2px;
}
}
.share-poster {
.content {
color: rgba(82, 137, 250, 1);
cursor: pointer;
}
}
}
}
}
\ No newline at end of file
import React, { useState, useRef, useEffect, useContext } from 'react'
import { Input, Select, DatePicker, Tooltip, Button, Table, Dropdown, Menu, Modal } from 'antd';
import TeacherSelect from '@/modules/common/TeacherSelect';
import { Route, withRouter } from 'react-router-dom';
import Service from "@/common/js/service";
import moment from 'moment';
import { PageControl } from "@/components";
import AddExam from './AddExam';
import User from "@/common/js/user";
import { XMContext } from "@/store/context";
import ExamShareModal from './ExamShareModal'
import DataAnalysic from './DataAnalysic'
import './index.less'
const { RangePicker } = DatePicker;
const { Search } = Input;
const { Option } = Select;
interface sortType {
type: "ascend" | "descend" | null | undefined
}
interface fixType {
left :boolean | "right" | "left" | undefined,
right: "right" | "left" ,
}
const fixStr:fixType={
left:'left',
right:'right'
}
function ExaminationManager(props: any) {
const queryInit: any = {
examName: '',
current: 1, size: 10, order: 'EXAM_START_TIME_DESC'
}
const sortStatus: sortType = {
type: undefined
}
const sortEnum = {
}
const { match } = props;
const sortState: any = false;
const ctx: any = useContext(XMContext);
const [query, setQuery] = useState(queryInit);
const [expandFilter, setExpandFilter] = useState(false);
const [total, setTotal] = useState(0);
const [list, setList] = useState([]);
const [field, setfield] = useState('');
const [order, setOrder] = useState(sortStatus.type);
const [modal, setModal] = useState(null);
const [questionCntSort, setQuestionCntSort] = useState(sortState)
const queryRef = useRef({});
const orderEnum = {
userCnt: {
ascend: 'USER_CNT_ASC',
descend: 'USER_CNT_DESC'
},
passCnt: {
ascend: 'PASS_CNT_ASC',
descend: 'PASS_CNT_DESC'
},
examCreateTime: {
ascend: 'EXAM_START_TIME_ASC',
descend: 'EXAM_START_TIME_DESC'
}
}
const columns = [
{
title: "考试",
// fixed:fixStr.left,
width:320,
dataIndex: "examName",
render: (text: any, record: any) => {
var _text = '未开始', _color = 'rgba(255, 183, 20, 1)';
if (moment().valueOf() > record.examEndTime) {
_text = '已结束';
_color = 'rgba(153, 153, 153, 1)';
} else if (moment().valueOf() > record.examStartTime) {
_text = '进行中';
_color = 'rgba(59, 189, 170, 1)';
}
return <div style={{ width: 320 }}>
<div className='oneLineText' style={{ width: 320,color:'#333',fontWeight:'bold' }} >{text}</div>
<div> <span >{moment(record.examStartTime).format("YYYY-MM-DD HH:mm")}~{moment(record.examEndTime).format("YYYY-MM-DD HH:mm")} </span> <div className="status" style={{ border: `1px solid ${_color}`,borderRadius:2, color: _color }}>{_text}</div></div>
<div>创建人:{record.examCreator}</div>
</div>
},
},
{
title: "考试时长",
dataIndex: "examDuration",
render: (text: any) => <span>{(text || 0) / 60 / 1000}分钟</span>,
},
{
title: "及格分/总分",
dataIndex: "totalScore",
render: (text: any, record: any) => <span>{`${record.passScore || 0}/${record.totalScore || 0}`}</span>,
},
{
title: "题目数量",
align:fixStr.right,
dataIndex: "questionCnt",
},
{
title: "参与人数",
dataIndex: "userCnt",
align:fixStr.right,
sorter: true,
sortOrder: field === "userCnt" ? order : sortStatus.type,
},
{
title: "及格数",
dataIndex: "passCnt",
align:fixStr.right,
sorter: true,
sortOrder: field === "passCnt" ? order : sortStatus.type,
},
{
title: "创建时间",
dataIndex: "examCreateTime",
sorter: true,
sortOrder: field === "examCreateTime" ? order : sortStatus.type,
render: (text: any, record: any) => <span>{moment(text).format("YYYY-MM-DD HH:mm")}</span>,
},
{
title: "操作",
fixed:fixStr.right,
dataIndex: "operate",
render: (text: any, record: any) => <div className="table_operate">
{
ctx.xmState?.userPermission?.SeeExamData() && [<div
key="data"
className="operate__item"
onClick={() => {
props.history.push({
pathname: `${match.url}/analysic/${record.examId}`
})
}}
>
数据
</div>,
<span className="operate__item split" > | </span>]
}
<div
key="share"
className="operate__item"
onClick={() => { shareModal(record) }}
>
分享
</div>
{
((ctx.xmState?.userPermission?.AddExam() && (moment().valueOf() < record.examStartTime)) || (ctx.xmState?.userPermission?.DelExam() && (moment().valueOf() + 30 * 60 * 1000 < record.examStartTime))) && [<span className="operate__item split" > | </span>, <Dropdown overlay={getOpe(record)}>
<span className='more'>更多</span>
</Dropdown>]
}
</div>,
},
];
function shareModal(record: any) {
const modal = <ExamShareModal
data={record}
close={() => {
setModal(null)
}}
/>
setModal(modal as any)
}
function getOpe(item: any) {
return <Menu>
{
ctx.xmState?.userPermission?.AddExam() && (moment().valueOf() < item.examStartTime) && <Menu.Item key="0">
<span
onClick={() => {
if (moment().valueOf() + 5 * 60 * 1000 > item.examStartTime) {
Modal.info({
title: '无法编辑',
content: '离考试开始时间小于5分钟,为保证答题数据的准确性,不能再进行编辑了',
})
} else {
props.history.push({
pathname: `${match.url}/edit/${item.examId}`
})
}
}}
>
编辑
</span>
</Menu.Item>
}
{
ctx.xmState?.userPermission?.DelExam() && (moment().valueOf() + 30 * 60 * 1000 < item.examStartTime) && <Menu.Item key="1">
<span
onClick={() => {
deleteExam(item)
}}
>
删除
</span>
</Menu.Item>
}
</Menu>
}
function deleteExam(item: any) {
Modal.confirm({
title: '删除考试',
content: '确定删除该考试吗?',
okText: '删除',
cancelText: '取消',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
Service.Hades("public/hades/deleteExam", {
"examId": item.examId,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
source: 0
}).then(() => {
getList()
})
}
})
}
function getList() {
const _query = { ...queryRef.current };
// if(_query.examCreator){
// _query.examCreator =parseInt(_query.examCreator)
// }
Service.Hades("public/hades/queryExamPageList", {
..._query, userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
source: 0
}).then((res) => {
setList(res.result?.records || [])
setTotal(parseInt(res.result.total))
})
}
useEffect(() => {
queryRef.current = query;
getList();
}, [query])
function onShowSizeChange(current: any, size: any) {
( queryRef.current as any).size =size
}
function onChange(pagination: any, filters: any, sorter: any, extra: any) {
setfield(sorter.field);
setOrder(sorter.order)
console.log(sorter.field, sorter.order, (orderEnum as any)[sorter.field])
let _query: any = { ...queryRef.current };
_query.order = (orderEnum as any)[sorter.field][sorter.order] || 'EXAM_START_TIME_DESC'
setQuery(_query)
}
return <div className="page examination-manager">
<div className="content-header">考试</div>
<div className="box content-body">
<div className="xm-search-filter">
<div style={{ display: 'flex' }}>
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">考试名称:</span>
<Search
value={query.examName}
className='search-input'
placeholder="搜索考试名称"
onChange={(e) => {
const _query = { ...query }
_query.examName = e.target.value
_query.current = 1;
setQuery(_query);
}}
onSearch={() => { }}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<TeacherSelect val={query.examCreator}
onChange={(examCreator: any) => {
const _query = { ...query }
_query.examCreator = examCreator;
_query.current = 1;
setQuery(_query);
}}
roleCodes={["CloudManager", 'StoreManager']}
></TeacherSelect>
<div className="search-condition__item">
<span className="search-name">考试时间:</span>
<RangePicker
className='search-input'
value={[
query.examStartTime ? moment(Number(query.examStartTime)) : null,
query.examStartTime ? moment(Number(query.examEndTime)) : null
]}
onChange={(date: any) => {
const _query = { ...query }
_query.examStartTime = date && date[0]?.startOf('day').valueOf();
_query.examEndTime = date && date[1]?.endOf('day').valueOf();
_query.current = 1;
setQuery(_query);
}} />
</div>
{
!!expandFilter && <div className="search-condition__item">
<span className="search-name">创建时间:</span>
<RangePicker
className='search-input'
value={[
query.createStartTime ? moment(Number(query.createStartTime)) : null,
query.createStartTime ? moment(Number(query.createEndTime)) : null
]}
onChange={(date: any) => {
const _query = { ...query }
_query.createStartTime = date && date[0]?.startOf('day').valueOf();
_query.createEndTime = date && date[1]?.endOf('day').valueOf();
_query.current = 1;
setQuery(_query);
}} />
</div>
}
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={() => {
setfield('')
setQuery({ current: 1, size: 10, order: 'EXAM_START_TIME_DESC' });
}}>&#xe61b; </span></Tooltip>
<span style={{ cursor: 'pointer' }} className="fold-btn" onClick={() => {
setExpandFilter(!expandFilter)
}}>{expandFilter ? <span><span>收起</span><span className="iconfont icon fold-icon" >&#xe82d; </span> </span> : <span>展开<span className="iconfont icon fold-icon" >&#xe835; </span></span>}</span>
</div>
</div>
</div>
{
ctx.xmState?.userPermission?.AddExam() && <Button type='primary' onClick={() => {
props.history.push({
pathname: `${match.url}/add`
})
}} style={{ margin: '4px 0 16px' }}>新建考试</Button>
}
<div className="content">
<Table
bordered
size="small"
columns={columns}
dataSource={list}
scroll={{ x: 1150 }}
onChange={onChange}
pagination={false}
style={{ margin: '0px 0 16px' }}
>
</Table>
{total > 0 &&
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
onShowSizeChange={onShowSizeChange}
toPage={(page: any) => {
console.log(page)
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query)
}}
/>
}
</div>
</div>
<Route path={`${match.url}/add`} render={() => {
return <AddExam freshList={() => {
let _query: any = { ...queryRef.current };
if (_query.current != 1) {
_query.current = 1;
setQuery(_query)
} else {
getList()
}
}} />;
}} />
<Route path={`${match.url}/edit/:id`} render={() => {
return <AddExam type='edit' freshList={() => {
getList()
}} />;
}} />
<Route path={`${match.url}/analysic/:id`} render={() => {
return <DataAnalysic />;
}} />
{
modal
}
</div>
}
export default withRouter(ExaminationManager);
\ No newline at end of file
.ExamPreviewModal{
// height: 880px;
.ant-modal-content .ant-modal-body{
padding: 0 24px !important;
max-height: 80vh !important;
}
.ExamPreview{
display: flex;
}
.left{
width: 460px;
.phone{
background:url(https://image.xiaomaiketang.com/xm/PpKc6zYKCG.png) ;
height:746px ;
background-size: 100% 100%;
padding-top: 1px;
.content{
width: 326px;
height: 520px;
background-color: #fff;
margin: 137px auto;
position: relative;
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
.topContent{
width: 100%;
height: 198px;
background: url(https://image.xiaomaiketang.com/xm/bNHBZZfFzb.png);
background-size: 100% 100%;
padding-top: 1px;
position: relative;
.title{
width: 274px;
height: 60px;
font-size: 22px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #FFFFFF;
// line-height: 30px;
text-align: center;
margin: 20px auto 0;
word-break: break-all;
}
.time{
font-size: 11px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
line-height: 16px;
transform: scale(0.9);
margin-top: 12px;
text-align: center;
}
.rule{
width: 298px;
height: 113px;
background: #FFFFFF;
box-shadow: 0px -13px 9px 0px rgba(0, 34, 121, 0.1);
border-radius: 3px 3px 0px 0px;
position: absolute;
left: 14px;
top: 124px;
background: #fff;
display: flex;
.item{
width: 25%;
text-align: center;
.num{
margin-top: 28px;
font-size: 17px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 24px;
}
.text{
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 16px;
transform: scale(0.9);
margin-top: 12px;
.dw{
transform: scale(0.8);
}
}
}
}
}
.fg{
margin-top: 60px;
text-align: center;
img{
width: 164px;
}
}
.examDesc{
width: 298px;
margin: 4px auto 0;
}
}
}
}
.right{
width: 353px;
.title{
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 22px;
text-align: center;
margin-bottom: 16px;
margin-top: 48px;
}
.rule{
background: #F4F6FA;
border-radius: 4px;
padding: 14px;
padding-bottom: 8px;
.item{
margin-bottom: 8px;
.name{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 14px;
width: 90px;
display: inline-block;
margin-right: 4px;
}
.text{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
}
}
}
}
}
\ No newline at end of file
import React, { useState, useRef, useEffect, useContext } from 'react'
import moment from 'moment';
import { Modal } from 'antd';
import './PreviewModal.less'
function PreviewModal(props: any) {
console.log(props.info)
const resultContentEnum = {
PASS_AND_SCORE: '显示考试分数和是否及格',
ONLY_SCORE: '仅显示考试分数',
ONLY_PASS: '仅显示是否及格'
}
const answerAnalysisEnum = {
ANALYSE_AND_RIGHT_OR_WRONG: '显示对错与解析',
RIGHT_OR_WRONG: '仅显示对错',
CAN_NOT_CHECK: '都不显示'
}
return <Modal
title="预览"
onCancel={props.onClose}
onOk={props.onClose}
maskClosable={true}
visible={true}
footer={null}
className='ExamPreviewModal'
closable={true}
width={800}
>
<div className="ExamPreview">
<div className="left">
<div className="phone">
<div className="content">
<div className="topContent">
<div className="title" style={{ fontSize: props.info.examName.length > 24 ? 13 : 22 ,marginTop:props.info.examName.length > 24?44:20}}>{props.info.examName || ' '}</div>
{
props.info.examStartTime && <div className="time">{moment(props.info.examStartTime).format("YYYY-MM-DD HH:mm")}~{moment(props.info.examEndTime).format("YYYY-MM-DD HH:mm")}</div>
}
<div className="rule">
<div className="item">
<div className="num">{props.info.totalScore || 0}</div>
<div className="text">总分 <span className="dw" style={{color:'#999'}}>(分)</span></div>
</div>
<div className="item">
<div className="num">{props.info.examTotal || 0}</div>
<div className="text">总题数<span className="dw" style={{color:'#999'}} >(道)</span></div>
</div>
<div className="item">
<div className="num">{props.info.passScore || 0}</div>
<div className="text">及格分<span className="dw" style={{color:'#999'}} >(分)</span></div>
</div>
<div className="item">
<div className="num">{props.info.examDuration || 60}</div>
<div className="text">时长<span className="dw" style={{color:'#999'}} >(分钟)</span></div>
</div>
</div>
</div>
<div className="fg">
<img src="https://image.xiaomaiketang.com/xm/8ASbb6TQ6Q.png" alt="" />
</div>
<div className="examDesc" style={{color:'#666'}} dangerouslySetInnerHTML={{ __html: props.info.examDesc }}></div>
</div>
</div>
</div>
<div className="right">
<div className="title">本次考试设置</div>
<div className="rule">
<div className="item">
<span className="name"> 身份证验证: </span>
<span className="text">{props.info.needPhone == 'NEED_PHONE_VERIFY' ? '需要验证手机号才能参加考试' : '仅需微信/企业微信授权登录验证'}</span>
</div>
<div className="item" style={{marginBottom:18}}>
<span className="name"> 选择乱序: </span>
<span className="text">{props.info.needOptionDisorder == 'OPTION_RANDOM' ? '选择题的选项随机排序' : '选择题按题目原有顺序展示'}</span>
</div>
<div className="item">
<span className="name"> 考试结果查看: </span>
<span className="text">{props.info.resultShow === 'IMMEDIATELY' ? '交卷后立即显示考试结果' : '到达考试截止日期才显示结果'}</span>
</div>
<div className="item">
<span className="name"> 考试结果内容: </span>
<span className="text">{(resultContentEnum as any)[props.info.resultContent]}</span>
</div>
<div className="item">
<span className="name"> 答案与解析: </span>
<span className="text">{(answerAnalysisEnum as any)[props.info.answerAnalysis]}</span>
</div>
</div>
</div>
</div>
</Modal>
}
export default PreviewModal
\ No newline at end of file
import React, { useState, useRef, useEffect, useContext } from 'react'
import { message, Modal } from 'antd';
import { withRouter } from 'react-router-dom';
import PaperContent from '../paper-manage/PaperContent'
import './AddExam.less';
import user from '@/common/js/user';
function SelectPaperModal(props: any) {
const [item, setItem] = useState(props.paperInfo);
const itemRef = useRef({})
useEffect(() => {
itemRef.current = item
console.log(item, 'khjkhjkhjk')
}, [item])
return <Modal
width={900}
title="选择试卷"
visible={true}
onOk={() => {
props.onSelect(itemRef.current);
props.close();
}
}
onCancel={() => { props.close() }}
>
<div style={{ maxHeight: 500 }}>
<PaperContent paperId={item.paperId} onSelect={(item: any) => { setItem(item) }} type='modal-select'></PaperContent>
</div>
</Modal >
}
export default withRouter(SelectPaperModal);
\ No newline at end of file
import React, { useState, useRef, useEffect } from 'react'
import Service from "@/common/js/service";
import { PageControl } from "@/components";
import { Input, Select, Tooltip, Table, Button } from 'antd';
import User from "@/common/js/user";
import moment from 'moment';
import './userData.less'
const { Search } = Input;
const { Option } = Select;
declare var window: any;
interface sortType {
type: "ascend" | "descend" | null | undefined
}
function DataAnalysic(props: any) {
const sortStatus: sortType = {
type: undefined
}
const useDataInit: any = {};
const queryInit: any = { current: 1, size: 10, };
const [useData, setUserData] = useState(useDataInit);
const [list, setList] = useState([]);
const [query, setQuery] = useState(queryInit);
const [total, setTotal] = useState(0);
const [field, setfield] = useState('');
const [allData, setAllData] = useState(0);
const [order, setOrder] = useState(sortStatus.type);
const userTypeEnum = {
WORK_WE_CHAT: '企业微信',
WE_CHAT: '微信'
}
const userExamStateEnum = {
EXAM: '进行中',
LACK_EXAM: '缺考',
FINISH_EXAM: '已考试'
}
const ExamPassColorEnum = {
EXAM_FAIL: 'rgba(255, 79, 79, 1)',
EXAM_PASS: 'rgba(59, 189, 170, 1)',
}
const ExamPassEnum = {
EXAM_FAIL: '不及格',
EXAM_PASS: '及格',
}
const userExamStateColorEnum = {
EXAM: 'rgba(35, 143, 255, 1)',
LACK_EXAM: 'rgba(204, 204, 204, 1)',
FINISH_EXAM: 'rgba(47, 200, 60, 1)'
}
const orderEnum = {
score: {
ascend: 'EXAM_SCORE_ASC',
descend: 'EXAM_SCORE_DESC'
},
userDuration: {
ascend: 'USER_DURATION_ASC',
descend: 'USER_DURATION_DESC'
},
}
const queryRef = useRef({});
useEffect(() => {
queryExamUserData();
}, [])
useEffect(() => {
queryRef.current = query;
queryExamUserDataList();
}, [query])
function queryExamUserData() {
Service.Hades('public/hades/queryExamUserData', {
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
setUserData(res.result)
})
}
function queryExamUserDataList() {
Service.Hades('public/hades/queryExamUserDataList', {
...query,
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
setList(res.result.records);
setTotal(parseInt(res.result.total))
if (!allData) {
setAllData(parseInt(res.result.total))
}
})
}
const columns = [
{
title: "用户",
dataIndex: "userName",
render: (text: any, record: any) => <span>{text}<span style={{ color: record.userSource === 'WORK_WE_CHAT' ? 'rgba(255, 157, 20, 1)' : 'rgba(29, 204, 101, 1)' }} >@{(userTypeEnum as any)[record.userSource]}</span></span>,
},
{
title: "手机号",
dataIndex: "phone",
},
{
title: "考试状态",
dataIndex: "userExamState",
render: (text: any) => <span> <span className='exstatus' style={{ background: (userExamStateColorEnum as any)[text] }}></span> {(userExamStateEnum as any)[text]}</span>,
},
{
title: "考试成绩",
dataIndex: "score",
sorter: true,
sortOrder: field === "score" ? order : sortStatus.type,
render: (text: any, record: any) => <span> {text} <span style={{ border: `1px solid ${(ExamPassColorEnum as any)[record.examPass]}`, fontSize: 12, color: (ExamPassColorEnum as any)[record.examPass], display: 'inline-block', padding: '0px 2px' }}>{(ExamPassEnum as any)[record.examPass]}</span></span>,
},
{
title: "进入考试时间",
dataIndex: "examStartTime",
render: (text: any) => <span>{moment(text).format("YYYY-MM-DD HH:mm")}</span>,
},
{
title: "考试用时",
dataIndex: "userDuration",
sorter: true,
sortOrder: field === "userDuration" ? order : sortStatus.type,
render: (text: any,record:any) => <span>{ record.userExamState==='FINISH_EXAM' ? window.formatHourTime(text):'-'} </span>,
},
];
function onChange(pagination: any, filters: any, sorter: any, extra: any) {
setfield(sorter.field);
setOrder(sorter.order)
console.log(sorter.field, sorter.order, (orderEnum as any)[sorter.field])
let _query: any = { ...queryRef.current };
_query.order = (orderEnum as any)[sorter.field][sorter.order]
setQuery(_query)
}
function download() {
Service.Hades('public/hades/exportExamData', {
// ...query,
examId: props.examId,
exportDataType: 'EXAM_USER_DATA',
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
const dom = (document as any).getElementById("load-play-back-excel")
dom.setAttribute('href', res.result);
dom.click();
})
}
return <div className="rr">
<a
target="_blank"
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
>
111
</a>
<div className="dataPanal">
<div className="item">
<div className="num">{useData.joinCnt || 0}</div>
<div className="percent"></div>
<div className="subTitle">参与人数</div>
</div>
<div className="item">
<div className="num">{useData.finishCnt || 0}</div>
<div className="percent">占比{parseInt(((useData.finishCnt || 0) / (useData.joinCnt || 1)) * 100 + '')}%</div>
<div className="subTitle">完成考试数 (人)</div>
</div>
<div className="item">
<div className="num">{useData.passCnt || 0}</div>
<div className="percent">占比{parseInt(((useData.passCnt || 0) / (useData.finishCnt || 1)) * 100 + '')}%</div>
<div className="subTitle">及格数 (人)</div>
</div>
<div className="item">
<div className="num">{useData.averageScore || 0}</div>
<div className="percent">总分{props.examDetail?.examPaper?.totalScore}</div>
<div className="subTitle">平均分</div>
</div>
<div className="item">
<div className="num"> {window.formatHourTime(useData.averageDuration || 0)} </div>
<div className="percent"></div>
<div className="subTitle">平均用时</div>
</div>
</div>
<div className="xm-search-filter" style={{ marginTop: 12 }}>
<div style={{ display: 'flex' }}>
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">用户:</span>
<Search
value={query.examName}
className='search-input'
placeholder="搜索用户名或手机号"
onChange={(e) => {
const _query = { ...query }
_query.searchKey = e.target.value
setQuery(_query);
}}
onSearch={() => { }}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span className="search-name">用户类型:</span>
<Select value={query.userSource} placeholder="请选择用户类型" onChange={(val) => {
const _query = { ...query }
_query.userSource = val
setQuery(_query);
}} className='search-input' allowClear>
{
Object.keys(userTypeEnum).map((key: any) => {
return <Option value={key}>{(userTypeEnum as any)[key]}</Option>
})
}
</Select>
</div>
<div className="search-condition__item">
<span className="search-name">考试状态:</span>
<Select value={query.userExamState} placeholder="请选择考试状态" onChange={(val) => {
const _query = { ...query }
_query.userExamState = val
setQuery(_query);
}} className='search-input' allowClear>
{
Object.keys(userExamStateEnum).map((key: any) => {
return <Option value={key}>{(userExamStateEnum as any)[key]}</Option>
})
}
</Select>
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={() => {
setfield('')
setQuery({ current: 1, size: 10 });
}}>&#xe61b; </span></Tooltip>
</div>
</div>
</div>
{
!!allData && <Button style={{ marginBottom: 12 }} onClick={download} >导出</Button>
}
<div className="content">
<Table
bordered
size="small"
columns={columns}
dataSource={list}
onChange={onChange}
pagination={false}
>
</Table>
{total > 0 &&
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
toPage={(page: any) => {
console.log(page)
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query)
}}
/>
}
</div>
</div>
}
export default DataAnalysic;
\ No newline at end of file
.dataAnalysic{
.titleBox{
font-size: 19px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
background: #FFFFFF;
// padding: 20px 24px;
// margin-bottom: 8px;
.tips{
width: 4px;
height: 16px;
background: #336DFF;
display: inline-block;
margin-right: 4px;
}
}
}
\ No newline at end of file
.examination-manager{
.status{
display: inline-block;
margin-left: 4px;
border: 1px solid #999;
padding: 2px 4px;
line-height: 16px;
}
}
\ No newline at end of file
.dataPanal{
border-radius: 4px;
border: 1px solid #E8E8E8;
display: flex;
.item{
text-align: center;
// width: 29.9%;
position: relative;
flex: 1;
.num{
font-size: 26px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
margin-top: 12px;
}
.percent{
margin-top: 6px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 17px;
height: 20px;
margin-bottom: 18px;
}
.subTitle{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
margin-bottom: 12px;
}
.type{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
span{
color: rgba(153, 153, 153, 1);
}
.icon{
color: rgba(204, 204, 204, 1);
font-size: 16px;
margin-right: 4px;
position: relative;
top: 1px;
}
}
&:after{
content: '';
width: 0px;
height: 40px;
position: absolute;
width: 1px;
background-color: rgba(232, 232, 232, 1);
top: 40px;
right: 0px;
}
&:last-child{
&:after{
display: none;
}
}
}
.exstatus{
width: 4px;
height: 4px;
background: rgb(35, 143, 255);
display: inline-block;
border-radius: 50%;
position: relative;
top: -4px;
}
}
\ No newline at end of file
......@@ -2,15 +2,15 @@
* @Author: yuananting
* @Date: 2021-02-22 17:51:28
* @LastEditors: yuananting
* @LastEditTime: 2021-03-24 15:02:53
* @Description: 助学工具-题库-题库新建或编辑题库分类模态框
* @LastEditTime: 2021-04-01 11:21:09
* @Description: 助学工具-新建编辑课程分类模态框
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Modal, Form, Input, message } from "antd";
import { Modal, Form, Input } from "antd";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
class NewEditQuestionBankCategory extends Component {
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
class NewEditCourseCategoryModal extends Component {
formRef = React.createRef();
constructor(props) {
super(props);
......@@ -22,20 +22,21 @@ class NewEditQuestionBankCategory extends Component {
}
componentDidMount() {
// document.getElementById("categoryName").setAttribute("style", "autocomplete","off")
this.queryCategoryTree();
}
// 查询分类树
queryCategoryTree = () => {
let query = {
bizType: "QUESTION",
count: false,
source: 0,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryCategoryTree(query).then((res) => {
const { result = [] } = res;
this.setState({ treeData: result });
AidToolService.queryCategoryTree(query).then((res) => {
const { categoryList = [] } = res.result;
this.setState({ treeData: categoryList });
});
};
......@@ -52,7 +53,7 @@ class NewEditQuestionBankCategory extends Component {
//新增
params.categoryName = categoryName;
if (addLevelType === "equal") {
params.parentId = node ? node.parentId : 0
params.parentId = node ? node.parentId : 0;
params.categoryLevel = node ? node.categoryLevel : 0;
} else {
params.parentId = node.id;
......@@ -60,7 +61,7 @@ class NewEditQuestionBankCategory extends Component {
}
try {
await this.formRef.current.validateFields();
QuestionBankService.addCategory(params).then((res) => {
AidToolService.addCategory(params).then((res) => {
if (res.success) {
this.props.close();
}
......@@ -76,7 +77,7 @@ class NewEditQuestionBankCategory extends Component {
params.categoryName = categoryName;
try {
await this.formRef.current.validateFields();
QuestionBankService.editCategory(params).then((res) => {
AidToolService.editCategory(params).then((res) => {
if (res.success) {
this.props.close();
}
......@@ -87,27 +88,48 @@ class NewEditQuestionBankCategory extends Component {
}
};
// 校验是否重名
checkExist = (sameLevelNodes, categoryName) => {
if (sameLevelNodes.length > 0 && sameLevelNodes[0].parentId === "0") {
if (categoryName === "未分类") {
return true;
}
}
var result = null;
sameLevelNodes.forEach((item) => {
if (result != null) {
return result;
}
if (item.categoryName === categoryName) {
result = item;
}
});
return result;
};
// 查询同级节点
getEqualLevelNodes = (data, parentId) => {
let nodes = [];
data.forEach((item) => {
if (item.parentId === parentId) {
nodes.push(item);
}
if (item.children) {
nodes.push(...this.getEqualLevelNodes(item.children, parentId));
if (item.sonCategoryList) {
nodes.push(...this.getEqualLevelNodes(item.sonCategoryList, parentId));
}
});
return nodes;
};
// 查询子级节点
getChildLevelNodes = (data, id) => {
let nodes = [];
data.forEach((item) => {
if (item.id === id && item.children) {
nodes.push(...item.children);
if (item.id === id && item.sonCategoryList) {
nodes.push(...item.sonCategoryList);
}
if (item.children) {
nodes.push(...this.getChildLevelNodes(item.children, id));
if (item.sonCategoryList) {
nodes.push(...this.getChildLevelNodes(item.sonCategoryList, id));
}
});
return nodes;
......@@ -124,28 +146,9 @@ class NewEditQuestionBankCategory extends Component {
return sameLevelNodes;
};
// 查询是否重名
checkExist = (sameLevelNodes, categoryName) => {
if ((sameLevelNodes.length > 0 && sameLevelNodes[0].parentId === "0")) {
if (categoryName === "未分类") {
return true;
}
}
var result = null;
sameLevelNodes.forEach((item) => {
if (result != null) {
return result;
}
if (item.categoryName === categoryName) {
result = item;
}
});
return result;
};
render() {
const { title, label, treeData, addLevelType } = this.props;
const { categoryName } = this.state;
const { title, label, addLevelType } = this.props;
const { categoryName, treeData } = this.state;
const _that = this;
return (
<Modal
......@@ -196,4 +199,4 @@ class NewEditQuestionBankCategory extends Component {
);
}
}
export default NewEditQuestionBankCategory;
export default NewEditCourseCategoryModal;
/*
* @Author: yuananting
* @Date: 2021-03-27 14:55:14
* @LastEditors: yuananting
* @LastEditTime: 2021-04-01 14:00:47
* @Description: 助学工具-试卷
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import PaperContent from "./PaperContent";
class PaperIndex extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="paper-manage-index page">
<div className="content-header">试卷</div>
<div className="box content-body">
<PaperContent type=''></PaperContent>
</div>
</div>
);
}
}
export default PaperIndex;
/*
* @Author: yuananting
* @Date: 2021-03-27 16:15:13
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 14:50:02
* @Description: 助学工具-新建/复制/编辑试卷
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import {
Form,
Button,
Input,
Table,
InputNumber,
ConfigProvider,
Empty,
Tooltip,
message,
Modal,
Spin,
} from "antd";
import { PlusOutlined } from "@ant-design/icons";
import ShowTips from "@/components/ShowTips";
import Breadcrumbs from "@/components/Breadcrumbs";
import "./OperatePaper.less";
import SelectQuestionModal from "./modal/SelectQuestionModal";
import PaperPreviewModal from "./modal/PreviewPaperModal";
import User from "@/common/js/user";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import Bus from "@/core/bus";
const questionTypeEnum = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
};
class OperatePaper extends Component {
formRef = React.createRef();
constructor(props) {
super(props);
this.state = {
formData: {
categoryId: getParameterByName("categoryId"), // 分类ID
singleChoiceCnt: 0, // 单选题数量
multiChoiceCnt: 0, // 多选题数量
judgeCnt: 0, // 判断题数量
gapFillingCnt: 0, // 填空题数量
indefiniteChoiceCnt: 0, // 不定项选择题数量
singleChoiceScore: 0, // 单选题目总分
multiChoiceScore: 0, // 多选题目总分
judgeScore: 0, // 判断题目总分
gapFillingScore: 0, // 填空题目总分
indefiniteChoiceScore: 0, //不定项选择总分
paperName: null, // 试卷名称
passRate: 60, // 及格线
passScore: 0, // 及格分
questionCnt: 0, // 题目数量
totalScore: 0, // 总分
questionList: [], // 题目列表
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
},
selectQuestionModal: null,
paperPreviewModal: null,
selectQuestionList: [],
currentOperate: "",
currentNav: "",
currentCategoryPapers: [],
loading: false,
check: false,
};
}
componentDidMount() {
this.queryCurrentCategoryPapers();
switch (getParameterByName("type")) {
case "new":
this.setState({ currentOperate: "new", currentNav: "新建试卷" });
break;
case "edit":
this.setState({ currentOperate: "edit", currentNav: "编辑试卷" }, () =>
this.queryPaperDetail()
);
break;
case "copy":
this.setState({ currentOperate: "copy", currentNav: "复制试卷" }, () =>
this.queryPaperDetail()
);
break;
}
}
// 获取当前分类下的所有试卷
queryCurrentCategoryPapers = () => {
const categoryId = getParameterByName("categoryId");
let params = {
current: 1,
size: 9999,
categoryId: categoryId === "null" ? null : categoryId,
paperName: null, // 试卷名称
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
AidToolService.queryPaperPageList(params).then((res) => {
const { records = [] } = res.result;
this.setState({ currentCategoryPapers: records });
});
};
// 编辑/复制试卷时获取相应试卷详情
queryPaperDetail = async () => {
this.setState({ loading: true });
const { currentOperate } = this.state;
let query = {
paperId: getParameterByName("paperId"),
source: 0,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
const res = await AidToolService.queryPaperDetail(query);
const { result } = res;
const { paperName, passRate } = result;
this.setState(
{
selectQuestionList: result.questionList.sort(
(a, b) => b.updateTime - a.updateTime
),
formData: {
...result,
paperName:
currentOperate === "copy" ? paperName + "(复制)" : paperName,
},
loading: false,
},
() => {
this.formRef.current.setFieldsValue({
paperName: this.state.formData.paperName,
passRate,
});
this.setFormData(result.questionList);
}
);
};
// 自定义表格空状态
customizeRenderEmpty = () => {
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
imageStyle={{
height: 100,
}}
description={"请在左上角添加题目"}
></Empty>
);
};
setFormData = (list, sorter) => {
const { formData } = this.state;
const _selectQuestionList = [...list];
// 各类型题目汇总
const singleQuestion = _.filter(
_selectQuestionList,
(item) => item.questionType === "SINGLE_CHOICE"
);
const multiQuestion = _.filter(
_selectQuestionList,
(item) => item.questionType === "MULTI_CHOICE"
);
const judgeQuestion = _.filter(
_selectQuestionList,
(item) => item.questionType === "JUDGE"
);
const gapQuestion = _.filter(
_selectQuestionList,
(item) => item.questionType === "GAP_FILLING"
);
const indefiniteQuestion = _.filter(
_selectQuestionList,
(item) => item.questionType === "INDEFINITE_CHOICE"
);
// 各类型题目总分值
const singleChoiceScore = singleQuestion.reduce((prev, cur) => {
return prev + Number(cur.score) || 0;
}, 0);
const multiChoiceScore = multiQuestion.reduce((prev, cur) => {
return prev + Number(cur.score) || 0;
}, 0);
const judgeScore = judgeQuestion.reduce((prev, cur) => {
return prev + Number(cur.score) || 0;
}, 0);
const gapFillingScore = gapQuestion.reduce((prev, cur) => {
return prev + Number(cur.score) || 0;
}, 0);
const indefiniteChoiceScore = indefiniteQuestion.reduce((prev, cur) => {
return prev + Number(cur.score) || 0;
}, 0);
const totalScore = _selectQuestionList.reduce((prev, cur) => {
return prev + Number(cur.score) || 0;
}, 0);
const sortedTableData = [
...singleQuestion,
...multiQuestion,
...indefiniteQuestion,
...judgeQuestion,
...gapQuestion,
];
let currentQuestionList = [];
switch (sorter) {
case "ascend":
currentQuestionList = sortedTableData;
break;
case "descend":
currentQuestionList = sortedTableData.reverse();
break;
case "default":
currentQuestionList = _selectQuestionList.sort(
(a, b) => b.updateTime - a.updateTime
);
break;
default:
currentQuestionList = _selectQuestionList;
break;
}
const passScore = Math.round(totalScore * formData.passRate * 0.01);
this.setState({
selectQuestionList: currentQuestionList,
formData: {
...formData,
singleChoiceCnt: singleQuestion.length,
multiChoiceCnt: multiQuestion.length,
judgeCnt: judgeQuestion.length,
gapFillingCnt: gapQuestion.length,
indefiniteChoiceCnt: indefiniteQuestion.length,
singleChoiceScore,
multiChoiceScore,
judgeScore,
gapFillingScore,
indefiniteChoiceScore,
passScore,
questionCnt: _selectQuestionList.length,
totalScore,
},
});
};
// 选择题目
chooseQuestion = () => {
const m = (
<SelectQuestionModal
getSelectedQuestion={this.state.selectQuestionList}
setSelectedQuestion={(list) => {
this.setState({ selectQuestionModal: null }, () => {
this.setFormData(list.sort((a, b) => b.updateTime - a.updateTime));
});
}}
close={() => {
this.setState({
selectQuestionModal: null,
});
}}
/>
);
this.setState({ selectQuestionModal: m });
};
// 移动已选题目
handleMoveItem = (index, moveLength) => {
const { selectQuestionList } = this.state;
const item = selectQuestionList.splice(index + moveLength, 1);
selectQuestionList.splice(index, 0, item[0]);
this.setState({ selectQuestionList }, () =>
this.setFormData(this.state.selectQuestionList)
);
};
// 移除已选题目
handleDelItem = (delQuestionId) => {
const { selectQuestionList } = this.state;
this.setState(
{
selectQuestionList: [...selectQuestionList].filter(
(item) => item.questionId !== delQuestionId
),
},
() => this.setFormData(this.state.selectQuestionList)
);
};
// 校验试卷名称是否存在
checkExist = (paperName) => {
const { currentCategoryPapers, currentOperate } = this.state;
var result = null;
currentCategoryPapers.forEach((item) => {
if (result != null) {
return result;
}
if (["new", "copy"].includes(currentOperate)) {
if (item.paperName === paperName) {
result = item;
}
} else if (currentOperate === "edit") {
if (
item.paperName === paperName &&
item.paperId !== getParameterByName("paperId")
) {
result = item;
}
}
});
return result;
};
validatePaperName = (paperName) => {
if (this.state.check && !paperName) {
return "请输入试卷名称";
}
if (this.checkExist(paperName)) {
return "该试卷名称已存在";
}
};
// 保存试卷
savePaper = async () => {
this.setState({ check: true });
const { selectQuestionList, formData, currentOperate } = this.state;
const categoryId = getParameterByName("categoryId");
let questionList = [];
if (
!formData.passRate ||
!formData.paperName ||
this.checkExist(formData.paperName)
) {
return;
}
if (selectQuestionList.length === 0) {
return message.warning("请选择题目");
}
selectQuestionList.forEach((item, index) => {
questionList.push({
categoryId: item.categoryId,
portionScore: item.portionScore || 0,
questionId: item.questionId,
questionType: item.questionType,
score: item.score || 2,
sort: index,
});
});
this.setState(
{
formData: {
...formData,
questionList,
},
},
() => {
if (["new", "copy"].includes(currentOperate)) {
AidToolService.createPaper(this.state.formData)
.then((res) => {
if (res.success) {
message.success(
currentOperate === "new" ? "新建成功" : "复制成功"
);
window.RCHistory.push({
pathname: `/paper-manage-index?categoryId=${categoryId}`,
});
Bus.trigger(
"queryPaperPageList",
categoryId,
selectQuestionList.length
);
Bus.trigger("queryCategoryTree", "remain");
}
})
.catch((e) => {
window.RCHistory.push({
pathname: `/paper-manage-index?categoryId=${categoryId}`,
});
Bus.trigger(
"queryPaperPageList",
categoryId,
selectQuestionList.length
);
Bus.trigger("queryCategoryTree", "remain");
});
} else if (currentOperate === "edit") {
AidToolService.editPaper({
...this.state.formData,
paperId: getParameterByName("paperId"),
})
.then((res) => {
if (res.success) {
message.success("编辑成功");
window.RCHistory.push({
pathname: `/paper-manage-index?categoryId=${categoryId}`,
});
Bus.trigger(
"queryPaperPageList",
categoryId,
selectQuestionList.length
);
}
})
.catch((e) => {
window.RCHistory.push({
pathname: `/paper-manage-index?categoryId=${categoryId}`,
});
Bus.trigger(
"queryPaperPageList",
categoryId,
selectQuestionList.length
);
});
}
}
);
};
// 预览试卷
previewPaper = () => {
const { selectQuestionList, formData } = this.state;
const m = (
<PaperPreviewModal
previewPage="paper-operate"
paperInfo={{ ...formData, questionList: selectQuestionList }}
close={() => {
this.setState({
paperPreviewModal: null,
});
}}
/>
);
this.setState({ paperPreviewModal: m });
};
// 取消/返回
handleGoBack = () => {
Modal.confirm({
title: "确定要返回吗?",
content: "返回后,本次编辑的内容将不被保存",
okText: "确认返回",
cancelText: "留在本页",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe6f4;</span>
),
onOk: () => {
window.RCHistory.push({
pathname: `/paper-manage-index?categoryId=${getParameterByName(
"categoryId"
)}`,
});
},
});
};
// 题型排序
sortByQuestionType = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
if (columnKey === "questionType") {
this.setFormData(this.state.selectQuestionList, order || "default");
}
};
// 表头设置
parseColumns = () => {
const { selectQuestionList } = this.state;
const columns = [
{
title: "序号",
dataIndex: "index",
key: "index",
width: "10%",
render: (val, record, index) => {
return <span>{index + 1}</span>;
},
},
{
title: "题型",
dataIndex: "questionType",
key: "questionType",
width: "16%",
sorter: true,
showSorterTooltip: false,
filters: [
{
text: "单选题",
value: "SINGLE_CHOICE",
},
{
text: "多选题",
value: "MULTI_CHOICE",
},
{
text: "判断题",
value: "JUDGE",
},
{
text: "填空题",
value: "GAP_FILLING",
},
{
text: "不定项选择题",
value: "INDEFINITE_CHOICE",
},
],
filterMultiple: true,
onFilter: (value, record) => record.questionType.indexOf(value) === 0,
render: (val) => questionTypeEnum[val],
},
{
title: "题目",
dataIndex: "questionStem",
key: "questionStem",
ellipsis: {
showTitle: false,
},
render: (val) => {
var handleVal = val;
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
return (
<Tooltip
overlayClassName="aid-tool-list"
title={
<div style={{ maxWidth: 700, width: "auto" }}>{handleVal}</div>
}
placement="topLeft"
overlayStyle={{ maxWidth: 700 }}
>
{handleVal}
</Tooltip>
);
},
},
{
title: (
<span>
分值{" "}
<Tooltip title="多选题和填空题的漏选/半对得分不能高于题目本身分值">
<span
className="icon iconfont"
style={{ color: "#BFBFBF", fontSize: 14 }}
>
&#xe7c4;
</span>
</Tooltip>
</span>
),
dataIndex: "score",
key: "score",
width: "12%",
render: (val, record, index) => {
return (
<InputNumber
min={1}
max={100}
value={record.score || 2}
onChange={(value) => {
const _selectQuestionList = [...selectQuestionList];
this.setState(
{
selectQuestionList: _selectQuestionList.map((item) =>
item.questionId === record.questionId
? { ...item, score: value }
: item
),
},
() => this.setFormData(this.state.selectQuestionList)
);
}}
/>
);
},
},
{
dataIndex: "portionScore",
key: "portionScore",
width: "18%",
render: (val, record, index) => {
return (
["MULTI_CHOICE", "GAP_FILLING", "INDEFINITE_CHOICE"].includes(
record.questionType
) && (
<div>
{record.questionType === "GAP_FILLING" ? (
<span>半对得</span>
) : (
<span>漏选得</span>
)}{" "}
<InputNumber
min={0}
max={record.score - 1}
value={record.portionScore || 0}
onChange={(value) => {
const _selectQuestionList = [...selectQuestionList];
this.setState(
{
selectQuestionList: _selectQuestionList.map((item) =>
item.questionId === record.questionId
? { ...item, portionScore: value }
: item
),
},
() => this.setFormData(this.state.selectQuestionList)
);
}}
/>
</div>
)
);
},
},
{
title: "操作",
dataIndex: "operate",
key: "operate",
width: "16%",
render: (val, record, index) => {
return (
<div className="record-operate">
<div
className={
index > 0 ? "record-operate__item" : "record-operate__ban"
}
onClick={() => {
this.handleMoveItem(index, -1);
}}
>
上移
</div>
<span className="record-operate__item split"> | </span>
<div
className={
index < selectQuestionList.length - 1
? "record-operate__item"
: "record-operate__ban"
}
onClick={() => {
this.handleMoveItem(index, 1);
}}
>
下移
</div>
<span className="record-operate__item split"> | </span>
<div
className="record-operate__item"
onClick={() => {
this.handleDelItem(record.questionId);
}}
>
移除
</div>
</div>
);
},
},
];
return columns;
};
render() {
const {
selectQuestionModal,
paperPreviewModal,
selectQuestionList,
currentNav,
formData,
loading,
check,
} = this.state;
const {
singleChoiceCnt,
multiChoiceCnt,
judgeCnt,
gapFillingCnt,
indefiniteChoiceCnt,
singleChoiceScore,
multiChoiceScore,
judgeScore,
gapFillingScore,
indefiniteChoiceScore,
paperName,
passRate,
passScore,
questionCnt,
totalScore,
} = formData;
return (
<div className="page operate-paper-page">
<Breadcrumbs navList={currentNav} goBack={() => this.handleGoBack()} />
<Spin spinning={loading}>
<div className="box">
<div className="show-tips">
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企培保有依据国家规定及平台规则进行处理的权利" />
</div>
<Form ref={this.formRef} style={{ marginTop: 24 }}>
<Form.Item
name="paperName"
label="试卷名称:"
required
validateStatus={this.validatePaperName(paperName) ? "error" : ""}
help={this.validatePaperName(paperName)}
>
<Input
value={paperName}
autoComplete="off"
maxLength={40}
style={{ width: 300 }}
placeholder="请输入试卷名称(40字以内)"
onChange={(e) => {
this.setState({
formData: {
...formData,
paperName: e.target.value.trim(),
},
});
}}
/>
</Form.Item>
<Form.Item
name="passRate"
label="及格线:"
required
validateStatus={check && !passRate ? "error" : ""}
help={check && !passRate && "请输入及格线"}
>
<div>
<InputNumber
min={1}
max={100}
value={passRate}
onChange={(value) => {
this.setState(
{
formData: {
...formData,
passRate: parseInt(value) || undefined,
},
},
() => this.setFormData(selectQuestionList)
);
}}
/>{" "}
%
<span className="score-info">
总分({Number(totalScore) || 0})*及格线(
{Number(passRate) || 0}
%)=及格分数(
{Number(passScore) || 0}
</span>
</div>
</Form.Item>
<Button
className="choose-btn"
type="primary"
icon={<PlusOutlined />}
onClick={this.chooseQuestion}
>
自选题目
</Button>
{questionCnt > 0 && (
<div className="paper-info-tip" style={{ margin: "0 auto 12px" }}>
总计<span>{totalScore}</span>分,共<span>{questionCnt}</span>
题。{" "}
{singleChoiceCnt > 0 &&
`单选题${singleChoiceCnt}题,共${singleChoiceScore}分;`}
{multiChoiceCnt > 0 &&
`多选题${multiChoiceCnt}题,共${multiChoiceScore}分;`}
{judgeCnt > 0 && `判断题${judgeCnt}题,共${judgeScore}分,`}
{gapFillingCnt > 0 &&
`填空题${gapFillingCnt}题,共${gapFillingScore}分,`}
{indefiniteChoiceCnt > 0 &&
`不定项选择题${indefiniteChoiceCnt}题,共${indefiniteChoiceScore}分`}
</div>
)}
<ConfigProvider renderEmpty={this.customizeRenderEmpty}>
<Table
className="table-style"
scroll={{ y: 350 }}
columns={this.parseColumns()}
dataSource={selectQuestionList}
pagination={false}
onChange={this.sortByQuestionType}
/>
</ConfigProvider>
</Form>
</div>
<div className="footer">
<Button onClick={this.handleGoBack}>取消</Button>
<Button onClick={this.previewPaper}>预览</Button>
<Button type="primary" onClick={this.savePaper}>
保存
</Button>
</div>
</Spin>
{selectQuestionModal}
{paperPreviewModal}
</div>
);
}
}
export default OperatePaper;
.operate-paper-page {
.box {
margin-bottom: 66px !important;
.table-style {
border: 1px solid #f0f0f0 !important;
}
.ant-tabs {
color: #666666;
}
.score-info {
color: #999999;
margin-left: 12px;
}
.choose-btn {
margin-top: 8px;
margin-bottom: 12px;
}
.paper-info-tip {
height: 20px;
color: #999999;
line-height: 20px;
span {
color: #ffb714;
}
}
.record-operate {
display: flex;
&__item {
color: #5289fa;
cursor: pointer;
&.split {
margin: 0 8px;
color: #bfbfbf;
}
}
&__ban {
color: #cccccc;
pointer-events: none;
}
}
}
.footer {
position: fixed;
bottom: 0;
height: 58px;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 252px;
background: #fff;
border-top: 1px solid #e8e8e8;
z-index: 999;
.ant-btn {
margin-left: 10px;
}
}
}
.aid-tool-list {
.ant-tooltip-inner {
max-width: 700px !important;
}
}
.ant-dropdown-menu-item-selected > span {
color: #333333;
}
\ No newline at end of file
/*
* @Author: yuananting
* @Date: 2021-03-27 14:55:14
* @LastEditors: yuananting
* @LastEditTime: 2021-04-07 10:25:44
* @Description: 助学工具-试卷主页面
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import "./PaperContent.less";
import CourseCategorySiderTree from "../components/CourseCategorySiderTree";
import PaperList from "./components/PaperList";
class PaperContent extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className={this.props.type == "modal-select"? "paper-content-page paper-content-modal":"paper-content-page"} >
<div
style={{ borderRight: "0.5px solid #EEEEEE", paddingRight: "4px" }}
>
<div className="sider" >
<CourseCategorySiderTree
type={this.props.type}
fromModule="PAPER_INDEX"
/>
</div>
</div>
<div className="content" >
<PaperList
type={this.props.type}
paperId={this.props.paperId}
onSelect={this.props.onSelect}
/>
</div>
</div>
);
}
}
export default PaperContent;
.paper-content-page {
display: flex;
&.paper-content-modal{
max-height: 500px;
.sider,.content{
max-height: 100%;
overflow: auto;
}
.content{
height: auto;
overflow: auto;
}
}
.sider {
min-width: 244px;
}
.content {
width: 100%;
margin-left: 24px;
height: calc(100vh - 160px);
}
}
\ No newline at end of file
/*
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 11:04:03
* @Description: 助学工具-题库-试卷列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import {
Table,
Dropdown,
Row,
Input,
Tooltip,
Menu,
Button,
Modal,
message,
ConfigProvider,
Empty,
} from "antd";
import { PageControl } from "@/components";
import "./PaperList.less";
import { Route, withRouter } from "react-router-dom";
import OperatePaper from "@/modules/teach-tool/paper-manage/OperatePaper";
import User from "@/common/js/user";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import _ from "underscore";
import PaperPreviewModal from "../modal/PreviewPaperModal";
import Bus from "@/core/bus";
const { Search } = Input;
class PaperList extends Component {
constructor(props) {
super(props);
this.state = {
query: {
current: 1,
size: 10,
categoryId: null, // 当前题库分类Id
paperName: null, // 试卷名称
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
},
selectedRowKeys: [this.props.paperId],
dataSource: [],
paperPreviewModal: null, // 试卷预览模态框
loading: false,
};
}
componentDidMount() {
this.queryPaperPageList();
Bus.bind("queryPaperPageList", (selectedCategoryId, count) => {
selectedCategoryId =
selectedCategoryId === "null" ? null : selectedCategoryId;
this.setState({ loading: true });
if (count >= 100) {
setTimeout(() => {
this.InitSearch(selectedCategoryId);
}, 5000);
} else {
this.InitSearch(selectedCategoryId);
}
});
}
componentWillUnmount() {
Bus.unbind("queryPaperPageList", this.queryPaperPageList);
}
// 初始化列表查询
InitSearch = (categoryId) => {
const _query = {
...this.state.query,
categoryId,
current: 1,
paperName: null, // 试卷名称
};
this.setState({ query: _query }, () => {
this.queryPaperPageList();
});
};
// 查询试卷列表
queryPaperPageList = async () => {
const res = await AidToolService.queryPaperPageList(this.state.query);
Bus.trigger("queryCategoryTree", "remain");
const { records = [], total = 0 } = res.result;
this.setState({ dataSource: records, total, loading: false });
};
// 预览试卷
previewPaper = (record) => {
const m = (
<PaperPreviewModal
previewPage="paper-list"
categoryId={this.state.query.categoryId}
paperId={record.paperId}
close={() => {
this.setState({
paperPreviewModal: null,
});
}}
/>
);
this.setState({ paperPreviewModal: m });
};
// 复制试卷
copyPaper = (record) => {
const { categoryId } = this.state.query;
const { match } = this.props;
window.RCHistory.push({
pathname: `${match.url}/paper-operate-page?type=copy&paperId=${record.paperId}&categoryId=${categoryId}`,
});
};
// 编辑试卷
editPaper = (record) => {
if (record.relatedExam === 0) {
const { categoryId } = this.state.query;
const { match } = this.props;
window.RCHistory.push({
pathname: `${match.url}/paper-operate-page/operate?type=edit&paperId=${record.paperId}&categoryId=${categoryId}`,
});
} else {
return Modal.info({
title: "无法编辑",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe834;</span>
),
content:
"该试卷已被考试采用,无法继续编辑。如需修改,请复制一份进行修改。",
okText: "我知道了",
});
}
};
// 删除试卷
deletePaper = (record) => {
let params = {
paperId: record.paperId,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
AidToolService.deletePaper(params).then((res) => {
if (res.success) {
message.success("删除成功");
const { query, total } = this.state;
const { size, current } = query;
const _query = query;
if (total / size < current) {
if (total % size === 1) {
_query.current = 1;
}
}
this.setState({ query: _query }, () => {
this.queryPaperPageList();
});
}
});
};
// 删除试卷确认
confirmDeletePaper = (record) => {
if (record.relatedExam === 0) {
return Modal.confirm({
title: "删除试卷",
content: "确认要删除该试卷吗?",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
cancelText: "取消",
onOk: () => {
this.deletePaper(record);
},
});
} else {
return Modal.info({
title: "删除试卷",
content: "该试卷已被考试采用,无法删除。",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe834; </span>
),
okText: "我知道了",
});
}
};
// 自定义表格空状态
customizeRenderEmpty = () => {
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
imageStyle={{
height: 100,
}}
description={"还没有试卷"}
></Empty>
);
};
// 表头设置
parseColumns = () => {
const columns = [
{
title: "试卷",
key: "paperName",
dataIndex: "paperName",
ellipsis: {
showTitle: false,
},
render: (val, record) => {
var handleVal = val;
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
return (
<Tooltip
overlayClassName="tool-list"
title={
<div style={{ maxWidth: 700, width: "auto" }}>{handleVal}</div>
}
placement="topLeft"
overlayStyle={{ maxWidth: 700 }}
>
{handleVal}
</Tooltip>
);
},
},
{
title: "及格分/总分",
key: "score",
dataIndex: "score",
width: this.props.type !== "modal-select" ? "16%" : "24%",
render: (val, record) => {
return (
<span>
{record.passScore}/{record.totalScore}
</span>
);
},
},
{
title: "题目数量",
key: "questionCnt",
dataIndex: "questionCnt",
width: this.props.type !== "modal-select" ? "12%" : "20%",
align: "right",
render: (val, record) => {
return this.props.type !== "modal-select" ? (
<span>{record.questionCnt}</span>
) : (
<Tooltip
overlayClassName="tool-list"
title={
<div>
<div className="item">
单选题:{record.singleChoiceCnt || 0}
</div>
<div className="item">
多选题:{record.multiChoiceCnt || 0}
</div>
<div className="item">判断题:{record.judgeCnt || 0}</div>
<div className="item">
填空题:{record.gapFillingCnt || 0}
</div>
<div className="item">
不定项选择题:{record.indefiniteChoiceCnt || 0}
</div>
</div>
}
placement="top"
overlayStyle={{ maxWidth: 700 }}
>
<span>{record.questionCnt}</span>
</Tooltip>
);
},
},
];
if (this.props.type !== "modal-select") {
const isPermiss = ["CloudManager", "StoreManager"].includes(
User.getUserRole()
);
columns.push({
title: "关联考试数",
key: "relatedExam",
dataIndex: "relatedExam",
width: this.props.type !== "modal-select" ? "16%" : "24%",
align: "right",
});
columns.push({
title: "操作",
key: "operate",
dataIndex: "operate",
width: "24%",
render: (val, record) => {
return (
<div className="record-operate">
<div
className="record-operate__item"
onClick={() => this.previewPaper(record)}
>
预览
</div>
{isPermiss && (
<span className="record-operate__item split"> | </span>
)}
{isPermiss && (
<div
className="record-operate__item"
onClick={() => this.copyPaper(record)}
>
复制
</div>
)}
{isPermiss && (
<span className="record-operate__item split"> | </span>
)}
{isPermiss && (
<Dropdown overlay={this.initDropMenu(record)}>
<div className="record-operate__item">更多</div>
</Dropdown>
)}
</div>
);
},
});
}
return columns;
};
// 操作更多下拉项
initDropMenu = (item) => {
return (
<Menu>
<Menu.Item key="edit">
<span onClick={() => this.editPaper(item)}>编辑</span>
</Menu.Item>
<Menu.Item key="del">
<span onClick={() => this.confirmDeletePaper(item)}>删除</span>
</Menu.Item>
</Menu>
);
};
// 页展示数修改
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
let _query = this.state.query;
_query.size = size;
this.setState({ query: _query }, () => this.queryPaperPageList());
};
onSelectChange = (selectedRowKeys, selectedRows) => {
this.setState({
selectedRowKeys,
});
this.props.onSelect(selectedRows[0] || {});
};
render() {
const {
dataSource = [],
total,
query,
paperPreviewModal,
selectedRowKeys,
loading,
} = this.state;
const { current, size, categoryId, paperName } = query;
const rowSelection = {
type: "radio",
selectedRowKeys,
onChange: this.onSelectChange,
};
const isPermiss = ["CloudManager", "StoreManager"].includes(
User.getUserRole()
);
const { match } = this.props;
return (
<div className={"paper-list " + this.props.type}>
<div className="paper-list-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-label">试卷:</span>
<Search
placeholder="搜索试卷名称"
value={paperName}
style={{ width: 177 }}
onChange={(e) => {
this.setState({
query: {
...query,
paperName: e.target.value.trim(),
current: 1,
},
});
}}
onSearch={() => {
this.queryPaperPageList();
}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
</div>
</Row>
</div>
{this.props.type !== "modal-select" && isPermiss && categoryId && (
<Button
type="primary"
onClick={() => {
window.RCHistory.push({
pathname: `${match.url}/paper-operate-page?type=new&categoryId=${categoryId}`,
});
}}
>
新建试卷
</Button>
)}
<div className="paper-list-content">
<ConfigProvider renderEmpty={this.customizeRenderEmpty}>
{this.props.type !== "modal-select" ? (
<Table
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
bordered
loading={loading}
onChange={this.handleChangeTable}
/>
) : (
<Table
rowKey={(record) => record.id}
dataSource={dataSource}
size={this.props.type == "modal-select"?'small':'middle'}
rowKey={(item) => {
return item.paperId;
}}
rowSelection={rowSelection}
columns={this.parseColumns()}
pagination={false}
bordered
onChange={this.handleChangeTable}
/>
)}
</ConfigProvider>
{total > 0 && (
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={total}
size={this.props.type == "modal-select"?'small':'middle'}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.setState({ query: _query }, () =>
this.queryPaperPageList()
);
}}
showSizeChanger={true}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
)}
{paperPreviewModal}
</div>
<Route
path={`${match.url}/paper-operate-page`}
component={OperatePaper}
/>
</div>
);
}
}
export default withRouter(PaperList);
.paper-list {
.ant-radio-wrapper{
left: -10px;
}
.paper-list-filter {
position: relative;
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
margin-bottom: 16px;
.search-label {
vertical-align: middle;
display: inline-block;
height: 32px;
line-height: 32px;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left: 4px;
}
}
}
}
.data-icon {
cursor: pointer;
}
.paper-list-content {
position: relative;
margin-top: 16px;
.empty-list-tip {
color: #ffb714;
cursor: pointer;
}
.record-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.record-operate {
display: flex;
&__item {
color: #5289fa;
cursor: pointer;
&.split {
margin: 0 8px;
color: #bfbfbf;
}
}
}
}
&.modal-select{
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: auto;
}
}
}
}
.tool-list {
.ant-tooltip-inner {
max-width: 700px !important;
}
}
.fill-line {
padding: 0 10px;
border-bottom: 1px solid;
}
/*
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-04-13 13:56:05
* @Description: 助学工具-新建试卷-选择题目列表
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import {
Table,
ConfigProvider,
Empty,
Row,
Input,
Select,
Tooltip,
message,
} from "antd";
import { PageControl } from "@/components";
import "./SelectQuestionList.less";
import User from "@/common/js/user";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import _ from "underscore";
import Bus from "@/core/bus";
const { Search } = Input;
const questionTypeEnum = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
};
const questionTypeList = [
{
label: "单选题",
value: "SINGLE_CHOICE",
},
{
label: "多选题",
value: "MULTI_CHOICE",
},
{
label: "判断题",
value: "JUDGE",
},
{
label: "填空题",
value: "GAP_FILLING",
},
{
label: "不定项选择题",
value: "INDEFINITE_CHOICE",
},
];
class SelectQuestionList extends Component {
constructor(props) {
super(props);
this.state = {
query: {
current: 1,
size: 10,
order: "UPDATED_DESC",
categoryId: null, // 当前题库分类Id
questionName: null, // 题目名称
questionType: null, // 题目类型
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
},
dataSource: [],
selectQuestionKeys: [],
};
}
componentDidMount() {
this.queryQuestionPageListWithContent()
Bus.bind('queryQuestionPageListWithContent', (selectedCategoryId) => {
selectedCategoryId = selectedCategoryId === "null" ? null : selectedCategoryId;
this.InitSearch(selectedCategoryId)
})
}
componentWillUnmount() {
Bus.unbind('queryQuestionPageListWithContent', this.queryQuestionPageListWithContent)
}
// 初始化列表查询
InitSearch = (categoryId) => {
const _query = {
...this.state.query,
categoryId,
current: 1,
order: "UPDATED_DESC", // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
};
this.setState({ query: _query }, () => {
this.queryQuestionPageListWithContent();
});
};
// 排序
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
let sort = "UPDATED_DESC";
if (order === "ascend") {
sort = "ACCURACY_ASC";
}
if (order === "descend") {
sort = "ACCURACY_DESC";
}
const _query = this.state.query;
_query.order = sort;
_query.current = 1;
this.setState({ query: _query }, () => this.queryQuestionPageListWithContent());
};
queryQuestionPageListWithContent = () => {
AidToolService.queryQuestionPageListWithContent(this.state.query).then((res) => {
const { records = [], total = 0 } = res.result;
this.setState({ dataSource: records, total });
});
};
// 清空搜索条件
handleReset = () => {
const _query = {
...this.state.query,
current: 1,
order: "UPDATED_DESC", // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
};
this.setState({ query: _query }, () => {
this.queryQuestionPageListWithContent();
});
};
// 表头设置
parseColumns = () => {
const columns = [
{
title: "题目",
key: "questionStem",
dataIndex: "questionStem",
ellipsis: {
showTitle: false,
},
render: (val) => {
var handleVal = val;
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
return (
<Tooltip
overlayClassName="tool-list"
title={
<div style={{ maxWidth: 700, width: "auto" }}>{handleVal}</div>
}
placement="topLeft"
overlayStyle={{ maxWidth: 700 }}
>
{handleVal}
</Tooltip>
);
},
},
{
title: "题型",
key: "questionTypeEnum",
dataIndex: "questionTypeEnum",
render: (val) => {
return questionTypeEnum[val];
},
},
{
title: "正确率",
key: "accuracy",
dataIndex: "accuracy",
sorter: true,
showSorterTooltip: false,
render: (val) => {
return parseInt(val * 100) + "%";
},
},
];
return columns;
};
// 自定义表格空状态
customizeRenderEmpty = () => {
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
imageStyle={{
height: 100,
}}
description={"还没有题目"}
></Empty>
);
};
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
let _query = this.state.query;
_query.size = size;
this.setState({ query: _query }, () => this.queryQuestionPageListWithContent());
};
// 改变搜索条件
handleChangeQuery = (searchType, value) => {
this.setState(
{
query: {
...this.state.query,
[searchType]: value || null,
current: 1,
},
},
() => {
if (searchType === "questionName") return;
this.queryQuestionPageListWithContent();
}
);
};
// 选择题目-单项选择
selectQuestionRow = (record, selected) => {
const { selectQuestionKeys } = this.state;
let _list = [];
if (
selected ||
!_.find(selectQuestionKeys, (item) => item.id == record.id)
) {
_list = _.uniq(
selectQuestionKeys.concat([record]),
false,
(item) => item.id
);
} else {
_list = _.reject(selectQuestionKeys, (item) => item.id === record.id);
}
if (_list.length > 150) {
return message.warning("一份试卷最多支持150道题目");
}
this.setState({ selectQuestionKeys: _list });
};
// 选择题目-当前全选
selectQuestionAll = (selected, selectedRows, changeRows) => {
const { selectQuestionKeys } = this.state;
let _list = [];
if (selected) {
_list = _.uniq(
selectQuestionKeys.concat(changeRows),
false,
(item) => item.id
);
} else {
_list = _.reject(selectQuestionKeys, (item) =>
_.find(changeRows, (data) => data.id === item.id)
);
}
if (_list.length > 150) {
message.warning("一份试卷最多支持150道题目");
const extraLength = _list.length - 150;
_list.splice(_list.length - extraLength, extraLength);
}
this.setState({ selectQuestionKeys: _list });
};
render() {
const {
dataSource = [],
total,
query,
selectQuestionKeys = [],
} = this.state;
const { current, size, questionName, questionType } = query;
const rowSelection = {
columnWidth: 48,
selectedRowKeys: _.pluck(selectQuestionKeys, "id"),
onSelect: (record, selected) => {
this.selectQuestionRow(record, selected);
},
onSelectAll: (selected, selectedRows, changeRows) => {
this.selectQuestionAll(selected, selectedRows, changeRows);
},
};
const singleCount = _.filter(
selectQuestionKeys,
(item) => item.questionTypeEnum === "SINGLE_CHOICE"
).length;
const multiCount = _.filter(
selectQuestionKeys,
(item) => item.questionTypeEnum === "MULTI_CHOICE"
).length;
const judgeCount = _.filter(
selectQuestionKeys,
(item) => item.questionTypeEnum === "JUDGE"
).length;
const gapCount = _.filter(
selectQuestionKeys,
(item) => item.questionTypeEnum === "GAP_FILLING"
).length;
const indefiniteCount = _.filter(
selectQuestionKeys,
(item) => item.questionTypeEnum === "INDEFINITE_CHOICE"
).length;
return (
<div className="select-question-list">
<div className="select-question-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-label">题目:</span>
<Search
placeholder="搜索题目名称"
value={questionName}
style={{ width: 178 }}
onChange={(e) => {
this.handleChangeQuery("questionName", e.target.value);
}}
onSearch={() => {
this.queryQuestionPageListWithContent();
}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span className="search-label">题型:</span>
<Select
placeholder="请选择题目类型"
value={questionType}
style={{ width: 178 }}
showSearch
allowClear
enterButton={<span className="icon iconfont">&#xe832;</span>}
filterOption={(inputVal, option) =>
option.props.children.includes(inputVal)
}
onChange={(value) => {
if (_.isEmpty(value)) {
this.handleChangeQuery("questionType", value);
}
}}
onSelect={(value) => {
this.handleChangeQuery("questionType", value);
}}
>
{_.map(questionTypeList, (item, index) => {
return (
<Select.Option value={item.value} key={item.key}>
{item.label}
</Select.Option>
);
})}
</Select>
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选">
<span
className="resetBtn iconfont icon"
onClick={this.handleReset}
>
&#xe61b;{" "}
</span>
</Tooltip>
</div>
</Row>
</div>
<div className="select-tip-box">
<div>
<span className="icon iconfont tip-icon">&#xe61d;</span>
<span style={{marginRight: 8}}>已选<span style={{color: "#FFB714"}}>{selectQuestionKeys.length}</span></span>
{selectQuestionKeys.length > 0 && (
<span>(
{singleCount > 0 && ` 单选题${singleCount}题 `}
{multiCount > 0 && `多选题${multiCount}题 `}
{judgeCount > 0 && `判断题${judgeCount}题 `}
{gapCount > 0 && `填空题${gapCount}题 `}
{indefiniteCount > 0 && `不定项选择题${indefiniteCount}题 `})
</span>
)}
</div>
{selectQuestionKeys.length > 0 && (
<span className="clear-btn" onClick={() => this.setState({selectQuestionKeys:[]})}>清空</span>
)}
</div>
<div className="select-question-content">
<ConfigProvider renderEmpty={this.customizeRenderEmpty}>
<Table
rowSelection={rowSelection}
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
onChange={this.handleChangeTable}
bordered
/>
</ConfigProvider>
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={total}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.setState({ query: _query }, () =>
this.queryQuestionPageListWithContent()
);
}}
showSizeChanger={true}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
</div>
</div>
);
}
}
export default SelectQuestionList;
.select-question-list {
.select-question-filter {
position: relative;
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
margin-right: 3%;
margin-bottom: 16px;
.search-label {
vertical-align: middle;
display: inline-block;
height: 32px;
line-height: 32px;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left: 4px;
}
}
}
}
.data-icon {
cursor: pointer;
}
.select-question-content {
position: relative;
margin-top: 16px;
.empty-list-tip {
color: #ffb714;
cursor: pointer;
}
.record-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.record-operate {
display: flex;
&__item {
color: #5289fa;
cursor: pointer;
&.split {
margin: 0 8px;
color: #bfbfbf;
}
}
}
}
.select-tip-box {
background: #fff4dd;
border-radius: 4px;
padding: 6px 16px;
display: flex;
justify-content: space-between;
.tip-icon {
color: #ff9d14;
font-size: 14px;
margin-right: 4px;
}
.select-num {
color: #666666;
font-size: 14px;
}
.clear-btn {
color: #5289fa;
font-size: 14px;
cursor: pointer;
}
}
}
.tool-list {
.ant-tooltip-inner {
max-width: 700px !important;
}
}
/*
* @Author: yuananting
* @Date: 2021-03-27 11:15:03
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 13:22:10
* @Description: 助学工具-试卷-预览试卷
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Modal, ConfigProvider, Empty } from "antd";
import User from "@/common/js/user";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import "./PreviewPaperModal.less";
import ScanFileModal from "@/modules/resource-disk/modal/ScanFileModal";
import _ from "underscore";
import XMAudio from "../../components/XMAudio";
import { NUM_TO_WORD_MAP } from "@/common/constants/punchClock/punchClock";
const questionTypeList = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
};
class PreviewPaperModal extends Component {
formRef = React.createRef();
constructor(props) {
super(props);
this.state = {
paperInfo: {},
};
}
componentDidMount() {
const { previewPage, paperInfo } = this.props;
if (previewPage === "paper-list") {
this.queryPaperDetail();
} else if (previewPage === "paper-operate") {
this.setState({ paperInfo });
}
}
// 题目预览
queryPaperDetail = () => {
let query = {
categoryId: this.props.categoryId,
paperId: this.props.paperId,
source: 0,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
AidToolService.viewPaper(query).then((res) => {
const { result = [] } = res;
this.setState({ paperInfo: result });
});
};
// 查看图片
handleScanFile = (scanFileType, scanFileAddress) => {
this.setState({
showScanFile: true,
scanFileAddress,
scanFileType,
});
};
renderStem = (questionStemList, questionType, score, questionIndex) => {
const textContent = _.filter(questionStemList, (item) => {
return item.type == "RICH_TEXT";
});
const mediaContent = _.filter(questionStemList, (item) => {
return item.type !== "RICH_TEXT";
});
let content = "";
if (textContent.length > 0) {
content = textContent[0].content;
if (questionType === "GAP_FILLING") {
content = content.replace(
/_/g,
`<input
class="add-fill-line"
disabled
correctAnswerList=""
id=${window.random_string(16)}
value=" "
/>`
);
content = content.replace(/value="填空"/g, "value=' '");
}
}
let textDom = (
<div
key={0}
className="input-box"
dangerouslySetInnerHTML={{
__html: content,
}}
/>
);
return (
<div className="stem-line-item">
<div className="text">
<div className="question-tip">
<span className="question-sort">{questionIndex + 1}</span>
<span className="question-type-score">
({questionTypeList[questionType]} | {score}分)
</span>
</div>
{textDom}
</div>
{this.renderMediaContent(mediaContent)}
</div>
);
};
renderOption = (optionItem, questionType) => {
const { questionOptionContentList, optionSort } = optionItem;
const textContent = _.filter(questionOptionContentList, (item) => {
return item.type == "RICH_TEXT";
});
const mediaContent = _.filter(questionOptionContentList, (item) => {
return item.type !== "RICH_TEXT";
});
let content = textContent.length > 0 && textContent[0].content;
if (questionType !== "GAP_FILLING") {
let textDom = (
<span
key={0}
className="input-box"
dangerouslySetInnerHTML={{
__html: content,
}}
/>
);
return (
<div className="option-line-item">
<div className="text">
{NUM_TO_WORD_MAP[optionSort]}: {textDom}
</div>
{mediaContent.length > 0 && this.renderMediaContent(mediaContent)}
</div>
);
}
};
renderAnswer = (optionList, gapFillingAnswerList, questionType) => {
if (questionType === "GAP_FILLING") {
{
return (
<div className="answer-line-item">
<div style={{ marginBottom: 8 }}>正确答案: </div>
{_.map(gapFillingAnswerList, (gapItem, gapIndex) => {
const { correctAnswerList } = gapItem;
return (
<div className="gap-list">
<span>[填空{gapIndex + 1}] </span>
{_.map(correctAnswerList, (answerItem, answerIndex) => {
return (
<span>
{answerItem}
{answerIndex < correctAnswerList.length - 1 && (
<span></span>
)}
</span>
);
})}
<span>; </span>
</div>
);
})}
</div>
);
}
} else {
const correctAnswerOption = _.filter(
optionList,
(item) => item.isCorrectAnswer === 1
);
const correctOptionSort = correctAnswerOption.map((item) => {
return NUM_TO_WORD_MAP[item.optionSort];
});
return (
<div className="answer-line-item">
<div style={{ marginBottom: 8 }}>正确答案: </div>
<span style={{ lineHeight: "20px", color: "#666666" }}>
{correctOptionSort.length > 0 && correctOptionSort.join("、")}
</span>
</div>
);
}
};
renderMediaContent = (mediaContent) => {
return (
<div className="media">
{_.map(mediaContent, (mediaItem, mediaIndex) => {
let dom = "";
let { type, content, size } = mediaItem;
switch (type) {
case "PICTURE":
dom = (
<div key={mediaIndex + 1} className="picture-box">
<img
src={content}
onClick={() => this.handleScanFile("JPG", content)}
/>
</div>
);
break;
case "VOICE":
dom = (
<div key={mediaIndex + 1} className="voice-box">
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
this.setState({});
}}
index={mediaIndex + 1}
size={size || 1000}
/>
</div>
);
break;
case "AUDIO":
dom = (
<div key={mediaIndex} className="voice-box">
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
this.setState({});
}}
index={mediaIndex}
size={size || 1000}
/>
</div>
);
break;
}
return dom;
})}
</div>
);
};
// 自定义空状态
customizeRenderEmpty = () => {
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
imageStyle={{
height: 100,
}}
description={"暂无内容"}
></Empty>
);
};
render() {
const {
paperInfo,
showScanFile,
scanFileType,
scanFileAddress,
} = this.state;
const { paperName, questionList } = paperInfo;
return (
<div>
<Modal
className="paper-preview-modal"
visible={true}
title="试卷预览"
width={560}
centered={true}
footer={null}
onCancel={this.props.close}
>
{paperName && <div className="paper-title">{paperName}</div>}
{questionList && questionList.length > 0 ? (
<div className="question-list-box">
{_.map(questionList, (questionItem, questionIndex) => {
const {
questionStemList,
optionList,
gapFillingAnswerList,
questionType,
score,
} = questionItem;
return (
<div className="question-info-item">
{this.renderStem(
questionStemList,
questionType,
score,
questionIndex
)}
{_.map(optionList, (optionItem, optionIndex) => {
return this.renderOption(optionItem, questionType);
})}
{this.renderAnswer(
optionList,
gapFillingAnswerList,
questionType
)}
</div>
);
})}
</div>
) : (
this.customizeRenderEmpty()
)}
</Modal>
{showScanFile && (
<ScanFileModal
fileType={scanFileType}
item={{
ossAddress: scanFileAddress,
}}
close={() => {
this.setState({ showScanFile: false });
}}
/>
)}
</div>
);
}
}
export default PreviewPaperModal;
.paper-preview-modal {
.paper-title {
width: 400px;
font-size: 19px;
font-weight: 500;
color: #333333;
line-height: 26px;
margin: 0 auto 24px;
text-align: center;
}
.question-list-box {
.question-info-item {
margin-bottom: 24px;
padding-bottom: 24px;
border-bottom: 1px solid #e8e8e8;
.stem-line-item {
color: #666666;
margin-bottom: 20px;
.question-sort {
font-size: 16px;
font-weight: 500;
color: #333333;
margin-right: 4px;
}
.question-type-score {
color: #999999;
}
}
.option-line-item {
color: #666666;
margin-top: 12px;
line-height: 20px;
}
.answer-line-item {
color: #333333;
line-height: 20px;
margin-top: 20px;
.gap-list {
display: inline;
color: #666666;
line-height: 20px;
}
}
}
.question-info-item:last-child {
border-bottom: none;
}
.text {
margin-bottom: 8px;
color: #666666;
.question-tip {
margin-bottom: 8px;
}
.input-box {
line-height: 20px;
* {
display: inline;
}
.add-fill-line {
padding: 0 10px;
border-bottom: 1px solid !important;
margin: 0 4px;
text-align: center;
border: none;
width: 54px;
}
}
}
.media {
.picture-box {
width: 88px;
height: 88px;
border-radius: 4px;
overflow: hidden;
align-items: center;
justify-content: center;
margin-right: 12px;
margin-top: 8px;
position: relative;
display: inline-flex;
border: 1px solid #e8e8e8;
img {
max-width: 100%;
max-height: 100%;
border-radius: 4px;
vertical-align: middle;
width: auto;
height: auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.voice-box {
margin-bottom: 12px;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.1);
padding: 10px 20px;
width: 320px;
}
}
}
}
.paper-preview-modal.ant-modal {
max-height: 60% !important;
}
.paper-preview-modal.ant-modal .ant-modal-content .ant-modal-body {
min-height: 145px !important;
}
/*
* @Author: yuananting
* @Date: 2021-03-29 10:52:26
* @LastEditors: yuananting
* @LastEditTime: 2021-04-07 15:23:06
* @Description: 助学工具-试卷-新建选择题目弹窗
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Modal } from "antd";
import CourseCategorySiderTree from "../../components/CourseCategorySiderTree";
import SelectQuestionList from "../components/SelectQuestionList";
import "./SelectQuestionModal.less";
class SelectQuestionModal extends Component {
listRef = React.createRef();
constructor(props) {
super(props);
this.state = {
selectedCategoryId: null,
};
}
componentDidMount() {
this.listRef.current.state.selectQuestionKeys = this.props.getSelectedQuestion.map(
(item) => {
item.id = item.questionId;
item.questionTypeEnum = item.questionType;
return item;
}
);
}
render() {
return (
<Modal
className="select-question-modal"
destroyOnClose={true}
title="选择题目"
visible={true}
width={1080}
onOk={() => {
this.props.setSelectedQuestion(
this.listRef.current.state.selectQuestionKeys.map((item) => {
item.questionId = item.id || item.questionId;
item.questionType = item.questionTypeEnum || item.questionType;
item.score = item.score || 2;
if (
["MULTI_CHOICE", "GAP_FILLING", "INDEFINITE_CHOICE"].includes(
item.questionTypeEnum
)
) {
item.portionScore = item.portionScore || 0;
}
return item;
})
);
}}
onCancel={this.props.close}
>
<div className="box content-body" style={{maxHeight:500}}>
<div
style={{ borderRight: "0.5px solid #EEEEEE", paddingRight: "4px",overflowY:'auto',overflowX:'hidden'}}
>
<div className="sider">
<CourseCategorySiderTree fromModule="QUESTION_MODAL" />
</div>
</div>
<div className="content" style={{height:'auto',overflowY:'auto'}}>
<SelectQuestionList ref={this.listRef} />
</div>
</div>
</Modal>
);
}
}
export default SelectQuestionModal;
.select-question-modal {
.content-body {
display: flex;
height: calc(~'100% - 48px');
.sider {
min-width: 244px;
}
.content {
width: 100%;
margin-left: 24px;
height: calc(100vh - 160px);
}
}
.ant-table-column-sorters {
padding: 0;
}
}
.select-question-modal.ant-modal {
max-height: 70% !important;
}
\ No newline at end of file
......@@ -2,56 +2,35 @@
* @Author: yuananting
* @Date: 2021-02-21 17:51:01
* @LastEditors: yuananting
* @LastEditTime: 2021-03-24 13:55:56
* @Description: 助学工具-题库-题库主页面
* @LastEditTime: 2021-04-07 10:45:07
* @Description: 助学工具-题库
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import "./QuestionBankIndex.less";
import QuestionBankSider from "./components/QuestionBankSider";
import QuestionManageContent from "./components/QuestionManageContent";
import "./Index.less";
import CourseCategorySiderTree from "../components/CourseCategorySiderTree";
import QuestionList from "./components/QuestionList";
class QuestionBankIndex extends Component {
class QuestionIndex extends Component {
constructor(props) {
super(props);
this.state = {
selectedCategoryId: "",
};
this.state = {};
}
componentDidMount() {}
getCategoryIdFromSider = (selectedCategoryId) => {
if (selectedCategoryId && selectedCategoryId.length > 0) {
this.setState({ selectedCategoryId: selectedCategoryId[0] });
}
};
updatedSiderTreeFromList = (currentTotal, updatedCategoryId) => {
this.setState({ currentTotal });
this.setState({ updatedCategoryId });
};
render() {
return (
<div className="question-bank-index page">
<div className="question-manage-index page">
<div className="content-header">题目</div>
<div className="box content-body">
<div style={{borderRight: "0.5px solid #EEEEEE", paddingRight: "4px"}}>
<div
style={{ borderRight: "0.5px solid #EEEEEE", paddingRight: "4px" }}
>
<div className="sider">
<QuestionBankSider
getSelectedCategoryId={this.getCategoryIdFromSider.bind(this)}
currentTotal={this.state.currentTotal}
updatedCategoryId={this.state.updatedCategoryId}
/>
<CourseCategorySiderTree fromModule="QUESTION_INDEX" />
</div>
</div>
<div className="content">
<QuestionManageContent
updatedSiderTree={this.updatedSiderTreeFromList.bind(this)}
selectedCategoryId={this.state.selectedCategoryId}
/>
<QuestionList />
</div>
</div>
</div>
......@@ -59,4 +38,4 @@ class QuestionBankIndex extends Component {
}
}
export default QuestionBankIndex;
export default QuestionIndex;
/*
* @Author: yuananting
* @Date: 2021-02-21 18:27:43
* @LastEditors: yuananting
* @LastEditTime: 2021-03-24 16:15:03
* @Description: 助学工具-题库-题库主页面样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-bank-index {
.question-manage-index {
.content-body {
display: flex;
.site-layout-background {
background: #fff;
}
.sider {
min-width: 244px;
}
......
......@@ -2,26 +2,27 @@
* @Author: yuananting
* @Date: 2021-02-25 13:46:35
* @LastEditors: yuananting
* @LastEditTime: 2021-03-25 18:25:34
* @Description: 助学工具-题库-题目管理-新增题目
* @LastEditTime: 2021-04-07 09:48:34
* @Description: 助学工具-题库-新建/编辑题目
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Tabs, Button, Tooltip, message, Modal } from "antd";
import Breadcrumbs from "@/components/Breadcrumbs";
import ShowTips from "@/components/ShowTips";
import "./AddNewQuestion.less";
import NewQuestionTab from "./components/NewQuestionTab";
import "./OperateQuestion.less";
import OperateQuestionTab from "./components/OperateQuestionTab";
import {
defineJudgeOptionInfo,
defineOptionInfo,
defineQuestionInfo,
} from "./components/model";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
} from "../components/model";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import User from "@/common/js/user";
import Bus from '@/core/bus';
const { TabPane } = Tabs;
class AddNewQuestion extends Component {
class OperateQuestion extends Component {
constructor(props) {
super(props);
let activeKey = "";
......@@ -59,6 +60,7 @@ class AddNewQuestion extends Component {
return p;
};
// 编辑题目时获取相应题目详情
queryQuestionDetails = () => {
let query = {
id: getParameterByName("id"),
......@@ -66,7 +68,7 @@ class AddNewQuestion extends Component {
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryQuestionDetails(query).then((res) => {
AidToolService.queryQuestionDetails(query).then((res) => {
const { result = [] } = res;
let stemContent = _.find(
result.questionStemList,
......@@ -104,6 +106,7 @@ class AddNewQuestion extends Component {
});
};
// 保存并继续添加时重新构建题目结构
handleRest = (type) => {
this.setState({ currentOperate: "add" });
switch (type) {
......@@ -147,6 +150,67 @@ class AddNewQuestion extends Component {
chooseOptions.push(defineJudgeOptionInfo(content));
};
// 取消编辑并返回上一级路由
handleGoBack = () => {
Modal.confirm({
title: "确定要返回吗?",
content: "返回后,本次编辑的内容将不被保存",
okText: "确认返回",
cancelText: "留在本页",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe6f4;</span>
),
onOk: () => {
window.RCHistory.push({
pathname: `/question-manage-index?categoryId=${getParameterByName("categoryId")}`,
});
},
});
};
// 校验保存题目
confirmSaveQuestion = (next) => {
const {
singleChoiceContent,
multiChoiceContent,
judgeContent,
gapFillingContent,
indefiniteChoiceContent,
} = this.state;
switch (this.state.activeKey) {
case "SINGLE_CHOICE":
if (this.singleChoiceRef.checkInput() === 0) {
this.saveCurrentQuestion(singleChoiceContent, "SINGLE_CHOICE", next);
}
break;
case "MULTI_CHOICE":
if (this.multiChoiceRef.checkInput() === 0) {
this.saveCurrentQuestion(multiChoiceContent, "MULTI_CHOICE", next);
}
break;
case "JUDGE":
if (this.judgeRef.checkInput() === 0) {
this.saveCurrentQuestion(judgeContent, "JUDGE", next);
}
break;
case "GAP_FILLING":
if (this.gapRef.checkInput() === 0) {
this.saveCurrentQuestion(gapFillingContent, "GAP_FILLING", next);
}
break;
case "INDEFINITE_CHOICE":
if (this.indefiniteRef.checkInput() === 0) {
this.saveCurrentQuestion(
indefiniteChoiceContent,
"INDEFINITE_CHOICE",
next
);
}
break;
}
};
// 确认保存题目
saveCurrentQuestion = (content, type, next) => {
content.questionStemList.map((item, index) => {
item.sort = index;
......@@ -169,13 +233,13 @@ class AddNewQuestion extends Component {
params = {
...content,
id: getParameterByName("id"),
categoryId: categoryId || null,
categoryId: categoryId === "null" ? null : categoryId,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
QuestionBankService.editQuestion(params).then((res) => {
AidToolService.editQuestion(params).then((res) => {
if (res.success) {
message.success("保存成功");
if (next === "add") {
......@@ -183,20 +247,22 @@ class AddNewQuestion extends Component {
}
if (next === "close") {
window.RCHistory.push({
pathname: `/question-bank-index?categoryId=${params.categoryId}`,
pathname: `/question-manage-index?categoryId=${categoryId}`,
});
Bus.trigger("queryCategoryTree", "remain")
Bus.trigger("queryQuestionPageList", categoryId)
}
}
});
} else {
params = {
...content,
categoryId: getParameterByName("categoryId"),
categoryId,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
QuestionBankService.addQuestion(params).then((res) => {
AidToolService.addQuestion(params).then((res) => {
if (res.success) {
message.success("保存成功");
if (next === "add") {
......@@ -204,71 +270,16 @@ class AddNewQuestion extends Component {
}
if (next === "close") {
window.RCHistory.push({
pathname: `/question-bank-index?categoryId=${params.categoryId}`,
pathname: `/question-manage-index?categoryId=${categoryId}`,
});
Bus.trigger("queryCategoryTree", "remain")
Bus.trigger("queryQuestionPageList", categoryId)
}
}
});
}
};
// 取消编辑并返回上一级路由
handleGoBack = () => {
Modal.confirm({
title: "确定要返回吗?",
content: "返回后,本次编辑的内容将不被保存",
okText: "确认返回",
cancelText: "留在本页",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe6f4;</span>
),
onOk: () => {
window.RCHistory.goBack();
},
});
};
confirmSaveQuestion = (next) => {
const {
singleChoiceContent,
multiChoiceContent,
judgeContent,
gapFillingContent,
indefiniteChoiceContent,
} = this.state;
switch (this.state.activeKey) {
case "SINGLE_CHOICE":
if (this.singleChoiceRef.checkInput() === 0) {
this.saveCurrentQuestion(singleChoiceContent, "SINGLE_CHOICE", next);
}
break;
case "MULTI_CHOICE":
if (this.multiChoiceRef.checkInput() === 0) {
this.saveCurrentQuestion(multiChoiceContent, "MULTI_CHOICE", next);
}
break;
case "JUDGE":
if (this.judgeRef.checkInput() === 0) {
this.saveCurrentQuestion(judgeContent, "JUDGE", next);
}
break;
case "GAP_FILLING":
if (this.gapRef.checkInput() === 0) {
this.saveCurrentQuestion(gapFillingContent, "GAP_FILLING", next);
}
break;
case "INDEFINITE_CHOICE":
if (this.indefiniteRef.checkInput() === 0) {
this.saveCurrentQuestion(
indefiniteChoiceContent,
"INDEFINITE_CHOICE",
next
);
}
break;
}
};
handleLogger = (en, cn) => {
const { onLogger } = this.props;
onLogger && onLogger(en, cn);
......@@ -285,7 +296,7 @@ class AddNewQuestion extends Component {
} = this.state;
const categoryId = getParameterByName("categoryId");
return (
<div className="page add-new-question">
<div className="page operate-question-page">
<Breadcrumbs
navList={
getParameterByName("id") && this.state.currentOperate === "edit"
......@@ -308,7 +319,13 @@ class AddNewQuestion extends Component {
<TabPane
tab={
<span>
<span className="icon iconfont" style={{ color: activeKey === "SINGLE_CHOICE" ? "#ffb714" : "#CCCCCC" }}>
<span
className="icon iconfont"
style={{
color:
activeKey === "SINGLE_CHOICE" ? "#ffb714" : "#CCCCCC",
}}
>
&#xe7fa;{" "}
</span>
<span>单选题</span>
......@@ -316,7 +333,7 @@ class AddNewQuestion extends Component {
}
key="SINGLE_CHOICE"
>
<NewQuestionTab
<OperateQuestionTab
questionTypeKey={activeKey}
onRef={(ref) => {
this.singleChoiceRef = ref;
......@@ -331,7 +348,13 @@ class AddNewQuestion extends Component {
<TabPane
tab={
<span>
<span className="icon iconfont" style={{ color: activeKey === "MULTI_CHOICE" ? "#ffb714" : "#CCCCCC" }}>
<span
className="icon iconfont"
style={{
color:
activeKey === "MULTI_CHOICE" ? "#ffb714" : "#CCCCCC",
}}
>
&#xe7fb;{" "}
</span>
<span>多选题</span>
......@@ -339,7 +362,7 @@ class AddNewQuestion extends Component {
}
key="MULTI_CHOICE"
>
<NewQuestionTab
<OperateQuestionTab
questionTypeKey={activeKey}
onRef={(ref) => {
this.multiChoiceRef = ref;
......@@ -354,7 +377,12 @@ class AddNewQuestion extends Component {
<TabPane
tab={
<span>
<span className="icon iconfont" style={{ color: activeKey === "JUDGE" ? "#ffb714" : "#CCCCCC" }}>
<span
className="icon iconfont"
style={{
color: activeKey === "JUDGE" ? "#ffb714" : "#CCCCCC",
}}
>
&#xe7fc;{" "}
</span>
<span>判断题</span>
......@@ -362,7 +390,7 @@ class AddNewQuestion extends Component {
}
key="JUDGE"
>
<NewQuestionTab
<OperateQuestionTab
questionTypeKey={activeKey}
onRef={(ref) => {
this.judgeRef = ref;
......@@ -376,7 +404,13 @@ class AddNewQuestion extends Component {
<TabPane
tab={
<span>
<span className="icon iconfont" style={{ color: activeKey === "GAP_FILLING" ? "#ffb714" : "#CCCCCC" }}>
<span
className="icon iconfont"
style={{
color:
activeKey === "GAP_FILLING" ? "#ffb714" : "#CCCCCC",
}}
>
&#xe7fd;{" "}
</span>
<span>填空题</span>
......@@ -384,7 +418,7 @@ class AddNewQuestion extends Component {
}
key="GAP_FILLING"
>
<NewQuestionTab
<OperateQuestionTab
questionTypeKey={activeKey}
onRef={(ref) => {
this.gapRef = ref;
......@@ -398,18 +432,31 @@ class AddNewQuestion extends Component {
<TabPane
tab={
<span>
<span className="icon iconfont" style={{ color: activeKey === "INDEFINITE_CHOICE" ? "#ffb714" : "#CCCCCC" }}>
<span
className="icon iconfont"
style={{
color:
activeKey === "INDEFINITE_CHOICE"
? "#ffb714"
: "#CCCCCC",
}}
>
&#xe7fe;{" "}
</span>
<span>不定项选择题 </span>
<Tooltip title="至少有一项正确,至多不限的选择题,多项选择题的一种特殊形式">
<span className="icon iconfont" style={{ color: "#BFBFBF" }}>&#xe7c4;</span>
<span
className="icon iconfont"
style={{ color: "#BFBFBF" }}
>
&#xe7c4;
</span>
</Tooltip>
</span>
}
key="INDEFINITE_CHOICE"
>
<NewQuestionTab
<OperateQuestionTab
questionTypeKey={activeKey}
onRef={(ref) => {
this.indefiniteRef = ref;
......@@ -454,4 +501,4 @@ class AddNewQuestion extends Component {
}
}
export default AddNewQuestion;
export default OperateQuestion;
/*
* @Author: yuananting
* @Date: 2021-02-25 13:52:01
* @LastEditors: yuananting
* @LastEditTime: 2021-03-18 09:32:11
* @Description: 助学工具-题库-题目管理-新增题目样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.add-new-question {
position: relative !important;
.operate-question-page {
.box {
margin-bottom: 66px !important;
.ant-tabs {
......
......@@ -2,8 +2,8 @@
* @Author: yuananting
* @Date: 2021-02-25 14:34:29
* @LastEditors: yuananting
* @LastEditTime: 2021-03-25 19:52:09
* @Description: 助学工具-题库-题目管理-新建题目Tab
* @LastEditTime: 2021-03-29 16:22:15
* @Description: 助学工具-题库-操作题目Tab
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
......@@ -17,21 +17,24 @@ import {
Input,
Popover,
} from "antd";
import "./NewQuestionTab.less";
import "./OperateQuestionTab.less";
import QuestionEditor from "./QuestionEditor";
import { PlusOutlined, CloseOutlined } from "@ant-design/icons";
import {
NUM_TO_WORD_MAP,
MEDIA_FILE_ACCEPT,
} from "@/common/constants/punchClock/punchClock";
import { defineOptionInfo, defineJudgeOptionInfo } from "./model";
import XMAudio from "./XMAudio";
import XMRecord from "./XMRecord";
import {
defineOptionInfo,
defineJudgeOptionInfo,
} from "../../components/model";
import XMAudio from "../../components/XMAudio";
import XMRecord from "../../components/XMRecord";
import ScanFileModal from "@/modules/resource-disk/modal/ScanFileModal";
import SelectPrepareFileModal from "@/modules/prepare-lesson/modal/SelectPrepareFileModal";
import _ from "lodash";
class NewQuestionTab extends Component {
class OperateQuestionTab extends Component {
constructor(props) {
super(props);
const { questionInfo = {} } = props;
......@@ -73,8 +76,10 @@ class NewQuestionTab extends Component {
// 选择题(单选 多选 不定项)-插入4条默认选项
for (var i = 0; i < 4; i++) {
this.handleAddOption();
this.setState({ [`optionsValidate_${i}`]: "success" });
this.setState({ [`optionsText_${i}`]: "" });
this.setState({
[`optionsValidate_${i}`]: "success",
[`optionsText_${i}`]: "",
});
}
}
} else if (this.props.questionTypeKey === "JUDGE") {
......@@ -105,16 +110,18 @@ class NewQuestionTab extends Component {
shouldComponentUpdate(nextProps, nextState) {
const { questionInfo } = nextProps;
if (this.props.questionInfo !== questionInfo) {
this.setState({
this.setState(
{
gapFillingAnswer: JSON.parse(
JSON.stringify(questionInfo.gapFillingAnswerList)
),
});
this.setState(
{
stemContent: JSON.parse(
JSON.stringify(questionInfo.questionStemList)
),
chooseOptions: JSON.parse(JSON.stringify(questionInfo.optionList)),
questionAnswerDesc: JSON.parse(
JSON.stringify(questionInfo.questionAnswerDescList)
),
},
() => {
const con = questionInfo.questionStemList[0].content;
......@@ -126,15 +133,7 @@ class NewQuestionTab extends Component {
});
this.setState({ blanksList: _blanksList || [] });
}
); // 题干内容
this.setState({
chooseOptions: JSON.parse(JSON.stringify(questionInfo.optionList)),
}); // 单选多选不定项-选项列表
this.setState({
questionAnswerDesc: JSON.parse(
JSON.stringify(questionInfo.questionAnswerDescList)
),
}); // 答案解析
);
this._onSetState();
}
return true;
......@@ -155,159 +154,7 @@ class NewQuestionTab extends Component {
});
};
transferStemDocument = (txt) => {
const template = `<div class='stem'>${txt}</div>`;
let doc = new DOMParser().parseFromString(template, "text/html");
let div = doc.querySelector(".stem");
return div;
};
// 保存校验
checkInput = () => {
let validateError = 0;
// 题干校验
let stemContent = _.find(
this.state.stemContent,
(contentItem) => contentItem.type === "RICH_TEXT"
);
let stem = stemContent.content.replace(/<[^>]+>/g, "");
stem = stem.replace(/\&nbsp\;/gi, "");
stem = stem.replace(/\s+/g, "");
if (this.props.questionTypeKey === "GAP_FILLING") {
if (this.state.blanksList.length === 0 || stem.length === 0) {
this.setState({ stemValidate: "error" });
this.setState({
stemText: (
<div style={{ marginTop: 8, minWidth: "523px" }}>
请输入正确格式,示例:党章规定,凡事有
<span style={{ padding: "0 10px", borderBottom: "1px solid" }}>
填空1
</span>
人以上的
<span style={{ padding: "0 10px", borderBottom: "1px solid" }}>
填空2
</span>
,都应该成立党的基层组织
</div>
),
});
validateError++;
} else {
this.setState({ stemValidate: "success" });
this.setState({ stemText: "" });
}
} else {
if (stem.length === 0) {
this.setState({ stemValidate: "error" });
this.setState({ stemText: "请输入题干" });
validateError++;
} else {
this.setState({ stemValidate: "success" });
this.setState({ stemText: "" });
}
}
// 选项校验
let optionUnChecked = 0;
const { chooseOptions } = this.state;
if (this.props.questionTypeKey === "GAP_FILLING") {
this.state.gapFillingAnswer.forEach((item, index) => {
if (item.correctAnswerList.length === 0) {
this.setState({ [`optionsValidate_${index}`]: "error" });
this.setState({ [`optionsText_${index}`]: "请输入答案" });
validateError++;
} else {
this.setState({ [`optionsValidate_${index}`]: "success" });
this.setState({ [`optionsText_${index}`]: "" });
}
});
} else {
chooseOptions.forEach((item, index) => {
const optionContent = item.questionOptionContentList;
optionUnChecked = item.isCorrectAnswer
? optionUnChecked
: optionUnChecked + 1;
let optionInput = optionContent[0].content.replace(/<[^>]+>/g, "");
optionInput = optionInput.replace(/\&nbsp\;/gi, "");
optionInput = optionInput.replace(/\s+/g, "");
if (
optionContent.length === 1 &&
optionContent[0].type === "RICH_TEXT" &&
optionInput.length === 0
) {
this.setState({ [`optionsValidate_${index}`]: "error" });
this.setState({ [`optionsText_${index}`]: "请输入选项" });
validateError++;
} else {
this.setState({ [`optionsValidate_${index}`]: "success" });
this.setState({ [`optionsText_${index}`]: "" });
}
});
var chooseIcon = [];
if (["SINGLE_CHOICE", "JUDGE"].includes(this.props.questionTypeKey)) {
chooseIcon = document.getElementsByClassName("ant-radio-inner");
} else if (
["MULTI_CHOICE", "INDEFINITE_CHOICE"].includes(
this.props.questionTypeKey
)
) {
chooseIcon = document.getElementsByClassName("ant-checkbox-inner");
}
if (optionUnChecked === chooseOptions.length) {
this.setState({ radioValidate: "error" });
chooseIcon.forEach((item) => {
item.setAttribute("style", "border:1px solid #ff4d4f;");
});
this.setState({
radioText: (
<span>
正确答案
<br />
不能为空
</span>
),
});
validateError++;
} else {
this.setState({ radioValidate: "success" });
this.setState({ radioText: "" });
chooseIcon.forEach((item) => {
item.removeAttribute("style");
});
}
if (
this.props.questionTypeKey === "MULTI_CHOICE" &&
this.state.chooseOptions.length - optionUnChecked === 1
) {
this.setState({ radioValidate: "error" });
this.setState({ radioText: "最少选两个" });
chooseIcon.forEach((item) => {
item.setAttribute("style", "border:1px solid #ff4d4f;");
});
validateError++;
}
}
return validateError;
};
/**
* 预览
*
* @memberof QuestionInputItem
*/
handleScanFile = (scanFileType, scanFileAddress) => {
this.setState({
showScanFile: true,
scanFileAddress,
scanFileType,
});
};
/**
* 添加选项
*
* @memberof QuestionInputItem
*/
// 添加选项
handleAddOption = (content) => {
const { chooseOptions } = this.state;
if (chooseOptions.length >= 20) {
......@@ -318,22 +165,7 @@ class NewQuestionTab extends Component {
}
};
/**
* 初始化判断选项
*
* @memberof QuestionInputItem
*/
initJudgeOption = (content) => {
const { chooseOptions } = this.state;
chooseOptions.push(defineJudgeOptionInfo(content));
this._onSetState();
};
/**
* 删除选项
*
* @memberof QuestionInputItem
*/
// 删除选项
handleDelOption = (optionIndex) => {
const { chooseOptions } = this.state;
this.handleLogger("delete_option", "删除选项");
......@@ -345,11 +177,7 @@ class NewQuestionTab extends Component {
}
};
/**
* 移动选项
*
* @memberof QuestionInputItem
*/
// 移动选项
handleMoveOption = (optionIndex, moveLength) => {
const { chooseOptions } = this.state;
const optionItem = chooseOptions.splice(optionIndex + moveLength, 1);
......@@ -357,11 +185,8 @@ class NewQuestionTab extends Component {
chooseOptions.splice(optionIndex, 0, optionItem[0]);
this._onSetState();
};
/**
* 选择上传文件类型
*
* @memberof QuestionInputItem
*/
// 选择上传文件类型
handleChangeMedia = (key, uploadItemTarget, contentType) => {
const pictureMediaArr = _.filter(uploadItemTarget, (mediaItem) => {
return mediaItem.type === "PICTURE";
......@@ -485,8 +310,6 @@ class NewQuestionTab extends Component {
// 录音
this.setState({
showRecord: true,
});
this.setState({
onAudioFinish: function () {
change();
},
......@@ -580,8 +403,43 @@ class NewQuestionTab extends Component {
});
}
// 完成语音录制
handleFinishRecord = (mp3URL, duration) => {
const originArr = mp3URL.split(".");
const originType = originArr[originArr.length - 1];
this.state.onAudioFinish();
const { uploadItemTarget, contentType } = this.state;
uploadItemTarget.push({
contentType,
type: "AUDIO",
contentName: `${window.random_string(16)}.${originType}`, // 文件名
fileType: originType, // 文件后缀
content: mp3URL,
size: duration,
});
this._onSetState({ showRecord: false });
};
// 取消录制
handleCancelRecord = () => {
this.setState({ showRecord: false });
};
// 云盘文件筛选
handleSelectMedia = (file) => {
this.uploadFile(file);
this.setState({
showSelectFileModal: false,
});
};
// 填空内容改变-联动答案
changeBlankCount = (data, idx) => {
let _gap = this.state.gapFillingAnswer;
if (data.length === 0) {
_gap = [];
} else {
if (data.length <= idx) {
_gap.splice(idx, 1);
} else {
......@@ -604,6 +462,7 @@ class NewQuestionTab extends Component {
return item;
});
}
}
this.setState(
{
......@@ -614,6 +473,7 @@ class NewQuestionTab extends Component {
);
};
// 填空-tag新增答案
addAnswerTag = (optionItem) => {
const _blanksList = this.state.blanksList;
_blanksList.forEach((item) => {
......@@ -624,7 +484,7 @@ class NewQuestionTab extends Component {
this.setState({ blanksList: _blanksList });
};
// 填空选项
// 确认输入填空选项
handleInputConfirm = (optionItem, val) => {
var tagContent = val.replace(/\&nbsp\;/gi, "");
tagContent = val.replace(/\s+/g, "");
......@@ -644,12 +504,13 @@ class NewQuestionTab extends Component {
}
});
this.setState({ gapFillingAnswer: _gapFillingAnswer }, () =>
this._onSetState()
this.setState(
{ gapFillingAnswer: _gapFillingAnswer, blanksList: _blanksList },
() => this._onSetState()
);
this.setState({ blanksList: _blanksList });
};
// 填空-删除已填答案tag
handleTagClose = (optionItem, removedTag, removedIndex) => {
const _blanksList = this.state.blanksList;
const { gapFillingAnswer } = this.state;
......@@ -663,13 +524,13 @@ class NewQuestionTab extends Component {
);
}
});
this.setState({ gapFillingAnswer: _gapFillingAnswer }, () =>
this._onSetState()
this.setState(
{ gapFillingAnswer: _gapFillingAnswer, blanksList: _blanksList },
() => this._onSetState()
);
this.setState({ blanksList: _blanksList });
};
// 输入框关闭
// 填空-输入框关闭
handleInputClose = (optionItem) => {
const _blanksList = this.state.blanksList;
_blanksList.forEach((item) => {
......@@ -681,16 +542,15 @@ class NewQuestionTab extends Component {
this.setState({ blanksList: _blanksList });
};
handleInputEdit = (optionItem, index) => {
const _blanksList = this.state.blanksList;
_blanksList.forEach((item) => {
if (item.id === optionItem.id) {
item.correctAnswerList.map();
}
});
this.setState({ blanksList: _blanksList });
// 填空-字符串转html
transferStemDocument = (txt) => {
const template = `<div class='stem'>${txt}</div>`;
let doc = new DOMParser().parseFromString(template, "text/html");
let div = doc.querySelector(".stem");
return div;
};
// 填空答案渲染
renderGapFillingAnswer = (optionItem, optionIndex) => {
const { gapFillingAnswer } = this.state;
const list =
......@@ -752,16 +612,161 @@ class NewQuestionTab extends Component {
);
};
// 初始化判断选项
initJudgeOption = (content) => {
const { chooseOptions } = this.state;
chooseOptions.push(defineJudgeOptionInfo(content));
this._onSetState();
};
// 判断题选项渲染
renderJudgeOption = (judgeOptions) => {
return (
<div dangerouslySetInnerHTML={{ __html: judgeOptions[0].content }} />
);
};
/**
* 渲染输入内容
*
* @memberof QuestionInputItem
*/
// 保存校验
checkInput = () => {
let validateError = 0;
// 题干校验
let stemContent = _.find(
this.state.stemContent,
(contentItem) => contentItem.type === "RICH_TEXT"
);
let stem = stemContent.content.replace(/<[^>]+>/g, "");
stem = stem.replace(/\&nbsp\;/gi, "");
stem = stem.replace(/\s+/g, "");
if (this.props.questionTypeKey === "GAP_FILLING") {
if (this.state.blanksList.length === 0 || stem.length === 0) {
this.setState({
stemValidate: "error",
stemText: (
<div style={{ marginTop: 8, minWidth: "523px" }}>
请输入正确格式,示例:党章规定,凡事有
<span style={{ padding: "0 10px", borderBottom: "1px solid" }}>
填空1
</span>
人以上的
<span style={{ padding: "0 10px", borderBottom: "1px solid" }}>
填空2
</span>
,都应该成立党的基层组织
</div>
),
});
validateError++;
} else {
this.setState({ stemValidate: "success", stemText: "" });
}
} else {
if (stem.length === 0) {
this.setState({ stemValidate: "error", stemText: "请输入题干" });
validateError++;
} else {
this.setState({ stemValidate: "success", stemText: "" });
}
}
// 选项校验
let optionUnChecked = 0;
const { chooseOptions } = this.state;
if (this.props.questionTypeKey === "GAP_FILLING") {
this.state.gapFillingAnswer.forEach((item, index) => {
if (item.correctAnswerList.length === 0) {
this.setState({
[`optionsValidate_${index}`]: "error",
[`optionsText_${index}`]: "请输入答案",
});
validateError++;
} else {
this.setState({
[`optionsValidate_${index}`]: "success",
[`optionsText_${index}`]: "",
});
}
});
} else {
chooseOptions.forEach((item, index) => {
const optionContent = item.questionOptionContentList;
optionUnChecked = item.isCorrectAnswer
? optionUnChecked
: optionUnChecked + 1;
let optionInput = optionContent[0].content.replace(/<[^>]+>/g, "");
optionInput = optionInput.replace(/\&nbsp\;/gi, "");
optionInput = optionInput.replace(/\s+/g, "");
if (
optionContent.length === 1 &&
optionContent[0].type === "RICH_TEXT" &&
optionInput.length === 0
) {
this.setState({
[`optionsValidate_${index}`]: "error",
[`optionsText_${index}`]: "请输入选项",
});
validateError++;
} else {
this.setState({
[`optionsValidate_${index}`]: "success",
[`optionsText_${index}`]: "",
});
}
});
var chooseIcon = [];
if (["SINGLE_CHOICE", "JUDGE"].includes(this.props.questionTypeKey)) {
chooseIcon = document.getElementsByClassName("ant-radio-inner");
} else if (
["MULTI_CHOICE", "INDEFINITE_CHOICE"].includes(
this.props.questionTypeKey
)
) {
chooseIcon = document.getElementsByClassName("ant-checkbox-inner");
}
if (optionUnChecked === chooseOptions.length) {
this.setState({
radioValidate: "error",
radioText: (
<span>
正确答案
<br />
不能为空
</span>
),
});
chooseIcon.forEach((item) => {
item.setAttribute("style", "border:1px solid #ff4d4f;");
});
validateError++;
} else {
this.setState({ radioValidate: "success", radioText: "" });
chooseIcon.forEach((item) => {
item.removeAttribute("style");
});
}
if (
this.props.questionTypeKey === "MULTI_CHOICE" &&
this.state.chooseOptions.length - optionUnChecked === 1
) {
this.setState({ radioValidate: "error", radioText: "最少选两个" });
chooseIcon.forEach((item) => {
item.setAttribute("style", "border:1px solid #ff4d4f;");
});
validateError++;
}
}
return validateError;
};
// 预览
handleScanFile = (scanFileType, scanFileAddress) => {
this.setState({
showScanFile: true,
scanFileAddress,
scanFileType,
});
};
// 渲染输入内容
renderContent = (
contentList,
placehold,
......@@ -1059,57 +1064,6 @@ class NewQuestionTab extends Component {
);
};
/**
* 取消上传
*
* @memberof QuestionInputItem
*/
handleAbort = (uploadItem, index) => {
const { uploadItemTarget } = this.state;
const { xhr } = uploadItem;
xhr && xhr.abort && xhr.abort();
uploadItemTarget.splice(index, 1);
this._onSetState();
};
/**
* 完成语音录制
*
* @memberof QuestionInputItem
*/
handleFinishRecord = (mp3URL, duration) => {
const originArr = mp3URL.split(".");
const originType = originArr[originArr.length - 1];
this.state.onAudioFinish();
const { uploadItemTarget, contentType } = this.state;
uploadItemTarget.push({
contentType,
type: "AUDIO",
contentName: `${window.random_string(16)}.${originType}`, // 文件名
fileType: originType, // 文件后缀
content: mp3URL,
size: duration,
});
this._onSetState({ showRecord: false });
};
/**
* 取消录制
*
* @memberof QuestionInputItem
*/
handleCancelRecord = () => {
this.setState({ showRecord: false });
};
handleSelectMedia = (file) => {
this.uploadFile(file);
this.setState({
showSelectFileModal: false,
});
};
render() {
const {
stemContent,
......@@ -1423,4 +1377,4 @@ class NewQuestionTab extends Component {
}
}
export default NewQuestionTab;
export default OperateQuestionTab;
......@@ -175,12 +175,6 @@
margin-right: 187px;
}
// .question-item_options {
// display: flex;
// align-items: center;
// padding-bottom: 15px;
// }
.question-item_options__list {
flex: 1;
position: relative;
......@@ -440,38 +434,3 @@
}
}
}
.question_skeleton {
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
background-size: 400% 100%;
animation: question-editor_skeleton__loading 1.4s ease infinite;
}
.question_skeleton__editor {
min-height: 33px;
max-height: 140px;
overflow: hidden;
}
.question_skeleton__img {
width: 88px;
height: 88px;
}
.question_skeleton__voice {
height: 48px;
width: 280px;
}
.question_skeleton__video {
width: 100%;
height: 100%;
}
@keyframes question-editor_skeleton__loading {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
import React, { Component } from "react";
import E from "wangeditor";
import { message, Button } from "antd";
import { message } from "antd";
import UploadOss from "@/core/upload";
import "./QuestionEditor.less";
const MEDIA_MAP = [
......
......@@ -2,14 +2,13 @@
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-03-25 14:32:22
* @Description: 助学工具-题库-题目管理主页面列表数据
* @LastEditTime: 2021-04-12 14:14:19
* @Description: 助学工具-题库-题目列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import {
Table,
Switch,
ConfigProvider,
Empty,
Row,
......@@ -22,12 +21,15 @@ import {
message,
} from "antd";
import { PageControl } from "@/components";
import "./QuestionManageContent.less";
import "./QuestionList.less";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import _ from "underscore";
import QuestionPreviewModal from "../modal/QuestionPreviewModal";
import PreviewQuestionModal from "../modal/PreviewQuestionModal";
import BatchImportQuestionModal from "../modal/BatchImportQuestionModal";
import { Route, withRouter } from "react-router-dom";
import OperateQuestion from "../OperateQuestion";
import Bus from "@/core/bus";
const { Search } = Input;
......@@ -62,7 +64,7 @@ const questionTypeList = [
},
];
class QuestionManageContent extends Component {
class QuestionList extends Component {
constructor(props) {
super(props);
this.state = {
......@@ -77,87 +79,111 @@ class QuestionManageContent extends Component {
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
},
questionTypeList: [], // 题型列表
dataSource: [],
totalCount: 0,
QuestionPreviewModal: null, // 题目预览模态框
dataSource: [], // 题库列表
previewQuestionModal: null, // 题目预览模态框
batchImportQuestionModal: null, // 批量导入模态框
};
}
componentDidMount() {}
shouldComponentUpdate(nextProps, nextState) {
let { selectedCategoryId } = nextProps;
const _query = this.state.query;
if (this.props.selectedCategoryId !== selectedCategoryId) {
if (selectedCategoryId === "null") {
selectedCategoryId = null;
}
_query.categoryId = selectedCategoryId;
_query.questionName = null;
_query.questionType = null;
_query.current = 1;
this.setState({ query: _query }, () => this.queryQuestionPageList());
componentDidMount() {
this.queryQuestionPageList();
Bus.bind("queryQuestionPageList", (selectedCategoryId) => {
selectedCategoryId =
selectedCategoryId === "null" ? null : selectedCategoryId;
this.InitSearch(selectedCategoryId);
});
}
return true;
componentWillUnmount() {
Bus.unbind("queryQuestionPageList", this.queryQuestionPageList);
}
queryQuestionPageList = (remain) => {
const _query = this.state.query;
if (_query.categoryId === "0") _query.categoryId = null;
QuestionBankService.queryQuestionPageList(_query).then((res) => {
const { records = [], total = 0 } = res.result;
this.setState({ dataSource: records });
this.setState({ total }, () =>
this.props.updatedSiderTree(total, this.props.selectedCategoryId)
);
});
// 初始化列表查询
InitSearch = (categoryId) => {
const _query = {
...this.state.query,
categoryId,
current: 1,
order: "UPDATED_DESC", // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
};
handleCreateQuestionBank = () => {
window.RCHistory.push({
pathname: `/create-new-question?categoryId=${this.state.query.categoryId}`,
this.setState({ query: _query }, () => {
this.queryQuestionPageList();
});
};
delCategoryConfirm(record) {
return Modal.confirm({
title: "提示",
content: "确定要删除此题目吗?",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
cancelText: "取消",
onOk: () => {
this.deleteQuestion(record);
// 改变搜索条件
handleChangeQuery = (searchType, value) => {
this.setState(
{
query: {
...this.state.query,
[searchType]: value || null,
current: 1,
},
});
},
() => {
if (searchType === "questionName") return;
this.queryQuestionPageList();
}
);
};
deleteQuestion = (record) => {
let params = {
id: record.id,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
// 重置搜索条件
handleReset = () => {
const _query = {
...this.state.query,
current: 1,
order: "UPDATED_DESC", // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
};
QuestionBankService.deleteQuestion(params).then((res) => {
if (res.success) {
message.success("删除成功");
const { query, total } = this.state;
const { size, current } = query;
const _query = query;
if (total / size < current) {
if (total % size === 1) {
_query.current = 1;
}
}
this.setState({ query: _query }, () => this.queryQuestionPageList());
}
this.setState({ query: _query }, () => {
this.queryQuestionPageList();
});
};
// 题库列表查询
queryQuestionPageList = () => {
AidToolService.queryQuestionPageList(this.state.query).then((res) => {
const { records = [], total = 0 } = res.result;
this.setState({ dataSource: records, total });
});
};
// 自定义表格空状态
customizeRenderEmpty = () => {
const { categoryId } = this.state.query;
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
imageStyle={{
height: 100,
}}
description={
<div>
<span>还没有题目</span>
{["CloudManager", "StoreManager"].includes(User.getUserRole()) &&
categoryId && (
<span>
,快去
<span
className="empty-list-tip"
onClick={() => {
this.handleCreateQuestion();
}}
>
新建一个
</span>
吧!
</span>
)}
</div>
}
></Empty>
);
};
// 排序
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
......@@ -180,49 +206,9 @@ class QuestionManageContent extends Component {
this.setState({ query: _query }, () => this.queryQuestionPageList());
};
// 清空搜索条件
handleReset = () => {
const _query = {
...this.state.query,
current: 1,
order: "ACCURACY_DESC", // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
};
this.setState({ query: _query }, () => {
this.queryQuestionPageList();
});
};
previewQuestion = (id) => {
const m = (
<QuestionPreviewModal
id={id}
close={() => {
this.setState({
QuestionPreviewModal: null,
});
}}
/>
);
this.setState({ QuestionPreviewModal: m });
};
toEditQuetion = (id, type) => {
const { categoryId } = this.state.query;
if (categoryId) {
window.RCHistory.push({
pathname: `/create-new-question?id=${id}&type=${type}&categoryId=${categoryId}`,
});
} else {
window.RCHistory.push({
pathname: `/create-new-question?id=${id}&type=${type}`,
});
}
};
// 表头设置
parseColumns = () => {
// 权限判断
const isPermiss = ["CloudManager", "StoreManager"].includes(
User.getUserRole()
);
......@@ -234,15 +220,14 @@ class QuestionManageContent extends Component {
ellipsis: {
showTitle: false,
},
render: (val, record) => {
render: (val) => {
var handleVal = val;
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/<\s?img[^>]*>/gi, "【图片】");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
return (
<Tooltip
overlayClassName="tool-list"
overlayClassName="aid-tool-list"
title={
<div style={{ maxWidth: 700, width: "auto" }}>{handleVal}</div>
}
......@@ -271,7 +256,7 @@ class QuestionManageContent extends Component {
showSorterTooltip: false,
width: "14%",
render: (val) => {
return val + "%";
return parseInt(val * 100) + "%";
},
},
{
......@@ -306,7 +291,7 @@ class QuestionManageContent extends Component {
<div
className="record-operate__item"
onClick={() =>
this.toEditQuetion(record.id, record.questionTypeEnum)
this.editQuestion(record.id, record.questionTypeEnum)
}
>
编辑
......@@ -318,7 +303,7 @@ class QuestionManageContent extends Component {
{isPermiss && (
<div
className="record-operate__item"
onClick={() => this.delCategoryConfirm(record)}
onClick={() => this.delQuestionConfirm(record)}
>
删除
</div>
......@@ -331,39 +316,7 @@ class QuestionManageContent extends Component {
return columns;
};
// 自定义表格空状态
customizeRenderEmpty = () => {
const { categoryId } = this.state.query;
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
imageStyle={{
height: 100,
}}
description={
<div>
<span>还没有题目</span>
{["CloudManager", "StoreManager"].includes(User.getUserRole()) &&
!["0", null].includes(categoryId) && (
<span>
,快去
<span
className="empty-list-tip"
onClick={() => {
this.handleCreateQuestionBank();
}}
>
新建一个
</span>
吧!
</span>
)}
</div>
}
></Empty>
);
};
// 设置表格每页展示条数
onShowSizeChange = (current, size) => {
if (current == size) {
return;
......@@ -373,44 +326,111 @@ class QuestionManageContent extends Component {
this.setState({ query: _query }, () => this.queryQuestionPageList());
};
// 改变搜索条件
handleChangeQuery = (searchType, value) => {
this.setState(
{
query: {
...this.state.query,
[searchType]: value || null,
current: 1,
},
// 预览题目
previewQuestion = (id) => {
const m = (
<PreviewQuestionModal
id={id}
close={() => {
this.setState({
previewQuestionModal: null,
});
}}
/>
);
this.setState({ previewQuestionModal: m });
};
// 编辑题目
editQuestion = (id, type) => {
const { categoryId } = this.state.query;
const { match } = this.props;
window.RCHistory.push({
pathname: `${match.url}/question-operate-page?id=${id}&type=${type}&categoryId=${categoryId}`,
});
};
// 删除题目确认弹窗
delQuestionConfirm(record) {
return Modal.confirm({
title: "提示",
content: "确定要删除此题目吗?",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
cancelText: "取消",
onOk: () => {
this.deleteQuestion(record);
},
() => {
if (searchType === "questionName") return;
});
}
// 删除题目
deleteQuestion = (record) => {
let params = {
id: record.id,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
AidToolService.deleteQuestion(params).then((res) => {
if (res.success) {
message.success("删除成功");
const { query, total } = this.state;
const { size, current } = query;
const _query = query;
if (total / size < current) {
if (total % size === 1) {
_query.current = 1;
}
}
this.setState({ query: _query }, () => {
this.queryQuestionPageList();
Bus.trigger("queryCategoryTree", "remain");
});
}
);
});
};
// 创建题目-跳转新建页
handleCreateQuestion = () => {
const { match } = this.props;
window.RCHistory.push({
pathname: `${match.url}/question-operate-page?categoryId=${this.state.query.categoryId}`,
});
};
// 批量导入弹窗
batchImportQuestion = () => {
const { categoryId } = this.state.query;
const ImportQuestionModal = (
const m = (
<BatchImportQuestionModal
close={() => {
this.setState({ ImportQuestionModal: null }, () => {
this.setState({ batchImportQuestionModal: null }, () => {
this.queryQuestionPageList();
Bus.trigger("queryCategoryTree", "remain");
});
}}
categoryId={categoryId}
/>
);
this.setState({ ImportQuestionModal });
this.setState({ batchImportQuestionModal: m });
};
render() {
const { dataSource = [], total, query } = this.state;
const {
dataSource = [],
total,
query,
previewQuestionModal,
batchImportQuestionModal,
} = this.state;
const { current, size, categoryId, questionName, questionType } = query;
const { match } = this.props;
return (
<div className="question-manage-content">
<div className="question-manage-filter">
<div className="question-list">
<div className="question-list-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
......@@ -418,7 +438,7 @@ class QuestionManageContent extends Component {
<Search
placeholder="搜索题目名称"
value={questionName}
style={{ width: "calc(100% - 84px)" }}
style={{ width: 178 }}
onChange={(e) => {
this.handleChangeQuery("questionName", e.target.value);
}}
......@@ -434,7 +454,7 @@ class QuestionManageContent extends Component {
<Select
placeholder="请选择题目类型"
value={questionType}
style={{ width: "calc(100% - 70px)" }}
style={{ width: 178 }}
showSearch
allowClear
enterButton={<span className="icon iconfont">&#xe832;</span>}
......@@ -474,15 +494,15 @@ class QuestionManageContent extends Component {
</Row>
</div>
{["CloudManager", "StoreManager"].includes(User.getUserRole()) &&
!["0", null].includes(categoryId) && (
categoryId && (
<Space size={16}>
<Button type="primary" onClick={this.handleCreateQuestionBank}>
<Button type="primary" onClick={this.handleCreateQuestion}>
新建题目
</Button>
<Button onClick={this.batchImportQuestion}>批量导入</Button>
</Space>
)}
<div className="question-manage-list">
<div className="question-list-content">
<ConfigProvider renderEmpty={this.customizeRenderEmpty}>
<Table
rowKey={(record) => record.id}
......@@ -510,12 +530,16 @@ class QuestionManageContent extends Component {
/>
</div>
)}
{this.state.QuestionPreviewModal}
{this.state.ImportQuestionModal}
{previewQuestionModal}
{batchImportQuestionModal}
</div>
<Route
path={`${match.url}/question-operate-page`}
component={OperateQuestion}
/>
</div>
);
}
}
export default QuestionManageContent;
export default withRouter(QuestionList);
/*
* @Author: yuananting
* @Date: 2021-02-25 11:26:28
* @LastEditors: yuananting
* @LastEditTime: 2021-03-25 14:32:01
* @Description: 助学工具-题库-题目管理右侧内容样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-manage-content {
.question-manage-filter {
.question-list {
.question-list-filter {
position: relative;
.search-condition {
width: calc(100% - 80px);
......@@ -16,7 +8,6 @@
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 16px;
.search-label {
......@@ -49,7 +40,7 @@
.data-icon {
cursor: pointer;
}
.question-manage-list {
.question-list-content {
position: relative;
margin-top: 16px;
.empty-list-tip {
......@@ -76,13 +67,8 @@
}
}
}
.tool-list {
.aid-tool-list {
.ant-tooltip-inner {
max-width: 700px !important;
}
}
.fill-line {
padding: 0 10px;
border-bottom: 1px solid;
}
/*
* @Author: zhangyi
* @Date: 2019-12-09 10:29:55
* @Last Modified by: mikey.wanghaofeng
* @Last Modified time: 2020-09-25 11:03:47
* @Author: yuananting
* @Date: 2021-03-27 11:15:03
* @LastEditors: yuananting
* @LastEditTime: 2021-03-29 16:22:28
* @Description: 助学工具-题库-批量导入题目
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Modal, Button, Upload, message, Spin, Progress } from "antd";
import { Modal, Button, message, Spin } from "antd";
import "./BatchImportQuestionModal.less";
import SelectPrepareFileModal from "@/modules/prepare-lesson/modal/SelectPrepareFileModal";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import { LoadingOutlined } from "@ant-design/icons";
class BatchImportQuestionModal extends Component {
constructor(props) {
......@@ -65,7 +66,7 @@ class BatchImportQuestionModal extends Component {
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
const res = await QuestionBankService.batchImport(params);
const res = await AidToolService.batchImport(params);
const { result } = res;
const { bizSuccess, bizMessage } = result;
if (res) {
......@@ -222,9 +223,11 @@ class BatchImportQuestionModal extends Component {
type="primary"
className="down-btn"
onClick={() => {
this.setState({ status: "init" });
this.setState({ uploadFile: null });
this.setState({ showSelectFileModal: true });
this.setState({
status: "init",
uploadFile: null,
showSelectFileModal: true,
});
}}
>
重新上传文件
......
@import '../../../core/mixins.less';
@import "@/core/mixins.less";
.import-score-modal {
.step-section {
margin-bottom: 24px;
......@@ -49,7 +48,7 @@
}
}
.file-box :hover {
background-color: #FFF8E8;
background-color: #fff8e8;
.del-img {
visibility: visible !important;
}
......@@ -64,7 +63,7 @@
}
.down-btn {
text-align: left;
color: #5289FA;
color: #5289fa;
font-size: 12px;
display: block;
margin-top: 8px;
......@@ -74,18 +73,18 @@
width: 200px;
.ant-upload-list-item-name {
.text-overflow-ellipsis();
width:70%;
width: 70%;
}
}
}
.import-status-box {
height:430px;
height: 430px;
overflow: hidden;
.status-content {
margin:auto;
margin: auto;
text-align: center;
margin-top:100px;
>img {
margin-top: 100px;
> img {
width: 76px;
}
.status {
......@@ -98,7 +97,7 @@
color: #666666;
margin-bottom: 16px;
.num {
color: #FC9C6B;
color: #fc9c6b;
}
}
}
......
/*
* @Author: yuananting
* @Date: 2021-03-27 11:15:03
* @LastEditors: yuananting
* @LastEditTime: 2021-04-01 19:47:12
* @Description: 助学工具-题库-预览题目
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Modal, Divider } from "antd";
import { Modal } from "antd";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import "./QuestionPreviewModal.less";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import "./PreviewQuestionModal.less";
import ScanFileModal from "@/modules/resource-disk/modal/ScanFileModal";
import _ from "underscore";
import XMAudio from "../components/XMAudio";
import XMAudio from "../../components/XMAudio";
import { NUM_TO_WORD_MAP } from "@/common/constants/punchClock/punchClock";
const questionTypeList = {
SINGLE_CHOICE: "单选题",
......@@ -15,7 +23,7 @@ const questionTypeList = {
INDEFINITE_CHOICE: "不定项选择题",
};
class QuestionPreviewModal extends Component {
class PreviewQuestionModal extends Component {
formRef = React.createRef();
constructor(props) {
super(props);
......@@ -36,12 +44,13 @@ class QuestionPreviewModal extends Component {
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryQuestionDetails(query).then((res) => {
AidToolService.queryQuestionDetails(query).then((res) => {
const { result = [] } = res;
this.setState({ questionInfo: result });
});
};
// 查看图片或视频
handleScanFile = (scanFileType, scanFileAddress) => {
this.setState({
showScanFile: true,
......@@ -50,13 +59,6 @@ class QuestionPreviewModal extends Component {
});
};
transferStemDocument = (txt) => {
const template = `<p class='content'>${txt}</p>`;
let doc = new DOMParser().parseFromString(template, "text/html");
let p = doc.querySelector(".content");
return p;
};
render() {
const {
showScanFile,
......@@ -96,7 +98,7 @@ class QuestionPreviewModal extends Component {
return (
<div>
<Modal
className="question-preview-modal"
className="preview-question-modal"
visible={true}
title="题目预览"
width={560}
......@@ -225,7 +227,10 @@ class QuestionPreviewModal extends Component {
)}
{["JUDGE"].includes(questionTypeEnum) &&
_.map(questionOptionContentList, (item, index) => {
item.content = item.content.replace(/<\/?[^>]*>/g, "");
item.content = item.content.replace(
/<\/?[^>]*>/g,
""
);
return <span key={index}>{item.content}</span>;
})}
</div>
......@@ -444,4 +449,4 @@ class QuestionPreviewModal extends Component {
);
}
}
export default QuestionPreviewModal;
export default PreviewQuestionModal;
......@@ -17,7 +17,7 @@
}
.question-stem {
margin-bottom: 16px;
border-bottom: 1px solid #E8E8E8;
border-bottom: 1px solid #e8e8e8;
padding-bottom: 16px;
&__title {
height: 22px;
......@@ -140,7 +140,7 @@
}
.question-answer {
margin-bottom: 16px;
border-bottom: 1px solid #E8E8E8;
border-bottom: 1px solid #e8e8e8;
padding-bottom: 16px;
img {
max-width: 88px;
......@@ -277,6 +277,6 @@
}
}
}
.question-preview-modal.ant-modal {
.preview-question-modal.ant-modal {
max-height: 60% !important;
}
/*
* @Author: 吴文洁
* @Date: 2020-04-29 10:26:32
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-07 16:12:14
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 21:45:42
* @Description: 内容线路由配置
*/
import Home from '@/modules/home/Home';
......@@ -27,11 +27,12 @@ import PlanPage from '@/modules/plan-manage/PlanPage';
import AddPlanPage from '@/modules/plan-manage/AddPlan';
import LearningDataPage from '@/modules/plan-manage/LearningData';
import StoreInfoPage from '@/modules/store-manage/StoreInfo';
import CourseCategoryManage from '@/modules/teach-tool/components/CourseCategoryManage';
import QuestionManageIndex from '@/modules/teach-tool/question-manage/Index';
import PaperManageIndex from '@/modules/teach-tool/paper-manage/Index';
import ExaminationManagerIndex from '@/modules/teach-tool/examination-manager/Index';
import KnowledgeBase from "@/modules/knowledge-base/index";
import CollegeInfoPage from '@/modules/college-manage/CollegeInfoPage';
import QuestionBankIndex from '@/modules/teach-tool/QuestionBankIndex';
import QuestionCategoryManage from '@/modules/teach-tool/QuestionCategoryManage';
import AddNewQuestion from '@/modules/teach-tool/AddNewQuestion';
const mainRoutes = [
{
......@@ -116,19 +117,24 @@ const mainRoutes = [
name: "资料云盘",
},
{
path: "/question-bank-index",
component: QuestionBankIndex,
name: "题库",
path: '/question-manage-index',
component:QuestionManageIndex,
name: '题库'
},
{
path: "/question-category-manage",
component: QuestionCategoryManage,
name: "分类管理",
path: '/paper-manage-index',
component:PaperManageIndex,
name: '试卷'
},
{
path: "/create-new-question",
component: AddNewQuestion,
name: "新增题目",
path: '/examination-manage-index',
component:ExaminationManagerIndex,
name: '考试'
},
{
path: '/course-category-manage',
component:CourseCategoryManage,
name: '分类管理'
},
{
path: "/switch-route",
......
/*
* @Author: yuananting
* @Date: 2021-02-21 15:53:31
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-27 10:32:29
* @LastEditors: yuananting
* @LastEditTime: 2021-03-27 15:20:42
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -67,8 +67,18 @@ export const menuList: any = [
{
groupName: "题库",
groupCode: "QuestionBank",
link: '/question-bank-index'
}
link: '/question-manage-index'
},
{
groupName: "试卷",
groupCode: "ExamPaper",
link: '/paper-manage-index'
},
{
groupName: "考试",
groupCode: "CloudExam",
link: '/examination-manage-index'
},
]
},
{
......@@ -94,7 +104,7 @@ export const menuList: any = [
{
groupName: "课程分类",
groupCode: "CourseCategory",
link: '/question-category-manage'
link: '/course-category-manage'
},
{
groupName: "学院装修",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment