Browse Source

feat(业务管理): 新增合同底稿功能

- 添加合同底稿列表页面和相关API
- 实现合同底稿的新增、编辑和删除功能
- 集成电子签名功能,支持创建合同模板
- 优化学校开放时间和课程管理页面布局
- 修复地图组件的初始化和销毁问题
zhangtao 6 hours ago
parent
commit
6bcf84e6db

+ 2 - 0
src/hooks/web/useMap.ts

@@ -193,6 +193,8 @@ export function useAMapEnhanced(options: EnhancedAMapOptions = {}) {
         geolocation.getCurrentPosition((status: string, result: any) => {
           if (status === 'complete') {
             // 定位到当前位置
+            console.log(result, '当前位置信息');
+
             getAddressByLngLat(result.position.lng, result.position.lat);
             if (result.position) {
               globalMap?.setCenter(result.position);

+ 22 - 0
src/hooks/web/useRouteTab.ts

@@ -0,0 +1,22 @@
+import { useMultipleTabStore } from '@/store/modules/multipleTab';
+import { storeToRefs } from 'pinia';
+import { ref } from 'vue';
+import { onBeforeRouteLeave } from 'vue-router';
+import { router } from '/@/router';
+
+/**
+ * 通用设置tab标签标题
+ * @param typeList
+ */
+export function useRouteTabText(typeList: string[]) {
+  onBeforeRouteLeave(async (to, from, next) => {
+    to.meta.title = typeList[Number(to.query.type)];
+    const { getTabList } = storeToRefs(useMultipleTabStore());
+    const closeTab = getTabList.value.filter((it) => it.name == to.name).filter((it) => it.fullPath != to.fullPath);
+    if (closeTab.length) {
+      useMultipleTabStore().closeTabByKey(closeTab[0].fullPath, router);
+    }
+    await useMultipleTabStore().updateCacheTab();
+    next();
+  });
+}

+ 0 - 31
src/utils/map.ts

@@ -49,37 +49,6 @@ class myMap {
     return this.aMap;
   }
 }
-export function initMap(): Promise<typeof AMap> {
-  return new Promise((resolve, reject) => {
-    //如果全局已经存在AMap对象 则直接返回
-    if (window.AMap) {
-      resolve(window.AMap);
-      return;
-    }
-    AMapLoader.load<typeof AMap>({
-      key: import.meta.env.VITE_MAPKEY, // 申请好的Web端开发者Key,首次调用 load 时必填
-      version: '2.0', // 指定要加载的 JS API 的版本,缺省时默认为 1.4.15
-      plugins: ['AMap.PlaceSearch,'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
-      // "AMapUI": { // 是否加载 AMapUI,缺省不加载
-      //     "version": '1.1', // AMapUI 版本
-      //     "plugins": [], // 需要加载的 AMapUI ui插件
-      // },
-      // "Loca": { // 是否加载 Loca, 缺省不加载
-      //     "version": '2.0' // Loca 版本
-      // },
-    })
-      .then((AMap) => {
-        window.AMap = AMap;
-        resolve(AMap); //防止上方全局添加amap失败手动返回Amap
-      })
-      .catch((e) => {
-        console.error(e); //加载错误提示
-        reject(e);
-      });
-  });
-}
-export default myMap;
-
 export function matchProvinceByFirstThreeDigits(code: string | number): { code: string; name: string }[] {
   const codeStr = code.toString();
   if (codeStr.length < 3) {

+ 10 - 0
src/views/businessManagement/competition/competition.api.ts

@@ -9,6 +9,7 @@ enum Api {
   edit = '/app/appGame/edit',
   deleteOne = '/app/appGame/delete',
   detaile = '/app/appGame/queryById',
+  uploadScore = '/app/appGame/uploadScore',
 }
 export const list = (params) => defHttp.get({ url: Api.list, params });
 
@@ -33,3 +34,12 @@ export const saveOrUpdate = (params, isUpdate) => {
 export const getDetaile = (params) => {
   return defHttp.get({ url: Api.detaile, params });
 };
+/**
+ * 发布成绩
+ * @param params
+ * @returns
+ */
+
+export const uploadScoreData = (params) => {
+  return defHttp.put({ url: Api.uploadScore, params });
+};

+ 39 - 3
src/views/businessManagement/competition/competition.data.ts

@@ -4,7 +4,7 @@ import { FormSchema } from '/@/components/Table';
 import { BasicColumn } from '/@/components/Table';
 import { getAddress } from '../courses/courses.api';
 import { Image, RadioChangeEvent } from 'ant-design-vue';
-import { h, ref, unref, VNode } from 'vue';
+import { h, VNode } from 'vue';
 import dayjs from 'dayjs';
 import { useAMapEnhanced } from '/@/hooks/web/useMap';
 /**
@@ -198,6 +198,8 @@ export const formSchema: FormSchema[] = [
       ],
       async onchange(e: RadioChangeEvent) {
         const { initMap, destroyMap, getCurrentPosition } = useAMapEnhanced();
+        console.log('加载地图,', e.target.value);
+
         if (e.target.value == 1) {
           await initMap('container');
           await getCurrentPosition();
@@ -209,7 +211,7 @@ export const formSchema: FormSchema[] = [
     defaultValue: 0,
   },
   {
-    field: 'address',
+    field: 'provinceCode',
     label: '地址',
     component: 'JAreaSelect',
     required: true,
@@ -226,7 +228,7 @@ export const formSchema: FormSchema[] = [
     show: false,
   },
   {
-    field: 'addressString',
+    field: 'address',
     label: '输入地址',
     component: 'Input',
     show: false,
@@ -396,3 +398,37 @@ export const SchedulePricesColums: BasicColumn[] = [
     width: 80,
   },
 ];
+
+export const GradesFrom: FormSchema[] = [
+  {
+    field: 'name',
+    label: '赛事名称 ',
+    component: 'Input',
+    required: true,
+    componentProps: {
+      disabled: true,
+    },
+  },
+  {
+    field: 'address',
+    label: '比赛地点: ',
+    component: 'Input',
+    required: true,
+    componentProps: {
+      disabled: true,
+    },
+  },
+  {
+    field: 'gameResults',
+    label: '赛事成绩',
+    component: 'JEditor',
+    required: true,
+  },
+  {
+    field: 'id',
+    label: '',
+    component: 'Input',
+    required: true,
+    show: false,
+  },
+];

+ 22 - 11
src/views/businessManagement/competition/competitionCommon.vue

@@ -41,7 +41,7 @@
               @change="handleSearch(0)"
             ></JAreaSelect>
             <div class="mt20px">
-              <Input v-model:value="model['addressString']">
+              <Input v-model:value="model['address']">
                 <template #suffix>
                   <Button @click="handleSearch(1)">定位</Button>
                 </template>
@@ -49,7 +49,7 @@
             </div>
             <div class="mt20px mb20px relative">
               <div id="container" class="h-500px"></div>
-              <div id="panel"></div>
+              <!-- <div id="panel"></div> -->
             </div>
           </FormItem>
         </template>
@@ -87,7 +87,7 @@
   import { getSprotProject } from '@/api/common/api';
   import { getDetaile, saveOrUpdate } from './competition.api';
   import dayjs from 'dayjs';
-  import { ref, toRefs, unref, watch, watchEffect } from 'vue';
+  import { nextTick, onUnmounted, ref, toRefs, unref, watch, watchEffect } from 'vue';
   import { areAllItemsAllFieldsFilled } from '/@/utils';
   import { message } from 'ant-design-vue/lib';
   import { useRoute } from 'vue-router';
@@ -97,7 +97,7 @@
   import { matchCityByFirstFourDigits, matchProvinceByFirstThreeDigits } from '/@/utils/map';
   const { close: closeTab } = useTabs();
   const route = useRoute();
-  const { locateByCityAndAddress, setCityByCode, currentObj, lnglat } = useAMapEnhanced();
+  const { locateByCityAndAddress, setCityByCode, currentObj, lnglat, initMap, getAddressByLngLat, destroyMap } = useAMapEnhanced();
 
   const [registerForm, { setProps, resetFields, setFieldsValue, updateSchema, validate, clearValidate, getFieldsValue }] = useForm({
     schemas: formSchema,
@@ -248,6 +248,13 @@
         };
       }),
     });
+    if (res.game.siteType == 1) {
+      await initMap('container');
+      await getAddressByLngLat(res.game.longitude, res.game.latitude);
+      nextTick(() => {
+        handleSearch(1);
+      });
+    }
   }
 
   watch(
@@ -267,10 +274,10 @@
         const proce = getDataByCode(Number(proviceCode[0].code));
         const cityCode = matchCityByFirstFourDigits(proce, currentObj.value.currentAdcode);
         setFieldsValue({
-          provinceCode: Number(proviceCode[0].code),
+          provinceCode: proviceCode[0].code,
           cityCode: cityCode?.value,
           areaCode: currentObj.value.currentAdcode,
-          addressString: currentObj.value.currentAddress,
+          address: currentObj.value.currentAddress,
           longitude: lnglat.value[0],
           latitude: lnglat.value[1],
         });
@@ -279,12 +286,13 @@
     { immediate: true }
   );
   async function handleSearch(type: number) {
-    const { addressString, areaCode } = getFieldsValue();
+    const { address, areaCode } = getFieldsValue();
     if (type == 0) {
       setCityByCode(areaCode);
     }
-    if (addressString && type == 1 && areaCode) {
-      const res = await locateByCityAndAddress(areaCode, addressString);
+    if (address && type == 1 && areaCode) {
+      const res = await locateByCityAndAddress(areaCode, address);
+
       setFieldsValue({
         longitude: res.location.lng,
         latitude: res.location.lat,
@@ -294,9 +302,12 @@
       message.error('请选择省市区');
     }
   }
+  onUnmounted(() => {
+    destroyMap();
+  });
 </script>
 <style>
-  #panel {
+  /* #panel {
     position: absolute;
     background-color: white;
     max-height: 300px;
@@ -304,5 +315,5 @@
     top: 10px;
     right: 10px;
     width: 280px;
-  }
+  } */
 </style>

+ 74 - 0
src/views/businessManagement/competition/competitonModel.vue

@@ -0,0 +1,74 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" title="发布成绩" @ok="handleSubmit" width="900px" destroyOnClose>
+    <div class="px-10">
+      <BasicForm @register="registerForm">
+        <template #title1>
+          <TypographyTitle :level="4">基础信息</TypographyTitle>
+          <Divider></Divider>
+        </template>
+        <template #title2>
+          <TypographyTitle :level="4">服务保障</TypographyTitle>
+          <Divider></Divider>
+        </template>
+        <template #title3>
+          <TypographyTitle :level="4">购买须知</TypographyTitle>
+          <Divider></Divider>
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { TypographyTitle, Divider } from 'ant-design-vue';
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { GradesFrom } from './competition.data';
+  import { uploadScoreData } from './competition.api';
+  import { useShopInfoStore } from '/@/store/modules/shopInfo';
+  import { useUserStore } from '/@/store/modules/user';
+  // 声明Emits
+  const emit = defineEmits(['success', 'register']);
+  const isUpdate = ref(true);
+  const isDetail = ref(false);
+  //表单配置
+  const [registerForm, { resetFields, setFieldsValue, validate, setProps }] = useForm({
+    schemas: GradesFrom,
+    showActionButtonGroup: false,
+  });
+  //表单赋值
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+    //重置表单
+    await resetFields();
+    setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
+    isUpdate.value = !!data?.isUpdate;
+    isDetail.value = !!data?.showFooter;
+
+    if (unref(isUpdate)) {
+      console.log(data.record, 'asdasd');
+
+      //表单赋值
+      setProps({ disabled: !data?.showFooter });
+      await setFieldsValue({
+        ...data.record,
+      });
+    }
+  });
+
+  //表单提交事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+
+      setModalProps({ confirmLoading: true });
+      //提交表单
+      await uploadScoreData({ ...values });
+      //关闭弹窗
+      closeModal();
+      //刷新列表
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  }
+</script>

+ 48 - 18
src/views/businessManagement/competition/index.vue

@@ -24,16 +24,32 @@
         <TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
       </template> </BasicTable
   ></div>
+  <Modal v-model:open="openCode" title="赛事二维码">
+    <div class="p-20px flex items-center justify-center">
+      <div
+        ><QRCode :value="CodeText" ref="qrcodeCanvasRef" /> <a-button type="link" block class="mt20px" @click="downloadImage">下载图片</a-button></div
+      >
+    </div>
+  </Modal>
+  <competitionModal @register="registerModal" @success="reload"></competitionModal>
 </template>
 
 <script setup lang="ts" name="businessManagement-competition">
+  import { QRCode, Modal } from 'ant-design-vue';
   import { BasicTable, TableAction } from '/@/components/Table';
   import { useListPage } from '/@/hooks/system/useListPage';
   import { searchFormSchema, columns } from './competition.data';
   import { deleteOne, list } from './competition.api';
   import { getcompetition } from '@/api/businessManagement/competition';
+  import competitionModal from './competitonModel.vue';
   import { router } from '/@/router';
-
+  import { ref } from 'vue';
+  import { useRouteTabText } from '/@/hooks/web/useRouteTab';
+  import { useModal } from '/@/components/Modal';
+  const openCode = ref(false);
+  const qrcodeCanvasRef = ref();
+  const CodeText = ref('');
+  useRouteTabText(['添加赛事', '编辑赛事', '查看赛事']);
   const { prefixCls, tableContext } = useListPage({
     designScope: 'competition-template',
     tableProps: {
@@ -66,6 +82,7 @@
     // },
   });
   const [registerTable, { reload, expandAll, collapseAll }] = tableContext;
+  const [registerModal, { openModal }] = useModal();
   function getTableAction(record) {
     return [
       {
@@ -94,8 +111,22 @@
         },
         // auth: 'courses:nm_courses:delete',
       },
+      {
+        label: '发布成绩',
+        onClick: handleEditGrades.bind(null, record),
+        // auth: 'courses:nm_courses:delete',
+      },
+      {
+        label: '赛事二维码',
+        onClick: handleViewCode.bind(null, record),
+        // auth: 'courses:nm_courses:delete',
+      },
     ];
   }
+  function handleViewCode(record) {
+    CodeText.value = `/pages/index/eventsDetail/index?id=${record.id}`;
+    openCode.value = true;
+  }
   async function handleDelete(record) {
     await deleteOne({ id: record.id }, reload);
   }
@@ -106,24 +137,23 @@
   function handleEdit(record) {
     router.push({ name: 'businessManagement-competitionCommon', query: { type: 1, id: record.id } });
   }
-</script>
 
-<script lang="ts">
-  import { useMultipleTabStore } from '@/store/modules/multipleTab';
-  import { storeToRefs } from 'pinia';
-  const typeList = ['添加赛事', '编辑赛事', '查看赛事'];
-  export default {
-    async beforeRouteLeave(to, from, next) {
-      to.meta.title = typeList[Number(to.query.type)];
-      const { getTabList } = storeToRefs(useMultipleTabStore());
-      const closeTab = getTabList.value.filter((it) => it.name == to.name).filter((it) => it.fullPath != to.fullPath);
-      if (closeTab.length) {
-        useMultipleTabStore().closeTabByKey(closeTab[0].fullPath, router);
-      }
-      await useMultipleTabStore().updateCacheTab();
-      next();
-    },
-  };
+  function handleEditGrades(record) {
+    openModal(true, {
+      record: record.game,
+      isUpdate: true,
+      showFooter: true,
+    });
+  }
+  async function downloadImage() {
+    const url = await qrcodeCanvasRef.value.toDataURL();
+    const a = document.createElement('a');
+    a.download = 'competition.png';
+    a.href = url;
+    document.body.appendChild(a);
+    a.click();
+    document.body.removeChild(a);
+  }
 </script>
 
 <style scoped></style>

+ 79 - 0
src/views/businessManagement/courses/components/coursesModel.vue

@@ -0,0 +1,79 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose title="编辑补课课表" :width="800" @ok="handleSubmit">
+    <BasicForm @register="registerForm" name="ProjectForm">
+      <template #courses> </template>
+    </BasicForm>
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { coursesForm } from '../courses.data';
+  import { saveOrUpdate } from '../courses.api';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { createMessage } = useMessage();
+  // Emits声明
+  const emit = defineEmits(['register', 'success']);
+  const isUpdate = ref(true);
+  const isDetail = ref(false);
+  //表单配置
+  const [registerForm, { setProps, resetFields, setFieldsValue, validate, scrollToField }] = useForm({
+    labelWidth: 150,
+    schemas: coursesForm,
+    showActionButtonGroup: false,
+    baseColProps: { span: 24 },
+  });
+  //表单赋值
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+    //重置表单
+    await resetFields();
+    setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
+    isUpdate.value = !!data?.isUpdate;
+    isDetail.value = !!data?.showFooter;
+    if (unref(isUpdate)) {
+      //表单赋值
+      await setFieldsValue({
+        ...data.record,
+      });
+    }
+    // 隐藏底部时禁用整个表单
+    setProps({ disabled: !data?.showFooter });
+  });
+  //表单提交事件
+  async function handleSubmit(v) {
+    try {
+      let values = await validate();
+      setModalProps({ confirmLoading: true });
+      //提交表单
+      await saveOrUpdate(values, isUpdate.value);
+      //关闭弹窗
+      closeModal();
+      //刷新列表
+      emit('success');
+    } catch ({ errorFields }) {
+      if (errorFields) {
+        const firstField = errorFields[0];
+        if (firstField) {
+          scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
+        }
+      }
+      return Promise.reject(errorFields);
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-calendar-picker) {
+    width: 100%;
+  }
+</style>

+ 19 - 0
src/views/businessManagement/courses/courses.api.ts

@@ -11,6 +11,8 @@ enum Api {
   detaile = 'app/appCourese/queryById',
   address = '/app/appCourese/queryAddressList',
   Coach = '/staff/staff/queryList',
+  queryCourseList = '/app/appCoursesPriceRules/queryListByCoursesId',
+  editPriceRules = '/app/appCoursesPriceRules/editPriceRules',
 }
 export const list = (params) => defHttp.get({ url: Api.list, params });
 
@@ -47,3 +49,20 @@ export const getAddress = (params) => {
 export const getCoachList = (params) => {
   return defHttp.get({ url: Api.Coach, params });
 };
+
+/**课程价格规则表-通过课程id查询
+ *
+ * @param params
+ * @returns
+ */
+export const queryCourseList = (params) => {
+  return defHttp.get({ url: Api.queryCourseList, params });
+};
+/**
+ * 课程价格规则表-补课列表编辑
+ * @param params
+ * @returns
+ */
+export const editPriceRules = (params) => {
+  return defHttp.get({ url: Api.editPriceRules, params });
+};

+ 17 - 0
src/views/businessManagement/courses/courses.data.ts

@@ -323,3 +323,20 @@ export const publishcoursesColums: BasicColumn[] = [
     width: 80,
   },
 ];
+
+export const coursesForm: FormSchema[] = [
+  {
+    label: '课程名称',
+    field: 'name',
+    component: 'Input',
+    componentProps: {
+      disabled: true,
+    },
+  },
+  {
+    label: '补课课表',
+    field: 'dtoList',
+    component: 'Input',
+    slot: 'courses',
+  },
+];

+ 19 - 20
src/views/businessManagement/courses/index.vue

@@ -38,23 +38,24 @@
     </BasicTable>
     <!-- 表单区域 -->
   </div>
+  <coursesModel @register="registerModal"></coursesModel>
 </template>
 
 <script lang="ts" name="courses" setup>
   import { Switch } from 'ant-design-vue';
   import { router } from '/@/router';
-
+  import coursesModel from './components/coursesModel.vue';
   import { ref, reactive, computed, unref } from 'vue';
   import { BasicTable, TableImg, TableAction } from '/@/components/Table';
   import { useModal } from '/@/components/Modal';
   import { useListPage } from '/@/hooks/system/useListPage';
   import { columns, searchFormSchema } from './courses.data';
-  import { list, deleteOne } from './courses.api';
+  import { list, deleteOne, editPriceRules, queryCourseList } from './courses.api';
   import { useUserStore } from '/@/store/modules/user';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useShopInfoStore } from '/@/store/modules/shopInfo';
+  import { useRouteTabText } from '/@/hooks/web/useRouteTab';
   const queryParam = reactive<any>({});
-  const checkedKeys = ref<Array<string | number>>([]);
   const userStore = useUserStore();
   const { createMessage } = useMessage();
   useShopInfoStore().getCurrentDep();
@@ -142,26 +143,24 @@
         },
         // auth: 'courses:nm_courses:delete',
       },
+      {
+        label: '编辑补课课表',
+        onClick: handleEditCourses.bind(null, record),
+        // auth: 'courses:nm_courses:delete',
+      },
     ];
   }
