Procházet zdrojové kódy

Merge branch 'cc-dev' into 'dev'

完成应急物资页面

See merge request product-group-fe/sfy-safety-group/sfy-safety!130
陈昶 před 9 měsíci
rodič
revize
9d68d0cbb6
32 změnil soubory, kde provedl 1902 přidání a 71 odebrání
  1. 108 2
      src/api/emergency-supplier/index.ts
  2. 11 0
      src/api/system/person-group.ts
  3. 5 11
      src/components/BasicTable.vue
  4. 30 0
      src/components/TableColumn.vue
  5. 3 0
      src/constant/dict.ts
  6. 2 2
      src/hooks/useFormConfigHook.ts
  7. 18 1
      src/hooks/useTableConfigHook.ts
  8. 21 2
      src/router/routers/disaster.ts
  9. 63 3
      src/router/routers/emergency.ts
  10. 2 1
      src/types/basic-table/index.ts
  11. 61 2
      src/types/emergency-supplier/index.ts
  12. 12 0
      src/types/person-group/type.ts
  13. 50 0
      src/views/emergency/emergency-supplies/PageEmergencyDetail.vue
  14. 79 7
      src/views/emergency/emergency-supplies/PageEmergencyList.vue
  15. 61 0
      src/views/emergency/emergency-supplies/PageEmergencyListInfo.vue
  16. 343 0
      src/views/emergency/emergency-supplies/PageInventoryCheck.vue
  17. 176 0
      src/views/emergency/emergency-supplies/src/components/AddEmergencyItem.vue
  18. 42 0
      src/views/emergency/emergency-supplies/src/components/ChangeRecord.vue
  19. 176 0
      src/views/emergency/emergency-supplies/src/components/EditEmergencyItem.vue
  20. 4 0
      src/views/emergency/emergency-supplies/src/components/InventoryTask.vue
  21. 66 0
      src/views/emergency/emergency-supplies/src/components/ViewEmergencyItem.vue
  22. 246 0
      src/views/emergency/emergency-supplies/src/config/form.ts
  23. 35 2
      src/views/emergency/emergency-supplies/src/config/index.ts
  24. 42 18
      src/views/emergency/emergency-supplies/src/config/search.ts
  25. 134 6
      src/views/emergency/emergency-supplies/src/config/table.ts
  26. 42 11
      src/views/emergency/emergency-supplies/src/constant/index.ts
  27. 51 0
      src/views/emergency/emergency-supplies/src/hook.ts
  28. 5 0
      src/views/emergency/emergency-supplies/src/styles/info-common.scss
  29. 3 0
      src/views/emergency/emergency-supplies/src/styles/info.scss
  30. 6 0
      src/views/emergency/emergency-supplies/src/styles/page-common.scss
  31. 3 0
      src/views/emergency/emergency-supplies/src/svg/img-preview.svg
  32. 2 3
      utils/devProxy/staff/proxy.ts

+ 108 - 2
src/api/emergency-supplier/index.ts

@@ -1,6 +1,16 @@
-import { http } from '@/utils/http/axios/mock';
+import { http } from '@/utils/http/axios';
 import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
-import type { EmergencySupplyListQuery, EmergencySupplyListResponse } from '@/types/emergency-supplier';
+import type {
+  EmergencySupplyListQuery,
+  EmergencySupplyListResponse,
+  EmergencySupplyDetailResponse,
+  AddEmergencyItemForm,
+  EditEmergencyItemForm,
+  ChangeRecordListRes,
+  InventoryTaskListRes,
+  InventoryCheckListQuery,
+  InventoryCheckListResponse,
+} from '@/types/emergency-supplier';
 
 /**
  * 获取物资清单列表
@@ -12,3 +22,99 @@ export function getEmergencySupplyList(query: QueryPageRequest<EmergencySupplyLi
     data: query,
   });
 }
+/**
+ * 删除应急物资
+ */
+export const deleteEmergencySupplyList = (supplyId: number) => {
+  return http.request({
+    url: `/emergencySupplies/deleteEmergencySupply?supplyId=${supplyId}`,
+    method: 'post',
+  });
+};
+/**
+ * 查询应急物资详情
+ */
+export const getEmergencySupplyDetail = (supplyId: number) => {
+  return http.request<EmergencySupplyDetailResponse>({
+    url: `/emergencySupplies/queryEmergencySupplyInfo?supplyId=${supplyId}`,
+    method: 'get',
+  });
+};
+/**
+ * 添加应急物资
+ */
+export const addEmergencySupply = (data: AddEmergencyItemForm) => {
+  return http.request({
+    url: '/emergencySupplies/saveEmergencySupply',
+    method: 'post',
+    data,
+  });
+};
+/**
+ * 修改应急物资
+ */
+export const editEmergencySupply = (data: EditEmergencyItemForm) => {
+  return http.request({
+    url: '/emergencySupplies/updateEmergencySupply',
+    method: 'post',
+    data,
+  });
+};
+/**
+ * 查询物资变更记录
+ */
+export const getSupplyChangeRecord = (supplyId: number) => {
+  return http.request<ChangeRecordListRes[]>({
+    url: `/emergencySupplies/querySupplyChangeRecord?supplyId=${supplyId}`,
+    method: 'post',
+  });
+};
+/**
+ * 获取盘点任务列表
+ */
+export const getInventoryTaskList = () => {
+  return http.request<InventoryTaskListRes[]>({
+    url: '/emergencySupplies/queryInventoryTaskList',
+    method: 'get',
+  });
+};
+
+/**
+ * 查询盘点物资
+ */
+export const getInventoryCheckList = (query: QueryPageRequest<InventoryCheckListQuery>) => {
+  return http.request<QueryPageResponse<InventoryCheckListResponse>>({
+    url: '/emergencySupplies/queryInventorySuppliesPage',
+    method: 'post',
+    data: query,
+  });
+};
+/**
+ * 盘点任务确认
+ */
+export const confirmInventoryTask = (taskId: number) => {
+  return http.request({
+    url: `/emergencySupplies/updateInventoryTaskConfirm?taskId=${taskId}`,
+    method: 'post',
+  });
+};
+/**
+ * 查询是否可以盘点
+ */
+export const getInventoryTaskConfirm = (taskId: number) => {
+  return http.request<{ taskState: number }>({
+    url: '/emergencySupplies/queryInventoryTaskById',
+    method: 'get',
+    params: { taskId },
+  });
+};
+/**
+ * 发起盘点任务
+ */
+export const saveInventoryTask = (taskName: string, endTime: string) => {
+  return http.request<{ taskState: number }>({
+    url: '/emergencySupplies/saveInventoryTask',
+    method: 'post',
+    data: { taskName, endTime },
+  });
+};

+ 11 - 0
src/api/system/person-group.ts

@@ -9,6 +9,7 @@ import {
   QueryAvailablePersonPageRes,
   UserGroupOption,
   PersonGroupItem,
+  QueryUserInfoByUserNameRes,
 } from '@/types/person-group/type';
 
 /**
@@ -115,3 +116,13 @@ export const queryUserInfoByIds = (userIds: number[]) => {
     method: 'get',
   });
 };
+
+/**
+ * 根据用户名查询用户列表
+ */
+export const queryUserInfoByUserName = (username: string) => {
+  return http.request<QueryUserInfoByUserNameRes[]>({
+    url: `/admin/user/queryUserListByUsername?username=${username}`,
+    method: 'post',
+  });
+};

+ 5 - 11
src/components/BasicTable.vue

@@ -12,18 +12,11 @@
         <img :src="EmptyImg" alt="empty" />
         <span>{{ props.tableConfig.emptyText }}</span>
       </template>
-      <el-table-column
-        v-for="column in props.tableConfig.columns"
-        :key="column.prop"
-        v-bind="column"
-        :prop="column.prop"
-        :label="column.label"
-      >
-        <template #default="scope">
-          <Render v-if="column.render" :render="column.render" :row="scope.row" />
-          <slot v-else-if="column.slot" :name="column.slot" :row="scope.row" />
+      <TableColumn v-for="column in props.tableConfig.columns" :key="column.prop || column.label" :column="column">
+        <template v-for="(_, name) in $slots" #[name]="slotData">
+          <slot :name="name" v-bind="slotData" />
         </template>
-      </el-table-column>
+      </TableColumn>
     </el-table>
     <el-pagination
       v-if="props.tableConfig.pagination && props.tableConfig.pagination.total > 0"
@@ -41,6 +34,7 @@
 
 <script lang="ts" setup>
   import { ref } from 'vue';
+  import TableColumn from './TableColumn.vue';
   import type { ElTable } from 'element-plus';
   import type { BasicTableProps } from '@/types/basic-table';
   import { PAGE_SIZE_CONFIG, DEFAULT_PAGINATION_LAYOUT } from '@/constant/pagination';

+ 30 - 0
src/components/TableColumn.vue

@@ -0,0 +1,30 @@
+<template>
+  <el-table-column v-if="column.children && column.children.length" :label="column.label" v-bind="getColumnAttrs">
+    <TableColumn v-for="child in column.children" :key="child.prop || child.label" :column="child">
+      <template v-for="(_, name) in $slots" #[name]="slotData">
+        <slot :name="name" v-bind="slotData" />
+      </template>
+    </TableColumn>
+  </el-table-column>
+  <el-table-column v-else :prop="column.prop" :label="column.label" v-bind="getColumnAttrs">
+    <template #default="scope">
+      <Render v-if="column.render" :render="column.render" :row="scope.row" />
+      <slot v-else-if="column.slot" :name="column.slot" :row="scope.row" />
+    </template>
+  </el-table-column>
+</template>
+
+<script setup lang="ts">
+  import { computed } from 'vue';
+  import Render from './Render.vue';
+  import type { TableColumnProps } from '@/types/basic-table';
+
+  const props = defineProps<{
+    column: TableColumnProps;
+  }>();
+
+  const getColumnAttrs = computed(() => {
+    const { children, prop, label, render, slot, ...rest } = props.column;
+    return rest;
+  });
+</script>

+ 3 - 0
src/constant/dict.ts

@@ -6,4 +6,7 @@ export const DICT_CODE = {
   SAFETY_LEVEL: 'disaster_safety_impact',
   EMERGENCY_EVENT: 'emergency_event',
   EMERGENCY_SUPPLY: 'emergency_supply',
+  PARK: 'emergency_park',
+  LOCATION_ZJ: 'zhangjiang_park',
+  LOCATION_DC: 'dachang_park',
 };

