zhangjie 1 жил өмнө
parent
commit
f4a0808da6

+ 35 - 0
src/api/base.ts

@@ -0,0 +1,35 @@
+import axios from 'axios';
+import type {
+  TeachingListPageParam,
+  TeachingListPageRes,
+  TeachingUpdateParams,
+} from './types/base';
+import { AbleParams } from './types/common';
+
+// 教学点管理
+// 教学点管理-查询
+export function teachingListPage(
+  params: TeachingListPageParam
+): Promise<TeachingListPageRes> {
+  return axios.post('/api/admin/teaching/query', {}, { params });
+}
+// 教学点管理-新增编辑
+export function updateTeaching(
+  datas: TeachingUpdateParams
+): Promise<{ id: string }> {
+  return axios.post('/api/admin/teaching/save', datas);
+}
+// 教学点管理-启用禁用
+export function ableTeaching(params: AbleParams): Promise<boolean> {
+  return axios.post('/api/admin/teaching/enable', {}, { params });
+}
+// // 教学点管理-导入模板下载
+export function teachingTemplate(): Promise<Blob> {
+  return axios.post(
+    '/api/admin/teaching/template',
+    {},
+    {
+      responseType: 'blob',
+    }
+  );
+}

+ 4 - 3
src/api/order.ts