+  async function handleEditCourses(record) {
+    const res = await queryCourseList({ coursesType: 1, id: record.id });
+    openModal(true, {
+      record,
+      isUpdate: false,
+      showFooter: true,
+    });
+  }
+  useRouteTabText(['发布课程', '编辑课程', '查看课程']);
 </script>
-<script lang="ts">
-  import { useMultipleTabStore } from '@/store/modules/multipleTab';
-  import { storeToRefs } from 'pinia';
-  const typeList = ['发布课程', '编辑课程', '查看课程'];
-  export default {
-    async beforeRouteLeave(to, from, next) {
-      to.meta.title = typeList[Number(to.query.type)];
-      const { getTabList } = storeToRefs(useMultipleTabStore());
-      const closeTab = getTabList.value.filter((it) => it.name == to.name).filter((it) => it.fullPath != to.fullPath);
-      if (closeTab.length) {
-        useMultipleTabStore().closeTabByKey(closeTab[0].fullPath, router);
-      }
-      await useMultipleTabStore().updateCacheTab();
-      next();
-    },
-  };
-</script>
+
 <style lang="less" scoped>
   :deep(.ant-picker),
   :deep(.ant-input-number) {

+ 9 - 1
src/views/businessManagement/schoolOpen/index.vue

@@ -3,7 +3,10 @@
     <div class="px-4">
       <BasicForm @register="registerForm">
         <template #title1>
-          <TypographyTitle :level="4">开放时间</TypographyTitle>
+          <div class="flex">
+            <TypographyTitle :level="4">开放时间</TypographyTitle>
+            <a-button type="link" @click="handleClick">查看教学日与非教学日日历</a-button>
+          </div>
           <Divider></Divider>
         </template>
         <template #title2>
@@ -36,6 +39,8 @@
   import { getDetails, saveOrUpdate } from './schoolOpen.api';
   import { useShopInfoStore } from '/@/store/modules/shopInfo';
   import { onUnmounted, ref } from 'vue';
+  import { useRouter } from 'vue-router';
+  const router = useRouter();
   useShopInfoStore().getCurrentDep();
   const [registerForm, { setProps, resetFields, setFieldsValue, updateSchema, validate, clearValidate, getFieldsValue }] = useForm({
     schemas: formSchema,
@@ -96,4 +101,7 @@
   onUnmounted(() => {
     useShopInfoStore().isShowSelect = false;
   });
+  function handleClick() {
+    router.push({ path: '/informationManagement/teachorNoteach' });
+  }
 </script>

+ 24 - 0
src/views/informationManagement/ContractList/ContractList.api.ts

@@ -0,0 +1,24 @@
+import { ex } from '@fullcalendar/core/internal-common';
+import { defHttp } from '/@/utils/http/axios';
+enum Api {
+  list = '/api/esign/queryContractInfo',
+  getOrgIdentityInfos = '/api/esign/getOrgIdentityInfos',
+  uploadContractTemplate = '/api/esign/uploadContractTemplate',
+  createTemplate = '/api/esign/createTemplate',
+}
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) => defHttp.get({ url: Api.list, params });
+
+export const getOrgIdentityInfos = (params) => {
+  return defHttp.get({ url: Api.getOrgIdentityInfos, params });
+};
+export const uploadContractTemplate = (params) => {
+  return defHttp.post({ url: Api.uploadContractTemplate, params });
+};
+
+export const createTemplate = (params) => {
+  return defHttp.get({ url: Api.createTemplate, params });
+};

+ 86 - 0
src/views/informationManagement/ContractList/ContractList.data.ts

@@ -0,0 +1,86 @@
+import { h } from 'vue';
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { Tag } from 'ant-design-vue';
+import { BasicUpload } from '/@/components/Upload';
+//列表数据
+export const columns: BasicColumn[] = [
+  {
+    title: '底稿名称',
+    align: 'center',
+    dataIndex: 'contractName',
+  },
+  {
+    title: '类型',
+    align: 'center',
+    dataIndex: 'avatar',
+    slots: { customRender: 'img' },
+  },
+  {
+    title: '说明',
+    align: 'center',
+    dataIndex: 'remark',
+  },
+  {
+    title: '底稿上传状态',
+    align: 'center',
+    dataIndex: 'status',
+    customRender: ({ record }) => {
+      return h(Tag, { color: 'green' }, () => '成功');
+    },
+  },
+  {
+    title: '更新时间',
+    align: 'center',
+    dataIndex: 'updateTime',
+  },
+];
+//查询数据
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '底稿名称',
+    field: 'contractName',
+    component: 'Input',
+    colProps: { span: 6 },
+  },
+];
+//表单数据
+export const formSchema: FormSchema[] = [
+  {
+    label: '底稿名称',
+    field: 'contractName',
+    component: 'Input',
+    required: true,
+  },
+  {
+    label: '说明',
+    field: 'remark',
+    component: 'InputTextArea',
+    required: true,
+  },
+  {
+    label: '文件上传',
+    field: 'filePath',
+    component: 'Input',
+    required: true,
+    // renderComponentContent() {
+    //   return h(BasicUpload);
+    // },
+  },
+  // TODO 主键隐藏字段,目前写死为ID
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+];
+
+/**
+ * 流程表单调用这个方法获取formSchema
+ * @param param
+ */
+export function getBpmFormSchema(_formData): FormSchema[] {
+  // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema
+  return formSchema;
+}