+ 2 - 2
src/hooks/useFormConfigHook.ts

@@ -22,12 +22,12 @@ import { msgConfirm } from '@/utils/element-plus/messageBox';
 export const useFormConfigHook = <T extends Record<string, any> = Record<string, any>>(
   config: FormConfig[],
   data: T,
-  rules: FormRules<T>,
+  rules?: FormRules<T>,
 ) => {
   const ruleFormConfig = ref<FormConfig[]>(config);
   const ruleFormData = reactive<T>(cloneDeep(data));
   const initRuleFormData = ref<T>();
-  const formRules = reactive<FormRules<T>>(rules);
+  const formRules = reactive<FormRules<T>>(rules || {});
   const cloneRuleFormData = () => {
     initRuleFormData.value = cloneDeep(ruleFormData as T);
   };

+ 18 - 1
src/hooks/useTableConfigHook.ts

@@ -4,9 +4,26 @@ import { reactive } from 'vue';
 
 /**
  * 表格配置hook
- * @param columns 表格列配置
+ * @param columns 表格列配置,支持多级表头
  * @param options 表格选项配置 element-plus表格配置
  * @example
+ * // 普通表头
+ * const columns = [
+ *   { prop: 'id', label: 'ID' },
+ *   { prop: 'name', label: '姓名' }
+ * ];
+ *
+ * // 多级表头
+ * const columns = [
+ *   { prop: 'id', label: 'ID' },
+ *   {
+ *     label: '个人信息',
+ *     children: [
+ *       { prop: 'name', label: '姓名' },
+ *       { prop: 'age', label: '年龄' }
+ *     ]
+ *   }
+ * ];
  * const { tableConfig, pagination } = useTableConfig(columns, options);
  * @returns 表格配置项和分页处理方法
  * @description 用于BasicTable组件的配置

+ 21 - 2
src/router/routers/disaster.ts

@@ -22,7 +22,6 @@ const disasterPreventionRoute = {
       redirect: '',
     },
     {
-      // component: '/disaster/monitor/PageMonitor',
       component: '/disaster/monitor/splitScreenRetrieval/SplitScreenRetrieval',
       id: 1026,
       meta: {
@@ -64,6 +63,26 @@ const disasterPreventionRoute = {
           path: 'warning-info',
           redirect: '',
         },
+        {
+          component: '/disaster/disaster-warning/PageWarningInfoItem',
+          id: 1036,
+          meta: {
+            activeMenu: '/disaster-prevention/disaster-warning/warning-info',
+            alwaysShow: false,
+            frameSrc: '',
+            hidden: false,
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+            title: '预警信息详情',
+          },
+          name: 'warning-info-item',
+          parentId: 1027,
+          path: 'warning-info-item',
+          redirect: '',
+        },
         {
           component: '/disaster/disaster-warning/PageDefenseNotice',
           id: 1037,
@@ -88,7 +107,7 @@ const disasterPreventionRoute = {
           component: '/disaster/disaster-warning/PageDefenseNoticeItem',
           id: 1038,
           meta: {
-            activeMenu: null,
+            activeMenu: '/disaster-prevention/disaster-warning/defense-notice',
             alwaysShow: false,
             frameSrc: '',
             hidden: false,

+ 63 - 3
src/router/routers/emergency.ts

@@ -100,10 +100,10 @@ const emergencyManagementRoute = {
       },
       children: [
         {
-          id: 200401,
+          id: 2005,
           parentId: 2004,
-          name: 'supplies-list',
-          path: 'supplies-list',
+          name: 'emergency-list',
+          path: 'emergency-list',
           component: '/emergency/emergency-supplies/PageEmergencyList',
           redirect: '',
           meta: {
@@ -119,6 +119,66 @@ const emergencyManagementRoute = {
             title: '物资清单',
           },
         },
+        {
+          id: 2006,
+          parentId: 2004,
+          name: 'inventory-check',
+          path: 'inventory-check',
+          component: '/emergency/emergency-supplies/PageInventoryCheck',
+          redirect: '',
+          meta: {
+            activeMenu: null,
+            alwaysShow: false,
+            frameSrc: '',
+            hidden: false,
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+            title: '物资盘点',
+          },
+        },
+        {
+          id: 2007,
+          parentId: 2004,
+          name: 'emergency-list-info',
+          path: 'emergency-list-info',
+          component: '/emergency/emergency-supplies/PageEmergencyListInfo',
+          redirect: '',
+          meta: {
+            activeMenu: '/emergency-management/emergency-supplies/emergency-list',
+            alwaysShow: false,
+            frameSrc: '',
+            hidden: false,
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+            title: '物资清单详情',
+          },
+        },
+        {
+          id: 2008,
+          parentId: 2004,
+          name: 'emergency-list-detail',
+          path: 'emergency-list-detail/:id',
+          component: '/emergency/emergency-supplies/PageEmergencyDetail',
+          redirect: '',
+          meta: {
+            activeMenu: '/emergency-management/emergency-supplies/emergency-list',
+            alwaysShow: false,
+            frameSrc: '',
+            hidden: false,
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+            title: '物资清单详情',
+          },
+        },
       ],
     },
   ],

+ 2 - 1
src/types/basic-table/index.ts

@@ -1,8 +1,9 @@
 export interface TableColumnProps {
-  prop: string; // 字段名
+  prop?: string; // 字段名
   label: string; // 列名
   slot?: string; // 自定义插槽名
   render?: Function; // 自定义渲染函数
+  children?: TableColumnProps[]; // 子列,用于多级表头
   [key: string]: any; // element-plus 表格列配置
 }
 

+ 61 - 2
src/types/emergency-supplier/index.ts

@@ -9,7 +9,10 @@ interface BasicResponse {
   id: number;
   emergencyType: string;
   supplyType: string;
+  supplyName: string;
   remark: string;
+  supplementQuantity: number;
+  park: string;
 }
 
 export interface EmergencySupplyListQuery extends BasicListQuery {
@@ -18,19 +21,75 @@ export interface EmergencySupplyListQuery extends BasicListQuery {
   status?: number | null;
 }
 
+export interface InventoryCheckListQuery extends BasicListQuery {
+  taskId?: number | null;
+  inventoryResult?: number | null;
+}
+
 export interface EmergencySupplyListResponse extends BasicResponse {
   requiredQuantity: number;
   currentQuantity: number;
   unit: string;
-  park: string;
   location: string;
   keeperName: string;
   expirationDate: string;
   status: number;
-  supplementQuantity: number;
+}
+
+export interface InventoryCheckListResponse extends BasicResponse {
+  beforeKeeperName: string;
+  afterKeeperName: string;
+  beforeQuantity: number;
+  afterQuantity: number;
+  beforeLocation: string;
+  afterLocation: string;
+  inventoryResult: number;
+  imageList: string;
 }
 
 export interface InventoryTaskForm {
   taskName: string;
   endTime: string;
 }
+
+export interface AddEmergencyItemForm
+  extends Omit<
+    EmergencySupplyListResponse,
+    'id' | 'status' | 'supplementQuantity' | 'requiredQuantity' | 'currentQuantity'
+  > {
+  requiredQuantity: number | null;
+  currentQuantity: number | null;
+  keeperId: number | null;
+}
+
+export interface EmergencySupplyDetailResponse extends AddEmergencyItemForm {
+  id: number | null;
+  keeperName: string;
+}
+
+export interface EditEmergencyItemForm extends EmergencySupplyDetailResponse {
+  emergencyTypeName: string;
+  supplyTypeName: string;
+}
+
+export interface ViewEmergencyItemForm extends EditEmergencyItemForm {
+  parkName: string;
+  locationName: string;
+}
+
+export interface ChangeRecordListRes {
+  id: number;
+  changeType: number;
+  detail: string;
+  beforeQuantity: number;
+  changeQuantity: number;
+  afterQuantity: number;
+  operatorName: string;
+  updatedAt: string;
+}
+
+export interface InventoryTaskListRes {
+  id: number;
+  taskName: string;
+  endTime: string; //任务截止时间
+}

+ 12 - 0
src/types/person-group/type.ts

@@ -120,3 +120,15 @@ export interface QueryUserFirstLevelTreeRes {
   userList: PersonGroupItem[];
   children: QueryUserFirstLevelTreeRes[];
 }
+
+/**
+ * 根据姓名查询用户信息返回结果
+ */
+export interface QueryUserInfoByUserNameRes {
+  id: number;
+  username: string;
+  realname: string;
+  mobile: string;
+  deptName: string;
+  jobName: string;
+}

+ 50 - 0
src/views/emergency/emergency-supplies/PageEmergencyDetail.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="safety-platform-container">
+    <div class="safety-platform-container__header">
+      <div class="bread">
+        <BreadcrumbBack />
+        <div class="breadcrumb-title">应急物资详情</div>
+      </div>
+      <el-tabs v-model="activeName">
+        <el-tab-pane v-for="item in EMERGENCY_SUPPLY_TABS" :key="item.value" :label="item.label" :name="item.value" />
+      </el-tabs>
+    </div>
+    <main class="safety-platform-container__main">
+      <component :is="dynamicComponent" :id="id" />
+    </main>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { useRoute } from 'vue-router';
+  import { computed, ref,defineAsyncComponent } from 'vue';
+  import { EMERGENCY_SUPPLY_TABS } from './src/constant';
+
+  const route = useRoute();
+  const activeName = ref(EMERGENCY_SUPPLY_TABS[0].value);
+  const id = Number(route.params.id);
+  const dynamicComponent = computed(()=>{
+    if(activeName.value === EMERGENCY_SUPPLY_TABS[0].value){
+      return defineAsyncComponent(()=>import('./src/components/ViewEmergencyItem.vue'))
+    }
+    return defineAsyncComponent(()=>import('./src/components/ChangeRecord.vue'))
+  })
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  .bread {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  .safety-platform-container__header {
+    padding-bottom: 0 !important;
+  }
+  :deep(.el-tabs__header) {
+    margin: 0;
+  }
+  :deep(.el-tabs__item) {
+    font-size: 14px !important;
+  }
+</style>

+ 79 - 7
src/views/emergency/emergency-supplies/PageEmergencyList.vue

@@ -10,9 +10,10 @@
             type="primary"
             class="search-table-container--button"
             :icon="Plus"
+            @click="handleAddSupply"
             v-if="emergencySupplyPermissions"
-            >添加物资</el-button
-          >
+            >添加物资
+          </el-button>
           <el-button
             type="primary"
             class="search-table-container--button"
@@ -30,7 +31,7 @@
             @update:searchData="handleSearch"
           >
             <template #emergencyType>
-              <el-select v-model="searchData.emergencyType" placeholder="请选择应急事件类型">
+              <el-select v-model="searchData.emergencyType" placeholder="请选择应急事件类型" filterable>
                 <el-option
                   v-for="item in emergencyEventDice"
                   :key="item.itemCode"
@@ -40,7 +41,7 @@
               </el-select>
             </template>
             <template #supplyType>
-              <el-select v-model="searchData.supplyType" placeholder="请选择物资类型">
+              <el-select v-model="searchData.supplyType" placeholder="请选择物资类型" filterable>
                 <el-option
                   v-for="item in emergencySupplyDice"
                   :key="item.itemCode"
@@ -49,6 +50,16 @@
                 />
               </el-select>
             </template>
+            <template #park>
+              <el-select v-model="searchData.park" placeholder="请选择园区">
+                <el-option
+                  v-for="item in parkDice"
+                  :key="item.itemCode"
+                  :label="item.itemValue"
+                  :value="item.itemCode"
+                />
+              </el-select>
+            </template>
           </BasicSearch>
         </header>
         <BasicTable :tableData="tableData" :tableConfig="tableConfig">
@@ -58,21 +69,40 @@
           <template #supplyType="scope">
             <span>{{ getEmergencySupply(scope.row.supplyType) }}</span>
           </template>
+          <template #park="scope">
+            <span>{{ getPark(scope.row.park) }}</span>
+          </template>
+          <template #location="scope">
+            <span>{{ getLocation(scope.row.location) }}</span>
+          </template>
           <template #status="scope">
             <span :style="{ color: scope.row.status === EMERGENCY_SUPPLY_STATUS.DAMAGED ? 'red' : '' }">
               {{ getEmergencyStatus(scope.row.status) }}
             </span>
           </template>
+          <template #remark="scope">
+            <div class="remark--div">
+              <el-tooltip
+                :content="scope.row.remark"
+                placement="top"
+                effect="light"
+                :popper-class="['custom-tooltip', scope.row.remark.length > 10 ? '' : 'custom-tooltip--opacity0']"
+              >
+                <span>{{ scope.row.remark }}</span>
+              </el-tooltip>
+            </div>
+          </template>
           <template #action="scope">
             <div class="action-container--div">
-              <ActionButton text="查看" />
-              <ActionButton text="编辑" v-if="emergencySupplyPermissions" />
+              <ActionButton text="查看" @click="handleViewDetail(scope.row.id)"/>
+              <ActionButton text="编辑" v-if="emergencySupplyPermissions" @click="handleEditSupply(scope.row.id)" />
               <ActionButton
                 text="删除"
                 v-if="emergencySupplyPermissions"
                 :popconfirm="{
                   title: '确定删除?',
                 }"
+                @confirm="handleDeleteSupply(scope.row.id)"
               />
             </div>
           </template>
@@ -84,8 +114,10 @@
 </template>
 
 <script setup lang="ts">
+  import { useRouter } from 'vue-router';
   import { ref, reactive, onMounted } from 'vue';
   import { Plus } from '@element-plus/icons-vue';
+  import { ElMessage } from 'element-plus';
   // @ts-ignore
   import SvgIcon from '@/components/SvgIcon/SvgIcon.vue';
   import BasicSearch from '@/components/BasicSearch.vue';
@@ -104,10 +136,11 @@
   import { useEmergencySuppliesHook } from './src/hook';
   import type { EmergencySupplyListQuery, EmergencySupplyListResponse } from '@/types/emergency-supplier';
   import type { QueryPageRequest } from '@/types/basic-query';
-  import { getEmergencySupplyList } from '@/api/emergency-supplier';
+  import { getEmergencySupplyList, deleteEmergencySupplyList } from '@/api/emergency-supplier';
   import { EMERGENCY_PERMISSIONS } from '@/views/emergency/src/constant';
   import { EMERGENCY_SUPPLY_STATUS } from './src/constant';
 
+  const router = useRouter();
   const {
     emergencyEventDice,
     getEmergencyEventDict,
@@ -116,6 +149,11 @@
     getEmergencySupplyDict,
     getEmergencySupply,
     getEmergencyStatus,
+    parkDice,
+    getParkDict,
+    getPark,
+    getLocationDict,
+    getLocation,
   } = useEmergencySuppliesHook();
   const { permissions } = useUserInfoHook();
   const { tableConfig, pagination } = useTableConfig(SUPPLY_LIST_TABLE_COLUMNS, SUPPLY_LIST_TABLE_OPTIONS);
@@ -168,14 +206,48 @@
     tableConfig.loading = false;
   };
 