@@ -1,6 +1,7 @@
 import axios from 'axios';
 import type {
   OptionItem,
+  TaskListPageParam,
   TaskListPageRes,
   TaskItemDetail,
   TaskRuleUpdateParams,
@@ -28,9 +29,9 @@ export function agentQuery(teachingId: string): Promise<OptionItem[]> {
 }
 
 // task-manage
-export function taskListPage(params: {
-  name: string;
-}): Promise<TaskListPageRes> {
+export function taskListPage(
+  params: TaskListPageParam
+): Promise<TaskListPageRes> {
   return axios.post('/api/admin/apply/task/page', {}, { params });
 }
 export function taskDetailInfo(id: string): Promise<TaskItemDetail> {

+ 29 - 0
src/api/types/base.ts

@@ -0,0 +1,29 @@
+import { PageResult, PageParams } from './common';
+
+export interface TeachingListFilter {
+  name: string;
+  code: string;
+  enable: boolean;
+}
+export type TeachingListPageParam = PageParams<TeachingListFilter>;
+
+export interface TeachingItem {
+  id: number;
+  name: string;
+  code: string;
+  cityId: number;
+  cityName: string;
+  capacity: number;
+  enable: boolean;
+}
+
+export type TeachingListPageRes = PageResult<TeachingItem>;
+
+export interface TeachingUpdateParams {
+  id?: number;
+  name: string;
+  code: string;
+  cityId: number;
+  capacity: number;
+  enable?: boolean;
+}

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

@@ -5,6 +5,11 @@ export interface OptionItem {
   name: string;
 }
 
+export interface TaskListFilter {
+  name: string;
+}
+export type TaskListPageParam = PageParams<TaskListFilter>;
+
 export interface TaskItem {
   id: string;
   name: string;

+ 24 - 0
src/router/routes/modules/base.ts

@@ -0,0 +1,24 @@
+import { DEFAULT_LAYOUT } from '../base';
+import { AppRouteRecordRaw } from '../types';
+
+const routes: AppRouteRecordRaw = {
+  path: '/base',
+  name: 'base',
+  component: DEFAULT_LAYOUT,
+  meta: {
+    requiresAuth: true,
+  },
+  children: [
+    {
+      path: 'teaching-manage',
+      name: 'TeachingManage',
+      component: () => import('@/views/base/teaching-manage/index.vue'),
+      meta: {
+        title: '教学点管理',
+        requiresAuth: true,
+      },
+    },
+  ],
+};
+
+export default routes;

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

@@ -1,6 +1,6 @@
 import { AppRouteRecordRaw } from '../types';
 
-const LOGIN: AppRouteRecordRaw = {
+const routes: AppRouteRecordRaw = {
   path: '/login-home',
   component: () => import('@/views/login/home.vue'),
   children: [
@@ -16,4 +16,4 @@ const LOGIN: AppRouteRecordRaw = {
   ],
 };
 
-export default LOGIN;
+export default routes;

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

@@ -1,7 +1,7 @@
 import { DEFAULT_LAYOUT } from '../base';
 import { AppRouteRecordRaw } from '../types';
 
-const ORDER: AppRouteRecordRaw = {
+const routes: AppRouteRecordRaw = {
   path: '/order',
   name: 'order',
   component: DEFAULT_LAYOUT,
@@ -48,4 +48,4 @@ const ORDER: AppRouteRecordRaw = {
   ],
 };
 
-export default ORDER;
+export default routes;

+ 20 - 1
src/store/modules/app/menuData.ts

@@ -2,7 +2,7 @@ export const menus = [
   {
     id: '1',
     name: '考试预约管理',
-    url: 'base',
+    url: 'order',
     type: 'MENU',
     parentId: '-1',
     sequence: 1,
@@ -48,4 +48,23 @@ export const menus = [
     enable: true,
     roles: ['ADMIN'],
   },
+  {
+    id: '11',
+    name: '基础信息管理',
+    url: 'base',
+    type: 'MENU',
+    parentId: '-1',
+    sequence: 2,
+    enable: true,
+  },
+  {
+    id: '12',
+    name: '教学点管理',
+    url: 'TeachingManage',
+    type: 'MENU',
+    parentId: '11',
+    sequence: 1,
+    enable: true,
+    roles: ['ADMIN'],
+  },
 ];

+ 4 - 0
src/types/global.ts

@@ -1,3 +1,5 @@
+import type { FieldRule } from '@arco-design/web-vue/es/form';
+
 export interface AnyObject {
   [key: string]: unknown;
 }
@@ -35,3 +37,5 @@ export interface GeneralChart {
   xAxis: string[];
   data: Array<{ name: string; value: number[] }>;
 }
+
+export type FormRules<T> = Partial<Record<T, FieldRule[]>>;

+ 200 - 0
src/views/base/teaching-manage/index.vue

@@ -0,0 +1,200 @@
+<template>
+  <div class="part-box is-filter">
+    <a-space class="filter-line" :size="12" wrap>
+      <a-input v-model.trim="searchModel.name" placeholder="请输入" allow-clear>
+        <template #prefix>教学点名称</template>
+      </a-input>
+      <a-input v-model.trim="searchModel.code" placeholder="请输入" allow-clear>
+        <template #prefix>教学点代码</template>
+      </a-input>
+      <a-select
+        v-model="searchModel.enable"
+        placeholder="请选择"
+        allow-clear
+        :options="ableOptions"
+      >
+        <template #prefix>状态</template>
+      </a-select>
+
+      <a-button type="primary" @click="toPage(1)">查询</a-button>
+    </a-space>
+  </div>
+  <div class="part-box">
+    <a-space class="part-action" :size="6">
+      <a-button type="text" @click="toAdd">
+        <template #icon>
+          <svg-icon name="icon-add" />
+        </template>
+        新增
+      </a-button>
+      <a-divider direction="vertical" />
+      <a-button type="text" @click="toImport">
+        <template #icon>
+          <svg-icon name="icon-import" />
+        </template>
+        导入
+      </a-button>
+    </a-space>
+    <a-table
+      class="page-table"
+      :columns="columns"
+      :data="dataList"
+      :pagination="pagination"
+      :scroll="{ x: 1200 }"
+      :bordered="false"
+    >
+      <template #enable="{ record }">
+        <status-tag type="enable" :value="record.enable" />
+      </template>
+      <template #action="{ record }">
+        <a-button type="text" class="btn-primary" @click="toEdit(record)"
+          >编辑</a-button
+        >
+        <a-button
+          :class="record.enable ? 'btn-danger' : 'btn-primary'"
+          type="text"
+          @click="toEnable(record)"
+          >{{ record.enable ? '禁用' : '启用' }}</a-button
+        >
+        <a-button
+          v-if="!record.cancel"
+          type="text"
+          class="btn-primary"
+          @click="toPlace(record)"
+          >考点</a-button
+        >
+      </template>
+    </a-table>
+
+    <!-- ImportDialog -->
+    <ImportDialog
+      ref="importDialogRef"
+      title="导入教学点"
+      upload-url="/api/admin/teaching/import"
+      :format="['xls', 'xlsx']"
+      :download-handle="downloadTemplate"
+      download-filename="教学点导入模板.xlsx"
+      :auto-upload="false"
+      @upload-success="getList"
+    />
+    <!-- ModifyTeaching -->
+    <ModifyTeaching
+      ref="modifyTeachingRef"
+      :row-data="curRow"
+      @modified="getList"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { reactive, ref } from 'vue';
+  import { Message, TableColumnData } from '@arco-design/web-vue';
+  import { teachingListPage, teachingTemplate, ableTeaching } from '@/api/base';
+  import { TeachingItem } from '@/api/types/base';
+  import useTable from '@/hooks/table';
+  import useDictOption from '@/hooks/dict-option';
+  import { modalConfirm } from '@/utils/arco';
+  import { downloadByApi } from '@/utils/download';
+  import { useAppStore } from '@/store';
+
+  import ImportDialog from '@/components/import-dialog/index.vue';
+  import ModifyTeaching from './modifyTeaching.vue';
+
+  defineOptions({
+    name: 'TeachingManage',
+  });
+
+  const appStore = useAppStore();
+  appStore.setInfo({ breadcrumbs: ['基础信息管理', '教学点管理'] });
+
+  const { optionList: ableOptions } = useDictOption('ABLE_TYPE');
+
+  const searchModel = reactive({
+    name: '',
+    code: '',
+    enable: null,
+  });
+
+  const columns: TableColumnData[] = [
+    {
+      title: '代码',
+      dataIndex: 'code',
+      width: 120,
+    },
+    {
+      title: '教学点名称',
+      dataIndex: 'name',
+      width: 200,
+    },
+    {
+      title: '城市',
+      dataIndex: 'cityName',
+      width: 160,
+    },
+    {
+      title: '容量',
+      dataIndex: 'capacity',
+      width: 160,
+    },
+    {
+      title: '状态',
+      slotName: 'status',
+      width: 100,
+    },
+    {
+      title: '操作',
+      slotName: 'action',
+      width: 180,
+      fixed: 'right',
+      cellClass: 'action-column',
+    },
+  ];
+  const { dataList, pagination, toPage, getList } = useTable<TeachingItem[]>(
+    teachingListPage,
+    searchModel,
+    true
+  );
+
+  async function downloadTemplate() {
+    const res = await downloadByApi(() => teachingTemplate()).catch((e) => {
+      Message.error(e || '下载失败,请重新尝试!');
+    });
+    if (!res) return;
+    Message.success('下载成功!');
+  }
+
+  // table action
+  // 导入
+  const importDialogRef = ref(null);
+  function toImport() {
+    importDialogRef.value?.open();
+  }
+
+  const modifyTeachingRef = ref(null);
+  const curRow = ref({});
+  function toAdd() {
+    curRow.value = {};
+    modifyTeachingRef.value?.open();
+  }
+  function toEdit(row: TeachingItem) {
+    curRow.value = row;
+    modifyTeachingRef.value?.open();
+  }
+
+  async function toEnable(row: TeachingItem) {
+    const action = row.enable ? '禁用' : '启用';
+    const confirmRes = await modalConfirm(
+      '提示',
+      `确定要${action}任务【${row.name}】吗?`
+    ).catch(() => false);
+    if (confirmRes !== 'confirm') return;
+
+    await ableTeaching({ id: row.id, enable: !row.enable });
+    Message.success('操作成功!');
+    getList();
+  }
+
+  function toPlace(row: TeachingItem) {
+    console.log(row);
+  }
+</script>

+ 153 - 0
src/views/base/teaching-manage/modifyTeaching.vue

@@ -0,0 +1,153 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    :width="500"
+    title-align="start"
+    top="10vh"
+    :align-center="false"
+    @before-open="modalBeforeOpen"
+  >
+    <template #title> {{ title }} </template>
+    <a-form ref="formRef" :model="formData" :rules="rules" auto-label-width>
+      <a-form-item field="name" label="教学点名称">
+        <a-input
+          v-model.trim="formData.name"
+          placeholder="请输入"
+          allow-clear
+          :disabled="isEdit"
+        ></a-input>
+      </a-form-item>
+      <a-form-item field="code" label="教学点代码">
+        <a-input
+          v-model.trim="formData.code"
+          placeholder="请输入"
+          allow-clear
+        ></a-input>
+      </a-form-item>
+      <a-form-item field="capacity" label="容量">
+        <a-input-number
+          v-model="formData.capacity"
+          placeholder="请输入"
+          :style="{ width: '120px' }"
+          :min="1"
+          :max="99999"
+          :step="1"
+        />
+      </a-form-item>
+      <a-form-item field="cityId" label="城市">
+        <a-select
+          v-model="formData.cityId"
+          placeholder="请选择"
+          filter-option
+          allow-clear
+          :options="[]"
+        >
+        </a-select>
+      </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 { computed, reactive, ref } from 'vue';
+  import { Message } from '@arco-design/web-vue';
+  import type { FormInstance } from '@arco-design/web-vue/es/form';
+  import { updateTeaching } from '@/api/base';
+  import useLoading from '@/hooks/loading';
+  import useModal from '@/hooks/modal';
+  import { objAssign, objModifyAssign } from '@/utils/utils';
+  import { TeachingItem, TeachingUpdateParams } from '@/api/types/base';
+  import { FormRules } from '@/types/global';
+
+  defineOptions({
+    name: 'ModifyTeaching',
+  });
+
+  /* modal */
+  const { visible, open, close } = useModal();
+  defineExpose({ open, close });
+
+  const defaultFormData = {
+    id: null,
+    name: '',
+    code: '',
+    cityId: null,
+    capacity: undefined,
+  };
+
+  const props = defineProps<{
+    rowData: TeachingItem;
+  }>();
+  const emit = defineEmits(['modified']);
+
+  const isEdit = computed(() => !!props.rowData.id);
+  const title = computed(() => `${isEdit.value ? '编辑' : '新增'}教学点`);
+
+  const formRef = ref<FormInstance>();
+  const formData = reactive<TeachingUpdateParams>({ ...defaultFormData });
+  const rules: FormRules<TeachingUpdateParams> = {
+    name: [
+      {
+        required: true,
+        message: '请输入名称',
+      },
+      {
+        max: 50,
+        message: '名称不能超过50',
+      },
+    ],
+    code: [
+      {
+        required: true,
+        message: '请输入代码',
+      },
+      {
+        max: 50,
+        message: '代码不能超过50',
+      },
+    ],
+    capacity: [
+      {
+        required: true,
+        message: '请输入容量',
+      },
+    ],
+    cityId: [
+      {
+        required: true,
+        message: '请选择城市',
+      },
+    ],
+  };
+
+  /* confirm */
+  const { loading, setLoading } = useLoading();
+  async function confirm() {
+    const err = await formRef.value?.validate();
+    if (err) return;
+
+    setLoading(true);
+    const datas = objAssign(formData, {});
+    const res = await updateTeaching(datas).catch(() => false);
+    setLoading(false);
+    if (!res) return;
+    Message.success('修改成功!');
+    emit('modified', datas);
+    close();
+  }
+  /* init modal */
+  function modalBeforeOpen() {
+    if (props.rowData.id) {
+      objModifyAssign(formData, props.rowData);
+    } else {
+      objModifyAssign(formData, defaultFormData);
+    }
+  }
+</script>