Bladeren bron

feat: test-超管

zhangjie 17 uur geleden
bovenliggende
commit
d2de74eb18

+ 16 - 2
src/api/admin.ts

@@ -1,5 +1,6 @@
 import axios, { AxiosResponse } from 'axios';
 import {
+  SchoolItem,
   SchoolListPageParam,
   SchoolListPageRes,
   SchoolUpdateParam,
@@ -18,6 +19,10 @@ export function schoolListPage(
 ): Promise<SchoolListPageRes> {
   return axios.post('/api/admin/sys/school/list', params);
 }
+// 学校信息查询
+export function getSchoolInfo(schoolId: number): Promise<SchoolItem> {
+  return axios.post('/api/admin/sys/school/info', { schoolId });
+}
 // 学校信息编辑
 export function updateSchool(
   datas: SchoolUpdateParam
@@ -50,12 +55,17 @@ export function getSchoolStatusInfo(): Promise<SchoolStatusInfo> {
 // 导出拆分科目模板
 export function splitCourseTemplate(): Promise<AxiosResponse<blob>> {
   return axios.post(
-    '/api/admin/school/exportSplitCourseTemplate',
+    '/api/admin/subject/split/template',
     {},
     { responseType: 'blob' }
   );
 }
 
+// 重置密钥
+export function resetSchoolSecret(schoolId: number): Promise<CommonActionRes> {
+  return axios.post('/api/admin/sys/school/rest-access', { schoolId });
+}
+
 // 授权管理
 // 授权管理信息
 export function getAuthInfo(): Promise<AuthInfo> {
@@ -65,7 +75,11 @@ export function getAuthInfo(): Promise<AuthInfo> {
 export function updateAuthInfo(
   datas: AuthUpdateParams
 ): Promise<CommonActionRes> {
-  return axios.post('/api/admin/sys/auth/update', datas);
+  return axios.post('/api/admin/sys/auth/update', datas, {
+    headers: {
+      'Content-Type': 'multipart/form-data',
+    },
+  });
 }
 
 // 导出硬件信息

+ 5 - 0
src/api/exam.ts

@@ -15,6 +15,11 @@ export function getExamList(
 ): Promise<ExamListPageRes> {
   return axios.post('/api/admin/exam/query', params);
 }
+// 拆分科目的考试查询
+export function getSplitCourseExamList(schoolId: number): Promise<ExamItem[]> {
+  return axios.post('/api/admin/subject/split/exam', { schoolId });
+}
+
 // 考试详情
 export function getExamDetail(examId: number): Promise<ExamItem> {
   return axios.post('/api/admin/exam/find', { examId });

+ 16 - 12
src/api/types/admin.ts

@@ -38,13 +38,15 @@ export type SchoolListPageRes = PageResult<SchoolItem>;
 export type SchoolUpdateParam = Partial<SchoolItem>;
 
 export interface SchoolUpdateAdminParam {
-  id: number;
+  id?: number;
   // 管理员登录名
   loginName: string;
   // 管理员名称
   name: string;
   // 密码
   password: string;
+  // 学校id
+  schoolId: number;
 }
 
 export interface SchoolStatusInfo {
@@ -86,28 +88,30 @@ export interface SettingUpdateParam {
 // auth
 export interface AuthInfo {
   // 是否授权
-  auth: boolean;
+  authText: string;
   // 过期时间
-  expireTime: string | null;
+  expireTime: string;
   // 授权模式
-  authMode: string;
+  typeText: string;
   // 是否开始双评
-  doubleTrack: boolean;
+  doubleTrackText: string;
   // 开启删除分组警告
-  groupDeleteWarn: boolean;
+  groupDeleteWarnText: string;
   // 试评模式
-  trialMode: string;
+  trialModeText: string;
   // 支持客观题卡
-  yjsObjectiveEnable: boolean;
+  objectiveEnableText: string;
+  // 版本
+  version: string;
 }
 
 export interface AuthUpdateParams {
   // 授权模式
-  authMode: 'offline' | 'online' | '';
+  type: 'ONLINE' | 'OFFLINE';
   // 授权文件
-  offlineFile: File | null;
+  file?: File | null;
   // 访问key
-  accessKey: string;
+  accessKey?: string;
   // 访问secret
-  accessSecret: string;
+  accessSecret?: string;
 }

+ 5 - 2
src/api/types/user.ts

@@ -139,7 +139,7 @@ export interface RoleItem {
   // 更新时间
   updateTime: string;
   // 更新人
-  updateName: string;
+  updaterName: string;
 }
 export type RoleListPageRes = PageResult<RoleItem>;
 export interface RoleListFilter {
@@ -161,12 +161,15 @@ export interface RolePrivilegeItem {
   // 父级权限编码
   parentCode: string;
 }
+export type RolePrivilegeTreeItem = RolePrivilegeItem & {
+  children: RolePrivilegeTreeItem[];
+};
 export interface RolePrivilegeUpdateParam {
   // 角色编码
   role: string;
   schoolId: number;
   // 选中的权限编码
-  selectPrivileges: string[];
+  selectPrivileges: string;
 }
 
 export interface UserMenuItem {

+ 1 - 1
src/assets/style/element-custom.scss

@@ -67,7 +67,7 @@
   }
 
   .el-pagination__sizes .el-select {
-    width: 100px;
+    width: 120px;
   }
   .el-pagination__sizes .el-select__wrapper {
     border-radius: 6px;

+ 1 - 0
src/assets/style/element.scss

@@ -18,3 +18,4 @@
 @use 'element-plus/theme-chalk/src/dialog.scss';
 @use 'element-plus/theme-chalk/src/alert.scss';
 @use 'element-plus/theme-chalk/src/notification.scss';
+@use 'element-plus/theme-chalk/src/tree.scss';

+ 38 - 0
src/assets/style/pages.scss

@@ -352,3 +352,41 @@
     }
   }
 }
+
+.modify-role-privilege-dialog {
+  .el-dialog {
+    margin-bottom: 0;
+    height: 100%;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+
+    .el-dialog__body {
+      flex: 1;
+      overflow: auto;
+
+      display: flex;
+      flex-direction: column;
+
+      .privilege-title {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 15px;
+      }
+
+      .privilege-title h4 {
+        margin: 0;
+        color: #303133;
+      }
+
+      .privilege-tree {
+        border: 1px solid #dcdfe6;
+        border-radius: 4px;
+        padding: 10px;
+        overflow-y: auto;
+        flex: 1;
+      }
+    }
+  }
+}

+ 16 - 10
src/components/import-dialog/index.vue

@@ -42,6 +42,7 @@
           :on-change="handleFileChange"
           :on-error="handleError"
           :on-success="handleSuccess"
+          :on-remove="handleRemove"
           :limit="1"
         >
           <el-icon class="el-icon--upload"><upload-filled /></el-icon>
@@ -62,7 +63,8 @@
       <el-button @click="close">取消</el-button>
       <el-button
         type="primary"
-        :disabled="loading || !canUpload"
+        :load="loading"
+        :disabled="!canUpload"
         @click="confirm"
         >确认</el-button
       >
@@ -141,13 +143,16 @@
   ]);
 
   const uploadRef = ref();
-  const canUpload = ref(false);
   const uploadDataDict = ref({});
   const headers = ref({ md5: '' });
   const result = ref({ success: true, message: '' });
   const loading = ref(false);
   const customFileList = ref<UploadFile[]>([]);
 
+  const canUpload = computed(() => {
+    return customFileList.value.length > 0;
+  });
+
   const dfilename = computed(() => {
     if (props.downloadFilename) return props.downloadFilename;
     return props.downloadUrl ? props.downloadUrl.split('/').pop() : '';
@@ -163,7 +168,10 @@
       await props.beforeSubmitHandle().catch(() => {
         handleResult = false;
       });
-      if (!handleResult) return;
+      if (!handleResult) {
+        loading.value = false;
+        return;
+      }
     }
     uploadRef.value?.submit();
   }
@@ -173,6 +181,10 @@
     return props.format.some((item) => item.toLocaleLowerCase() === fileFormat);
   }
 
+  function handleRemove() {
+    customFileList.value = [];
+  }
+
   function handleFileChange(uploadFile: UploadFile, uploadFiles: UploadFiles) {
     if (uploadFiles.length) {
       // Element Plus 的 onChange 会在文件状态改变时都触发,包括移除
@@ -211,7 +223,6 @@
         message: '',
       };
     }
-    canUpload.value = customFileList.value[0]?.status === 'ready';
   }
 
   async function handleBeforeUpload(file: File): Promise<boolean | File> {
@@ -233,10 +244,8 @@
     const md5 = await fileMD5(file);
     headers.value.md5 = md5;
 
-    if (!props.autoUpload) return Promise.reject();
-
     loading.value = true;
-    return Promise.resolve();
+    return true;
   }
 
   function customRequest(options: UploadRequestOptions) {
@@ -265,7 +274,6 @@
   }
 
   function handleError(error: Error, uploadFile: UploadFile) {
-    canUpload.value = false;
     loading.value = false;
     result.value = {
       success: false,
@@ -281,7 +289,6 @@
     emit('uploadError', result.value);
   }
   function handleSuccess(response: UploadResult, uploadFile: UploadFile) {
-    canUpload.value = false;
     loading.value = false;
     if (!response.success) {
       handleError(new Error(response.message), uploadFile);
@@ -327,7 +334,6 @@
   }
 
   function modalBeforeOpen() {
-    canUpload.value = false;
     result.value = {
       success: true,
       message: '',

+ 133 - 137
src/views/admin/auth-manage/AuthManage.vue

@@ -1,88 +1,109 @@
 <template>
-  <div class="part-box">
-    <el-tabs v-model="activeTab">
-      <el-tab-pane label="授权信息" name="info">
-        <el-descriptions :column="1" border class="info-descriptions">
-          <el-descriptions-item label="当前信息">
-            {{ authInfo.auth ? '已授权' : '未授权' }}
-          </el-descriptions-item>
-          <el-descriptions-item label="过期时间">
-            {{ authInfo.expireTime || '无' }}
-          </el-descriptions-item>
-          <el-descriptions-item label="授权模式">
-            <span>{{ authInfo.authMode }}</span>
-            <span>:</span>
-            <span v-if="authInfo.doubleTrack">双评轨迹授权</span>
-            <span v-if="authInfo.yjsObjectiveEnable">客观题卡</span>
-            <span v-if="authInfo.groupDeleteWarn">开启删除分组警告</span>
-            <span v-if="authInfo.trialMode"
-              >试评:{{ authInfo.trialMode }}</span
-            >
-          </el-descriptions-item>
-        </el-descriptions>
-      </el-tab-pane>
-      <el-tab-pane label="更新授权" name="update">
-        <el-form
-          ref="formRef"
-          :model="updateForm"
-          :rules="rules"
-          label-width="120px"
-          class="update-form"
+  <el-tabs v-model="activeTab" type="card" class="page-tab">
+    <el-tab-pane name="info">
+      <template #label>
+        <el-button :type="activeTab === 'info' ? 'primary' : 'default'"
+          >授权信息</el-button
         >
-          <el-form-item label="授权模式">
-            <el-select
-              v-model="updateForm.authMode"
-              placeholder="请选择授权模式"
-            >
-              <el-option label="离线激活" value="offline"></el-option>
-              <el-option label="在线激活" value="online"></el-option>
-            </el-select>
-          </el-form-item>
-
-          <template v-if="updateForm.authMode === 'offline'">
-            <el-form-item label="导出">
-              <el-button type="primary" @click="exportHardwareInfo"
-                >导出硬件信息</el-button
-              >
-            </el-form-item>
-            <el-form-item label="导入授权文件" prop="offlineFile">
-              <el-upload
-                action="#"
-                :before-upload="beforeUpload"
-                :show-file-list="true"
-                :limit="1"
-              >
-                <el-button type="primary">选择文件</el-button>
-                <template #tip>
-                  <div class="el-upload__tip"> 请选择授权文件进行导入 </div>
-                </template>
-              </el-upload>
-            </el-form-item>
-          </template>
-
-          <template v-if="updateForm.authMode === 'online'">
-            <el-form-item label="密钥" prop="onlineKey">
-              <el-input
-                v-model="updateForm.onlineKey"
-                placeholder="请输入密钥"
-              ></el-input>
-            </el-form-item>
-            <el-form-item label="秘钥" prop="onlineSecret">
-              <el-input
-                v-model="updateForm.onlineSecret"
-                placeholder="请输入秘钥"
-              ></el-input>
-            </el-form-item>
-          </template>
-
-          <el-form-item>
-            <el-button type="primary" :loading="loading" @click="handleSave"
-              >保存</el-button
-            >
-          </el-form-item>
-        </el-form>
-      </el-tab-pane>
-    </el-tabs>
+      </template>
+    </el-tab-pane>
+    <el-tab-pane name="update">
+      <template #label>
+        <el-button :type="activeTab === 'update' ? 'primary' : 'default'"
+          >更新授权</el-button
+        >
+      </template>
+    </el-tab-pane>
+  </el-tabs>
+
+  <div class="part-box is-border">
+    <!-- 更新授权 -->
+    <el-form
+      v-if="activeTab === 'update'"
+      ref="formRef"
+      :model="updateForm"
+      :rules="rules"
+      label-width="120px"
+      class="update-form"
+    >
+      <el-form-item label="授权模式">
+        <el-select v-model="updateForm.type" placeholder="请选择授权模式">
+          <el-option label="离线激活" value="OFFLINE"></el-option>
+          <el-option label="在线激活" value="ONLINE"></el-option>
+        </el-select>
+      </el-form-item>
+
+      <template v-if="updateForm.type === 'OFFLINE'">
+        <el-form-item label="导出">
+          <el-button @click="exportHardwareInfo">导出硬件信息</el-button>
+        </el-form-item>
+        <el-form-item label="导入授权文件" prop="file">
+          <el-upload
+            action="#"
+            :before-upload="beforeUpload"
+            :show-file-list="true"
+            :limit="1"
+            accept=".lic"
+          >
+            <el-button>选择文件</el-button>
+          </el-upload>
+        </el-form-item>
+      </template>
+
+      <template v-if="updateForm.type === 'ONLINE'">
+        <el-form-item label="密钥" prop="accessKey">
+          <el-input
+            v-model="updateForm.accessKey"
+            placeholder="请输入密钥"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="秘钥" prop="accessSecret">
+          <el-input
+            v-model="updateForm.accessSecret"
+            placeholder="请输入秘钥"
+          ></el-input>
+        </el-form-item>
+      </template>
+
+      <el-form-item>
+        <el-button type="primary" :loading="loading" @click="handleSave"
+          >保存</el-button
+        >
+      </el-form-item>
+    </el-form>
+
+    <!-- 授权信息 -->
+    <el-descriptions
+      v-if="activeTab === 'info'"
+      :column="1"
+      border
+      class="info-descriptions"
+    >
+      <el-descriptions-item label="是否授权">
+        {{ authInfo.authText }}
+      </el-descriptions-item>
+      <el-descriptions-item label="过期时间">
+        {{ authInfo.expireTime || '无' }}
+      </el-descriptions-item>
+      <el-descriptions-item label="授权模式">
+        {{ authInfo.typeText }}
+      </el-descriptions-item>
+      <el-descriptions-item label="双评轨迹">
+        {{ authInfo.doubleTrackText }}
+      </el-descriptions-item>
+      <el-descriptions-item label="删除分组警告">
+        {{ authInfo.groupDeleteWarnText }}
+      </el-descriptions-item>
+      <el-descriptions-item label="试评模式">
+        {{ authInfo.trialModeText }}
+      </el-descriptions-item>
+      <el-descriptions-item label="客观题卡">
+        {{ authInfo.objectiveEnableText }}
+      </el-descriptions-item>
+      <el-descriptions-item label="版本">
+        {{ authInfo.version || '无' }}
+      </el-descriptions-item>
+    </el-descriptions>
   </div>
 </template>
 
@@ -92,7 +113,6 @@
   import type { FormInstance, FormRules } from 'element-plus';
   import { getAuthInfo, updateAuthInfo } from '@/api/admin';
   import { AuthInfo, AuthUpdateParams } from '@/api/types/admin';
-  import { modalConfirm } from '@/utils/ui';
   import { downloadExport } from '@/utils/download-export';
 
   import useLoading from '@/hooks/loading';
@@ -100,28 +120,27 @@
   const activeTab = ref('info');
 
   const authInfo = reactive<AuthInfo>({
-    auth: false,
-    expireTime: null,
-    authMode: '离线激活',
-    doubleTrack: false,
-    groupDeleteWarn: false,
-    trialMode: '成绩汇总模式',
-    yjsObjectiveEnable: false,
+    authText: '未授权',
+    expireTime: '',
+    typeText: '离线激活',
+    doubleTrackText: '未开启',
+    groupDeleteWarnText: '未开启',
+    trialModeText: '成绩汇总模式',
+    objectiveEnableText: '未支持',
+    version: '',
   });
 
   const updateForm = reactive<AuthUpdateParams>({
-    authMode: 'offline', // 默认离线激活
-    offlineFile: null,
+    type: 'OFFLINE', // 默认离线激活
+    file: null,
     accessKey: '',
     accessSecret: '',
   });
 
   const formRef = ref<FormInstance>();
   const rules: FormRules<keyof AuthUpdateParams> = {
-    authMode: [{ required: true, message: '请选择模式', trigger: 'change' }],
-    offlineFile: [
-      { required: true, message: '请选择授权文件', trigger: 'change' },
-    ],
+    type: [{ required: true, message: '请选择模式', trigger: 'change' }],
+    file: [{ required: true, message: '请选择授权文件', trigger: 'change' }],
     accessKey: [
       {
         required: true,
@@ -152,15 +171,11 @@
   const fetchAuthInfo = async () => {
     // 实际应调用API获取数据
     const response = await getAuthInfo();
-    Object.assign(authInfo, response.data);
+    Object.assign(authInfo, response);
   };
 
   onMounted(() => {
-    // fetchAuthInfo();
-    // 模拟数据
-    authInfo.status = '已授权';
-    authInfo.expireTime = '2025-12-31';
-    authInfo.authMode = '离线激活: 双评轨迹授权 试评: 成绩汇总模式';
+    fetchAuthInfo();
   });
 
   // 导出授权管理信息
@@ -175,74 +190,55 @@
     //   ElMessage.error('上传文件大小不能超过 2MB!');
     //   return false;
     // }
-    updateForm.offlineFile = file;
+    updateForm.file = file;
     ElMessage.success(`文件 ${file.name} 已选择`);
-    return false; // 阻止el-upload自动上传
+    return false;
   };
 
   const { loading, setLoading } = useLoading();
   const handleSave = async () => {
+    if (loading.value) return;
+    if (!formRef.value) return;
+
     const valid = await formRef.value?.validate().catch(() => false);
     if (!valid) return;
 
-    const confirm = await modalConfirm(
-      '确定要保存当前的授权设置吗?',
-      '提示'
-    ).catch(() => false);
-    if (!confirm) return;
-
-    if (!formRef.value) return;
-
-    // 实际应调用API保存授权信息
     try {
       setLoading(true);
-      let params = {};
-      if (updateForm.authMode === 'offline') {
+      let params = {} as AuthUpdateParams;
+      if (updateForm.type === 'OFFLINE') {
         const formData = new FormData();
-        formData.append('file', updateForm.offlineFile);
-        formData.append('authMode', updateForm.authMode);
+        formData.append('file', updateForm.file);
+        formData.append('type', updateForm.type);
         params = formData;
         await updateAuthInfo(formData);
-      } else if (updateForm.authMode === 'online') {
+      } else if (updateForm.type === 'ONLINE') {
         params = {
-          authMode: updateForm.authMode,
+          type: updateForm.type,
           accessKey: updateForm.accessKey,
           accessSecret: updateForm.accessSecret,
         };
         await updateAuthInfo(params);
       }
       ElMessage.success('授权信息保存成功');
-      fetchAuthInfo(); // 重新获取授权信息
-      activeTab.value = 'info'; // 切换回信息展示
+      activeTab.value = 'info';
     } catch (error) {
-      ElMessage.error('授权信息保存失败');
+      console.log(error);
     } finally {
       setLoading(false);
     }
-
-    // ElMessage.success('模拟保存成功');
-    // fetchAuthInfo();
-    // activeTab.value = 'info';
+    await fetchAuthInfo();
   };
 </script>
 
 <style scoped lang="scss">
   .info-descriptions {
-    margin-top: 20px;
     :deep(.el-descriptions__label) {
-      width: 120px; // 统一标签宽度
+      width: 120px;
       font-weight: bold;
     }
   }
-
   .update-form {
-    margin-top: 20px;
     max-width: 600px;
   }
-
-  .el-upload__tip {
-    font-size: 12px;
-    color: #909399;
-    margin-top: 7px;
-  }
 </style>

+ 53 - 116
src/views/admin/role-manage/ModifyRolePrivilege.vue

@@ -7,41 +7,30 @@
     :close-on-press-escape="false"
     top="0"
     append-to-body
+    modal-class="modify-role-privilege-dialog"
     @close="handleClose"
     @open="modalBeforeOpen"
   >
-    <div class="privilege-content">
-      <div class="privilege-title">
-        <h4>权限配置</h4>
-        <el-space>
-          <el-button size="small" @click="expandAll">展开全部</el-button>
-          <el-button size="small" @click="collapseAll">收起全部</el-button>
-          <el-button size="small" type="primary" @click="checkAll"
-            >全选</el-button
-          >
-          <el-button size="small" @click="uncheckAll">取消全选</el-button>
-        </el-space>
-      </div>
-
-      <el-tree
-        ref="treeRef"
-        :data="treeData"
-        :props="treeProps"
-        show-checkbox
-        node-key="code"
-        :default-checked-keys="checkedKeys"
-        :default-expand-all="false"
-        class="privilege-tree"
-      >
-        <template #default="{ data }">
-          <span class="tree-node">
-            <span class="node-label">{{ data.name }}</span>
-            <span class="node-code">({{ data.code }})</span>
-          </span>
-        </template>
-      </el-tree>
+    <div class="privilege-title">
+      <h4>权限配置</h4>
+      <el-space>
+        <el-button size="small" @click="checkAll">全选</el-button>
+        <el-button size="small" @click="uncheckAll">取消全选</el-button>
+      </el-space>
     </div>
 
+    <el-tree
+      ref="treeRef"
+      :data="treeData"
+      :props="treeProps"
+      show-checkbox
+      node-key="code"
+      :default-checked-keys="checkedKeys"
+      :default-expand-all="true"
+      class="privilege-tree"
+    >
+    </el-tree>
+
     <template #footer>
       <span class="dialog-footer">
         <el-button @click="close">取消</el-button>
@@ -56,7 +45,11 @@
 <script setup lang="ts">
   import { ref, computed } from 'vue';
   import { ElMessage, ElTree } from 'element-plus';
-  import type { RoleItem, RolePrivilegeItem } from '@/api/types/user';
+  import type {
+    RoleItem,
+    RolePrivilegeItem,
+    RolePrivilegeTreeItem,
+  } from '@/api/types/user';
   import { getRolePrivilegeList, updateRolePrivilege } from '@/api/user';
   import useModal from '@/hooks/modal';
   import useLoading from '@/hooks/loading';
@@ -71,7 +64,7 @@
 
   interface Props {
     rowData: RoleItem;
-    schooldId: number;
+    schoolId: number;
   }
 
   const props = withDefaults(defineProps<Props>(), {
@@ -94,21 +87,20 @@
   // 构建树形数据
   const buildTreeData = (list: RolePrivilegeItem[]) => {
     const map = new Map();
-    const roots: any[] = [];
-
+    const roots: RolePrivilegeTreeItem[] = [];
     // 创建节点映射
     list.forEach((item) => {
       map.set(item.code, {
         ...item,
         children: [],
-      });
+      } as RolePrivilegeTreeItem);
     });
 
     // 构建树形结构
     list.forEach((item) => {
-      const node = map.get(item.code);
+      const node = map.get(item.code) as RolePrivilegeTreeItem;
       if (item.parentCode && map.has(item.parentCode)) {
-        map.get(item.parentCode).children.push(node);
+        (map.get(item.parentCode) as RolePrivilegeTreeItem).children.push(node);
       } else {
         roots.push(node);
       }
@@ -117,20 +109,20 @@
     return roots;
   };
 
-  // 获取已选中的权限
-  const getCheckedKeys = (list: RolePrivilegeItem[]) => {
-    return list.filter((item) => item.enable).map((item) => item.code);
-  };
-
-  // 展开全部
-  const expandAll = () => {
-    const allKeys = privilegeList.value.map((item) => item.code);
-    treeRef.value?.setExpandedKeys(allKeys);
-  };
-
-  // 收起全部
-  const collapseAll = () => {
-    treeRef.value?.setExpandedKeys([]);
+  // 获取所有叶子节点
+  const getLeafKeys = (data: RolePrivilegeTreeItem[]) => {
+    const leafKeys: string[] = [];
+    const traverse = (nodes: RolePrivilegeTreeItem[]) => {
+      nodes.forEach((node) => {
+        if (node.children.length === 0) {
+          leafKeys.push(node.code);
+        } else {
+          traverse(node.children);
+        }
+      });
+    };
+    traverse(data);
+    return leafKeys;
   };
 
   // 全选
@@ -157,14 +149,11 @@
     try {
       const checkedNodes = treeRef.value?.getCheckedKeys() || [];
       const halfCheckedNodes = treeRef.value?.getHalfCheckedKeys() || [];
-      const selectPrivileges = [
-        ...checkedNodes,
-        ...halfCheckedNodes,
-      ] as string[];
+      const selectPrivileges = [...checkedNodes, ...halfCheckedNodes].join(',');
 
       await updateRolePrivilege({
         role: props.rowData.code,
-        schoolId: props.schooldId,
+        schoolId: props.schoolId,
         selectPrivileges,
       });
 
@@ -184,13 +173,19 @@
 
     try {
       const response = await getRolePrivilegeList({
-        schoolId: props.schooldId,
+        schoolId: props.schoolId,
         role: props.rowData.code,
       });
 
       privilegeList.value = response;
       treeData.value = buildTreeData(response);
-      checkedKeys.value = getCheckedKeys(response);
+      checkedKeys.value = response
+        .filter((item) => item.enable)
+        .map((item) => item.code);
+      const allLeafKeys = getLeafKeys(treeData.value);
+      checkedKeys.value = checkedKeys.value.filter((key) =>
+        allLeafKeys.includes(key)
+      );
 
       // 延迟设置选中状态,确保树组件已渲染
       setTimeout(() => {
@@ -202,61 +197,3 @@
     }
   }
 </script>
-
-<style scoped>
-  .privilege-content {
-    max-height: 60vh;
-    overflow-y: auto;
-  }
-
-  .privilege-title {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 15px;
-    padding-bottom: 10px;
-    border-bottom: 1px solid #ebeef5;
-  }
-
-  .privilege-title h4 {
-    margin: 0;
-    color: #303133;
-  }
-
-  .privilege-tree {
-    border: 1px solid #dcdfe6;
-    border-radius: 4px;
-    padding: 10px;
-    max-height: 400px;
-    overflow-y: auto;
-  }
-
-  .tree-node {
-    display: flex;
-    align-items: center;
-    width: 100%;
-  }
-
-  .node-label {
-    font-weight: 500;
-    color: #303133;
-  }
-
-  .node-code {
-    margin-left: 8px;
-    font-size: 12px;
-    color: #909399;
-  }
-
-  :deep(.el-tree-node__content) {
-    height: 32px;
-  }
-
-  :deep(.el-tree-node__expand-icon) {
-    color: #c0c4cc;
-  }
-
-  :deep(.el-tree-node__expand-icon.expanded) {
-    color: #409eff;
-  }
-</style>

+ 10 - 3
src/views/admin/role-manage/RoleManage.vue

@@ -18,6 +18,7 @@
       </el-form-item>
       <el-form-item>
         <el-button type="primary" @click="toPage(1)">查询</el-button>
+        <el-button @click="toBack">返回</el-button>
       </el-form-item>
     </el-form>
   </div>
@@ -32,7 +33,7 @@
       <el-table-column prop="name" label="角色名称" min-width="120" />
       <el-table-column prop="code" label="角色编码" min-width="120" />
       <el-table-column prop="updateTime" label="更新时间" width="180" />
-      <el-table-column prop="updateName" label="更新人" width="120" />
+      <el-table-column prop="updaterName" label="更新人" width="120" />
       <el-table-column label="操作" width="120" fixed="right">
         <template #default="scope">
           <el-button type="primary" link @click="onEdit(scope.row)">
@@ -62,7 +63,7 @@
 
 <script setup lang="ts">
   import { onMounted, reactive, ref } from 'vue';
-  import { useRoute } from 'vue-router';
+  import { useRoute, useRouter } from 'vue-router';
   import { getRoleList, getAllRoleList } from '@/api/user';
   import { RoleItem, RoleListFilter } from '@/api/types/user';
   import useTable from '@/hooks/table';
@@ -74,6 +75,7 @@
   });
 
   const route = useRoute();
+  const router = useRouter();
 
   const searchModel = reactive<RoleListFilter>({
     schoolId: Number(route.params.schoolId),
@@ -81,7 +83,7 @@
   });
 
   const { dataList, pagination, loading, getList, toPage, pageSizeChange } =
-    useTable<RoleItem>(getRoleList, searchModel, false);
+    useTable<RoleItem>(getRoleList, searchModel, true);
 
   const roleList = ref([] as RoleItem[]);
 
@@ -90,6 +92,11 @@
     roleList.value = await getAllRoleList(searchModel.schoolId);
   }
 
+  // 返回
+  function toBack() {
+    router.go(-1);
+  }
+
   // table action
   const curRow = ref({} as RoleItem);
   const modifyRolePrivilegeRef = ref();

+ 29 - 4
src/views/admin/school-manage/ModifySchool.vue

@@ -60,7 +60,10 @@
     <template #footer>
       <span class="dialog-footer">
         <el-button @click="close">取消</el-button>
-        <el-button type="primary" :loading="loading" @click="confirm"
+        <el-button v-if="isEdit && !props.rowData.code" @click="onResetSk"
+          >重置密钥</el-button
+        >
+        <el-button type="primary" :loading="loading" @click="onSubmit"
           >确定</el-button
         >
       </span>
@@ -80,7 +83,8 @@
   import useModal from '@/hooks/modal';
   import useLoading from '@/hooks/loading';
   import { objAssign, objModifyAssign } from '@/utils/utils';
-  import { updateSchool } from '@/api/admin';
+  import { modalConfirm } from '@/utils/ui';
+  import { getSchoolInfo, updateSchool, resetSchoolSecret } from '@/api/admin';
 
   defineOptions({
     name: 'ModifySchool',
@@ -128,15 +132,36 @@
     city: [
       { required: false, message: '最多50字符', max: 50, trigger: 'change' },
     ],
+    address: [
+      { required: false, message: '最多200字符', max: 200, trigger: 'change' },
+    ],
   };
 
+  /* reset secret */
+  const { loading: resetLoading, setLoading: setResetLoading } = useLoading();
+  async function onResetSk() {
+    if (resetLoading.value) return;
+    const confirm = await modalConfirm('确定要重置秘钥信息吗?', '提示');
+    if (!confirm) return;
+
+    setResetLoading(true);
+    const res = await resetSchoolSecret(props.rowData.id).catch(() => {});
+    setResetLoading(false);
+    if (!res) return;
+    ElMessage.success('重置成功!');
+
+    const school = await getSchoolInfo(props.rowData.id);
+    objModifyAssign(formModel, school);
+    emit('modified');
+  }
+
   const handleClose = () => {
     formRef.value?.resetFields();
   };
 
-  /* confirm */
+  /* onSubmit */
   const { loading, setLoading } = useLoading();
-  async function confirm() {
+  async function onSubmit() {
     const valid = await formRef.value?.validate().catch(() => false);
     if (!valid) return;
 

+ 25 - 13
src/views/admin/school-manage/ModifySchoolAdmin.vue

@@ -51,7 +51,7 @@
 </template>
 
 <script setup lang="ts">
-  import { ref, reactive } from 'vue';
+  import { ref, reactive, computed } from 'vue';
   import type { FormInstance, FormRules } from 'element-plus';
   import { ElMessage } from 'element-plus';
   import useModal from '@/hooks/modal';
@@ -82,17 +82,20 @@
 
   const initialFormState: SchoolUpdateAdminParam & { confirmPassword: string } =
     {
-      id: 0,
+      id: undefined,
       name: '',
       loginName: '',
       password: '',
       confirmPassword: '',
+      schoolId: '',
     };
 
   const formModel = reactive({ ...initialFormState });
 
+  const isEdit = computed(() => Boolean(formModel.id));
+
   const validatePass = (rule: any, value: any, callback: any) => {
-    if (value === '') {
+    if (!isEdit.value && value === '') {
       callback(new Error('请输入密码'));
     } else {
       if (formModel.confirmPassword !== '') {
@@ -104,6 +107,11 @@
   };
 
   const validatePass2 = (rule: any, value: any, callback: any) => {
+    if (isEdit.value && !formModel.password) {
+      callback();
+      return;
+    }
+
     if (value === '') {
       callback(new Error('请再次输入密码'));
     } else if (value !== formModel.password) {
@@ -113,16 +121,18 @@
     }
   };
 
-  const rules = reactive<FormRules>({
-    name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
-    loginName: [{ required: true, message: '请输入账号', trigger: 'blur' }],
-    password: [
-      { required: true, validator: validatePass, trigger: 'blur' },
-      { min: 6, message: '密码长度至少为6位', trigger: 'blur' },
-    ],
-    confirmPassword: [
-      { required: true, validator: validatePass2, trigger: 'blur' },
-    ],
+  const rules = computed<FormRules>(() => {
+    return {
+      name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
+      loginName: [{ required: true, message: '请输入账号', trigger: 'blur' }],
+      password: [
+        { required: !isEdit.value, validator: validatePass, trigger: 'blur' },
+        { min: 6, message: '密码长度至少为6位', trigger: 'blur' },
+      ],
+      confirmPassword: [
+        { required: !isEdit.value, validator: validatePass2, trigger: 'blur' },
+      ],
+    };
   });
 
   const handleClose = () => {
@@ -140,6 +150,7 @@
       setLoading(true);
       const params = {
         ...formModel,
+        schoolId: props.rowData.id,
       };
       // delete params.confirmPassword;
       await updateSchoolAdmin(params);
@@ -158,5 +169,6 @@
   async function modalBeforeOpen() {
     const res = await getSchoolAdminInfo(props.rowData.id);
     objModifyAssign(formModel, res || {});
+    formModel.password = '';
   }
 </script>

+ 28 - 9
src/views/admin/school-manage/SchoolManage.vue

@@ -77,7 +77,7 @@
   <ImportDialog
     ref="importDialogRef"
     title="科目拆分"
-    upload-url="/api/admin/site/import"
+    upload-url="/api/admin/subject/split/save"
     :upload-data="uploadData"
     :format="['xls', 'xlsx']"
     :download-handle="() => downloadExport('splitCourseTemplate')"
@@ -86,12 +86,24 @@
     :before-submit-handle="beforeSubmitHandle"
     @upload-success="importSuccess"
   >
-    <el-form ref="formRef" label-width="100px">
+    <el-form ref="formRef" label-width="70px">
       <el-form-item label="名称">
-        <el-input :value="curRow.name" readonly disabled />
+        <el-input :value="curRow.name" readonly />
       </el-form-item>
       <el-form-item label="考试" prop="exam">
-        <SelectExam v-model="importExamId" size="large" />
+        <el-select
+          v-model="importExamId"
+          placeholder="请选择"
+          clearable
+          filterable
+        >
+          <el-option
+            v-for="item in courseList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
       </el-form-item>
     </el-form>
   </ImportDialog>
@@ -99,9 +111,10 @@
 
 <script setup lang="ts">
   import { computed, reactive, ref, onMounted } from 'vue';
-  import { useRoute } from 'vue-router';
+  import { useRouter } from 'vue-router';
   import { ElMessage } from 'element-plus';
   import { schoolListPage, syncSchool, getSchoolStatusInfo } from '@/api/admin';
+  import { getSplitCourseExamList } from '@/api/exam';
   import {
     SchoolItem,
     SchoolListFilter,
@@ -118,7 +131,7 @@
     name: 'SchoolManage',
   });
 
-  const route = useRoute();
+  const router = useRouter();
 
   // 学校状态信息
   const schoolStatusInfo = ref<SchoolStatusInfo>({} as SchoolStatusInfo);
@@ -151,7 +164,7 @@
     modifySchoolAdminRef.value?.open();
   }
   function onEditRoleAuth(row: SchoolItem) {
-    route.push({
+    router.push({
       name: 'RoleManage',
       params: {
         schoolId: row.id,
@@ -180,17 +193,23 @@
 
   // 科目拆分
   const importDialogRef = ref();
-  const importExamId = ref(0);
+  const importExamId = ref<number>();
   const uploadData = computed(() => {
     return {
       examId: importExamId.value,
       schoolId: curRow.value.id,
     };
   });
-  function onSplitCourse(row: SchoolItem) {
+  async function onSplitCourse(row: SchoolItem) {
     curRow.value = row;
+    await getCourseList();
     importDialogRef.value?.open();
   }
+  const courseList = ref([]);
+  async function getCourseList() {
+    const res = await getSplitCourseExamList(curRow.value.id);
+    courseList.value = res || [];
+  }
 
   async function beforeSubmitHandle() {
     if (!importExamId.value) {

+ 13 - 6
src/views/admin/set-manage/SetManage.vue

@@ -23,7 +23,7 @@
   >
     <el-form ref="formRef" :model="formModel" :rules="rules" label-width="80px">
       <el-form-item label="类型">
-        <el-input v-model="formModel.name" readonly disabled />
+        <el-input v-model="formModel.name" readonly />
       </el-form-item>
       <el-form-item label="详情" prop="description">
         <el-input
@@ -33,7 +33,12 @@
           :rows="4"
           placeholder="请输入详情"
         />
-        <el-switch v-else v-model="formModel.description" />
+        <el-switch
+          v-else
+          v-model="formModel.description"
+          active-value="on"
+          inactive-value="off"
+        />
       </el-form-item>
     </el-form>
     <template #footer>
@@ -52,8 +57,7 @@
   import type { FormInstance, FormRules } from 'element-plus';
   import { ElMessage } from 'element-plus';
   import { SettingItem } from '@/api/types/admin';
-
-  // 假设有API获取和更新配置
+  import { objModifyAssign } from '@/utils/utils';
   import { getSystemSettings, updateSystemSetting } from '@/api/admin';
 
   defineOptions({
@@ -99,8 +103,11 @@
   });
 
   const handleEdit = (row: SettingItem) => {
-    Object.assign(formModel, row);
+    objModifyAssign(formModel, row);
     dialogVisible.value = true;
+    if (isEnableDetail.value) {
+      formModel.description = formModel.description === 'on' ? 'on' : 'off';
+    }
   };
 
   const handleDialogClose = () => {
@@ -117,11 +124,11 @@
       await updateSystemSetting(formModel);
       ElMessage.success('保存成功');
       dialogVisible.value = false;
-      await loadSettings(); // 重新加载数据
     } catch (error) {
       console.error('保存失败:', error);
     } finally {
       loading.value = false;
     }
+    await loadSettings();
   };
 </script>

+ 1 - 1
src/views/login/home.vue

@@ -20,7 +20,7 @@
         {{ appStore.system.versionName }}
       </span>
       <span v-if="appStore.system.versionDate">
-        {{ appStore.system.versionDate }}
+        build{{ appStore.system.versionDate }}
       </span>
       <a href="https://beian.miit.gov.cn/" target="_blank">
         鄂ICP备12000033号-3</a