+  const handleDeleteSupply = async (id: number) => {
+    await deleteEmergencySupplyList(id);
+    await getTableData();
+    ElMessage.success('删除成功');
+  };
+
   const inventoryTaskRef = ref<InstanceType<typeof InventoryTask>>();
   const handleInventory = () => {
     inventoryTaskRef.value?.openDialog();
   };
 
+  const defaultRouterName = 'emergency-list-info';
+  const handleAddSupply = () => {
+    router.push({
+      name: defaultRouterName,
+      query: {
+        type: 'add',
+      },
+    });
+  };
+  const handleEditSupply = (id: number) => {
+    router.push({
+      name: defaultRouterName,
+      query: {
+        id,
+      },
+    });
+  };
+  const handleViewDetail = (id:number) => {
+    router.push({
+      name: 'emergency-list-detail',
+      params:{
+        id
+      }
+    })
+  }
+
   onMounted(() => {
     getEmergencyEventDict();
     getEmergencySupplyDict();
+    getParkDict();
+    getLocationDict('all');
     getTableData();
     emergencySupplyPermissions.value = Boolean(
       permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.SUPPLY_LIST),

+ 61 - 0
src/views/emergency/emergency-supplies/PageEmergencyListInfo.vue

@@ -0,0 +1,61 @@
+<template>
+  <div class="safety-platform-container">
+    <div class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <div class="breadcrumb-title">{{ headerTitle }}</div>
+    </div>
+    <main class="safety-platform-container__main">
+      <component ref="dynamicComponentRef" :is="dynamicComponent" :id="id" />
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">取消</el-button>
+      <el-button type="primary" @click="handleSubmit">提交</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { useRoute, useRouter } from 'vue-router';
+  import { ref, computed, defineAsyncComponent } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import { addEmergencySupply, editEmergencySupply } from '@/api/emergency-supplier';
+
+  const route = useRoute();
+  const router = useRouter();
+  const type = String(route.query.type);
+  const id = Number(route.query.id);
+  const dynamicComponentRef = ref();
+  const headerTitle = computed(() => {
+    const title = '应急物资';
+    if (type === 'add') {
+      return `添加${title}`;
+    }
+    return `编辑${title}`;
+  });
+  const dynamicComponent = computed(() => {
+    if (type === 'add') {
+      return defineAsyncComponent(() => import('./src/components/AddEmergencyItem.vue'));
+    }
+    return defineAsyncComponent(() => import('./src/components/EditEmergencyItem.vue'));
+  });
+  const handleSubmit = async() => {
+    if (!dynamicComponentRef.value) return;
+    const message = type === 'add' ? '添加' : '修改';
+    const res = await dynamicComponentRef.value.handleValidate();
+    if (!res) return;
+    const data = dynamicComponentRef.value.getFormData();
+    console.log(data);
+    if (type === 'add') {
+      await addEmergencySupply(data);
+    } else {
+      await editEmergencySupply(data);
+    }
+    ElMessage.success(`${message}成功`);
+    router.back();
+  };
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use './src/styles/info-common.scss' as *;
+</style>

+ 343 - 0
src/views/emergency/emergency-supplies/PageInventoryCheck.vue

@@ -0,0 +1,343 @@
+<template>
+  <div class="safety-platform-container">
+    <div class="safety-platform-container__header">
+      <div class="breadcrumb-title">物资盘点</div>
+    </div>
+    <div class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header class="disaster-precaution__header">
+          <BasicSearch
+            :searchConfig="INVENTORY_LIST_SEARCH_CONFIG"
+            :searchData="searchData"
+            @update:searchData="handleSearch"
+          >
+            <template #taskId>
+              <el-select v-model="searchData.taskId" placeholder="请选择盘点任务" filterable @change="handleTaskChange">
+                <el-option
+                  v-for="item in inventoryTaskOptions"
+                  :key="item.id"
+                  :label="item.taskName"
+                  :value="item.id"
+                />
+              </el-select>
+            </template>
+            <template #emergencyType>
+              <el-select v-model="searchData.emergencyType" placeholder="请选择应急事件类型" filterable>
+                <el-option
+                  v-for="item in emergencyEventDice"
+                  :key="item.itemCode"
+                  :label="item.itemValue"
+                  :value="item.itemCode"
+                />
+              </el-select>
+            </template>
+            <template #supplyType>
+              <el-select v-model="searchData.supplyType" placeholder="请选择物资类型" filterable>
+                <el-option
+                  v-for="item in emergencySupplyDice"
+                  :key="item.itemCode"
+                  :label="item.itemValue"
+                  :value="item.itemCode"
+                />
+              </el-select>
+            </template>
+          </BasicSearch>
+          <div class="date-time-container" v-if="endTime">
+            <span>任务期限:{{ endTime }}</span>
+            <el-button type="primary" :disabled="isTaskExpired" @click="handleConfirm" v-if="isInventory"
+              >盘点确认</el-button
+            >
+          </div>
+        </header>
+        <BasicTable :tableData="tableData" :tableConfig="tableConfig">
+          <template #emergencyType="scope">
+            <span>{{ getEmergencyEvent(scope.row.emergencyType) }}</span>
+          </template>
+          <template #supplyType="scope">
+            <span>{{ getEmergencySupply(scope.row.supplyType) }}</span>
+          </template>
+          <template #afterKeeperName="scope">
+            <span :class="{ 'font-red': scope.row.afterKeeperName !== scope.row.beforeKeeperName }">
+              {{ scope.row.afterKeeperName }}
+            </span>
+          </template>
+          <template #afterQuantity="scope">
+            <span :class="{ 'font-red': scope.row.afterQuantity !== scope.row.beforeQuantity }">
+              {{ scope.row.afterQuantity }}
+            </span>
+          </template>
+          <template #supplementQuantity="scope">
+            <span v-if="scope.row.supplementQuantity > 0">
+              <span class="font-red">{{ scope.row.supplementQuantity }}</span>
+            </span>
+            <span v-else>
+              <span>0</span>
+            </span>
+          </template>
+          <template #park="scope">
+            <span>{{ getPark(scope.row.park) }}</span>
+          </template>
+          <template #beforeLocation="scope">
+            <span>{{ getLocation(scope.row.beforeLocation) }}</span>
+          </template>
+          <template #afterLocation="scope">
+            <span :class="{ 'font-red': scope.row.afterLocation !== scope.row.beforeLocation }">
+              {{ getLocation(scope.row.afterLocation) }}
+            </span>
+          </template>
+          <template #inventoryResult="scope">
+            <span>{{ INVENTORY_RESULT_MAP[scope.row.inventoryResult] }}</span>
+          </template>
+          <template #remark="scope">
+            <div class="remark--div">
+              <el-tooltip
+                :content="scope.row.remark"
+                placement="top"
+                effect="light"
+                :popper-class="['custom-tooltip', scope.row.remark && scope.row.remark.length > 10 ? '' : 'custom-tooltip--opacity0']"
+              >
+                <span>{{ scope.row.remark }}</span>
+              </el-tooltip>
+            </div>
+          </template>
+          <template #imageList="scope">
+            <div class="image-list" v-if="scope.row.imageList && JSON.parse(scope.row.imageList).length > 0">
+              <el-image
+                :src="PreviewIcon"
+                :zoom-rate="1.2"
+                :max-scale="7"
+                :min-scale="0.2"
+                :preview-src-list="JSON.parse(scope.row.imageList)"
+                :initial-index="0"
+                show-progress
+                preview-teleported
+                fit="cover"
+              />
+            </div>
+            <span v-else>-</span>
+          </template>
+        </BasicTable>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted, watch, computed, onBeforeUnmount } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import BasicSearch from '@/components/BasicSearch.vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import {
+    INVENTORY_LIST_SEARCH_CONFIG,
+    INVENTORY_CHECK_TABLE_COLUMNS,
+    INVENTORY_CHECK_TABLE_OPTIONS,
+    INVENTORY_CHECK_TABLE_MAX_HEIGHT_PERMISSION,
+    INVENTORY_CHECK_TABLE_MAX_HEIGHT_DEFAULT,
+  } from './src/config';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import { useEmergencySuppliesHook } from './src/hook';
+  import { openMessageBox } from '@/utils/element-plus/messageBox';
+  import type {
+    InventoryCheckListQuery,
+    InventoryCheckListResponse,
+    InventoryTaskListRes,
+  } from '@/types/emergency-supplier';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import {
+    getInventoryTaskList,
+    getInventoryCheckList,
+    confirmInventoryTask,
+    getInventoryTaskConfirm,
+  } from '@/api/emergency-supplier';
+  import { INVENTORY_RESULT_MAP } from './src/constant';
+  import PreviewIcon from './src/svg/img-preview.svg';
+
+  const {
+    emergencyEventDice,
+    getEmergencyEventDict,
+    getEmergencyEvent,
+    emergencySupplyDice,
+    getEmergencySupplyDict,
+    getEmergencySupply,
+    getParkDict,
+    getPark,
+    getLocationDict,
+    getLocation,
+  } = useEmergencySuppliesHook();
+  const { tableConfig, pagination } = useTableConfig(INVENTORY_CHECK_TABLE_COLUMNS, INVENTORY_CHECK_TABLE_OPTIONS);
+  let inventoryCheckListQuery: QueryPageRequest<InventoryCheckListQuery> = {
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {},
+  };
+  const tableData = ref<InventoryCheckListResponse[]>([]);
+  const inventoryTaskOptions = ref<InventoryTaskListRes[]>([]);
+  const endTime = ref<string>('');
+  const isInventory = ref<boolean>(false);
+  const searchData = reactive<InventoryCheckListQuery>({
+    taskId: null,
+    emergencyType: null,
+    supplyType: null,
+    supplyName: null,
+    keeperName: null,
+    inventoryResult: null,
+  });
+
+  // 定时器引用
+  const pollTimer = ref<number | null>(null);
+
+  const isTaskExpired = computed(() => {
+    if (!endTime.value) return true;
+
+    const currentTime = new Date();
+    const taskEndTime = new Date(endTime.value);
+
+    return currentTime > taskEndTime;
+  });
+
+  const handleSearch = () => {
+    inventoryCheckListQuery.queryParam = {};
+    if (searchData.taskId) {
+      inventoryCheckListQuery.queryParam.taskId = searchData.taskId;
+    }
+    if (searchData.emergencyType) {
+      inventoryCheckListQuery.queryParam.emergencyType = searchData.emergencyType;
+    }
+    if (searchData.supplyType) {
+      inventoryCheckListQuery.queryParam.supplyType = searchData.supplyType;
+    }
+    if (searchData.supplyName) {
+      inventoryCheckListQuery.queryParam.supplyName = searchData.supplyName;
+    }
+    if (searchData.keeperName) {
+      inventoryCheckListQuery.queryParam.keeperName = searchData.keeperName;
+    }
+    if (searchData.inventoryResult) {
+      inventoryCheckListQuery.queryParam.inventoryResult = searchData.inventoryResult;
+    }
+    getTableData();
+  };
+  const getTableData = async () => {
+    tableConfig.loading = true;
+    const res = await getInventoryCheckList(inventoryCheckListQuery);
+    tableData.value = res.records;
+    pagination.total = res.totalRow;
+    tableConfig.loading = false;
+  };
+
+  const getFirstTaskInfo = () => {
+    const firstTask = inventoryTaskOptions.value[0];
+    if (!firstTask) return;
+    searchData.taskId = firstTask.id;
+    endTime.value = firstTask.endTime;
+  };
+
+  const getInventoryTaskOptions = async () => {
+    inventoryTaskOptions.value = await getInventoryTaskList();
+    await getFirstTaskInfo();
+    handleSearch();
+  };
+
+  const handleTaskChange = (id: number) => {
+    const task = inventoryTaskOptions.value.find((item) => item.id === id);
+    if (!task) return;
+    endTime.value = task.endTime;
+  };
+
+  // 检查任务状态
+  const checkTaskStatus = async () => {
+    if (!searchData.taskId) return;
+
+    try {
+      const res = await getInventoryTaskConfirm(searchData.taskId);
+      isInventory.value = res.taskState === 2;
+    } catch (error) {
+      ElMessage.error(`获取任务状态失败:${error}`);
+    }
+  };
+
+  // 开始轮询
+  const startPolling = () => {
+    // 清除之前的轮询
+    if (pollTimer.value) {
+      clearInterval(pollTimer.value);
+      pollTimer.value = null;
+    }
+
+    // 如果有任务ID,开始轮询
+    if (searchData.taskId) {
+      // 设置1分钟轮询一次
+      pollTimer.value = window.setInterval(checkTaskStatus, 30000);
+    }
+  };
+
+  const handleConfirm = async () => {
+    const confirmed = await openMessageBox('', '是否盘点该条任务?', 'warning');
+    if (!confirmed) return;
+    if (!searchData.taskId) return;
+    await confirmInventoryTask(searchData.taskId);
+    ElMessage.success('盘点成功');
+  };
+
+  onMounted(() => {
+    getInventoryTaskOptions();
+    getEmergencyEventDict();
+    getEmergencySupplyDict();
+    getParkDict();
+    getLocationDict('all');
+  });
+
+  // 组件销毁前清除定时器
+  onBeforeUnmount(() => {
+    if (pollTimer.value) {
+      clearInterval(pollTimer.value);
+      pollTimer.value = null;
+    }
+  });
+
+  watch(
+    () => searchData.taskId,
+    async (newId) => {
+      if (newId === null) {
+        getFirstTaskInfo();
+      } else {
+        // 任务ID变化时检查状态
+        await checkTaskStatus();
+        // 任务ID变化时重新启动轮询
+        startPolling();
+      }
+    },
+  );
+  watch(
+    () => endTime.value,
+    () => {
+      tableConfig.maxHeight = endTime.value
+        ? INVENTORY_CHECK_TABLE_MAX_HEIGHT_PERMISSION
+        : INVENTORY_CHECK_TABLE_MAX_HEIGHT_DEFAULT;
+    },
+  );
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use './src/styles/page-common.scss' as *;
+  .date-time-container {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    margin-top: 20px;
+    span {
+      font-size: 14px;
+      color: $primary-color;
+    }
+  }
+  .font-red {
+    color: red;
+  }
+  .image-list {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+</style>

+ 176 - 0
src/views/emergency/emergency-supplies/src/components/AddEmergencyItem.vue

@@ -0,0 +1,176 @@
+<template>
+  <div class="emergency-supply-container">
+    <BasicForm ref="formRef" :formData="ruleFormData" :formRules="customFormRules" :formConfig="ruleFormConfig">
+      <template #emergencyType>
+        <el-select v-model="ruleFormData.emergencyType" placeholder="请选择应急类型" filterable>
+          <el-option
+            v-for="item in emergencyEventDice"
+            :key="item.itemCode"
+            :label="item.itemValue"
+            :value="item.itemCode"
+          />
+        </el-select>
+      </template>
+      <template #supplyType>
+        <el-select v-model="ruleFormData.supplyType" placeholder="请选择物资类型" filterable>
+          <el-option
+            v-for="item in emergencySupplyDice"
+            :key="item.itemCode"
+            :label="item.itemValue"
+            :value="item.itemCode"
+          />
+        </el-select>
+      </template>
+      <template #requiredQuantity>
+        <el-input
+          v-model="ruleFormData.requiredQuantity"
+          placeholder="请输入应备数量"
+          @input="(value:string) => handleInputNumber(value,'require')"
+          @change="handleChangeQuantity"
+        />
+      </template>
+      <template #currentQuantity>
+        <el-input
+          v-model="ruleFormData.currentQuantity"
+          placeholder="请输入当前数量"
+          @input="(value:string) => handleInputNumber(value,'current')"
+          :disabled="Boolean(!ruleFormData.requiredQuantity)"
+        />
+      </template>
+      <template #park>
+        <el-select v-model="ruleFormData.park" placeholder="请选择园区" filterable @change="handleChangePark">
+          <el-option v-for="item in parkDice" :key="item.itemCode" :label="item.itemValue" :value="item.itemCode" />
+        </el-select>
+      </template>
+      <template #location>
+        <el-select v-model="ruleFormData.location" placeholder="请输入地点" :disabled="Boolean(!ruleFormData.park)">
+          <el-option v-for="item in locationDice" :key="item.itemCode" :label="item.itemValue" :value="item.itemCode" />
+        </el-select>
+      </template>
+      <template #keeperName>
+        <el-select
+          v-model="ruleFormData.keeperName"
+          placeholder="请输入保管人姓名"
+          value-key="id"
+          filterable
+          remote
+          :remote-method="remoteMethod"
+          :loading="loading"
+          @change="selectKeeper"
+        >
+          <el-option
+            v-for="item in userOptions"
+            :key="item.id"
+            :label="`${item.realname}(${item.username})${item.deptName}`"
+            :value="item"
+          />
+        </el-select>
+      </template>
+    </BasicForm>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, computed } from 'vue';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { useEmergencySuppliesHook } from '../hook';
+  import type { AddEmergencyItemForm } from '@/types/emergency-supplier';
+  import type { QueryUserInfoByUserNameRes } from '@/types/person-group/type';
+  import { type FormRules } from 'element-plus';
+  import { ADD_EMERGENCY_ITEM_FROM_CONFIG, ADD_EMERGENCY_ITEM_DATA, ADD_EMERGENCY_ITEM_RULES } from '../config';
+
+  const formRef = ref();
+  const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<AddEmergencyItemForm>(
+      ADD_EMERGENCY_ITEM_FROM_CONFIG,
+      ADD_EMERGENCY_ITEM_DATA,
+      ADD_EMERGENCY_ITEM_RULES,
+    );
+
+  // 验证当前数量不大于应备数量
+  const validateQuantity = (rule: any, value: any, callback: any) => {
+    if (value === '' || value === null || value === undefined) {
+      callback();
+      return;
+    }
+
+    const currentQuantity = Number(value);
+    const requiredQuantity = Number(ruleFormData.requiredQuantity);
+
+    if (currentQuantity > requiredQuantity) {
+      callback(new Error('当前数量不能大于应备数量'));
+    } else {
+      callback();
+    }
+  };
+
+  // 扩展原有的表单规则,添加自定义验证
+  const customFormRules = computed<FormRules>(() => {
+    return {
+      ...formRules,
+      currentQuantity: [
+        { required: true, message: '请输入当前数量', trigger: 'blur' },
+        { validator: validateQuantity, trigger: 'blur' },
+      ],
+    };
+  });
+
+  const handleInputNumber = (value: string, type: 'require' | 'current') => {
+    const onlyNumber = value.replace(/[^\d]/g, '');
+    const num = onlyNumber === '' ? null : Number(onlyNumber);
+    if (type === 'require') {
+      ruleFormData.requiredQuantity = num;
+    } else {
+      ruleFormData.currentQuantity = num;
+    }
+  };
+
+  const handleChangeQuantity = () => {
+    if (ruleFormData.currentQuantity === null) return;
+    formRef.value?.validateField('currentQuantity');
+  };
+  const selectKeeper = (value: QueryUserInfoByUserNameRes) => {
+    if(!value) return;
+    ruleFormData.keeperId = value.id;
+    ruleFormData.keeperName = value.realname;
+  };
+
+  const {
+    emergencyEventDice,
+    getEmergencyEventDict,
+    emergencySupplyDice,
+    getEmergencySupplyDict,
+    parkDice,
+    getParkDict,
+    locationDice,
+    handleChangePark,
+    userOptions,
+    loading,
+    remoteMethod,
+  } = useEmergencySuppliesHook();
+  const handleValidate = async () => {
+    if (!formRef.value) return;
+    const validateResult = await formRef.value.validateForm();
+    return validateResult;
+  };
+  const getFormData = () => {
+    cloneRuleFormData();
+    return ruleFormData;
+  };
+  onMounted(() => {
+    getEmergencyEventDict();
+    getEmergencySupplyDict();
+    getParkDict();
+    cloneRuleFormData();
+    beforeRouteLeave();
+  });
+  defineExpose({
+    handleValidate,
+    getFormData,
+  });
+</script>
+
+<style lang="scss" scoped>
+  @use '../styles/info.scss' as *;
+</style>

