zhangjie 1 anno fa
parent
commit
9aa76eda2d

+ 27 - 2
src/api/projectParamsManagementPage.ts

@@ -16,7 +16,7 @@ export function getProjectCourseList(params: {
   );
 }
 
-/** 导入项目参数配置 */
+/** 导入科目分数线 */
 export function importProjectParams(projectId: number, file: File) {
   const f = new FormData();
   f.append("projectId", projectId + "");
@@ -24,7 +24,7 @@ export function importProjectParams(projectId: number, file: File) {
   return httpApp.post<any, ImportResponse>(`/api/ess/projectCourse/import`, f);
 }
 
-/** 导出项目参数配置 */
+/** 导出科目分数线 */
 export function exportProjectParams(projectId: number) {
   return httpApp
     .get(
@@ -38,6 +38,31 @@ export function exportProjectParams(projectId: number) {
       return responseToFile(response);
     });
 }
+/** 导入科目学院 */
+export function importProjectCollege(projectId: number, file: File) {
+  const f = new FormData();
+  f.append("projectId", projectId + "");
+  f.append("file", file);
+  return httpApp.post<any, ImportResponse>(
+    `/api/ess/projectCourse/college/import`,
+    f
+  );
+}
+
+/** 导出科目学院 */
+export function exportProjectCollege(projectId: number) {
+  return httpApp
+    .get(
+      `/api/ess/projectCourse/college/export?` +
+        new URLSearchParams({ projectId: projectId + "" }),
+      {
+        responseType: "blob",
+      }
+    )
+    .then(function (response) {
+      return responseToFile(response);
+    });
+}
 
 /** 更新科目 */
 export function updateProjectCourse(params: {

+ 2 - 2
src/features/allAnalysis/ScoreRate.vue

@@ -20,7 +20,7 @@
           <a-col :span="12">
             <a-table
               class="page-table"
-              :scroll="{ y: 453 }"
+              :scroll="{ y: 453, x: true }"
               :data-source="course.segements"
               :pagination="false"
               bordered
@@ -73,7 +73,7 @@
           <a-col :span="12">
             <a-table
               class="page-table"
-              :scroll="{ y: 235 }"
+              :scroll="{ y: 235, x: true }"
               :data-source="course.rangeSegements"
               :pagination="false"
               bordered

+ 17 - 7
src/features/courseManagement/CourseManagement.vue

@@ -154,7 +154,7 @@
           </a-upload>
         </a-form-item>
         <a-form-item label="下载模板">
-          <a-button @click="downloadTpl">
+          <a-button :loading="downLoading" @click="downloadTpl">
             <template #icon>
               <svg-icon name="download"></svg-icon>
             </template>
@@ -175,13 +175,12 @@ import {
   getCourseList,
   importCourses,
   toggleCourses,
-  updateCourse,
 } from "@/api/courseManagementPage";
 import { useMainStore } from "@/store";
 import { Course, Course_Type } from "@/types";
 import { downloadFileURL } from "@/utils/utils";
 import { message } from "ant-design-vue";
-import { watch, onMounted, ref, reactive, toRaw, h } from "vue";
+import { watch, onMounted, ref, h } from "vue";
 import type { UploadProps } from "ant-design-vue";
 import useLoading from "@/hooks/loading";
 import ModifyCourse from "./ModifyCourse.vue";
@@ -326,6 +325,8 @@ const handleRemove = () => {
 const { loading: importLoading, setLoading: setImportLoading } = useLoading();
 let importModalVisible = $ref<boolean>(false);
 async function handleImport() {
+  if (importLoading.value) return;
+
   if (!rootOrgId) {
     void message.error("请先选择结构");
     return;
@@ -334,14 +335,13 @@ async function handleImport() {
     void message.warn({ content: "请选择文件" });
     return;
   }
-  setImportLoading(true);
 
+  setImportLoading(true);
   const res = await importCourses(rootOrgId, fileList[0] as any).catch(
     () => {}
   );
-  if (!res) return;
-
   setImportLoading(false);
+  if (!res) return;
 
   if (!res.data.hasError) {
     importModalVisible = false;
@@ -366,8 +366,18 @@ async function handleExport() {
 
 let selectIds = $ref<number[]>([]);
 
+const { loading: downLoading, setLoading: setDownLoading } = useLoading();
 async function downloadTpl() {
-  await downloadFileURL("/api/ess/course/template");
+  if (downLoading.value) return;
+  setDownLoading(true);
+  let err = false;
+  await downloadFileURL("/api/ess/course/template").catch(() => {
+    err = true;
+  });
+  setDownLoading(false);
+  if (err) return;
+
+  message.success("下载成功!");
 }
 
 const handleRowSelect = (selectedRowKeys: (string | number)[]) => {

+ 2 - 2
src/features/courseManagement/ModifyCourse.vue

@@ -124,8 +124,8 @@ const rules: Record<string, Rule[]> = {
 /* confirm */
 const { loading, setLoading } = useLoading();
 async function confirm() {
-  const err = await formRef.value?.validate();
-  if (err) return;
+  const valid = await formRef.value?.validate().catch(() => {});
+  if (!valid) return;
 
   setLoading(true);
   const datas = objAssign(formData, {});

+ 1 - 1
src/features/paperAnalysis/QuestionBianPai.vue

@@ -4,7 +4,7 @@
       <a-table
         class="page-table"
         rowKey="id"
-        :scroll="{ y: 453 }"
+        :scroll="{ y: 453, x: true }"
         :columns="columns"
         :data-source="props.questions"
         :pagination="false"

+ 52 - 11
src/features/paperAnalysis/QuestionTypeDifficulty.vue

@@ -18,17 +18,31 @@
 
     <a-modal
       v-model:open="importModalVisible"
-      title="题型设置导入"
+      title="题型分布设置"
       okText="确定"
       cancelText="取消"
       @ok="handleImport"
     >
-      <a-form>
+      <a-form :labelCol="{ style: { width: '72px' } }">
         <a-form-item label="文件地址">
-          <input id="file-input" :multiple="false" type="file" />
+          <a-upload
+            accept=".xls,.xlsx"
+            :before-upload="beforeUpload"
+            :disabled="importLoading"
+            :maxCount="1"
+            @remove="handleRemove"
+          >
+            <a-button>
+              <template #icon>
+                <svg-icon name="file"></svg-icon>
+              </template>
+              选择文件
+            </a-button>
+          </a-upload>
         </a-form-item>
         <a-form-item label="下载模板">
-          <a-button class="download-tpl-btn" @click="downloadTpl">
+          <a-button :loading="downLoading" @click="downloadTpl">
+            <template #icon> <svg-icon name="download"></svg-icon> </template>
             下载模板
           </a-button>
         </a-form-item>
@@ -45,6 +59,8 @@ import { downloadFileURL } from "@/utils/utils";
 import { message } from "ant-design-vue";
 import { h } from "vue";
 import DifficultyDistriTable from "./DifficultyDistriTable.vue";
+import type { UploadProps } from "ant-design-vue";
+import useLoading from "@/hooks/loading";
 
 const props = defineProps<{
   questions: SASQuestionGroup[];
@@ -103,17 +119,32 @@ function checkQuestionExist(item: TypeItem) {
 }
 
 /** <handleImport> */
+let fileList = $ref<UploadProps["fileList"]>([]);
+const beforeUpload: UploadProps["beforeUpload"] = (file) => {
+  fileList = [file];
+  return false;
+};
+const handleRemove = () => {
+  fileList = [];
+};
+const { loading: importLoading, setLoading: setImportLoading } = useLoading();
 let importModalVisible = $ref<boolean>(false);
 async function handleImport() {
-  const files = (document.querySelector("#file-input") as HTMLInputElement)
-    .files;
-  const fileToImport = files && files[0];
-  if (!fileToImport) {
+  if (importLoading.value) return;
+
+  if (!fileList?.length) {
     void message.warn({ content: "请选择文件" });
     return;
   }
-  (document.querySelector("#file-input") as HTMLInputElement).value = "";
-  const res = await importQuestionGroups(props.projectId, fileToImport);
+
+  setImportLoading(true);
+  const res = await importQuestionGroups(
+    props.projectId,
+    fileList[0] as any
+  ).catch(() => {});
+  setImportLoading(false);
+  if (!res) return;
+
   if (!res.data.hasError) {
     importModalVisible = false;
     void message.success({ content: "导入成功" });
@@ -129,7 +160,17 @@ async function handleImport() {
 }
 /** </handleImport> */
 
+const { loading: downLoading, setLoading: setDownLoading } = useLoading();
 async function downloadTpl() {
-  await downloadFileURL("/api/ess/sasQuestionGroup/template");
+  if (downLoading.value) return;
+  setDownLoading(true);
+  let err = false;
+  await downloadFileURL("/api/ess/sasQuestionGroup/template").catch(() => {
+    err = true;
+  });
+  setDownLoading(false);
+  if (err) return;
+
+  message.success("下载成功!");
 }
 </script>

+ 51 - 11
src/features/paperAnalysis/QuestionTypeDiscrimination.vue

@@ -23,12 +23,26 @@
       cancelText="取消"
       @ok="handleImport"
     >
-      <a-form>
+      <a-form :labelCol="{ style: { width: '72px' } }">
         <a-form-item label="文件地址">
-          <input id="file-input" :multiple="false" type="file" />
+          <a-upload
+            accept=".xls,.xlsx"
+            :before-upload="beforeUpload"
+            :disabled="importLoading"
+            :maxCount="1"
+            @remove="handleRemove"
+          >
+            <a-button>
+              <template #icon>
+                <svg-icon name="file"></svg-icon>
+              </template>
+              选择文件
+            </a-button>
+          </a-upload>
         </a-form-item>
         <a-form-item label="下载模板">
-          <a-button class="download-tpl-btn" @click="downloadTpl">
+          <a-button :loading="downLoading" @click="downloadTpl">
+            <template #icon> <svg-icon name="download"></svg-icon> </template>
             下载模板
           </a-button>
         </a-form-item>
@@ -39,12 +53,13 @@
 
 <script setup lang="ts">
 import { importQuestionGroups } from "@/api/paperAnalysisPage";
-// import EventBus from "@/plugins/eventBus";
 import { SASQuestionGroup } from "@/types";
 import { downloadFileURL } from "@/utils/utils";
 import { message } from "ant-design-vue";
 import { h } from "vue";
 import DiscriminationDistriTable from "./DiscriminationDistriTable.vue";
+import type { UploadProps } from "ant-design-vue";
+import useLoading from "@/hooks/loading";
 
 const props = defineProps<{
   questions: SASQuestionGroup[];
@@ -103,17 +118,32 @@ function checkQuestionExist(item: TypeItem) {
 }
 
 /** <handleImport> */
+let fileList = $ref<UploadProps["fileList"]>([]);
+const beforeUpload: UploadProps["beforeUpload"] = (file) => {
+  fileList = [file];
+  return false;
+};
+const handleRemove = () => {
+  fileList = [];
+};
+const { loading: importLoading, setLoading: setImportLoading } = useLoading();
 let importModalVisible = $ref<boolean>(false);
 async function handleImport() {
-  const files = (document.querySelector("#file-input") as HTMLInputElement)
-    .files;
-  const fileToImport = files && files[0];
-  if (!fileToImport) {
+  if (importLoading.value) return;
+
+  if (!fileList?.length) {
     void message.warn({ content: "请选择文件" });
     return;
   }
-  (document.querySelector("#file-input") as HTMLInputElement).value = "";
-  const res = await importQuestionGroups(props.projectId, fileToImport);
+
+  setImportLoading(true);
+  const res = await importQuestionGroups(
+    props.projectId,
+    fileList[0] as any
+  ).catch(() => {});
+  setImportLoading(false);
+  if (!res) return;
+
   if (!res.data.hasError) {
     importModalVisible = false;
     void message.success({ content: "导入成功" });
@@ -129,7 +159,17 @@ async function handleImport() {
 }
 /** </handleImport> */
 
+const { loading: downLoading, setLoading: setDownLoading } = useLoading();
 async function downloadTpl() {
-  await downloadFileURL("/api/ess/sasQuestionGroup/template");
+  if (downLoading.value) return;
+  setDownLoading(true);
+  let err = false;
+  await downloadFileURL("/api/ess/sasQuestionGroup/template").catch(() => {
+    err = true;
+  });
+  setDownLoading(false);
+  if (err) return;
+
+  message.success("下载成功!");
 }
 </script>

+ 2 - 2
src/features/paperAnalysis/SelectProject.vue

@@ -7,13 +7,13 @@
     @ok="handleOk"
   >
     <a-form :labelCol="{ span: 4 }">
-      <a-form-item>
+      <a-form-item label="项目">
         <ProjectsSelect
           v-model:value="projectId"
           :rootOrgId="rootOrgId"
           :disableIds="props.disableIds"
           :max-count="4"
-          placeholder="请选择对比项目"
+          placeholder="最多选择4个项目"
         />
       </a-form-item>
     </a-form>

+ 36 - 12
src/features/projectDataManagement/ProjectDataManagement.vue

@@ -46,7 +46,7 @@
         <a-tab-pane key="数据上传" tab="数据上传">
           <a-form :labelCol="{ style: { width: '72px' } }">
             <a-form-item label="模板文件">
-              <a-button :loading="loading" @click="downloadTpl">
+              <a-button :loading="downLoading" @click="downloadTpl">
                 <template #icon>
                   <svg-icon name="download"></svg-icon>
                 </template>
@@ -55,9 +55,11 @@
             </a-form-item>
             <a-form-item label="文件地址">
               <a-upload
-                accept=".lic"
-                :before-upload="() => false"
-                :showUploadList="false"
+                accept=".xls,.xlsx"
+                :before-upload="beforeUpload"
+                :disabled="importLoading"
+                :maxCount="1"
+                @remove="handleRemove"
               >
                 <a-button>
                   <template #icon>
@@ -97,6 +99,7 @@ import QueryString from "qs";
 import { onMounted, computed, watch } from "vue";
 import { useRoute } from "vue-router";
 import useLoading from "@/hooks/loading";
+import type { UploadProps } from "ant-design-vue";
 
 const store = useMainStore();
 store.currentLocation = "项目列表 / 数据管理";
@@ -108,7 +111,6 @@ const projectId = +route.params.projectId;
 let data = $ref<Project[]>([]);
 
 let project = computed(() => data[0] || {});
-const { loading, setLoading } = useLoading();
 
 async function search() {
   await fetchData();
@@ -127,20 +129,42 @@ onMounted(async () => {
   await search();
 });
 
+const { loading: downLoading, setLoading: setDownLoading } = useLoading();
 async function downloadTpl() {
-  await downloadFileURL("/api/ess/project/template");
+  if (downLoading.value) return;
+  setDownLoading(true);
+  let err = false;
+  await downloadFileURL("/api/ess/project/template").catch(() => {
+    err = true;
+  });
+  setDownLoading(false);
+  if (err) return;
+
+  message.success("下载成功!");
 }
 
+let fileList = $ref<UploadProps["fileList"]>([]);
+const beforeUpload: UploadProps["beforeUpload"] = (file) => {
+  fileList = [file];
+  return false;
+};
+const handleRemove = () => {
+  fileList = [];
+};
+const { loading: importLoading, setLoading: setImportLoading } = useLoading();
 async function handleImport() {
-  const files = (document.querySelector("#file-input") as HTMLInputElement)
-    .files;
-  const fileToImport = files && files[0];
-  if (!fileToImport) {
+  if (!fileList?.length) {
     void message.warn({ content: "请选择文件" });
     return;
   }
-  (document.querySelector("#file-input") as HTMLInputElement).value = "";
-  await importProjectDataSource(projectId, fileToImport);
+  setImportLoading(true);
+  let err = false;
+  await importProjectDataSource(projectId, fileList[0] as any).catch(() => {
+    err = true;
+  });
+  setImportLoading(false);
+  if (err) return;
+
   void message.success({ content: "导入成功" });
 }
 

+ 143 - 0
src/features/projectManagement/ModifyProject.vue

@@ -0,0 +1,143 @@
+<template>
+  <a-modal v-model:open="visible" :title="title" :width="500">
+    <a-form
+      ref="formRef"
+      :labelCol="{ style: { width: '90px' } }"
+      :model="formData"
+      :rules="rules"
+    >
+      <a-form-item v-if="isEdit" label="科目Id">
+        <a-input v-model:value="formData.id" disabled></a-input>
+      </a-form-item>
+      <a-form-item label="年份" name="year">
+        <a-select
+          v-model:value="formData.year"
+          :options="years"
+          style="width: 120px"
+        ></a-select>
+      </a-form-item>
+      <a-form-item label="项目名称" name="name">
+        <a-input v-model:value="formData.name"></a-input>
+      </a-form-item>
+    </a-form>
+
+    <template #footer>
+      <a-button type="primary" :disabled="loading" @click="confirm"
+        >确认</a-button
+      >
+      <a-button @click="close">取消</a-button>
+    </template>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import useModal from "@/hooks/modal";
+import { nextTick, reactive, ref, watch } from "vue";
+import type { Rule, FormInstance } from "ant-design-vue/es/form";
+import useLoading from "@/hooks/loading";
+import { updateProject } from "@/api/projectManagementPage";
+import { message } from "ant-design-vue";
+import { objAssign, objModifyAssign } from "@/utils/utils";
+import { Project } from "@/types";
+import { useMainStore } from "@/store";
+
+const store = useMainStore();
+
+/* modal */
+const { visible, open, close } = useModal();
+defineExpose({ open, close });
+
+const props = defineProps<{
+  rowData: Project;
+}>();
+const emit = defineEmits(["modified"]);
+
+const isEdit = $computed(() => !!props.rowData.id);
+const title = $computed(() => `${isEdit ? "编辑" : "新增"}项目`);
+
+const defaultFormData = {
+  id: undefined,
+  code: "",
+  name: "",
+  year: new Date().getFullYear(),
+  enable: true,
+  type: undefined,
+  rootOrgId: store.userInfo.rootOrgId,
+};
+type FormDataType = typeof defaultFormData;
+
+const formRef = ref<FormInstance>();
+const formData = reactive<FormDataType>({ ...defaultFormData });
+const rules: Record<string, Rule[]> = {
+  name: [
+    {
+      required: true,
+      message: "请输入项目名称",
+    },
+    {
+      max: 30,
+      message: "最多30个字符",
+    },
+  ],
+  year: [
+    {
+      required: true,
+      message: "请选择年份",
+    },
+  ],
+};
+const years = ref<Array<{ value: number; label: number }>>([]);
+function getYears() {
+  const startYear = 2010;
+  const count = 50;
+  const datas = [];
+  for (let index = 0; index < count; index++) {
+    const year = startYear + index;
+    datas[index] = {
+      value: year,
+      label: year,
+    };
+  }
+  years.value = datas;
+}
+getYears();
+
+/* confirm */
+const { loading, setLoading } = useLoading();
+async function confirm() {
+  const valid = await formRef.value?.validate().catch(() => {});
+  if (!valid) return;
+
+  setLoading(true);
+  const datas = objAssign(formData, {});
+  const res = await updateProject(datas).catch(() => false);
+  setLoading(false);
+  if (!res) return;
+  message.success("修改成功!");
+  emit("modified", datas);
+  close();
+}
+
+// init modal
+watch(
+  () => visible.value,
+  (val) => {
+    if (!val) {
+      formRef.value?.clearValidate();
+      return;
+    }
+    nextTick(() => {
+      modalBeforeOpen();
+    });
+  },
+  { immediate: true }
+);
+
+function modalBeforeOpen() {
+  if (props.rowData.id) {
+    objModifyAssign(formData, props.rowData);
+  } else {
+    objModifyAssign(formData, defaultFormData);
+  }
+}
+</script>

+ 20 - 56
src/features/projectManagement/ProjectManagement.vue

@@ -19,7 +19,7 @@
         class="part-action"
         :size="6"
       >
-        <a-button type="text" @click="newProject">
+        <a-button type="text" @click="toAdd">
           <template #icon> <svg-icon name="add"></svg-icon> </template>新增
         </a-button>
         <a-divider type="vertical" />
@@ -70,7 +70,7 @@
               <a-button
                 v-if="store.isGreaterThanEqualRootOrgAdmin"
                 type="text"
-                @click="showModal(record)"
+                @click="toEdit(record)"
                 >编辑</a-button
               >
               <a-button
@@ -158,30 +158,6 @@
       </a-table>
     </div>
 
-    <a-modal
-      v-model:open="visible"
-      title="项目信息页"
-      okText="确定"
-      cancelText="取消"
-      :width="438"
-      @ok="handleOk"
-    >
-      <a-form :labelCol="{ span: 4 }">
-        <a-form-item v-show="projectObj.id" label="项目id">
-          <a-input
-            v-model:value="projectObj.id"
-            :disabled="!!projectObj.id"
-          ></a-input>
-        </a-form-item>
-        <a-form-item label="年份">
-          <a-input v-model:value="projectObj.year" :maxlength="4"></a-input>
-        </a-form-item>
-        <a-form-item label="项目名称">
-          <a-input v-model:value="projectObj.name" :maxlength="50"></a-input>
-        </a-form-item>
-      </a-form>
-    </a-modal>
-
     <a-modal
       v-model:open="showRestartModalVisible"
       title="重新计算"
@@ -201,6 +177,13 @@
         </li>
       </ul>
     </a-modal>
+
+    <!-- ModifyProject -->
+    <ModifyProject
+      ref="modifyProjectRef"
+      :row-data="curRow"
+      @modified="search"
+    />
   </div>
 </template>
 
@@ -211,13 +194,13 @@ import {
   getProjectList,
   logsOfProject,
   restartProject,
-  updateProject,
 } from "@/api/projectManagementPage";
 import router from "@/router";
 import { useMainStore } from "@/store";
 import { Project } from "@/types";
 import { message, Modal } from "ant-design-vue";
 import { watch, onMounted, ref, reactive, toRaw } from "vue";
+import ModifyProject from "./ModifyProject.vue";
 
 const store = useMainStore();
 store.currentLocation = "";
@@ -321,35 +304,16 @@ onMounted(async () => {
   await search();
 });
 
-const visible = ref<boolean>(false);
-
-const showModal = (record: Project) => {
-  Object.assign(projectObj, record);
-  visible.value = true;
-};
-
-const handleOk = async () => {
-  await updateProject(toRaw(projectObj));
-  visible.value = false;
-  await search();
-  void message.success({ content: "操作成功" });
-};
-
-const initProject = <Project>(<unknown>{
-  id: undefined,
-  code: "",
-  name: "",
-  year: "",
-  enable: true,
-  type: undefined,
-  rootOrgId: store.userInfo.rootOrgId,
-});
-const projectObj = reactive({ ...initProject });
-
-const newProject = () => {
-  Object.assign(projectObj, initProject);
-  showModal(projectObj);
-};
+const modifyProjectRef = ref();
+const curRow = ref<Project>({} as Project);
+function toAdd() {
+  curRow.value = {} as Project;
+  modifyProjectRef.value?.open();
+}
+function toEdit(row: Project) {
+  curRow.value = row;
+  modifyProjectRef.value?.open();
+}
 
 function checkEmpty(selectIds: number[]): boolean {
   if (selectIds && selectIds.length > 0) {

+ 134 - 0
src/features/projectParamsManagement/ImportCollege.vue

@@ -0,0 +1,134 @@
+<template>
+  <a-modal
+    v-model:open="visible"
+    title="科目学院属性"
+    :width="438"
+    okText="确定"
+    cancelText="取消"
+    @ok="handleImport"
+  >
+    <a-form :labelCol="{ style: { width: '72px' } }">
+      <a-form-item label="批量导出">
+        <a-button :loading="exportLoading" @click="toExport">
+          <template #icon> <svg-icon name="download"></svg-icon> </template>
+          导出
+        </a-button>
+      </a-form-item>
+      <a-form-item label="批量导入">
+        <a-upload
+          accept=".xls,.xlsx"
+          :before-upload="beforeUpload"
+          :disabled="importLoading"
+          :maxCount="1"
+          @remove="handleRemove"
+        >
+          <a-button>
+            <template #icon>
+              <svg-icon name="file"></svg-icon>
+            </template>
+            导入
+          </a-button>
+        </a-upload>
+      </a-form-item>
+      <a-form-item label="下载模板">
+        <a-button :loading="downLoading" @click="downloadTpl">
+          <template #icon> <svg-icon name="download"></svg-icon> </template>
+          下载模板
+        </a-button>
+      </a-form-item>
+    </a-form>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import useModal from "@/hooks/modal";
+import {
+  exportProjectCollege,
+  importProjectCollege,
+} from "@/api/projectParamsManagementPage";
+import { downloadFileURL } from "@/utils/utils";
+import { h } from "vue";
+import { message } from "ant-design-vue";
+import type { UploadProps } from "ant-design-vue";
+import useLoading from "@/hooks/loading";
+
+/* modal */
+const { visible, open, close } = useModal();
+defineExpose({ open, close });
+
+const props = defineProps<{
+  projectId: number;
+}>();
+const emit = defineEmits(["modified"]);
+
+let fileList = $ref<UploadProps["fileList"]>([]);
+const beforeUpload: UploadProps["beforeUpload"] = (file) => {
+  fileList = [file];
+  return false;
+};
+const handleRemove = () => {
+  fileList = [];
+};
+const { loading: importLoading, setLoading: setImportLoading } = useLoading();
+async function handleImport() {
+  if (importLoading.value) return;
+
+  if (!fileList?.length) {
+    void message.warn({ content: "请选择文件" });
+    return;
+  }
+
+  setImportLoading(true);
+  const res = await importProjectCollege(
+    props.projectId,
+    fileList[0] as any
+  ).catch(() => {});
+  setImportLoading(false);
+  if (!res) return;
+
+  if (!res.data.hasError) {
+    void message.success({ content: "导入成功" });
+    emit("modified");
+    close();
+  } else {
+    // eslint-disable-next-line
+    const msg = res.data.failRecords.map((v) =>
+      h("div", `行号:${v.lineNum}, 错误:${v.msg}`)
+    );
+    void message.error({
+      content: h("span", ["导入失败", ...msg]),
+    });
+  }
+}
+
+/* download */
+const { loading: exportLoading, setLoading: setExportLoading } = useLoading();
+async function toExport() {
+  if (downLoading.value) return;
+  setExportLoading(true);
+  let err = false;
+  await exportProjectCollege(props.projectId).catch(() => {
+    err = true;
+  });
+  setExportLoading(false);
+  if (err) return;
+
+  message.success("导出成功!");
+}
+
+const { loading: downLoading, setLoading: setDownLoading } = useLoading();
+async function downloadTpl() {
+  if (downLoading.value) return;
+  setDownLoading(true);
+  let err = false;
+  await downloadFileURL("/api/ess/projectCourse/college/template", {
+    projectId: props.projectId,
+  }).catch(() => {
+    err = true;
+  });
+  setDownLoading(false);
+  if (err) return;
+
+  message.success("下载成功!");
+}
+</script>

+ 132 - 0
src/features/projectParamsManagement/ImportScoreLine.vue

@@ -0,0 +1,132 @@
+<template>
+  <a-modal
+    v-model:open="visible"
+    title="科目分数线"
+    :width="438"
+    okText="确定"
+    cancelText="取消"
+    @ok="handleImport"
+  >
+    <a-form :labelCol="{ style: { width: '72px' } }">
+      <a-form-item label="批量导出">
+        <a-button :loading="exportLoading" @click="toExport">
+          <template #icon> <svg-icon name="download"></svg-icon> </template>
+          导出
+        </a-button>
+      </a-form-item>
+      <a-form-item label="批量导入">
+        <a-upload
+          accept=".xls,.xlsx"
+          :before-upload="beforeUpload"
+          :disabled="importLoading"
+          :maxCount="1"
+          @remove="handleRemove"
+        >
+          <a-button>
+            <template #icon>
+              <svg-icon name="file"></svg-icon>
+            </template>
+            导入
+          </a-button>
+        </a-upload>
+      </a-form-item>
+      <a-form-item label="下载模板">
+        <a-button :loading="downLoading" @click="downloadTpl">
+          <template #icon> <svg-icon name="download"></svg-icon> </template>
+          下载模板
+        </a-button>
+      </a-form-item>
+    </a-form>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import useModal from "@/hooks/modal";
+import {
+  exportProjectParams,
+  importProjectParams,
+} from "@/api/projectParamsManagementPage";
+import { downloadFileURL } from "@/utils/utils";
+import { h } from "vue";
+import { message } from "ant-design-vue";
+import type { UploadProps } from "ant-design-vue";
+import useLoading from "@/hooks/loading";
+
+/* modal */
+const { visible, open, close } = useModal();
+defineExpose({ open, close });
+
+const props = defineProps<{
+  projectId: number;
+}>();
+const emit = defineEmits(["modified"]);
+
+let fileList = $ref<UploadProps["fileList"]>([]);
+const beforeUpload: UploadProps["beforeUpload"] = (file) => {
+  fileList = [file];
+  return false;
+};
+const handleRemove = () => {
+  fileList = [];
+};
+const { loading: importLoading, setLoading: setImportLoading } = useLoading();
+async function handleImport() {
+  if (importLoading.value) return;
+
+  if (!fileList?.length) {
+    void message.warn({ content: "请选择文件" });
+    return;
+  }
+
+  setImportLoading(true);
+  const res = await importProjectParams(
+    props.projectId,
+    fileList[0] as any
+  ).catch(() => {});
+  setImportLoading(false);
+  if (!res) return;
+
+  if (!res.data.hasError) {
+    void message.success({ content: "导入成功" });
+    emit("modified");
+    close();
+  } else {
+    // eslint-disable-next-line
+    const msg = res.data.failRecords.map((v) =>
+      h("div", `行号:${v.lineNum}, 错误:${v.msg}`)
+    );
+    void message.error({
+      content: h("span", ["导入失败", ...msg]),
+    });
+  }
+}
+
+/* download */
+const { loading: exportLoading, setLoading: setExportLoading } = useLoading();
+async function toExport() {
+  if (downLoading.value) return;
+  setExportLoading(true);
+  let err = false;
+  await exportProjectParams(props.projectId).catch(() => {
+    err = true;
+  });
+  setExportLoading(false);
+  if (err) return;
+
+  message.success("导出成功!");
+}
+
+const { loading: downLoading, setLoading: setDownLoading } = useLoading();
+async function downloadTpl() {
+  if (downLoading.value) return;
+  setDownLoading(true);
+  let err = false;
+  await downloadFileURL("/api/ess/projectCourse/template").catch(() => {
+    err = true;
+  });
+  setDownLoading(false);
+  if (err) return;
+
+  message.success("下载成功!");
+}
+</script>

+ 27 - 71
src/features/projectParamsManagement/ProjectParamsManagement.vue

@@ -13,14 +13,13 @@
 
     <div class="part-box">
       <a-space class="part-action" :size="6">
-        <!-- TODO:上传下载 -->
-        <a-button type="text" @click="importModalVisible = true">
+        <a-button type="text" @click="toImportScoreLine">
           <template #icon>
             <svg-icon name="course-score"></svg-icon>
           </template>
           科目分数线
         </a-button>
-        <a-button type="text" @click="handleExport">
+        <a-button type="text" @click="toImportCollege">
           <template #icon>
             <svg-icon name="course-college"></svg-icon>
           </template>
@@ -114,40 +113,32 @@
       </a-form>
     </a-modal>
 
-    <a-modal
-      v-model:open="importModalVisible"
-      title="单科线批量导入"
-      okText="确定"
-      cancelText="取消"
-      @ok="handleImport"
-    >
-      <a-form>
-        <a-form-item label="文件地址">
-          <input id="file-input" :multiple="false" type="file" />
-        </a-form-item>
-        <a-form-item label="下载模板">
-          <a-button class="download-tpl-btn" @click="downloadTpl"
-            >下载模板</a-button
-          >
-        </a-form-item>
-      </a-form>
-    </a-modal>
+    <ImportScoreLine
+      ref="importScoreLineRef"
+      :project-id="projectId"
+      @modified="search"
+    />
+    <ImportCollege
+      ref="importCollegeRef"
+      :project-id="projectId"
+      @modified="search"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
 import {
-  exportProjectParams,
   getProjectCourseList,
-  importProjectParams,
   updateProjectCourse,
 } from "@/api/projectParamsManagementPage";
 import { useMainStore } from "@/store";
 import { ProjectCourse } from "@/types";
-import { downloadFileURL, goBack } from "@/utils/utils";
+import { goBack } from "@/utils/utils";
 import { message } from "ant-design-vue";
-import { watch, onMounted, ref, reactive, toRaw, h } from "vue";
+import { watch, onMounted, ref, reactive, toRaw } from "vue";
 import { useRoute } from "vue-router";
+import ImportScoreLine from "./ImportScoreLine.vue";
+import ImportCollege from "./ImportCollege.vue";
 
 const store = useMainStore();
 store.currentLocation = "项目列表 / 参数配置";
@@ -192,6 +183,10 @@ const columns = [
     title: "科目",
     dataIndex: "course",
   },
+  {
+    title: "学院",
+    dataIndex: "college",
+  },
   {
     title: "起始计算分",
     dataIndex: "startScore",
@@ -204,18 +199,6 @@ const columns = [
     title: "复试科目线",
     dataIndex: "retestScore",
   },
-  // {
-  //   title: "国家总分线",
-  //   dataIndex: "nationalTotalScore",
-  // },
-  // {
-  //   title: "复试总分线",
-  //   dataIndex: "retestTotalScore",
-  // },
-  // {
-  //   title: "总分满分线",
-  //   dataIndex: "totalScoreLine",
-  // },
   {
     title: "操作",
     dataIndex: "action",
@@ -224,7 +207,6 @@ const columns = [
 ];
 
 onMounted(async () => {
-  // rootOrgId = store.userInfo.rootOrgId;
   await search();
 });
 
@@ -258,39 +240,13 @@ const initProject = <ProjectCourse>(<unknown>{
 const projectObj = reactive({ ...initProject });
 
 /** <handleImport> */
-let importModalVisible = $ref<boolean>(false);
-async function handleImport() {
-  const files = (document.querySelector("#file-input") as HTMLInputElement)
-    .files;
-  const fileToImport = files && files[0];
-  if (!fileToImport) {
-    void message.warn({ content: "请选择文件" });
-    return;
-  }
-  (document.querySelector("#file-input") as HTMLInputElement).value = "";
-  const res = await importProjectParams(projectId, fileToImport);
-  if (!res.data.hasError) {
-    importModalVisible = false;
-    void message.success({ content: "导入成功" });
-    await clickSearch();
-  } else {
-    // eslint-disable-next-line
-    const msg = res.data.failRecords.map((v) =>
-      h("div", `行号:${v.lineNum}, 错误:${v.msg}`)
-    );
-    void message.error({
-      content: h("span", ["导入失败", ...msg]),
-    });
-  }
+const importScoreLineRef = ref();
+function toImportScoreLine() {
+  importScoreLineRef.value?.open();
 }
-/** </handleImport> */
-
-async function handleExport() {
-  await exportProjectParams(projectId);
-  void message.success({ content: "导出成功" });
-}
-
-async function downloadTpl() {
-  await downloadFileURL("/api/ess/projectCourse/template");
+const importCollegeRef = ref();
+function toImportCollege() {
+  importCollegeRef.value?.open();
 }
+/** </handleImport> */
 </script>

+ 7 - 7
src/features/report/ReportCompare.vue

@@ -18,8 +18,8 @@
         </div>
         <table class="table summary-table">
           <colgroup>
-            <col style="width: 113px" />
-            <col style="width: 47px" />
+            <col style="width: 80px" />
+            <col style="width: 80px" />
             <col style="width: 47px" />
             <col style="width: 64px" />
             <col style="width: 64px" />
@@ -32,8 +32,8 @@
           </colgroup>
           <thead>
             <tr>
-              <th>科目名称</th>
-              <th>科目<br />代码</th>
+              <th>年份</th>
+              <th>科目代码</th>
               <th>满分</th>
               <th>最高分</th>
               <th>起始<br />计算分</th>
@@ -47,8 +47,8 @@
           </thead>
           <tbody>
             <tr v-for="item in props.comparePapers" :key="item.id">
-              <td class="line-text" style="max-width: 150px">
-                {{ item.courseName }}
+              <td>
+                {{ item.year }}
               </td>
               <td>{{ item.courseCode }}</td>
               <td>{{ item.totalScore }}</td>
@@ -279,7 +279,7 @@ function countChartOption() {
       },
       xAxis: {
         type: "category",
-        data: props.comparePapers.map((v) => v.projectName),
+        data: props.comparePapers.map((v) => v.year),
         axisLine: {
           show: false,
         },

+ 3 - 5
src/features/report/ReportMain.vue

@@ -118,10 +118,10 @@ let partNos = $shallowRef({
 });
 
 const hasCompareProject = computed(() => {
-  const pCount = compareProjectId?.length;
+  const pCount = compareProjectId?.length + 1;
 
   return (
-    !!pCount &&
+    !!compareProjectId?.length &&
     comparePapers.length === pCount &&
     compareCourses.length === pCount
   );
@@ -138,8 +138,6 @@ onMounted(async () => {
       projectId = +route.params.projectId;
       paperId = +route.params.paperId;
       compareProjectId = props.compareProjectId as number[];
-      console.log(compareProjectId);
-
       viewType = route.params.viewType as string;
 
       if (viewType !== "frame") {
@@ -229,7 +227,7 @@ async function fetchData() {
     await fetchCompareCourseData();
   }
 
-  const pCount = compareProjectId.length;
+  const pCount = compareProjectId.length + 1;
 
   if (
     !compareProjectId.length ||

+ 1 - 0
src/features/subOrg/SubOrg.vue

@@ -324,6 +324,7 @@ async function handleImport() {
 /** </handleImport> */
 
 async function handleExport() {
+  // @ts-ignore
   await exportOrgs({ rootOrgId, name, code, enable });
   void message.success({ content: "导出成功" });
 }

+ 2 - 2
src/features/userManagement/ModifyPwd.vue

@@ -67,8 +67,8 @@ const rules: Record<keyof FormDataType, Rule[]> = {
 /* confirm */
 const { loading, setLoading } = useLoading();
 async function confirm() {
-  const err = await formRef.value?.validate();
-  if (err) return;
+  const valid = await formRef.value?.validate().catch(() => {});
+  if (!valid) return;
 
   setLoading(true);
 

+ 2 - 2
src/features/userManagement/ModifyUser.vue

@@ -137,8 +137,8 @@ const rules: Record<string, Rule[]> = {
 /* confirm */
 const { loading, setLoading } = useLoading();
 async function confirm() {
-  const err = await formRef.value?.validate();
-  if (err) return;
+  const valid = await formRef.value?.validate().catch(() => {});
+  if (!valid) return;
 
   setLoading(true);
   const datas = objAssign(formData, {});

+ 15 - 4
src/features/userManagement/UserManagement.vue

@@ -147,7 +147,7 @@
           </a-upload>
         </a-form-item>
         <a-form-item label="下载模板">
-          <a-button @click="downloadTpl">
+          <a-button :loading="downLoading" @click="downloadTpl">
             <template #icon>
               <svg-icon name="download"></svg-icon>
             </template>
@@ -343,6 +343,8 @@ const handleRemove = () => {
 const { loading: importLoading, setLoading: setImportLoading } = useLoading();
 let importModalVisible = $ref<boolean>(false);
 async function handleImport() {
+  if (importLoading.value) return;
+
   if (!rootOrgId) {
     void message.error("请先选择结构");
     return;
@@ -353,11 +355,10 @@ async function handleImport() {
   }
 
   setImportLoading(true);
-
   const res = await importUsers(rootOrgId, fileList[0] as any).catch(() => {});
+  setImportLoading(false);
   if (!res) return;
 
-  setImportLoading(false);
   if (!res.data.hasError) {
     importModalVisible = false;
     void message.success({ content: "导入成功" });
@@ -385,8 +386,18 @@ async function handleExport() {
 
 let selectIds = $ref<number[]>([]);
 
+const { loading: downLoading, setLoading: setDownLoading } = useLoading();
 async function downloadTpl() {
-  await downloadFileURL("/api/ess/user/template");
+  if (downLoading.value) return;
+  setDownLoading(true);
+  let err = false;
+  await downloadFileURL("/api/ess/user/template").catch(() => {
+    err = true;
+  });
+  setDownLoading(false);
+  if (err) return;
+
+  message.success("下载成功!");
 }
 
 const handleRowSelect = (selectedRowKeys: (string | number)[]) => {

+ 3 - 0
src/styles/ant-custom.less

@@ -124,4 +124,7 @@
       }
     }
   }
+  .ant-table-body {
+    overflow: auto !important;
+  }
 }

+ 1 - 1
src/types/index.ts

@@ -171,7 +171,7 @@ export interface Project {
   creator: string;
   id: number;
   name: string;
-  year: string;
+  year: number;
   needCompute: boolean;
   rootOrgCode: string;
   rootOrgName: string;

+ 2 - 2
vite.config.ts

@@ -11,8 +11,8 @@ const resolvePath = (...args) => {
 
 // const SERVER_URL = "http://192.168.10.108:7180";
 // const SERVER_URL = "http://192.168.10.138:13800";
-// const SERVER_URL = "http://192.168.10.39:7180";
-const SERVER_URL = "http://192.168.10.106:7100";
+const SERVER_URL = "http://192.168.10.39:7100";
+// const SERVER_URL = "http://192.168.10.106:7100";
 
 // https://vitejs.dev/config/
 export default defineConfig({