ipConfig.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. <template>
  2. <div class="ip-config">
  3. <div class="box box-info">
  4. <div class="box-body">
  5. <el-form
  6. ref="formSearch"
  7. :model="formSearch"
  8. :inline="true"
  9. label-width="70px"
  10. >
  11. <el-form-item label="学习中心">
  12. <el-select
  13. v-model="formSearch.orgId"
  14. class="input"
  15. filterable
  16. clearable
  17. placeholder="请选择"
  18. >
  19. <el-option
  20. v-for="item in orgList4Search"
  21. :key="item.id"
  22. :label="item.name + ' - ' + item.code"
  23. :value="item.id"
  24. ></el-option>
  25. </el-select>
  26. </el-form-item>
  27. <el-form-item>
  28. <el-button
  29. size="small"
  30. type="primary"
  31. icon="el-icon-search"
  32. @click="resetPageAndSearchForm"
  33. >查询</el-button
  34. >
  35. </el-form-item>
  36. </el-form>
  37. <div class="block-seperator"></div>
  38. <span>操作:</span>
  39. <el-button
  40. size="small"
  41. type="primary"
  42. icon="el-icon-plus"
  43. @click="editItem(null)"
  44. >新增</el-button
  45. >
  46. <el-button
  47. size="small"
  48. type="primary"
  49. icon="el-icon-upload2"
  50. @click="impCourse"
  51. >批量导入</el-button
  52. >
  53. <el-button
  54. size="small"
  55. type="danger"
  56. :disabled="!selectedIds.length"
  57. icon="el-icon-delete"
  58. @click="deleteMult(selectedIds)"
  59. >批量删除</el-button
  60. >
  61. <el-button
  62. v-if="rolePrivileges.ORG_IP_CONFIG_SYNC"
  63. size="small"
  64. type="success"
  65. @click="openSynchronousDialog"
  66. >同步到考试</el-button
  67. >
  68. <el-button
  69. size="small"
  70. type="primary"
  71. icon="el-icon-download"
  72. @click="exportHandler"
  73. >导出</el-button
  74. >
  75. <el-table
  76. :data="tableData"
  77. border
  78. style="width: 100%; text-align: center; margin-top: 10px"
  79. @selection-change="selectChange"
  80. >
  81. <el-table-column type="selection" width="50"></el-table-column>
  82. <el-table-column prop="ip" width label="IP/IP段"></el-table-column>
  83. <el-table-column
  84. prop="orgCode"
  85. width
  86. label="学习中心代码"
  87. ></el-table-column>
  88. <el-table-column
  89. prop="orgName"
  90. width
  91. label="学习中心"
  92. ></el-table-column>
  93. <el-table-column prop="remark" width label="备注"></el-table-column>
  94. <el-table-column
  95. prop="updateTime"
  96. width
  97. label="操作时间"
  98. ></el-table-column>
  99. <el-table-column
  100. prop="updateName"
  101. width
  102. label="操作人"
  103. ></el-table-column>
  104. <el-table-column label="操作" width="300">
  105. <div slot-scope="scope">
  106. <el-button
  107. size="mini"
  108. type="primary"
  109. plain
  110. @click="editItem(scope.row)"
  111. >编辑</el-button
  112. >
  113. <el-button
  114. size="mini"
  115. type="danger"
  116. plain
  117. @click="deleteItem(scope.row)"
  118. >删除</el-button
  119. >
  120. </div>
  121. </el-table-column>
  122. </el-table>
  123. <div class="page pull-right">
  124. <el-pagination
  125. :current-page="currentPage"
  126. :page-size="pageSize"
  127. :page-sizes="[10, 20, 50, 100, 200, 300]"
  128. layout="total, sizes, prev, pager, next, jumper"
  129. :total="total"
  130. @current-change="handleCurrentChange"
  131. @size-change="handleSizeChange"
  132. ></el-pagination>
  133. </div>
  134. </div>
  135. </div>
  136. <el-dialog
  137. title="同步到考试"
  138. width="400px"
  139. :visible.sync="showSynchronousDialog"
  140. :close-on-click-modal="false"
  141. >
  142. <el-form
  143. ref="synchronousRef"
  144. :model="synchronousForm"
  145. :rules="synchronousRules"
  146. label-width="80px"
  147. >
  148. <el-form-item label="选择考试" prop="examId">
  149. <el-select
  150. v-model="synchronousForm.examId"
  151. filterable
  152. placeholder="请选择"
  153. >
  154. <el-option
  155. v-for="item in examList"
  156. :key="item.id"
  157. :label="item.name"
  158. :value="item.id"
  159. ></el-option>
  160. </el-select>
  161. <div style="font-size: 12px; color: #e6a23c">
  162. 提示:只能选择开启了IP特殊设置的考试
  163. </div>
  164. </el-form-item>
  165. </el-form>
  166. <div slot="footer">
  167. <el-button @click="showSynchronousDialog = false">取消</el-button>
  168. <el-button type="primary" @click="synchronousSubmit">同步</el-button>
  169. </div>
  170. </el-dialog>
  171. <el-dialog
  172. :title="curRow ? '修改' : '新增'"
  173. width="450px"
  174. :visible.sync="showEditDialog"
  175. :close-on-click-modal="false"
  176. @close="dialogBeforeClose"
  177. >
  178. <el-form
  179. ref="editRef"
  180. :model="editForm"
  181. :rules="editRules"
  182. label-width="80px"
  183. >
  184. <el-form-item label="学习中心" prop="orgId">
  185. <el-select
  186. v-model="editForm.orgId"
  187. filterable
  188. clearable
  189. placeholder="请选择"
  190. >
  191. <el-option
  192. v-for="item in orgList4Search"
  193. :key="item.id"
  194. :label="item.name + ' - ' + item.code"
  195. :value="item.id"
  196. ></el-option>
  197. </el-select>
  198. </el-form-item>
  199. <el-form-item label="IP/IP段" prop="ip">
  200. <div style="display: flex; align-items: center">
  201. <el-input v-model="editForm.ip" placeholder="请输入IP或者IP段" />
  202. <el-button
  203. type="primary"
  204. style="margin-left: 10px"
  205. @click="getIpAddress"
  206. >获取本机ip</el-button
  207. >
  208. </div>
  209. </el-form-item>
  210. <el-form-item label="备注" prop="remark">
  211. <el-input
  212. v-model="editForm.remark"
  213. type="textarea"
  214. maxlength="100"
  215. placeholder="请填写考点地址"
  216. />
  217. </el-form-item>
  218. </el-form>
  219. <div slot="footer">
  220. <el-button @click="showEditDialog = false">取消</el-button>
  221. <el-button type="primary" @click="editSubmit">保存</el-button>
  222. </div>
  223. </el-dialog>
  224. <!-- 导入弹窗 -->
  225. <el-dialog
  226. title="导入窗口"
  227. width="520px"
  228. :visible.sync="impDialog"
  229. :close-on-click-modal="false"
  230. >
  231. <el-form>
  232. <el-row>
  233. <el-form-item style="margin-left: 20px">
  234. <el-upload
  235. ref="upload"
  236. class="form_left"
  237. accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  238. :action="uploadAction"
  239. :headers="uploadHeaders"
  240. :data="uploadData"
  241. :before-upload="beforeUpload"
  242. :on-progress="uploadProgress"
  243. :on-success="uploadSuccess"
  244. :on-error="uploadError"
  245. :file-list="fileList"
  246. :auto-upload="false"
  247. :multiple="false"
  248. >
  249. <el-button
  250. slot="trigger"
  251. size="small"
  252. type="primary"
  253. icon="el-icon-search"
  254. >
  255. 选择文件
  256. </el-button>
  257. &nbsp;
  258. <el-button
  259. size="small"
  260. type="primary"
  261. icon="el-icon-check"
  262. @click="submitUpload"
  263. >
  264. 确认上传
  265. </el-button>
  266. <el-button
  267. size="small"
  268. type="primary"
  269. icon="el-icon-refresh"
  270. @click="removeFile"
  271. >
  272. 清空文件
  273. </el-button>
  274. <el-button
  275. size="small"
  276. type="primary"
  277. icon="el-icon-download"
  278. @click="exportFile"
  279. >
  280. 下载模板
  281. </el-button>
  282. <div slot="tip" class="el-upload__tip">只能上传xlsx文件</div>
  283. </el-upload>
  284. </el-form-item>
  285. </el-row>
  286. </el-form>
  287. </el-dialog>
  288. <!-- 导入错误信息列表 -->
  289. <el-dialog title="错误提示" :visible.sync="errDialog">
  290. <div
  291. v-for="errMessage in errMessages"
  292. :key="errMessage.lineNum"
  293. class="text-danger"
  294. >
  295. {{ errMessage }}
  296. </div>
  297. <span slot="footer" class="dialog-footer">
  298. <el-button @click="errDialog = false">确定</el-button>
  299. </span>
  300. </el-dialog>
  301. </div>
  302. </template>
  303. <script>
  304. import { CORE_API, EXAM_WORK_API } from "@/constants/constants.js";
  305. import { mapState } from "vuex";
  306. export default {
  307. name: "IpConfig",
  308. data() {
  309. return {
  310. rolePrivileges: {
  311. ORG_IP_CONFIG_SYNC: false,
  312. },
  313. examList: [],
  314. curRow: null,
  315. showSynchronousDialog: false,
  316. formSearch: {
  317. orgId: "",
  318. },
  319. getOrgList4SearchLoading: false,
  320. orgList4Search: [],
  321. tableData: [],
  322. currentPage: 1,
  323. pageSize: 10,
  324. total: 10,
  325. selectedIds: [],
  326. editForm: {
  327. orgId: "",
  328. ip: "",
  329. remark: "",
  330. },
  331. editRules: {
  332. orgId: [
  333. {
  334. required: true,
  335. message: "请选择学习中心",
  336. trigger: "blur",
  337. },
  338. ],
  339. ip: [
  340. {
  341. required: true,
  342. message: "请填写IP或IP段",
  343. trigger: "blur",
  344. },
  345. ],
  346. },
  347. synchronousForm: {
  348. examId: "",
  349. },
  350. synchronousRules: {
  351. examId: [
  352. {
  353. required: true,
  354. message: "请选择考试",
  355. trigger: "blur",
  356. },
  357. ],
  358. },
  359. showEditDialog: false,
  360. impDialog: false,
  361. uploadAction: EXAM_WORK_API + "/org/ip/import",
  362. uploadHeaders: {},
  363. uploadData: {},
  364. errMessages: [],
  365. errDialog: false,
  366. fileLoading: false,
  367. loading: false,
  368. fileList: [],
  369. };
  370. },
  371. computed: {
  372. ...mapState({ user: (state) => state.user }),
  373. },
  374. created() {
  375. this.initPrivileges();
  376. this.getOrgList4Search("");
  377. this.searchForm();
  378. this.getExamList();
  379. this.uploadHeaders = {
  380. key: this.user.key,
  381. token: this.user.token,
  382. };
  383. },
  384. methods: {
  385. initPrivileges() {
  386. let params = new URLSearchParams();
  387. params.append(
  388. "privilegeCodes",
  389. Object.keys(this.rolePrivileges).toString()
  390. );
  391. var url = CORE_API + "/rolePrivilege/checkPrivileges?" + params;
  392. this.$httpWithMsg.post(url).then((response) => {
  393. this.rolePrivileges = response.data;
  394. });
  395. },
  396. dialogBeforeClose() {
  397. this.$refs.editRef.clearValidate();
  398. },
  399. getExamList() {
  400. this.$httpWithMsg
  401. .get(
  402. EXAM_WORK_API +
  403. "/exam/queryByNameLike?enable=true&propertyKeys=IP_LIMIT&name=" +
  404. name
  405. )
  406. .then((response) => {
  407. this.examList = (response.data || []).filter((item) => {
  408. return item?.properties?.IP_LIMIT == "true";
  409. });
  410. });
  411. },
  412. getIpAddress() {
  413. // let year = new Date().getFullYear();
  414. // fetch(`https://${year}.ip138.com/`)
  415. // .then((x) => x.text())
  416. // .then((h) => {
  417. // let domParser = new DOMParser();
  418. // let doc = domParser.parseFromString(h, "text/html");
  419. // let text = doc?.querySelector("p")?.innerText?.trim();
  420. // let ip = text
  421. // .substring(text.indexOf("[") + 1, text.indexOf("]"))
  422. // .trim();
  423. // this.editForm.ip = ip;
  424. // });
  425. fetch("https://qifu-api.baidubce.com/ip/local/geo/v1/district")
  426. .then((res) => res.json())
  427. .then((res) => {
  428. this.editForm.ip = res?.ip || "";
  429. });
  430. },
  431. editSubmit() {
  432. this.$refs.editRef.validate((valid) => {
  433. if (valid) {
  434. let path = this.curRow ? "/org/ip/update" : "/org/ip/save";
  435. let url = EXAM_WORK_API + path;
  436. let data = {
  437. ...this.editForm,
  438. // ip: encodeURIComponent(this.editForm.ip),
  439. };
  440. if (this.curRow) {
  441. data.id = this.curRow.id;
  442. }
  443. this.$httpWithMsg
  444. .post(url, null, {
  445. params: data,
  446. })
  447. .then(() => {
  448. this.$notify({
  449. type: "success",
  450. message: (this.curRow ? "修改" : "新增") + "成功!",
  451. });
  452. this.resetPageAndSearchForm();
  453. this.showEditDialog = false;
  454. });
  455. }
  456. });
  457. },
  458. openSynchronousDialog() {
  459. this.synchronousForm.examId = "";
  460. this.showSynchronousDialog = true;
  461. },
  462. synchronousSubmit() {
  463. // this.showSynchronousDialog = false;
  464. this.$refs.synchronousRef.validate((valid) => {
  465. if (valid) {
  466. this.$httpWithMsg
  467. .post(EXAM_WORK_API + "/org/ip/exam/add", null, {
  468. params: { ...this.synchronousForm },
  469. })
  470. .then(() => {
  471. this.$notify({
  472. type: "success",
  473. message: "同步成功!",
  474. });
  475. this.resetPageAndSearchForm();
  476. this.showSynchronousDialog = false;
  477. });
  478. }
  479. });
  480. },
  481. getOrgList4Search(orgName) {
  482. this.getOrgList4SearchLoading = true;
  483. let url =
  484. CORE_API +
  485. "/org/query?rootOrgId=" +
  486. this.user.rootOrgId +
  487. "&name=" +
  488. orgName;
  489. this.$httpWithMsg
  490. .get(url)
  491. .then((response) => {
  492. this.getOrgList4SearchLoading = false;
  493. this.orgList4Search = response.data;
  494. })
  495. .catch((response) => {
  496. console.log(response);
  497. this.getOrgList4SearchLoading = false;
  498. });
  499. },
  500. resetPageAndSearchForm() {
  501. this.currentPage = 1;
  502. this.searchForm();
  503. },
  504. searchForm() {
  505. let url = EXAM_WORK_API + "/org/ip/page";
  506. this.$httpWithMsg
  507. .post(url, null, {
  508. params: {
  509. ...this.formSearch,
  510. pageNumber: this.currentPage,
  511. pageSize: this.pageSize,
  512. },
  513. headers: {
  514. "content-type": "application/x-www-form-urlencoded",
  515. },
  516. })
  517. .then((response) => {
  518. this.tableData = response.data.list;
  519. this.total = response.data.total;
  520. });
  521. },
  522. handleCurrentChange(val) {
  523. this.currentPage = val;
  524. this.searchForm();
  525. },
  526. handleSizeChange(val) {
  527. this.pageSize = val;
  528. this.searchForm();
  529. },
  530. editItem(item) {
  531. if (!item) {
  532. this.editForm = {
  533. orgId: "",
  534. ip: "",
  535. remark: "",
  536. };
  537. }
  538. if (item) {
  539. this.editForm.orgId = item?.orgId;
  540. this.editForm.ip = item?.ip;
  541. this.editForm.remark = item?.remark;
  542. }
  543. this.curRow = item;
  544. this.showEditDialog = true;
  545. },
  546. deleteItem(item) {
  547. console.log(item);
  548. this.deleteMult([item.id], true);
  549. },
  550. deleteMult(ids, bool) {
  551. let str = bool ? "是否删除该条数据?" : "是否删除所选数据?";
  552. this.$confirm(str, "提示", {
  553. confirmButtonText: "确定",
  554. cancelButtonText: "取消",
  555. type: "warning",
  556. }).then(() => {
  557. var url = EXAM_WORK_API + "/org/ip/delete";
  558. this.$httpWithMsg
  559. // .post(url, qs.stringify({ ids: ids.join(",") }), {
  560. .post(url, ids, {
  561. headers: {
  562. "content-type": "application/json",
  563. },
  564. })
  565. .then(() => {
  566. this.$notify({
  567. type: "success",
  568. message: "删除成功!",
  569. });
  570. this.searchForm();
  571. });
  572. });
  573. },
  574. selectChange(rows) {
  575. this.selectedIds = rows.map((item) => item.id);
  576. }, //导入
  577. impCourse() {
  578. this.impDialog = true;
  579. this.initUpload();
  580. },
  581. initUpload() {
  582. this.fileList = [];
  583. },
  584. beforeUpload(file) {
  585. console.log(file);
  586. },
  587. uploadProgress() {
  588. console.log("uploadProgress");
  589. },
  590. uploadSuccess(response) {
  591. if (!response.hasError) {
  592. this.$notify({
  593. message: "上传成功",
  594. type: "success",
  595. });
  596. this.fileLoading = false;
  597. this.impDialog = false;
  598. this.searchForm();
  599. } else {
  600. this.fileLoading = false;
  601. this.impDialog = false;
  602. this.errMessages = response.failRecords;
  603. this.errDialog = true;
  604. }
  605. },
  606. uploadError(response) {
  607. var json = JSON.parse(response.message);
  608. if (response.status == 500) {
  609. this.$notify({
  610. message: json.desc,
  611. type: "error",
  612. });
  613. }
  614. this.fileLoading = false;
  615. },
  616. //确定上传
  617. submitUpload() {
  618. if (!this.checkUpload()) {
  619. return false;
  620. }
  621. this.$refs.upload.submit();
  622. this.fileLoading = true;
  623. },
  624. checkUpload() {
  625. var fileList = this.$refs.upload.uploadFiles;
  626. if (fileList.length == 0) {
  627. this.$notify({
  628. message: "上传文件不能为空",
  629. type: "error",
  630. });
  631. return false;
  632. }
  633. if (fileList.length > 1) {
  634. this.$notify({
  635. message: "每次只能上传一个文件",
  636. type: "error",
  637. });
  638. return false;
  639. }
  640. for (let file of fileList) {
  641. if (!file.name.endsWith(".xlsx")) {
  642. this.$notify({
  643. message: "上传文件必须为xlsx格式",
  644. type: "error",
  645. });
  646. this.initUpload();
  647. return false;
  648. }
  649. }
  650. return true;
  651. },
  652. //清空文件
  653. removeFile() {
  654. // this.fileList = [];
  655. this.$refs.upload.clearFiles();
  656. },
  657. //下载模板
  658. exportFile() {
  659. window.location.href =
  660. EXAM_WORK_API +
  661. "/org/ip/template?$key=" +
  662. this.user.key +
  663. "&$token=" +
  664. this.user.token;
  665. },
  666. exportHandler() {
  667. var param = new URLSearchParams(this.formSearch);
  668. window.open(
  669. EXAM_WORK_API +
  670. "/org/ip/export" +
  671. "?$key=" +
  672. this.user.key +
  673. "&$token=" +
  674. this.user.token +
  675. "&" +
  676. param
  677. );
  678. },
  679. },
  680. };
  681. </script>
  682. <style lang="scss" scoped>
  683. .ip-config {
  684. margin-top: 0 !important;
  685. .el-dialog__body {
  686. .el-form-item {
  687. margin-bottom: 15px !important;
  688. :deep(.el-form-item__label) {
  689. margin-bottom: 2px !important;
  690. }
  691. }
  692. }
  693. }
  694. </style>