+ 42 - 0
src/views/emergency/emergency-supplies/src/components/ChangeRecord.vue

@@ -0,0 +1,42 @@
+<template>
+  <div class="emergency-supply-container">
+    <BasicTable :tableData="tableData" :tableConfig="tableConfig">
+      <template #changeType="scope">
+        <span>{{ CHANGE_TYPE_MAP[scope.row.changeType] }}</span>
+      </template>
+      <template #changeQuantity="scope">
+        <span v-if="scope.row.changeQuantity > 0">+</span>
+        <span>{{ scope.row.changeQuantity }}</span>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import { CHANGE_RECORD_TABLE_COLUMNS, CHANGE_RECORD_TABLE_OPTIONS } from '../config';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import { ChangeRecordListRes } from '@/types/emergency-supplier';
+  import { getSupplyChangeRecord } from '@/api/emergency-supplier';
+  import { CHANGE_TYPE_MAP } from '../constant';
+
+  const { tableConfig } = useTableConfig(CHANGE_RECORD_TABLE_COLUMNS, CHANGE_RECORD_TABLE_OPTIONS, false);
+  const props = defineProps<{
+    id: number;
+  }>();
+  const tableData = ref<ChangeRecordListRes[]>([]);
+  const getChangeRecord = async () => {
+    tableConfig.loading = true;
+    const res = await getSupplyChangeRecord(props.id);
+    tableData.value = res;
+    tableConfig.loading = false;
+  };
+  onMounted(() => {
+    getChangeRecord();
+  });
+</script>
+
+<style lang="scss" scoped>
+  @use '../styles/info.scss' as *;
+</style>