+ 70 - 0
src/views/informationManagement/ContractList/components/ContractListModal.vue

@@ -0,0 +1,70 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" @ok="handleSubmit" :width="800">
+    <div class="px-4">
+      <BasicForm @register="registerForm" name="SeparateAccountsForm"> </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+  import { TypographyTitle, Divider } from 'ant-design-vue';
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { formSchema } from '../ContractList.data';
+  import { uploadContractTemplate } from '../ContractList.api';
+  const isUpdate = ref(true);
+  const isDetail = ref(false);
+  const emit = defineEmits(['register', 'success']);
+  //表单配置
+  const [registerForm, { setProps, resetFields, setFieldsValue, validate, scrollToField, getFieldsValue }] = useForm({
+    labelWidth: 150,
+    schemas: formSchema,
+    showActionButtonGroup: false,
+    baseColProps: { span: 24 },
+  });
+  const title = computed(() => (!unref(isUpdate) ? '添加合同底稿' : '编辑合同底稿'));
+  //表单赋值
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+    //重置表单
+    await resetFields();
+    setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
+    isUpdate.value = !!data?.isUpdate;
+    isDetail.value = !!data?.showFooter;
+    if (unref(isUpdate)) {
+      //表单赋值
+      await setFieldsValue({
+        ...data.record,
+      });
+    }
+
+    // 隐藏底部时禁用整个表单
+    setProps({ disabled: !data?.showFooter });
+  });
+  //表单提交事件
+  async function handleSubmit(v) {
+    try {
+      let values = await validate();
+      setModalProps({ confirmLoading: true });
+      //提交表单
+      await uploadContractTemplate(values);
+      //关闭弹窗
+      closeModal();
+      //刷新列表
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-calendar-picker) {
+    width: 100%;
+  }
+</style>

+ 110 - 0
src/views/informationManagement/ContractList/index.vue

@@ -0,0 +1,110 @@
+<template>
+  <div>
+    <!--引用表格-->
+    <BasicTable @register="registerTable">
+      <template #tableTitle>
+        <a-button type="primary" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
+      </template>
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
+      </template>
+      <template #img="{ text }">
+        <TableImg :img-list="[text]" :size="120"></TableImg>
+      </template>
+    </BasicTable>
+    <!-- 表单区域 -->
+    <ContractListModal @register="registerModal"></ContractListModal>
+  </div>
+</template>
+
+<script lang="ts" setup name="cUserInfo">
+  import { reactive } from 'vue';
+  import { BasicTable, TableAction, TableImg } from '/@/components/Table';
+  import { useModal } from '/@/components/Modal';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  import ContractListModal from './components/ContractListModal.vue';
+  import { columns, searchFormSchema } from './ContractList.data';
+  import { list, getDetaile, getOrgIdentityInfos, createTemplate } from './ContractList.api';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useUserStore } from '/@/store/modules/user';
+  const queryParam = reactive<any>({});
+  const { createMessage, createWarningModal } = useMessage();
+  const { userInfo } = useUserStore();
+  //注册model
+  const [registerModal, { openModal }] = useModal();
+  //注册table数据
+  const { prefixCls, tableContext } = useListPage({
+    tableProps: {
+      title: 'ContractList',
+      api: list,
+      columns,
+      canResize: false,
+      formConfig: {
+        //labelWidth: 120,
+        schemas: searchFormSchema,
+        autoSubmitOnEnter: true,
+        showAdvancedButton: true,
+        fieldMapToNumber: [],
+        fieldMapToTime: [],
+      },
+      actionColumn: {
+        width: 300,
+        fixed: 'right',
+      },
+      beforeFetch: (params) => {
+        return Object.assign(params, queryParam);
+      },
+    },
+  });
+
+  const [registerTable] = tableContext;
+  async function handleView(record: Recordable) {
+    console.log(record);
+  }
+  /**
+   * 操作栏
+   */
+  function getTableAction(record) {
+    return [
+      {
+        label: '编辑底稿',
+        onClick: handleView.bind(null, record),
+      },
+      {
+        label: '制作合同',
+        onClick: handleGoEsign.bind(null, record),
+      },
+      {
+        label: '编辑合同',
+        onClick: handleView.bind(null, record),
+      },
+      {
+        label: '预览合同',
+        onClick: handleView.bind(null, record),
+      },
+    ];
+  }
+  async function handleGoEsign(record) {
+    const res = await createTemplate({ fileId: record.fileId });
+  }
+  /**
+   * 下拉操作栏
+   */
+  function getDropDownAction(record) {
+    return [];
+  }
+  async function handleAdd() {
+    // const res = await getOrgIdentityInfos({ orgCode: userInfo?.orgCode });
+    openModal(true, {
+      isUpdate: false,
+      showFooter: true,
+    });
+  }
+</script>
+
+<style lang="less" scoped>
+  :deep(.ant-picker),
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+</style>