Browse Source

feat: 考场排版设置

zhangjie 1 month ago
parent
commit
4f9a733025

+ 29 - 1
src/api/order.ts

@@ -14,6 +14,9 @@ import type {
   OrderRecordPrintParam,
   ExportOrderRecordDetailParam,
   AgentQueryParam,
+  RoomQueryParam,
+  OrderPeriodSetItem,
+  TimePeriodItem,
 } from './types/order';
 import { AbleParams, PageParams } from './types/common';
 
@@ -32,6 +35,10 @@ export function teachingQuery(params: {
 export function agentQuery(params: AgentQueryParam): Promise<OptionItem[]> {
   return axios.post('/api/admin/apply/agent/list', {}, { params });
 }
+// 通用查询-考场查询
+export function roomQuery(params: RoomQueryParam): Promise<OptionItem[]> {
+  return axios.post('/api/admin/room/list', {}, { params });
+}
 // 通用查询-城市查询
 export function cityQuery(): Promise<OptionItem[]> {
   return axios.post('/api/admin/teaching/city/list', {});
@@ -172,7 +179,9 @@ export function exportOrderRecordDetail(
 }
 
 // 时段列表(对应表格第一行)
-export function getTimeSliceList(params: any) {
+export function getTimeSliceList(params: {
+  taskId: string;
+}): Promise<string[]> {
   return axios.post('/api/admin/time/period/exam/site/list', {}, { params });
 }
 // 时段设置列表
@@ -188,11 +197,30 @@ export function getDateAndTimeList(params: any) {
 export function saveReservation(data: any, params: any) {
   return axios.post('/api/admin/time/period/exam/site/save', data, { params });
 }
+
 // 开启/关闭 自主预约
 export function toggleSelfYYStatus(params: any) {
   return axios.post('/api/admin/teaching/selfApplyEnable', {}, { params });
 }
 
+// 考场排版设置
+// 预约时段设置列表
+export function getRoomDateAndTimeList(
+  examRoomId: number
+): Promise<OrderPeriodSetItem[]> {
+  return axios.post(
+    '/api/admin/time/period/exam/room/detail/list',
+    {},
+    { params: { examRoomId } }
+  );
+}
+// 预约时段设置保存
+export function saveScheduling(data: TimePeriodItem[], examRoomId: number) {
+  return axios.post('/api/admin/time/period/exam/room/save', data, {
+    params: { examRoomId },
+  });
+}
+
 export function getUserList(data: any): any {
   return axios.post('/api/admin/user/page', data);
 }

+ 16 - 0
src/api/types/order.ts

@@ -9,6 +9,9 @@ export interface AgentQueryParam {
   id: number; // teachingId
   flag?: boolean;
 }
+export interface RoomQueryParam {
+  examSiteId: number;
+}
 
 export interface TaskListFilter {
   name: string;
@@ -117,3 +120,16 @@ export interface OrderRecordPrintParam {
 export interface ExportOrderRecordDetailParam {
   teachingId: number | null;
 }
+
+export interface TimePeriodItem {
+  id: number; // 注意:教学点管理员第一次设置和学校管理员新增了时段的情况下为空
+  timePeriodId: number;
+  timePeriodStr: string; // 如:08:00-09:30,作用:和原型中第一行做比较
+  enable: boolean; // false:关闭,true:开启
+  editable: boolean; // false:不可编辑,true:可编辑
+}
+export interface OrderPeriodSetItem {
+  dateStr: string;
+  timePeriodList: TimePeriodItem[];
+  batchStatus?: boolean; // false:未批量设置,true:已批量设置
+}

+ 2 - 0
src/components/index.ts

@@ -5,6 +5,7 @@ import SvgIcon from './svg-icon/index.vue';
 import SelectTask from './select-task/index.vue';
 import SelectTeaching from './select-teaching/index.vue';
 import SelectAgent from './select-agent/index.vue';
+import SelectRoom from './select-room/index.vue';
 import SelectRangeDatetime from './select-range-datetime/index.vue';
 import SelectRangeTime from './select-range-time/index.vue';
 import SelectCity from './select-city/index.vue';
@@ -18,6 +19,7 @@ export default {
     Vue.component('SelectTask', SelectTask);
     Vue.component('SelectTeaching', SelectTeaching);
     Vue.component('SelectAgent', SelectAgent);
+    Vue.component('SelectRoom', SelectRoom);
     Vue.component('SelectRangeDatetime', SelectRangeDatetime);
     Vue.component('SelectRangeTime', SelectRangeTime);
     Vue.component('SelectCity', SelectCity);

+ 106 - 0
src/components/select-room/index.vue

@@ -0,0 +1,106 @@
+<template>
+  <a-select
+    v-model="selected"
+    :placeholder="placeholder"
+    :allow-clear="clearable"
+    :disabled="disabled"
+    :options="optionList"
+    allow-search
+    popup-container="body"
+    v-bind="attrs"
+    :trigger-props="{ autoFitPopupMinWidth: true }"
+    style="width: 335px !important"
+    @change="onChange"
+  >
+    <template v-if="prefix" #prefix>考场</template>
+  </a-select>
+</template>
+
+<script setup lang="ts">
+  import { ref, useAttrs, watch } from 'vue';
+  import { roomQuery } from '@/api/order';
+
+  defineOptions({
+    name: 'SelectRoom',
+  });
+  type ValueType = number | Array<number> | null;
+
+  const props = defineProps<{
+    modelValue: ValueType;
+    clearable?: boolean;
+    disabled?: boolean;
+    placeholder?: string;
+    multiple?: boolean;
+    examSiteId: number | null;
+    prefix?: boolean;
+  }>();
+
+  const emit = defineEmits(['update:modelValue', 'change']);
+  const attrs = useAttrs();
+
+  interface OptionListItem {
+    value: number;
+    label: string;
+  }
+
+  const selected = ref<number | Array<number> | undefined>();
+  const optionList = ref<OptionListItem[]>([]);
+  const search = async () => {
+    optionList.value = [];
+    if (!props.examSiteId) return;
+
+    const resData = await roomQuery({
+      examSiteId: props.examSiteId,
+    });
+
+    optionList.value = (resData || []).map((item) => {
+      return { ...item, value: item.id, label: item.name };
+    });
+
+    if (selected.value) {
+      const targetOption = optionList.value.find(
+        (item) => item.value === selected.value
+      );
+      if (!targetOption) {
+        emit('update:modelValue', null);
+        emit('change', null);
+      }
+    }
+  };
+
+  const onChange = () => {
+    const selectedData = props.multiple
+      ? optionList.value.filter(
+          (item) =>
+            selected.value && (selected.value as number[]).includes(item.value)
+        )
+      : optionList.value.filter((item) => selected.value === item.value);
+    emit('update:modelValue', selected.value || null);
+    emit('change', props.multiple ? selectedData : selectedData[0]);
+  };
+
+  watch(
+    () => props.modelValue,
+    (val) => {
+      selected.value = val || undefined;
+    },
+    {
+      immediate: true,
+    }
+  );
+  watch(
+    () => props.examSiteId,
+    (val, oldval) => {
+      if (!val) {
+        optionList.value = [];
+        selected.value = undefined;
+        emit('update:modelValue', selected.value || null);
+      } else if (val !== oldval) {
+        search();
+      }
+    },
+    {
+      immediate: true,
+    }
+  );
+</script>

+ 9 - 0
src/router/routes/modules/order.ts

@@ -27,6 +27,15 @@ const routes: AppRouteRecordRaw = {
         requiresAuth: true,
       },
     },
+    {
+      path: 'room-scheduling-set',
+      name: 'RoomSchedulingSet',
+      component: () => import('@/views/order/room-scheduling-set/index.vue'),
+      meta: {
+        title: '考点预约设置',
+        requiresAuth: true,
+      },
+    },
     {
       path: 'student-import',
       name: 'StudentImport',

+ 14 - 4
src/store/modules/app/menuData.ts

@@ -28,13 +28,23 @@ export const menus = [
     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: 2,
+    sequence: 4,
     enable: true,
     roles: ['ADMIN', 'TEACHING'],
   },
@@ -44,7 +54,7 @@ export const menus = [
     url: 'StudentImport',
     type: 'MENU',
     parentId: 1,
-    sequence: 2,
+    sequence: 5,
     enable: true,
     roles: ['ADMIN'],
   },
@@ -54,7 +64,7 @@ export const menus = [
     url: 'StudentManage',
     type: 'MENU',
     parentId: 1,
-    sequence: 3,
+    sequence: 6,
     enable: true,
     roles: [],
   },
@@ -64,7 +74,7 @@ export const menus = [
     url: 'OrderRecordManage',
     type: 'MENU',
     parentId: 1,
-    sequence: 4,
+    sequence: 7,
     enable: true,
     roles: ['ADMIN', 'TEACHING'],
   },

+ 216 - 0
src/views/order/room-scheduling-set/index.vue

@@ -0,0 +1,216 @@
+<template>
+  <div class="part-box is-filter">
+    <a-space class="filter-line" :size="12" wrap>
+      <SelectTask
+        v-model="searchModel.taskId"
+        placeholder="请选择"
+        allow-clear
+        prefix
+      />
+      <SelectTeaching
+        v-model="searchModel.teachingId"
+        placeholder="请选择"
+        allow-clear
+        prefix-str="所约教学点"
+        :task-id="searchModel.taskId"
+        @get-options="getTeachOptions"
+      />
+      <SelectAgent
+        v-model="searchModel.examSiteId"
+        placeholder="请选择"
+        allow-clear
+        prefix
+        cascade
+        :teaching-id="searchModel.teachingId"
+      />
+      <SelectRoom
+        v-model="searchModel.examRoomId"
+        placeholder="请选择"
+        allow-clear
+        prefix
+        cascade
+        :exam-site-id="searchModel.examSiteId"
+      />
+      <a-button type="primary" @click="search">查询</a-button>
+    </a-space>
+  </div>
+  <div class="part-box">
+    <a-space
+      v-if="searchModel.examRoomId && searchModel.taskId && tableData.length"
+      class="part-action"
+      :size="12"
+    >
+      <a-button type="primary" @click="save">保存</a-button>
+      <span style="color: #ff9a2e">时段关联最小单位是考场</span>
+    </a-space>
+    <a-table
+      :key="tableKey"
+      class="page-table"
+      :columns="columns"
+      :data="tableData"
+      :pagination="false"
+      :scroll="{ x: 1200 }"
+      :bordered="false"
+      :loading="saveLoading || tableLoading"
+    >
+      <template #dateStr="{ record }"> {{ record.dateStr }} </template>
+      <template #batch="{ record, rowIndex }">
+        <a-switch
+          :default-checked="record.batchStatus"
+          @change="(bool:string | number | boolean)=>{
+            switchBatchChange(rowIndex,bool as boolean)
+          }"
+        >
+          <template #checked> 开启 </template>
+          <template #unchecked> 关闭 </template>
+        </a-switch></template
+      >
+      <template
+        v-for="head in sliceTimeArr"
+        #[head]="{ record, rowIndex }"
+        :key="head"
+      >
+        <a-switch
+          v-if="
+            record.timePeriodList?.find((item:any) => item.timePeriodStr == head)
+          "
+          :default-checked="
+            record.timePeriodList?.find((item) => item.timePeriodStr == head)
+              .enable
+          "
+          :disabled="
+            !record.timePeriodList?.find((item) => item.timePeriodStr == head)
+              .editable
+          "
+          @change="(bool:string | number | boolean)=>{
+            switchChange(rowIndex,head,bool as boolean)
+          }"
+        >
+          <template #checked> 开启 </template>
+          <template #unchecked> 关闭 </template>
+        </a-switch></template
+      >
+    </a-table>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { reactive, ref, computed, onMounted, watch } from 'vue';
+  import { Message, TableColumnData } from '@arco-design/web-vue';
+  import {
+    getTimeSliceList,
+    getRoomDateAndTimeList,
+    saveScheduling,
+    toggleSelfYYStatus,
+  } from '@/api/order';
+  import { TaskItem, OrderPeriodSetItem } from '@/api/types/order';
+  import useTable from '@/hooks/table';
+  import { timestampFilter } from '@/utils/filter';
+  import { modalConfirm } from '@/utils/arco';
+  import { useAppStore, useUserStore } from '@/store';
+
+  defineOptions({
+    name: 'RoomSchedulingSet',
+  });
+  const userStore = useUserStore();
+  const tableLoading = ref(false);
+
+  const tableKey = ref(`${Date.now()}`);
+  const appStore = useAppStore();
+  appStore.setInfo({ breadcrumbs: ['考试预约管理', '考场排版设置'] });
+
+  const searchModel = reactive({
+    taskId: null,
+    examSiteId: null,
+    teachingId: null,
+    examRoomId: null,
+  });
+  const sliceTimeArr = ref([] as string[]);
+
+  const columns = computed<TableColumnData[]>(() => {
+    return [
+      {
+        title: '日期/时段',
+        dateIndex: 'dateStr',
+        slotName: 'dateStr',
+      },
+      {
+        title: '批量操作',
+        slotName: 'batch',
+        minWidth: 100,
+      },
+      ...sliceTimeArr.value.map((head: string) => {
+        return {
+          title: head,
+          minWidth: 100,
+          slotName: head,
+          // render: (record: any, column: any, rowIndex: any) => {
+          //   return <span>2</span>;
+          // },
+        };
+      }),
+    ];
+  });
+
+  const tableData = ref<OrderPeriodSetItem[]>([]);
+  const getTimeSilce = async () => {
+    const res = await getTimeSliceList({ taskId: searchModel.taskId });
+    sliceTimeArr.value = res || [];
+  };
+  // onMounted(() => {});
+  const search = async () => {
+    if (!searchModel.examRoomId) {
+      Message.error(`请选择考场`);
+      tableData.value = [];
+      tableKey.value = `${Date.now()}`;
+      return;
+    }
+    await getTimeSilce();
+    tableLoading.value = true;
+    const res = getRoomDateAndTimeList({
+      examSiteId: searchModel.examSiteId || null,
+    }).catch(() => null);
+    tableLoading.value = false;
+
+    if (!res) return;
+    tableData.value = (res || []).map((item: OrderPeriodSetItem) => {
+      item.batchStatus = item.timePeriodList.some((v) => v.enable);
+      return item;
+    });
+    tableKey.value = `${Date.now()}`;
+  };
+
+  const switchChange = (rowIndex: number, head: string, bool: boolean) => {
+    // console.log(rowIndex, head, bool);
+    const row = tableData.value[rowIndex];
+    const timePeriodItem = row.timePeriodList.find(
+      (item) => item.timePeriodStr === head
+    );
+    if (!timePeriodItem) return;
+    timePeriodItem.enable = bool;
+    row.batchStatus = row.timePeriodList.some((v) => v.enable);
+    tableKey.value = `${Date.now()}`;
+  };
+
+  const switchBatchChange = (rowIndex: number, bool: boolean) => {
+    const row = tableData.value[rowIndex];
+    row.batchStatus = bool;
+    row.timePeriodList.forEach((item) => {
+      item.enable = bool;
+    });
+    tableKey.value = `${Date.now()}`;
+  };
+
+  const saveLoading = ref(false);
+  const save = async () => {
+    saveLoading.value = true;
+    const data = tableData.value.map((item) => item.timePeriodList).flat();
+    const res = await saveScheduling(data, searchModel.examSiteId).catch(
+      () => false
+    );
+    saveLoading.value = false;
+    if (!res) return;
+    Message.success('保存成功!');
+    search();
+  };
+</script>