+ 176 - 0
src/views/emergency/emergency-supplies/src/components/EditEmergencyItem.vue

@@ -0,0 +1,176 @@
+<template>
+  <div class="emergency-supply-container">
+    <BasicForm ref="formRef" :formData="ruleFormData" :formRules="customFormRules" :formConfig="ruleFormConfig">
+      <template #requiredQuantity>
+        <el-input
+          v-model="ruleFormData.requiredQuantity"
+          placeholder="请输入应备数量"
+          @input="(value:string) => handleInputNumber(value,'require')"
+          @change="handleChangeQuantity"
+        />
+      </template>
+      <template #currentQuantity>
+        <el-input
+          v-model="ruleFormData.currentQuantity"
+          placeholder="请输入当前数量"
+          @input="(value:string) => handleInputNumber(value,'current')"
+          :disabled="Boolean(!ruleFormData.requiredQuantity)"
+        />
+      </template>
+      <template #park>
+        <el-select v-model="ruleFormData.park" placeholder="请选择园区" filterable @change="editPark">
+          <el-option v-for="item in parkDice" :key="item.itemCode" :label="item.itemValue" :value="item.itemCode" />
+        </el-select>
+      </template>
+      <template #location>
+        <el-select v-model="ruleFormData.location" placeholder="请输入地点" :disabled="Boolean(!ruleFormData.park)">
+          <el-option v-for="item in locationDice" :key="item.itemCode" :label="item.itemValue" :value="item.itemCode" />
+        </el-select>
+      </template>
+      <template #keeperName>
+        <el-select
+          v-model="ruleFormData.keeperName"
+          placeholder="请输入保管人姓名"
+          value-key="id"
+          filterable
+          remote
+          :remote-method="remoteMethod"
+          :loading="loading"
+          @change="editKeeper"
+        >
+          <el-option
+            v-for="item in userOptions"
+            :key="item.id"
+            :label="`${item.realname}(${item.username})${item.deptName}`"
+            :value="item"
+          />
+        </el-select>
+      </template>
+    </BasicForm>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, computed } from 'vue';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { useEmergencySuppliesHook } from '../hook';
+  import type { EditEmergencyItemForm } from '@/types/emergency-supplier';
+  import type { QueryUserInfoByUserNameRes } from '@/types/person-group/type';
+  import { type FormRules } from 'element-plus';
+  import { getEmergencySupplyDetail } from '@/api/emergency-supplier';
+  import { EDIT_EMERGENCY_ITEM_FROM_CONFIG, EDIT_EMERGENCY_ITEM_DATA, EDIT_EMERGENCY_ITEM_RULES } from '../config';
+
+  const props = defineProps<{
+    id: number;
+  }>();
+  const formRef = ref();
+  const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<EditEmergencyItemForm>(
+      EDIT_EMERGENCY_ITEM_FROM_CONFIG,
+      EDIT_EMERGENCY_ITEM_DATA,
+      EDIT_EMERGENCY_ITEM_RULES,
+    );
+
+  // 验证当前数量不大于应备数量
+  const validateQuantity = (rule: any, value: any, callback: any) => {
+    if (value === '' || value === null || value === undefined) {
+      callback();
+      return;
+    }
+
+    const currentQuantity = Number(value);
+    const requiredQuantity = Number(ruleFormData.requiredQuantity);
+
+    if (currentQuantity > requiredQuantity) {
+      callback(new Error('当前数量不能大于应备数量'));
+    } else {
+      callback();
+    }
+  };
+
+  // 扩展原有的表单规则,添加自定义验证
+  const customFormRules = computed<FormRules>(() => {
+    return {
+      ...formRules,
+      currentQuantity: [
+        { required: true, message: '请输入当前数量', trigger: 'blur' },
+        { validator: validateQuantity, trigger: 'blur' },
+      ],
+    };
+  });
+
+  const handleInputNumber = (value: string, type: 'require' | 'current') => {
+    const onlyNumber = value.replace(/[^\d]/g, '');
+    const num = onlyNumber === '' ? null : Number(onlyNumber);
+    if (type === 'require') {
+      ruleFormData.requiredQuantity = num;
+    } else {
+      ruleFormData.currentQuantity = num;
+    }
+  };
+
+  const handleChangeQuantity = () => {
+    if (ruleFormData.currentQuantity === null) return;
+    formRef.value?.validateField('currentQuantity');
+  };
+
+  const {
+    getEmergencyEventDict,
+    getEmergencySupplyDict,
+    getEmergencyEvent,
+    getEmergencySupply,
+    parkDice,
+    getParkDict,
+    locationDice,
+    getLocationDict,
+    handleChangePark,
+    userOptions,
+    loading,
+    remoteMethod,
+  } = useEmergencySuppliesHook();
+  const getEmergencySupplyInfo = async () => {
+    const res = await getEmergencySupplyDetail(props.id);
+    for (const key in res) {
+      ruleFormData[key] = res[key as keyof typeof res];
+      ruleFormData.emergencyTypeName = getEmergencyEvent(res.emergencyType) || '';
+      ruleFormData.supplyTypeName = getEmergencySupply(res.supplyType) || '';
+    }
+    getLocationDict(ruleFormData.park as 'zhangjiang_park' | 'dachang_park');
+    cloneRuleFormData();
+  };
+  const editPark = (type: 'zhangjiang_park' | 'dachang_park') => {
+    handleChangePark(type);
+    ruleFormData.location = '';
+  };
+  const editKeeper = (value: QueryUserInfoByUserNameRes) => {
+    if (!value) return;
+    ruleFormData.keeperName = value.realname;
+    ruleFormData.keeperId = value.id;
+  };
+  const handleValidate = async () => {
+    if (!formRef.value) return;
+    const validateResult = await formRef.value.validateForm();
+    return validateResult;
+  };
+  const getFormData = () => {
+    cloneRuleFormData();
+    return ruleFormData;
+  };
+  onMounted(() => {
+    getEmergencyEventDict().then(async () => {
+      await getEmergencySupplyDict();
+      await getEmergencySupplyInfo();
+    });
+    getParkDict();
+    beforeRouteLeave();
+  });
+  defineExpose({
+    handleValidate,
+    getFormData,
+  });
+</script>
+
+<style lang="scss" scoped>
+  @use '../styles/info.scss' as *;
+</style>

