Browse Source

feat: 超管中心

zhangjie 1 week ago
parent
commit
31803f6b78

+ 4 - 0
components.d.ts

@@ -14,6 +14,8 @@ declare module '@vue/runtime-core' {
     ElButton: typeof import('element-plus/es')['ElButton'];
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'];
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker'];
+    ElDescriptions: typeof import('element-plus/es')['ElDescriptions'];
+    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'];
     ElDialog: typeof import('element-plus/es')['ElDialog'];
     ElForm: typeof import('element-plus/es')['ElForm'];
     ElFormItem: typeof import('element-plus/es')['ElFormItem'];
@@ -30,6 +32,8 @@ declare module '@vue/runtime-core' {
     ElSwitch: typeof import('element-plus/es')['ElSwitch'];
     ElTable: typeof import('element-plus/es')['ElTable'];
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn'];
+    ElTabPane: typeof import('element-plus/es')['ElTabPane'];
+    ElTabs: typeof import('element-plus/es')['ElTabs'];
     ElTooltip: typeof import('element-plus/es')['ElTooltip'];
     ElUpload: typeof import('element-plus/es')['ElUpload'];
     FileUpload: typeof import('./src/components/file-upload/index.vue')['default'];

+ 14 - 0
src/api/admin.ts

@@ -4,6 +4,8 @@ import {
   SchoolListPageRes,
   SchoolUpdateParam,
   SchoolUpdateAdminParam,
+  AuthInfo,
+  AuthUpdateParams,
 } from './types/admin';
 
 // 学校管理
@@ -25,3 +27,15 @@ export function updateSchoolAdmin(
 ): Promise<{ id: number }> {
   return axios.post('/api/admin/school/save', datas);
 }
+
+// 授权管理
+// 授权管理信息
+export function getAuthInfo(): Promise<AuthInfo> {
+  return axios.post('/api/admin/auth/info');
+}
+// 授权管理信息编辑
+export function updateAuthInfo(
+  datas: AuthUpdateParams
+): Promise<{ id: number }> {
+  return axios.post('/api/admin/auth/save', datas);
+}

+ 22 - 0
src/api/types/admin.ts

@@ -30,3 +30,25 @@ export interface SchoolUpdateAdminParam {
   adminName: string;
   password: string;
 }
+
+// set
+export interface SettingItem {
+  id: string | number; // 假设配置项有唯一ID
+  type: string;
+  detail: string | boolean | number;
+  key: string; // 后端可能用key来区分配置项
+}
+
+// auth
+export interface AuthInfo {
+  status: string;
+  expireTime: string | null;
+  authMode: string;
+}
+
+export interface AuthUpdateParams {
+  authMode: 'offline' | 'online' | '';
+  offlineFile: File | null;
+  onlineKey: string;
+  onlineSecret: string;
+}

+ 4 - 5
src/layout/default-layout.vue

@@ -51,11 +51,7 @@
               <span>{{ nav.name }}</span>
             </el-menu-item>
           </el-sub-menu>
-          <el-menu-item
-            v-else
-            :key="submenu.url + 'menu'"
-            :index="submenu.url + 'menu'"
-          >
+          <el-menu-item v-else :key="submenu.url + 'menu'" :index="submenu.url">
             <span>{{ submenu.name }}</span>
           </el-menu-item>
         </template>
@@ -146,6 +142,9 @@
   }
 
   onMounted(() => {
+    // TODO:开发设置
+    appStore.fetchServerMenu('ADMIN');
+
     initData();
   });
 

+ 2 - 2
src/router/routes/modules/admin.ts

@@ -21,7 +21,7 @@ const ADMIN: AppRouteRecordRaw = {
     {
       path: 'set-manage',
       name: 'SetManage',
-      component: () => import('@/views/admin/SetManage.vue'),
+      component: () => import('@/views/admin/set-manage/SetManage.vue'),
       meta: {
         title: '配置管理',
         requiresAuth: true,
@@ -30,7 +30,7 @@ const ADMIN: AppRouteRecordRaw = {
     {
       path: 'auth-manage',
       name: 'AuthManage',
-      component: () => import('@/views/admin/AuthManage.vue'),
+      component: () => import('@/views/admin/auth-manage/AuthManage.vue'),
       meta: {
         title: '授权管理',
         requiresAuth: true,

+ 7 - 7
src/store/modules/app/index.ts

@@ -21,7 +21,7 @@ function getMenu(privilegeData: UserMenuItem[]): {
         return nitem;
       });
   };
-  let menuList = getChildren(-1);
+  const menuList = getChildren(-1);
   const validRoutes: string[] = [];
   const toTree = (data: AppMenuItem[]) => {
     data.forEach((menu) => {
@@ -36,7 +36,7 @@ function getMenu(privilegeData: UserMenuItem[]): {
   };
   toTree(menuList);
 
-  menuList = menuList.filter((item) => item.children && item.children.length);
+  // menuList = menuList.filter((item) => item.children && item.children.length);
 
   return { menuList, validRoutes };
 }
@@ -65,12 +65,12 @@ const useAppStore = defineStore('app', {
       this.$patch(partial);
     },
     fetchServerMenu(type: RoleType | null) {
-      const userMenus = type
-        ? menus.filter((menu) => !menu.roles || menu.roles.includes(type))
-        : ([] as typeof menus);
+      // const userMenus = type
+      //   ? menus.filter((menu) => !menu.roles || menu.roles.includes(type))
+      //   : ([] as typeof menus);
       // // 开发时不管权限
-      // console.log(type);
-      // const userMenus = menus;
+      console.log(type);
+      const userMenus = menus;
       const { menuList, validRoutes } = getMenu(transformMenu(userMenus));
       this.appMenus = menuList;
       this.validRoutes = validRoutes;

+ 8 - 127
src/store/modules/app/menuData.ts

@@ -1,8 +1,8 @@
 export const menus = [
   {
     id: 1,
-    name: '考试预约管理',
-    url: 'apply',
+    name: '学校管理',
+    url: 'SchoolManage',
     type: 'MENU',
     parentId: -1,
     sequence: 1,
@@ -10,140 +10,21 @@ export const menus = [
   },
   {
     id: 2,
-    name: '预约任务管理',
-    url: 'TaskManage',
-    type: 'MENU',
-    parentId: 1,
-    sequence: 1,
-    enable: true,
-    roles: ['ADMIN'],
-  },
-  {
-    id: 15,
-    name: '考点预约设置',
-    url: 'ReservationSet',
-    type: 'MENU',
-    parentId: 1,
-    sequence: 2,
-    enable: true,
-    roles: ['ADMIN', 'TEACHING'],
-  },
-  {
-    id: 20,
-    name: '考场排班设置',
-    url: 'RoomSchedulingSet',
-    type: 'MENU',
-    parentId: 1,
-    sequence: 3,
-    enable: true,
-    roles: ['ADMIN', 'TEACHING'],
-  },
-  {
-    id: 16,
-    name: '考生管理',
-    url: 'StudentManage',
-    type: 'MENU',
-    parentId: 1,
-    sequence: 4,
-    enable: true,
-    roles: ['ADMIN', 'TEACHING'],
-  },
-  {
-    id: 3,
-    name: '考生信息导入',
-    url: 'StudentImport',
-    type: 'MENU',
-    parentId: 1,
-    sequence: 5,
-    enable: true,
-    roles: ['ADMIN'],
-  },
-  {
-    id: 4,
-    name: '考生管理',
-    url: 'StudentManage',
-    type: 'MENU',
-    parentId: 1,
-    sequence: 6,
-    enable: true,
-    roles: [],
-  },
-  {
-    id: 5,
-    name: '预约明细',
-    url: 'OrderRecordManage',
-    type: 'MENU',
-    parentId: 1,
-    sequence: 7,
-    enable: true,
-    roles: ['ADMIN', 'TEACHING'],
-  },
-  {
-    id: 11,
-    name: '基础信息管理',
-    url: 'base',
+    name: '配置管理',
+    url: 'SetManage',
     type: 'MENU',
     parentId: -1,
     sequence: 2,
     enable: true,
-  },
-  {
-    id: 12,
-    name: '教学点管理',
-    url: 'TeachingManage',
-    type: 'MENU',
-    parentId: 11,
-    sequence: 1,
-    enable: true,
     roles: ['ADMIN'],
   },
   {
-    id: 13,
-    name: '考点管理',
-    url: 'AgentManage',
-    type: 'MENU',
-    parentId: 11,
-    sequence: 2,
-    enable: true,
-    roles: ['ADMIN', 'TEACHING'],
-  },
-  {
-    id: 14,
-    name: '考场管理',
-    url: 'RoomManage',
-    type: 'MENU',
-    parentId: 11,
-    sequence: 3,
-    enable: true,
-    roles: ['ADMIN', 'TEACHING'],
-  },
-  {
-    id: 17,
-    name: '系统设置',
-    url: 'base',
+    id: 3,
+    name: '授权管理',
+    url: 'AuthManage',
     type: 'MENU',
     parentId: -1,
-    sequence: 1,
-    enable: true,
-  },
-  {
-    id: 18,
-    name: '账号管理',
-    url: 'AccountManage',
-    type: 'MENU',
-    parentId: 17,
-    sequence: 1,
-    enable: true,
-    roles: ['ADMIN'],
-  },
-  {
-    id: 19,
-    name: '我的任务',
-    url: 'MyTask',
-    type: 'MENU',
-    parentId: 17,
-    sequence: 1,
+    sequence: 3,
     enable: true,
-    roles: ['ADMIN', 'TEACHING'],
   },
 ];

+ 0 - 0
src/views/admin/AuthManage.vue


+ 0 - 0
src/views/admin/SetManage.vue


+ 236 - 0
src/views/admin/auth-manage/AuthManage.vue

@@ -0,0 +1,236 @@
+<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.status }}
+          </el-descriptions-item>
+          <el-descriptions-item label="过期时间">
+            {{ authInfo.expireTime || '无' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="授权模式">
+            {{ authInfo.authMode }}
+          </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-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>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted } from 'vue';
+  import { ElMessage } from 'element-plus';
+  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 useLoading from '@/hooks/loading';
+
+  const activeTab = ref('info');
+
+  const authInfo = reactive<AuthInfo>({
+    status: '未授权',
+    expireTime: null,
+    authMode: '离线激活',
+  });
+
+  const updateForm = reactive<AuthUpdateParams>({
+    authMode: 'offline', // 默认离线激活
+    offlineFile: null,
+    onlineKey: '',
+    onlineSecret: '',
+  });
+
+  const formRef = ref<FormInstance>();
+  const rules: FormRules<keyof AuthUpdateParams> = {
+    authMode: [{ required: true, message: '请选择模式', trigger: 'change' }],
+    offlineFile: [
+      { required: true, message: '请选择授权文件', trigger: 'change' },
+    ],
+    onlineKey: [
+      {
+        required: true,
+        message: '请输入key',
+        trigger: 'change',
+      },
+      {
+        max: 200,
+        message: '最多200字符',
+        trigger: 'change',
+      },
+    ],
+    onlineSecret: [
+      {
+        required: true,
+        message: '请输入secret',
+        trigger: 'change',
+      },
+      {
+        max: 200,
+        message: '最多200字符',
+        trigger: 'change',
+      },
+    ],
+  };
+
+  // 模拟获取授权信息
+  const fetchAuthInfo = async () => {
+    // 实际应调用API获取数据
+    const response = await getAuthInfo();
+    Object.assign(authInfo, response.data);
+  };
+
+  onMounted(() => {
+    // fetchAuthInfo();
+    // 模拟数据
+    authInfo.status = '已授权';
+    authInfo.expireTime = '2025-12-31';
+    authInfo.authMode = '离线激活: 双评轨迹授权 试评: 成绩汇总模式';
+  });
+
+  const exportHardwareInfo = () => {
+    // TODO:实际应调用API导出硬件信息
+    ElMessage.success('硬件信息导出指令已发送,请注意查收文件。');
+    // 例如:window.location.href = '/api/auth/export-hardware';
+  };
+
+  const beforeUpload = (file: File) => {
+    // 校验文件类型等,如果需要
+    // const isLt2M = file.size / 1024 / 1024 < 2;
+    // if (!isLt2M) {
+    //   ElMessage.error('上传文件大小不能超过 2MB!');
+    //   return false;
+    // }
+    updateForm.offlineFile = file;
+    ElMessage.success(`文件 ${file.name} 已选择`);
+    return false; // 阻止el-upload自动上传
+  };
+
+  const { loading, setLoading } = useLoading();
+  const handleSave = async () => {
+    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') {
+        const formData = new FormData();
+        formData.append('file', updateForm.offlineFile);
+        formData.append('authMode', updateForm.authMode);
+        params = formData;
+        await updateAuthInfo(formData);
+      } else if (updateForm.authMode === 'online') {
+        params = {
+          authMode: updateForm.authMode,
+          key: updateForm.onlineKey,
+          secret: updateForm.onlineSecret,
+        };
+        await updateAuthInfo(params);
+      }
+      ElMessage.success('授权信息保存成功');
+      fetchAuthInfo(); // 重新获取授权信息
+      activeTab.value = 'info'; // 切换回信息展示
+    } catch (error) {
+      ElMessage.error('授权信息保存失败');
+    } finally {
+      setLoading(false);
+    }
+
+    // ElMessage.success('模拟保存成功');
+    // fetchAuthInfo();
+    // activeTab.value = 'info';
+  };
+</script>
+
+<style scoped lang="less">
+  .info-descriptions {
+    margin-top: 20px;
+    :deep(.el-descriptions__label) {
+      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>

+ 2 - 2
src/views/admin/school-manage/ModifySchool.vue

@@ -122,8 +122,8 @@
   /* confirm */
   const { loading, setLoading } = useLoading();
   async function confirm() {
-    const err = await formRef.value?.validate();
-    if (err) return;
+    const valid = await formRef.value?.validate().catch(() => false);
+    if (!valid) return;
 
     setLoading(true);
     const datas = objAssign(formModel, {});

+ 3 - 1
src/views/admin/school-manage/ModifySchoolAdmin.vue

@@ -144,7 +144,9 @@
   async function confirm() {
     if (!formRef.value) return;
     try {
-      await formRef.value.validate();
+      const valid = await formRef.value?.validate().catch(() => false);
+      if (!valid) return;
+
       setLoading(true);
       const params = {
         ...formModel,

+ 0 - 6
src/views/admin/school-manage/SchoolManage.vue

@@ -112,12 +112,6 @@
     useTable<SchoolItem>(schoolListPage, searchModel, false);
 
   // table action
-  // 导入
-  // const importStudentDialogRef = ref();
-  // function toImport() {
-  //   importStudentDialogRef.value?.open();
-  // }
-
   const curRow = ref({} as SchoolItem);
   const modifySchoolRef = ref();
   function onEdit(row: SchoolItem) {

+ 165 - 0
src/views/admin/set-manage/SetManage.vue

@@ -0,0 +1,165 @@
+<template>
+  <div class="part-box">
+    <el-table class="page-table" :data="tableData">
+      <el-table-column prop="type" label="类型" width="180" />
+      <el-table-column prop="detail" label="详情" />
+      <el-table-column label="操作" width="100">
+        <template #default="scope">
+          <el-button type="primary" link @click="handleEdit(scope.row)"
+            >修改</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+
+  <!-- 配置项修改 -->
+  <el-dialog
+    v-model="dialogVisible"
+    title="修改配置"
+    width="600px"
+    :close-on-click-modal="false"
+    @close="handleDialogClose"
+  >
+    <el-form ref="formRef" :model="formModel" :rules="rules" label-width="80px">
+      <el-form-item label="类型">
+        <el-input v-model="formModel.type" readonly disabled />
+      </el-form-item>
+      <el-form-item label="详情" prop="detail">
+        <el-input
+          v-if="!isEnableDetail"
+          v-model="formModel.detail"
+          type="textarea"
+          :rows="4"
+          placeholder="请输入详情"
+        />
+        <el-switch v-else v-model="formModel.detail" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="dialogVisible = false">返回</el-button>
+        <el-button type="primary" :loading="loading" @click="handleSubmit"
+          >保存</el-button
+        >
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted, computed } from 'vue';
+  import type { FormInstance, FormRules } from 'element-plus';
+  import { ElMessage } from 'element-plus';
+  import { SettingItem } from '@/api/types/admin';
+
+  // 假设有API获取和更新配置
+  // import { getSystemSettings, updateSystemSetting } from '@/api/admin';
+
+  defineOptions({
+    name: 'SetManage',
+  });
+
+  const tableData = ref<SettingItem[]>([]);
+  const dialogVisible = ref(false);
+  const formRef = ref<FormInstance>();
+  const loading = ref(false);
+
+  const initialFormState: Partial<SettingItem> = {
+    id: '',
+    type: '',
+    detail: '',
+    key: '',
+  };
+  const formModel = reactive({ ...initialFormState });
+
+  const isEnableDetail = computed(() => {
+    return (
+      formModel.key === 'enable_marking_process' ||
+      formModel.key === 'calculate_scanned_cards'
+    );
+  });
+
+  const rules = reactive<FormRules>({
+    detail: [{ required: true, message: '请输入详情', trigger: 'blur' }],
+  });
+
+  // 模拟API调用获取数据
+  const mockFetchSettings = async () => {
+    return new Promise<SettingItem[]>((resolve) => {
+      setTimeout(() => {
+        resolve([
+          {
+            id: 1,
+            type: '图片服务',
+            detail: 'http://192.168.10.83:9004/',
+            key: 'image_service_url',
+          },
+          {
+            id: 2,
+            type: '评阅时长',
+            detail: '60',
+            key: 'review_duration_minutes',
+          },
+          {
+            id: 3,
+            type: '启动阅卷',
+            detail: false,
+            key: 'enable_marking_process',
+          },
+          {
+            id: 4,
+            type: '计算扫描卡张数',
+            detail: false,
+            key: 'calculate_scanned_cards',
+          },
+        ]);
+      }, 500);
+    });
+  };
+
+  const loadSettings = async () => {
+    try {
+      // const res = await getSystemSettings(); // 实际API调用
+      const res = await mockFetchSettings(); // 使用模拟数据
+      tableData.value = res;
+    } catch (error) {
+      console.error('获取配置失败:', error);
+      ElMessage.error('获取配置失败');
+    }
+  };
+
+  onMounted(() => {
+    loadSettings();
+  });
+
+  const handleEdit = (row: SettingItem) => {
+    Object.assign(formModel, row);
+    dialogVisible.value = true;
+  };
+
+  const handleDialogClose = () => {
+    formRef.value?.resetFields();
+    Object.assign(formModel, initialFormState);
+  };
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return;
+    try {
+      const valid = await formRef.value?.validate().catch(() => false);
+      if (!valid) return;
+      loading.value = true;
+      // const paramsToUpdate = { key: formModel.key, value: formModel.detail, id: formModel.id };
+      // await updateSystemSetting(paramsToUpdate); // 实际API调用
+      console.log('保存的配置:', formModel);
+      ElMessage.success('保存成功');
+      dialogVisible.value = false;
+      await loadSettings(); // 重新加载数据
+    } catch (error) {
+      console.error('保存失败:', error);
+      // ElMessage.error('保存失败');
+    } finally {
+      loading.value = false;
+    }
+  };
+</script>