+ 4 - 0
src/views/emergency/emergency-supplies/src/components/InventoryTask.vue

@@ -12,10 +12,12 @@
 
 <script setup lang="ts">
   import { ref } from 'vue';
+  import { ElMessage } from 'element-plus';
   import BasicDialog from '@/components/BasicDialog.vue';
   import BasicForm from '@/components/BasicForm.vue';
   import { useFormConfigHook } from '@/hooks/useFormConfigHook';
   import { InventoryTaskForm } from '@/types/emergency-supplier';
+  import { saveInventoryTask } from '@/api/emergency-supplier';
   import { INVENTORY_TASK_FROM_CONFIG, INVENTORY_TASK_FROM_DATA, INVENTORY_TASK_FROM_RULES } from '../config';
 
   const basicDialogRef = ref<InstanceType<typeof BasicDialog>>();
@@ -31,6 +33,8 @@
   const handleSumbit = async () => {
     const validate = await basicFormRef.value?.validateForm();
     if (!validate) return;
+    await saveInventoryTask(ruleFormData.taskName, ruleFormData.endTime);
+    ElMessage.success('发起盘点任务成功');
     basicDialogRef.value?.closeDialog();
   };
   const refreshFromData = () => {

+ 66 - 0
src/views/emergency/emergency-supplies/src/components/ViewEmergencyItem.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="emergency-supply-container">
+    <BasicForm ref="formRef" :formData="ruleFormData" :formConfig="ruleFormConfig" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted } from 'vue';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { useEmergencySuppliesHook } from '../hook';
+  import type { ViewEmergencyItemForm } from '@/types/emergency-supplier';
+  import { getEmergencySupplyDetail } from '@/api/emergency-supplier';
+  import { VIEW_EMERGENCY_ITEM_FROM_CONFIG, VIEW_EMERGENCY_ITEM_DATA } from '../config';
+
+  const props = defineProps<{
+    id: number;
+  }>();
+  const formRef = ref();
+  const DISABLED_EMERGENCY_ITEM_FROM_CONFIG = VIEW_EMERGENCY_ITEM_FROM_CONFIG.map(item => {
+    return {
+      ...item,
+      componentProps: {
+        ...(item.componentProps || {}),
+        disabled: true
+      }
+    };
+  });
+  const { ruleFormConfig, ruleFormData } = useFormConfigHook<ViewEmergencyItemForm>(
+    DISABLED_EMERGENCY_ITEM_FROM_CONFIG,
+    VIEW_EMERGENCY_ITEM_DATA,
+  );
+
+  const {
+    getEmergencyEventDict,
+    getEmergencySupplyDict,
+    getEmergencyEvent,
+    getEmergencySupply,
+    getParkDict,
+    getPark,
+    getLocationDict,
+    getLocation,
+  } = useEmergencySuppliesHook();
+  const getEmergencySupplyInfo = async () => {
+    const res = await getEmergencySupplyDetail(props.id);
+    for (const key in res) {
+      ruleFormData[key] = res[key as keyof typeof res];
+      ruleFormData.emergencyTypeName = getEmergencyEvent(res.emergencyType) || '';
+      ruleFormData.supplyTypeName = getEmergencySupply(res.supplyType) || '';
+      ruleFormData.parkName = getPark(res.park) || '';
+      ruleFormData.locationName = getLocation(res.location) || '';
+    }
+  };
+  onMounted(() => {
+    getEmergencyEventDict().then(async () => {
+      await getEmergencySupplyDict();
+      await getLocationDict('all');
+      await getEmergencySupplyInfo();
+    });
+    getParkDict();
+  });
+</script>
+
+<style lang="scss" scoped>
+  @use '../styles/info.scss' as *;
+</style>

+ 246 - 0
src/views/emergency/emergency-supplies/src/config/form.ts

@@ -43,3 +43,249 @@ export const INVENTORY_TASK_FROM_RULES = {
     { validator: validateFormTime, trigger: 'change' },
   ],
 };
+
+const BASIC_EMERGENCY_ITEM_FROM_CONFIG = {
+  REQUIRED_QUANTITY: {
+    label: '应备数量:',
+    prop: 'requiredQuantity',
+    slot: 'requiredQuantity',
+  },
+  CURRENT_QUANTITY: {
+    label: '当前数量:',
+    prop: 'currentQuantity',
+    slot: 'currentQuantity',
+  },
+  UNIT: {
+    label: '数量单位:',
+    prop: 'unit',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入数量单位',
+    },
+  },
+  PARK: {
+    label: '园区:',
+    prop: 'park',
+    slot: 'park',
+  },
+  LOCATION: {
+    label: '地点:',
+    prop: 'location',
+    slot: 'location',
+  },
+  KEEPER_NAME: {
+    label: '保管人:',
+    prop: 'keeperName',
+    slot: 'keeperName',
+  },
+  EXPIRATION_DATE: {
+    label: '使用期限:',
+    prop: 'expirationDate',
+    component: 'ElDatePicker',
+    componentProps: {
+      placeholder: '请选择使用期限',
+      type: 'date',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  REMARK: {
+    label: '备注:',
+    prop: 'remark',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入备注',
+      type: 'textarea',
+      rows: 5,
+    },
+  },
+};
+
+export const ADD_EMERGENCY_ITEM_FROM_CONFIG: FormConfig[] = [
+  {
+    label: '应急类型:',
+    prop: 'emergencyType',
+    slot: 'emergencyType',
+  },
+  {
+    label: '物资类型:',
+    prop: 'supplyType',
+    slot: 'supplyType',
+  },
+  {
+    label: '应急物资名称:',
+    prop: 'supplyName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入应急物资名称',
+    },
+  },
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.REQUIRED_QUANTITY,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.CURRENT_QUANTITY,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.UNIT,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.PARK,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.LOCATION,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.KEEPER_NAME,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.EXPIRATION_DATE,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.REMARK,
+];
+
+export const EDIT_EMERGENCY_ITEM_FROM_CONFIG: FormConfig[] = [
+  {
+    label: '应急类型:',
+    prop: 'emergencyTypeName',
+    component: 'ElInput',
+    componentProps: {
+      disabled: true,
+      placeholder: '请输入应急类型',
+    },
+  },
+  {
+    label: '物资类型:',
+    prop: 'supplyTypeName',
+    component: 'ElInput',
+    componentProps: {
+      disabled: true,
+      placeholder: '请输入物资类型',
+    },
+  },
+  {
+    label: '应急物资名称:',
+    prop: 'supplyName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入应急物资名称',
+      disabled: true,
+    },
+  },
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.REQUIRED_QUANTITY,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.CURRENT_QUANTITY,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.UNIT,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.PARK,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.LOCATION,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.KEEPER_NAME,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.EXPIRATION_DATE,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.REMARK,
+];
+
+export const VIEW_EMERGENCY_ITEM_FROM_CONFIG: FormConfig[] = [
+  {
+    label: '应急类型:',
+    prop: 'emergencyTypeName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入应急类型',
+    },
+  },
+  {
+    label: '物资类型:',
+    prop: 'supplyTypeName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入物资类型',
+    },
+  },
+  {
+    label: '应急物资名称:',
+    prop: 'supplyName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入应急物资名称',
+    },
+  },
+  {
+    label: '应备数量:',
+    prop: 'requiredQuantity',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入应备数量',
+    },
+  },
+  {
+    label: '当前数量:',
+    prop: 'currentQuantity',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入当前数量',
+    },
+  },
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.UNIT,
+  {
+    label: '园区:',
+    prop: 'parkName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入园区',
+    },
+  },
+  {
+    label: '地点:',
+    prop: 'locationName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入地点',
+    },
+  },
+  {
+    label: '保管人:',
+    prop: 'keeperName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入保管人',
+    },
+  },
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.EXPIRATION_DATE,
+  BASIC_EMERGENCY_ITEM_FROM_CONFIG.REMARK,
+];
+
+const EMERGENCY_ITEM_DATA = {
+  emergencyType: '',
+  emergencyTypeName: '',
+  supplyType: '',
+  supplyTypeName: '',
+  supplyName: '',
+  requiredQuantity: null,
+  currentQuantity: null,
+  unit: '',
+  park: '',
+  location: '',
+  expirationDate: '',
+  remark: '',
+  keeperId: null,
+  keeperName: '',
+};
+
+// 盘点任务表单数据
+export const ADD_EMERGENCY_ITEM_DATA = {
+  ...EMERGENCY_ITEM_DATA,
+};
+
+export const EDIT_EMERGENCY_ITEM_DATA = {
+  ...EMERGENCY_ITEM_DATA,
+  id: null,
+};
+
+export const VIEW_EMERGENCY_ITEM_DATA = {
+  ...EDIT_EMERGENCY_ITEM_DATA,
+  parkName: '',
+  locationName: '',
+};
+
+const BASIC_EMERGENCY_ITEM_RULES = {
+  requiredQuantity: [{ required: true, message: '请输入应备数量', trigger: 'change' }],
+  park: [{ required: true, message: '请选择园区', trigger: 'change' }],
+  location: [{ required: true, message: '请选择地点', trigger: 'change' }],
+  expirationDate: [{ validator: validateFormTime, trigger: 'change' }],
+  keeperName: [{ required: true, message: '请选择保管人', trigger: 'change' }],
+};
+
+export const ADD_EMERGENCY_ITEM_RULES = {
+  emergencyType: [{ required: true, message: '请选择应急类型', trigger: 'change' }],
+  supplyType: [{ required: true, message: '请选择物资类型', trigger: 'change' }],
+  supplyName: [{ required: true, message: '请输入应急物资名称', trigger: 'change' }],
+  ...BASIC_EMERGENCY_ITEM_RULES,
+};
+
+export const EDIT_EMERGENCY_ITEM_RULES = {
+  ...BASIC_EMERGENCY_ITEM_RULES,
+};

+ 35 - 2
src/views/emergency/emergency-supplies/src/config/index.ts

@@ -1,14 +1,33 @@
-import { SUPPLY_LIST_SEARCH_CONFIG } from './search';
+import { SUPPLY_LIST_SEARCH_CONFIG, INVENTORY_LIST_SEARCH_CONFIG } from './search';
 import {
   SUPPLY_LIST_TABLE_COLUMNS,
   SUPPLY_LIST_TABLE_OPTIONS,
   SUPPLY_LIST_TABLE_MAX_HEIGHT_DEFAULT,
   SUPPLY_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+  CHANGE_RECORD_TABLE_COLUMNS,
+  CHANGE_RECORD_TABLE_OPTIONS,
+  INVENTORY_CHECK_TABLE_COLUMNS,
+  INVENTORY_CHECK_TABLE_OPTIONS,
+  INVENTORY_CHECK_TABLE_MAX_HEIGHT_DEFAULT,
+  INVENTORY_CHECK_TABLE_MAX_HEIGHT_PERMISSION,
 } from './table';
-import { INVENTORY_TASK_FROM_CONFIG, INVENTORY_TASK_FROM_DATA, INVENTORY_TASK_FROM_RULES } from './form';
+import {
+  INVENTORY_TASK_FROM_CONFIG,
+  INVENTORY_TASK_FROM_DATA,
+  INVENTORY_TASK_FROM_RULES,
+  ADD_EMERGENCY_ITEM_FROM_CONFIG,
+  ADD_EMERGENCY_ITEM_DATA,
+  EDIT_EMERGENCY_ITEM_DATA,
+  ADD_EMERGENCY_ITEM_RULES,
+  EDIT_EMERGENCY_ITEM_FROM_CONFIG,
+  EDIT_EMERGENCY_ITEM_RULES,
+  VIEW_EMERGENCY_ITEM_DATA,
+  VIEW_EMERGENCY_ITEM_FROM_CONFIG,
+} from './form';
 
 export {
   SUPPLY_LIST_SEARCH_CONFIG,
+  INVENTORY_LIST_SEARCH_CONFIG,
   SUPPLY_LIST_TABLE_COLUMNS,
   SUPPLY_LIST_TABLE_OPTIONS,
   SUPPLY_LIST_TABLE_MAX_HEIGHT_DEFAULT,
@@ -16,4 +35,18 @@ export {
   INVENTORY_TASK_FROM_CONFIG,
   INVENTORY_TASK_FROM_DATA,
   INVENTORY_TASK_FROM_RULES,
+  ADD_EMERGENCY_ITEM_FROM_CONFIG,
+  ADD_EMERGENCY_ITEM_DATA,
+  EDIT_EMERGENCY_ITEM_DATA,
+  ADD_EMERGENCY_ITEM_RULES,
+  EDIT_EMERGENCY_ITEM_FROM_CONFIG,
+  EDIT_EMERGENCY_ITEM_RULES,
+  VIEW_EMERGENCY_ITEM_DATA,
+  VIEW_EMERGENCY_ITEM_FROM_CONFIG,
+  CHANGE_RECORD_TABLE_COLUMNS,
+  CHANGE_RECORD_TABLE_OPTIONS,
+  INVENTORY_CHECK_TABLE_COLUMNS,
+  INVENTORY_CHECK_TABLE_OPTIONS,
+  INVENTORY_CHECK_TABLE_MAX_HEIGHT_DEFAULT,
+  INVENTORY_CHECK_TABLE_MAX_HEIGHT_PERMISSION,
 };

+ 42 - 18
src/views/emergency/emergency-supplies/src/config/search.ts

@@ -1,18 +1,18 @@
 import type { SearchConfig } from '@/types/basic-search';
-import { LOCATION_OPTIONS, EMERGENCY_SUPPLY_STATUS_OPTIONS } from '../constant';
+import { EMERGENCY_SUPPLY_STATUS_OPTIONS, INVENTORY_RESULT_OPTIONS } from '../constant';
 
-export const SUPPLY_LIST_SEARCH_CONFIG: SearchConfig[] = [
-  {
+const BASIC_SEARCH_CONFIG = {
+  EMERGENCY_TYPE: {
     label: '应急类型:',
     prop: 'emergencyType',
     slot: 'emergencyType',
   },
-  {
+  SUPPLY_TYPE: {
     label: '物资类型:',
     prop: 'supplyType',
     slot: 'supplyType',
   },
-  {
+  SUPPLY_NAME: {
     label: '应急物资名称:',
     prop: 'supplyName',
     component: 'ElInput',
@@ -20,14 +20,24 @@ export const SUPPLY_LIST_SEARCH_CONFIG: SearchConfig[] = [
       placeholder: '请输入应急物资名称',
     },
   },
+  KEEPER_NAME: {
+    label: '保管人:',
+    prop: 'keeperName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入保管人姓名',
+    },
+  },
+};
+
+export const SUPPLY_LIST_SEARCH_CONFIG: SearchConfig[] = [
+  BASIC_SEARCH_CONFIG.EMERGENCY_TYPE,
+  BASIC_SEARCH_CONFIG.SUPPLY_TYPE,
+  BASIC_SEARCH_CONFIG.SUPPLY_NAME,
   {
     label: '园区:',
     prop: 'park',
-    component: 'ElSelect',
-    selectOptions: LOCATION_OPTIONS,
-    componentProps: {
-      placeholder: '请选择园区',
-    },
+    slot: 'park',
   },
   {
     label: '地点:',
@@ -37,14 +47,7 @@ export const SUPPLY_LIST_SEARCH_CONFIG: SearchConfig[] = [
       placeholder: '请输入地点',
     },
   },
-  {
-    label: '保管人:',
-    prop: 'keeperName',
-    component: 'ElInput',
-    componentProps: {
-      placeholder: '请输入保管人姓名',
-    },
-  },
+  BASIC_SEARCH_CONFIG.KEEPER_NAME,
   {
     label: '使用状态:',
     prop: 'status',
@@ -55,3 +58,24 @@ export const SUPPLY_LIST_SEARCH_CONFIG: SearchConfig[] = [
     },
   },
 ];
+
+export const INVENTORY_LIST_SEARCH_CONFIG: SearchConfig[] = [
+  {
+    label: '盘点任务:',
+    prop: 'taskId',
+    slot: 'taskId',
+  },
+  BASIC_SEARCH_CONFIG.EMERGENCY_TYPE,
+  BASIC_SEARCH_CONFIG.SUPPLY_TYPE,
+  BASIC_SEARCH_CONFIG.SUPPLY_NAME,
+  BASIC_SEARCH_CONFIG.KEEPER_NAME,
+  {
+    label: '盘点结果:',
+    prop: 'inventoryResult',
+    component: 'ElSelect',
+    selectOptions: INVENTORY_RESULT_OPTIONS,
+    componentProps: {
+      placeholder: '请选择盘点结果',
+    },
+  },
+];

+ 134 - 6
src/views/emergency/emergency-supplies/src/config/table.ts

@@ -43,10 +43,16 @@ const BASIC_TABLE_COLUMNS = {
     label: '应急物资名称',
     minWidth: '140px',
   },
+  PARK: {
+    label: '园区',
+    slot: 'park',
+    minWidth: '140px',
+  },
   REMARK: {
     prop: 'remark',
     label: '备注',
-    minWidth: '120px',
+    width: '200px',
+    slot: 'remark',
   },
   ACTION: {
     prop: 'action',
@@ -68,25 +74,24 @@ export const SUPPLY_LIST_TABLE_COLUMNS: TableColumnProps[] = [
     label: '应备数量',
     prop: 'requiredQuantity',
     minWidth: '120px',
+    align: 'right',
   },
   {
     label: '当前数量',
     prop: 'currentQuantity',
     minWidth: '120px',
+    align: 'right',
   },
   {
     label: '数量单位',
     prop: 'unit',
     minWidth: '120px',
   },
-  {
-    label: '园区',
-    prop: 'park',
-    minWidth: '140px',
-  },
+  BASIC_TABLE_COLUMNS.PARK,
   {
     label: '地点',
     prop: 'location',
+    slot: 'location',
     minWidth: '120px',
   },
   {
@@ -109,7 +114,130 @@ export const SUPPLY_LIST_TABLE_COLUMNS: TableColumnProps[] = [
     label: '需补充数量',
     prop: 'supplementQuantity',
     minWidth: '120px',
+    align: 'right',
   },
   BASIC_TABLE_COLUMNS.REMARK,
   BASIC_TABLE_COLUMNS.ACTION,
 ];
+
+// 变更记录表格配置
+export const CHANGE_RECORD_TABLE_COLUMNS: TableColumnProps[] = [
+  BASIC_TABLE_COLUMNS.INDEX,
+  {
+    label: '类型',
+    prop: 'changeType',
+    slot: 'changeType',
+    minWidth: '120px',
+  },
+  {
+    label: '详情',
+    prop: 'detail',
+    minWidth: '120px',
+  },
+  {
+    label: '变更数量',
+    prop: 'changeQuantity',
+    slot: 'changeQuantity',
+    minWidth: '120px',
+  },
+  {
+    label: '变更后数量',
+    prop: 'afterQuantity',
+    minWidth: '120px',
+    align: 'right',
+  },
+  {
+    label: '操作人',
+    prop: 'operatorName',
+  },
+];
+
+export const CHANGE_RECORD_TABLE_OPTIONS = {
+  ...TABLE_OPTIONS,
+  maxHeight: 'calc(90vh - 150px)',
+};
+
+// 物资盘点表格列配置
+export const INVENTORY_CHECK_TABLE_COLUMNS: TableColumnProps[] = [
+  BASIC_TABLE_COLUMNS.INDEX,
+  BASIC_TABLE_COLUMNS.EMERGENCY_TYPE,
+  BASIC_TABLE_COLUMNS.SUPPLY_TYPE,
+  BASIC_TABLE_COLUMNS.SUPPLY_NAME,
+  {
+    label: '保管人',
+    align: 'center',
+    children: [
+      {
+        label: '盘点前',
+        prop: 'beforeKeeperName',
+        minWidth: '120px',
+      },
+      {
+        label: '盘点后',
+        slot: 'afterKeeperName',
+        minWidth: '120px',
+      },
+    ],
+  },
+  {
+    label: '数量',
+    align: 'center',
+    children: [
+      {
+        label: '盘点前',
+        prop: 'beforeQuantity',
+        minWidth: '120px',
+        align: 'right',
+      },
+      {
+        label: '盘点后',
+        slot: 'afterQuantity',
+        minWidth: '120px',
+        align: 'right',
+      },
+      {
+        label: '需补充',
+        slot: 'supplementQuantity',
+        minWidth: '120px',
+        align: 'right',
+      },
+    ],
+  },
+  BASIC_TABLE_COLUMNS.PARK,
+  {
+    label: '地点',
+    align: 'center',
+    children: [
+      {
+        label: '盘点前',
+        slot: 'beforeLocation',
+        minWidth: '120px',
+      },
+      {
+        label: '盘点后',
+        slot: 'afterLocation',
+        minWidth: '120px',
+      },
+    ],
+  },
+  {
+    label: '盘点结果',
+    slot: 'inventoryResult',
+    minWidth: '120px',
+  },
+  BASIC_TABLE_COLUMNS.REMARK,
+  {
+    label: '盘点照片',
+    slot: 'imageList',
+    minWidth: '120px',
+    align: 'center',
+    fixed: 'right'
+  },
+];
+
+export const INVENTORY_CHECK_TABLE_MAX_HEIGHT_DEFAULT = 'calc(70vh - 100px)';
+export const INVENTORY_CHECK_TABLE_MAX_HEIGHT_PERMISSION = 'calc(70vh - 150px)';
+
+export const INVENTORY_CHECK_TABLE_OPTIONS = {
+  ...TABLE_OPTIONS
+};

+ 42 - 11
src/views/emergency/emergency-supplies/src/constant/index.ts

@@ -1,17 +1,6 @@
 /**
  * 应急物资常量
  */
-export const LOCATION_OPTIONS = [
-  {
-    label: '张江',
-    value: 'zhangjiang',
-  },
-  {
-    label: '大场',
-    value: 'dachang',
-  },
-];
-
 export enum EMERGENCY_SUPPLY_STATUS {
   INTACT = 0,
   DAMAGED,
@@ -27,3 +16,45 @@ export const EMERGENCY_SUPPLY_STATUS_OPTIONS = [
     value: EMERGENCY_SUPPLY_STATUS.DAMAGED,
   },
 ];
+
+export const EMERGENCY_SUPPLY_TABS = [
+  {
+    value: 'detail',
+    label: '物资详情',
+  },
+  {
+    value: 'record',
+    label: '变更记录',
+  },
+];
+
+enum CHANGE_TYPE {
+  CHECK = 1,
+  CHANGE,
+}
+
+export const CHANGE_TYPE_MAP = {
+  [CHANGE_TYPE.CHECK]: '物资盘点',
+  [CHANGE_TYPE.CHANGE]: '数量变更',
+};
+
+export enum INVENTORY_RESULT {
+  CHECKING = 1,
+  INTACT,
+  DAMAGED,
+  OVERDUE,
+}
+
+export const INVENTORY_RESULT_MAP = {
+  [INVENTORY_RESULT.CHECKING]: '盘点中',
+  [INVENTORY_RESULT.INTACT]: '完好',
+  [INVENTORY_RESULT.DAMAGED]: '缺损',
+  [INVENTORY_RESULT.OVERDUE]: '预期未完成',
+};
+
+export const INVENTORY_RESULT_OPTIONS = [
+  { label: INVENTORY_RESULT_MAP[INVENTORY_RESULT.CHECKING], value: INVENTORY_RESULT.CHECKING },
+  { label: INVENTORY_RESULT_MAP[INVENTORY_RESULT.INTACT], value: INVENTORY_RESULT.INTACT },
+  { label: INVENTORY_RESULT_MAP[INVENTORY_RESULT.DAMAGED], value: INVENTORY_RESULT.DAMAGED },
+  { label: INVENTORY_RESULT_MAP[INVENTORY_RESULT.OVERDUE], value: INVENTORY_RESULT.OVERDUE },
+];

+ 51 - 0
src/views/emergency/emergency-supplies/src/hook.ts

@@ -2,8 +2,10 @@
  * 应急物资公用hook
  */
 import { ref } from 'vue';
+import type { QueryUserInfoByUserNameRes } from '@/types/person-group/type';
 import type { SysDictDataDetail } from '@/api/dict';
 import { queryDictTypeDetail } from '@/api/dict';
+import { queryUserInfoByUserName } from '@/api/system/person-group';
 import { DICT_CODE } from '@/constant/dict';
 import { EMERGENCY_SUPPLY_STATUS_OPTIONS } from './constant';
 export const useEmergencySuppliesHook = () => {
@@ -28,6 +30,45 @@ export const useEmergencySuppliesHook = () => {
   const getEmergencyStatus = (status: number) => {
     return EMERGENCY_SUPPLY_STATUS_OPTIONS.find((item) => item.value === status)?.label;
   };
+  // 园区
+  const parkDice = ref<SysDictDataDetail[]>([]);
+  const getParkDict = async () => {
+    const parkRes = await queryDictTypeDetail(DICT_CODE.PARK);
+    parkDice.value = parkRes.sysDictDataList;
+  };
+  const getPark = (park: string) => {
+    return parkDice.value.find((item) => item.itemCode === park)?.itemValue;
+  };
+  // 地点
+  const locationDice = ref<SysDictDataDetail[]>([]);
+  const getLocationDict = async (type: 'zhangjiang_park' | 'dachang_park' | 'all') => {
+    const locationZJRes = await queryDictTypeDetail(DICT_CODE.LOCATION_ZJ);
+    const locationDCRes = await queryDictTypeDetail(DICT_CODE.LOCATION_DC);
+    if (type === 'all') {
+      locationDice.value = [...locationZJRes.sysDictDataList, ...locationDCRes.sysDictDataList];
+    } else if (type === 'zhangjiang_park') {
+      locationDice.value = locationZJRes.sysDictDataList;
+    } else if (type === 'dachang_park') {
+      locationDice.value = locationDCRes.sysDictDataList;
+    }
+  };
+  const handleChangePark = (type: 'zhangjiang_park' | 'dachang_park') => {
+    getLocationDict(type);
+  };
+  const getLocation = (location: string) => {
+    return locationDice.value.find((item) => item.itemCode === location)?.itemValue;
+  };
+  const userOptions = ref<QueryUserInfoByUserNameRes[]>([]);
+  const loading = ref<boolean>(false);
+  const remoteMethod = async (query: string) => {
+    if (!query) {
+      userOptions.value = [];
+      return;
+    }
+    loading.value = true;
+    userOptions.value = await queryUserInfoByUserName(query);
+    loading.value = false;
+  };
   return {
     emergencyEventDice,
     getEmergencyEventDict,
@@ -36,5 +77,15 @@ export const useEmergencySuppliesHook = () => {
     getEmergencySupplyDict,
     getEmergencySupply,
     getEmergencyStatus,
+    parkDice,
+    getParkDict,
+    getPark,
+    handleChangePark,
+    locationDice,
+    getLocationDict,
+    getLocation,
+    userOptions,
+    loading,
+    remoteMethod,
   };
 };

+ 5 - 0
src/views/emergency/emergency-supplies/src/styles/info-common.scss

@@ -0,0 +1,5 @@
+.safety-platform-container__header {
+  flex-direction: row !important;
+  justify-content: flex-start !important;
+  gap: 8px !important;
+}

+ 3 - 0
src/views/emergency/emergency-supplies/src/styles/info.scss

@@ -0,0 +1,3 @@
+.emergency-supply-container{
+    overflow-y: auto;
+}

+ 6 - 0
src/views/emergency/emergency-supplies/src/styles/page-common.scss

@@ -1,3 +1,9 @@
 .el-select {
   --el-select-width: 200px !important;
 }
+.remark--div {
+  width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}

+ 3 - 0
src/views/emergency/emergency-supplies/src/svg/img-preview.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="55" height="20" viewBox="0 0 55 20">
+  <text x="0" y="15" font-family="Arial, sans-serif" font-size="14px" fill="#1777ff">预览图片</text>
+</svg>

+ 2 - 3
utils/devProxy/staff/proxy.ts

@@ -3,9 +3,8 @@ import path from 'path';
 
 // staff环境
 const proxyStaff: PROXY_TYPE = {
-  // serverHost: 'http://192.168.13.68:8802/',
-  serverHost: 'http://192.168.22.146:8802/',
-  // serverHost: 'http://192.168.22.146:8802/',
+  serverHost: 'http://192.168.13.68:8802/',
+  // serverHost: 'http://192.168.22.121:8802/',
   loginHost: 'http://192.168.13.68:7200/login/#/',
   fileUploadHost: 'http://192.168.13.102:9000/',
   push_stream_host: 'http://192.168.13.68:7000/skyeye-admin/push_stream_host/',