Quellcode durchsuchen

feat: 应急管理物资申领功能

ai0187 vor 4 Monaten
Ursprung
Commit
f611505fde

+ 93 - 0
src/api/emergency-supplier/index.ts

@@ -10,6 +10,10 @@ import type {
   InventoryTaskListRes,
   InventoryCheckListQuery,
   InventoryCheckListResponse,
+  SupplyRequestListQuery,
+  SupplyRequestListItem,
+  SupplyRequestDetailItem,
+  ReceiveSupplyRequestDetailForm,
 } from '@/types/emergency-supplier';
 
 /**
@@ -118,6 +122,7 @@ export const saveInventoryTask = (taskName: string, endTime: string) => {
     data: { taskName, endTime },
   });
 };
+
 /**
  * 物资报废
  */
@@ -144,3 +149,91 @@ export const exportDiscardRecord = (startTime: string, endTime: string) => {
     },
   );
 };
+
+/**
+ * 分页查询应急物资申领计划列表
+ */
+export const getSupplyRequestList = (data: QueryPageRequest<SupplyRequestListQuery>) => {
+  return http.request<QueryPageResponse<SupplyRequestListItem>>({
+    url: '/emergencySupplies/queryEmergencySuppliesRequestPlanPage',
+    method: 'post',
+    data,
+  });
+};
+/**
+ * 删除应急物资申领计划
+ */
+export const deleteSupplyRequest = (planId: number) => {
+  return http.request({
+    url: `/emergencySupplies/deleteEmergencySuppliesRequestPlan?planId=${planId}`,
+    method: 'delete',
+  });
+};
+/**
+ * 创建物资申领计划
+ */
+export const createSupplyRequest = (data: SupplyRequestListItem) => {
+  return http.request({
+    url: '/emergencySupplies/saveEmergencySuppliesRequestPlan',
+    method: 'post',
+    data,
+  });
+};
+/**
+ * 编辑物资申领计划
+ */
+export const updateSupplyRequest = (data: SupplyRequestListItem) => {
+  return http.request({
+    url: '/emergencySupplies/updateEmergencySuppliesRequestPlan',
+    method: 'put',
+    data,
+  });
+};
+/**
+ * 查询应急物资申领计划详情
+ */
+export const getSupplyRequestDetail = (planId: number) => {
+  return http.request<SupplyRequestDetailItem[]>({
+    url: `/emergencySupplies/queryEmergencySuppliesRequestDetail?planId=${planId}`,
+    method: 'get',
+  });
+};
+/**
+ * 创建需求物资
+ */
+export const createSupplyRequestDetail = (data: SupplyRequestDetailItem) => {
+  return http.request({
+    url: '/emergencySupplies/saveRequestSupply',
+    method: 'post',
+    data,
+  });
+};
+/**
+ * 编辑需求物资
+ */
+export const updateSupplyRequestDetail = (data: SupplyRequestDetailItem) => {
+  return http.request({
+    url: '/emergencySupplies/updateRequestSupply',
+    method: 'put',
+    data,
+  });
+};
+/**
+ * 删除需求物资
+ */
+export const deleteSupplyRequestDetail = (infoId: number) => {
+  return http.request({
+    url: `/emergencySupplies/deleteRequestSupply?infoId=${infoId}`,
+    method: 'delete',
+  });
+};
+/**
+ * 领用物资
+ */
+export const receiveSupplyRequestDetail = (data: ReceiveSupplyRequestDetailForm) => {
+  return http.request({
+    url: '/emergencySupplies/receiveEmergencySupplies',
+    method: 'post',
+    data,
+  });
+};

+ 40 - 0
src/router/routers/emergency.ts

@@ -80,6 +80,46 @@ const emergencyManagementRoute = {
         title: '应急物资',
       },
       children: [
+        {
+          id: 200401,
+          parentId: 2004,
+          name: 'supply-request',
+          path: 'supply-request',
+          component: '/emergency/emergency-supplies/PageSupplyRequest',
+          redirect: '',
+          meta: {
+            activeMenu: null,
+            alwaysShow: false,
+            frameSrc: '',
+            hidden: false,
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+            title: '物资申领',
+          },
+        },
+        {
+          id: 200402,
+          parentId: 2004,
+          name: 'supply-request-detail',
+          path: 'supply-request-detail/:id',
+          component: '/emergency/emergency-supplies/PageSupplyRequestDetail',
+          redirect: '',
+          meta: {
+            activeMenu: '/emergency-management/emergency-supplies/supply-request',
+            alwaysShow: false,
+            frameSrc: '',
+            hidden: false,
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+            title: '物资申领详情',
+          },
+        },
         {
           id: 2005,
           parentId: 2004,

+ 85 - 0
src/types/emergency-supplier/index.ts

@@ -100,3 +100,88 @@ export interface InventoryTaskListRes {
   taskName: string;
   endTime: string; //任务截止时间
 }
+
+/**
+ * 物资申领
+ */
+export interface SupplyRequestListQuery {
+  planName?: string;
+  status?: number;
+}
+
+export interface SupplyRequestListItem {
+  /*自增主键 */
+  id?: number;
+  /*申领计划名称 */
+  planName?: string;
+  /*采购日期 */
+  purchaseDate?: string;
+  /*总价(元) */
+  totalPrice?: number;
+  /*状态:1-申请中 2-采购中 3-领用中 4-已领用 */
+  status?: number;
+  /*创建时间 */
+  createdAt?: string;
+  /*更新时间 */
+  updatedAt?: string;
+  /*0-未删除,大于0(时间戳)-已删除 */
+  isDeleted?: number;
+}
+
+export interface SupplyRequestDetailInfo {
+  /*自增主键 */
+  id: number;
+  /*申领计划id */
+  planId: number;
+  /*申领计划名称 */
+  planName: string;
+  /*物资名称 */
+  supplyName: string;
+  /*规格 */
+  specs: string;
+  /*单价(元) */
+  unitPrice: number;
+  /*小计 */
+  subtotal: string;
+  /*创建时间 */
+  createdAt: string;
+  /*更新时间 */
+  updatedAt: string;
+  /*0-未删除,大于0(时间戳)-已删除 */
+  isDeleted: number;
+}
+
+export interface SupplyRequestDetailList {
+  /*自增主键 */
+  id: number;
+  /*申领信息ID */
+  requestId: number;
+  /*需求部门ID */
+  deptId: number;
+  /*需求部门名称 */
+  deptName: string;
+  /*数量 */
+  quantity: number;
+  /*尺寸明细 */
+  sizeDetail: string;
+  /*状态:1-申请中 2-采购中 3-领用中 4-已领用 */
+  status: number;
+  /*创建时间 */
+  createdAt: string;
+  /*更新时间 */
+  updatedAt: string;
+  /*0-未删除,大于0(时间戳)-已删除 */
+  isDeleted: number;
+}
+
+export interface SupplyRequestDetailItem {
+  info: SupplyRequestDetailInfo;
+  detailList: SupplyRequestDetailList[];
+}
+
+export interface ReceiveSupplyRequestDetailForm {
+  supplyId: number; // 应急物资ID
+  quantity: number; // 领用数量
+  detailId: number; // 需求物资ID
+  planId: number; // 申领计划ID
+}

+ 205 - 0
src/views/emergency/emergency-supplies/PageSupplyRequest.vue

@@ -0,0 +1,205 @@
+<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">
+          <el-button type="primary" class="search-table-container--button" :icon="Plus" @click="handleCreateRequest">
+            创建申领计划
+          </el-button>
+          <BasicSearch
+            :searchConfig="SUPPLY_REQUEST_SEARCH_CONFIG"
+            :searchData="searchData"
+            @update-search-data="handleSearch"
+          >
+            <template #planName>
+              <el-input
+                v-model="searchData.planName"
+                placeholder="请输入物资申领计划"
+                clearable
+                @keyup.enter="handleSearch"
+              />
+            </template>
+          </BasicSearch>
+        </header>
+        <BasicTable
+          :tableData="tableData"
+          :tableConfig="tableConfig"
+          @update-page-size="handleSizeChange"
+          @update-page-number="handleCurrentChange"
+        >
+          <template #status="scope">
+            <span>{{ getStatusText(scope.row.status) }}</span>
+          </template>
+          <template #action="scope">
+            <div class="action-container--div">
+              <ActionButton text="详情" @click="handleViewDetail(scope.row.id)" />
+              <template v-if="scope.row.status === SUPPLY_REQUEST_STATUS.APPLYING">
+                <ActionButton text="发起采购" @click="handleStartPurchase(scope.row.id, scope.row.purchaseDate)" />
+                <ActionButton
+                  text="编辑"
+                  @click="handleEdit(scope.row.id, scope.row.planName, scope.row.purchaseDate)"
+                />
+                <ActionButton
+                  text="删除"
+                  :popconfirm="{
+                    title: '确定删除?',
+                  }"
+                  @confirm="handleDelete(scope.row.id)"
+                />
+              </template>
+              <template
+                v-else-if="
+                  scope.row.status === SUPPLY_REQUEST_STATUS.PURCHASING ||
+                  scope.row.status === SUPPLY_REQUEST_STATUS.RECEIVING
+                "
+              >
+                <ActionButton
+                  text="编辑"
+                  @click="handleEdit(scope.row.id, scope.row.planName, scope.row.purchaseDate)"
+                />
+              </template>
+            </div>
+          </template>
+        </BasicTable>
+      </div>
+    </div>
+  </div>
+  <SupplyRequestForm ref="supplyRequestFormRef" @success="handleFormSuccess" />
+  <StartPurchaseForm ref="startPurchaseFormRef" @success="handleFormSuccess" />
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted } from 'vue';
+  import { Plus } from '@element-plus/icons-vue';
+  import { ElMessage } from 'element-plus';
+  import { useRouter } from 'vue-router';
+  import BasicSearch from '@/components/BasicSearch.vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import ActionButton from '@/components/ActionButton.vue';
+  import SupplyRequestForm from './src/components/SupplyRequestForm.vue';
+  import StartPurchaseForm from './src/components/StartPurchaseForm.vue';
+  import {
+    SUPPLY_REQUEST_SEARCH_CONFIG,
+    SUPPLY_REQUEST_TABLE_COLUMNS,
+    SUPPLY_REQUEST_TABLE_OPTIONS,
+    SUPPLY_REQUEST_TABLE_MAX_HEIGHT_DEFAULT,
+  } from './src/config';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import { SUPPLY_REQUEST_STATUS, SUPPLY_REQUEST_STATUS_MAP } from './src/constant';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import type { SupplyRequestListQuery, SupplyRequestListItem } from '@/types/emergency-supplier';
+  import { getSupplyRequestList, deleteSupplyRequest } from '@/api/emergency-supplier';
+
+  const router = useRouter();
+
+  const { tableConfig, pagination } = useTableConfig(SUPPLY_REQUEST_TABLE_COLUMNS, SUPPLY_REQUEST_TABLE_OPTIONS);
+  let supplyRequestListQuery: QueryPageRequest<SupplyRequestListQuery> = {
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {},
+  };
+
+  const tableData = ref<SupplyRequestListItem[]>([]);
+  const searchData = reactive({
+    planName: '',
+    status: SUPPLY_REQUEST_STATUS.ALL,
+  });
+
+  const supplyRequestFormRef = ref<InstanceType<typeof SupplyRequestForm>>();
+  const startPurchaseFormRef = ref<InstanceType<typeof StartPurchaseForm>>();
+
+  // 获取状态文本
+  const getStatusText = (status: number) => {
+    return SUPPLY_REQUEST_STATUS_MAP[status] || '';
+  };
+
+  // 搜索处理
+  const handleSearch = () => {
+    supplyRequestListQuery.queryParam = {};
+    if (searchData.planName) {
+      supplyRequestListQuery.queryParam.planName = searchData.planName;
+    }
+    if (searchData.status !== null && searchData.status !== SUPPLY_REQUEST_STATUS.ALL) {
+      supplyRequestListQuery.queryParam.status = searchData.status;
+    }
+    getTableData();
+  };
+
+  // 获取表格数据
+  const getTableData = async () => {
+    tableConfig.loading = true;
+    const res = await getSupplyRequestList(supplyRequestListQuery);
+    tableData.value = res.records;
+    pagination.total = res.totalRow;
+    tableConfig.loading = false;
+  };
+
+  // 分页处理
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    supplyRequestListQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    supplyRequestListQuery.pageNumber = value;
+    getTableData();
+  };
+
+  // 创建申领计划
+  const handleCreateRequest = () => {
+    supplyRequestFormRef.value?.openDialog();
+  };
+
+  // 查看详情
+  const handleViewDetail = (id: number) => {
+    router.push({
+      name: 'supply-request-detail',
+      params: {
+        id,
+      },
+    });
+  };
+
+  // 发起采购
+  const handleStartPurchase = (id: number, purchaseDate: string) => {
+    startPurchaseFormRef.value?.openDialog(id, purchaseDate);
+  };
+
+  // 编辑
+  const handleEdit = (id: number, planName: string, purchaseDate: string) => {
+    supplyRequestFormRef.value?.openEditDialog({
+      id: id,
+      planName: planName,
+      purchaseDate: purchaseDate,
+    });
+  };
+
+  // 表单提交成功回调
+  const handleFormSuccess = () => {
+    getTableData();
+  };
+
+  // 删除
+  const handleDelete = async (id: number) => {
+    await deleteSupplyRequest(id);
+    await getTableData();
+    ElMessage.success('删除成功');
+  };
+
+  onMounted(() => {
+    getTableData();
+    tableConfig.maxHeight = SUPPLY_REQUEST_TABLE_MAX_HEIGHT_DEFAULT;
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use './src/styles/page-common.scss' as *;
+</style>

+ 271 - 0
src/views/emergency/emergency-supplies/PageSupplyRequestDetail.vue

@@ -0,0 +1,271 @@
+<template>
+  <div class="safety-platform-container">
+    <div class="safety-platform-container__header">
+      <div class="bread">
+        <BreadcrumbBack />
+        <div class="breadcrumb-title">{{ planName || '物资采购申领详情' }}</div>
+      </div>
+    </div>
+    <div class="safety-platform-container__main">
+      <div class="detail-container">
+        <header class="detail-header">
+          <el-button type="primary" :icon="Plus" @click="handleAddMaterial">添加需求物资</el-button>
+          <el-button type="primary" :icon="Download" @click="handleDownloadRecord">下载采购记录</el-button>
+        </header>
+        <div class="table-container">
+          <el-table :data="tableData" :span-method="handleSpanMethod" border v-loading="loading" style="width: 100%">
+            <el-table-column type="index" label="序号" width="80" align="center" />
+            <el-table-column prop="materialName" label="物资名称" min-width="140" align="center" />
+            <el-table-column prop="specification" label="规格" min-width="200" align="center" />
+            <el-table-column prop="unitPrice" label="单价 (元)" width="120" align="center" />
+            <el-table-column prop="subtotal" label="小计" min-width="200" align="center" />
+            <el-table-column prop="department" label="需求部门" min-width="140" align="center" />
+            <el-table-column prop="quantity" label="数量" width="100" align="center" />
+            <el-table-column prop="sizeDetails" label="尺寸明细" min-width="200" align="center" />
+            <el-table-column prop="status" label="状态" width="120" align="center">
+              <template #default="scope">
+                <span>{{ getStatusText(scope.row.status) }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="claim" label="领用" width="100" align="center">
+              <template #default="scope">
+                <el-link
+                  v-if="scope.row.status === SUPPLY_REQUEST_STATUS.PURCHASING && !scope.row.isClaimed"
+                  type="primary"
+                  @click="handleClaim(scope.row)"
+                >
+                  领用
+                </el-link>
+                <span v-else>-</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="action" label="物资操作" width="150" align="center" fixed="right">
+              <template #default="scope">
+                <div class="action-container">
+                  <el-link
+                    v-if="scope.row.status === SUPPLY_REQUEST_STATUS.APPLYING"
+                    type="primary"
+                    @click="handleEdit(scope.row)"
+                  >
+                    编辑
+                  </el-link>
+                  <el-link
+                    v-if="scope.row.status === SUPPLY_REQUEST_STATUS.APPLYING"
+                    type="primary"
+                    style="margin-left: 8px"
+                    @click="handleDelete(scope.row)"
+                  >
+                    删除
+                  </el-link>
+                  <span v-if="scope.row.status !== SUPPLY_REQUEST_STATUS.APPLYING">-</span>
+                </div>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted } from 'vue';
+  import { useRoute } from 'vue-router';
+  import { Plus, Download } from '@element-plus/icons-vue';
+  import { ElMessage, ElMessageBox } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import { SUPPLY_REQUEST_STATUS, SUPPLY_REQUEST_STATUS_MAP } from './src/constant';
+  import { getSupplyRequestDetail, deleteSupplyRequestDetail } from '@/api/emergency-supplier';
+
+  // 表格行数据类型
+  interface TableRowData {
+    id: number; // detailList 的 id
+    requestId: number; // detailList 的 requestId
+    infoId: number; // info 的 id,用于合并单元格
+    materialName: string; // info 的 supplyName
+    specification: string; // info 的 specs
+    unitPrice: number; // info 的 unitPrice
+    subtotal: string; // info 的 subtotal
+    department: string; // detailList 的 deptName
+    quantity: number; // detailList 的 quantity
+    sizeDetails: string; // detailList 的 sizeDetail
+    status: number; // detailList 的 status
+    isClaimed?: boolean; // 是否已领用(根据状态判断)
+  }
+
+  const route = useRoute();
+  const id = Number(route.params.id);
+
+  const loading = ref(false);
+  const planName = ref('');
+  const tableData = ref<TableRowData[]>([]);
+
+  // 获取状态文本
+  const getStatusText = (status: number) => {
+    return SUPPLY_REQUEST_STATUS_MAP[status] || '';
+  };
+
+  // 合并单元格方法
+  const handleSpanMethod = ({ row, rowIndex, columnIndex }: any) => {
+    // 需要合并的列索引:物资名称(1)、规格(2)、单价(3)、小计(4)
+    const mergeColumns = [1, 2, 3, 4];
+    if (!mergeColumns.includes(columnIndex)) {
+      return { rowspan: 1, colspan: 1 };
+    }
+
+    // 按照 infoId 来分组,找到同一个 info 下的所有行
+    const infoId = row.infoId;
+    const sameInfoRows = tableData.value.filter((item) => item.infoId === infoId);
+    const firstRowIndex = tableData.value.findIndex((item) => item.infoId === infoId);
+
+    // 如果是第一行,返回合并的行数
+    if (rowIndex === firstRowIndex) {
+      return {
+        rowspan: sameInfoRows.length,
+        colspan: 1,
+      };
+    }
+
+    // 其他行隐藏
+    return {
+      rowspan: 0,
+      colspan: 0,
+    };
+  };
+
+  // 获取详情数据
+  const getDetailData = async () => {
+    loading.value = true;
+    try {
+      const res = await getSupplyRequestDetail(id);
+
+      // 将 SupplyRequestDetailItem[] 转换为表格需要的扁平化数据结构
+      const flatData: TableRowData[] = [];
+
+      res.forEach((item) => {
+        // 设置计划名称(使用第一个 item 的 planName)
+        if (!planName.value && item.info.planName) {
+          planName.value = item.info.planName;
+        }
+
+        // 将每个 detailList 项与 info 合并成一行
+        item.detailList.forEach((detail) => {
+          flatData.push({
+            id: detail.id,
+            requestId: detail.requestId,
+            infoId: item.info.id, // 用于合并单元格
+            materialName: item.info.supplyName,
+            specification: item.info.specs,
+            unitPrice: item.info.unitPrice,
+            subtotal: item.info.subtotal,
+            department: detail.deptName,
+            quantity: detail.quantity,
+            sizeDetails: detail.sizeDetail,
+            status: detail.status,
+            isClaimed: detail.status === SUPPLY_REQUEST_STATUS.RECEIVED,
+          });
+        });
+      });
+
+      tableData.value = flatData;
+    } catch (error) {
+      console.error('获取详情失败:', error);
+      ElMessage.error('获取详情失败');
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  // 添加需求物资
+  const handleAddMaterial = () => {
+    // TODO: 打开添加物资的对话框
+    ElMessage.info('添加需求物资功能待实现');
+  };
+
+  // 下载采购记录
+  const handleDownloadRecord = () => {
+    // TODO: 实现下载采购记录功能
+    ElMessage.info('下载采购记录功能待实现');
+  };
+
+  // 领用
+  const handleClaim = async (row: TableRowData) => {
+    try {
+      await ElMessageBox.confirm('确定要领用该物资吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      });
+      // TODO: 调用领用接口
+      // await claimMaterial(row.id);
+      console.log('领用物资:', row);
+      ElMessage.success('领用成功');
+      await getDetailData();
+    } catch (error) {
+      if (error !== 'cancel') {
+        console.error('领用失败:', error);
+        ElMessage.error('领用失败');
+      }
+    }
+  };
+
+  // 编辑
+  const handleEdit = (row: TableRowData) => {
+    // TODO: 打开编辑对话框,需要传递 requestId 和 detailId
+    console.log('编辑物资:', row);
+    ElMessage.info('编辑功能待实现');
+  };
+
+  // 删除
+  const handleDelete = async (row: TableRowData) => {
+    try {
+      await ElMessageBox.confirm('确定要删除该物资吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      });
+      await deleteSupplyRequestDetail(row.infoId);
+      ElMessage.success('删除成功');
+      await getDetailData();
+    } catch (error) {
+      ElMessage.error('删除失败');
+    }
+  };
+
+  onMounted(() => {
+    getDetailData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use './src/styles/page-common.scss' as *;
+
+  .bread {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+
+  .detail-container {
+    background: #fff;
+    padding: 16px;
+    border-radius: 4px;
+  }
+
+  .detail-header {
+    display: flex;
+    gap: 12px;
+    margin-bottom: 16px;
+  }
+
+  .table-container {
+    width: 100%;
+  }
+
+  .action-container {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+</style>

+ 85 - 0
src/views/emergency/emergency-supplies/src/components/StartPurchaseForm.vue

@@ -0,0 +1,85 @@
+<template>
+  <BasicDialog ref="basicDialogRef" title="发起采购" @refresh="refreshFormData">
+    <template #form>
+      <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig" />
+    </template>
+    <template #footer>
+      <el-button type="primary" @click="handleSubmit">提交</el-button>
+      <el-button @click="basicDialogRef?.closeDialog">取消</el-button>
+    </template>
+  </BasicDialog>
+</template>
+
+<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 { START_PURCHASE_FORM_CONFIG, START_PURCHASE_FORM_DATA, START_PURCHASE_FORM_RULES } from '../config';
+  import { updateSupplyRequest } from '@/api/emergency-supplier';
+  import { SUPPLY_REQUEST_STATUS } from '../constant';
+
+  const emits = defineEmits<{
+    (e: 'success'): void;
+  }>();
+
+  const basicDialogRef = ref<InstanceType<typeof BasicDialog>>();
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const requestId = ref<number | undefined>(undefined);
+
+  const { ruleFormConfig, ruleFormData, formRules } = useFormConfigHook(
+    START_PURCHASE_FORM_CONFIG,
+    START_PURCHASE_FORM_DATA,
+    START_PURCHASE_FORM_RULES,
+  );
+
+  // 打开对话框
+  const openDialog = (id: number, existingPurchaseDate?: string) => {
+    requestId.value = id;
+    // 先设置采购日期,再打开对话框
+    // 如果有已有采购日期,则反显
+    ruleFormData.purchaseDate = existingPurchaseDate || '';
+    basicDialogRef.value?.openDialog();
+  };
+
+  // 提交表单
+  const handleSubmit = async () => {
+    const validate = await basicFormRef.value?.validateForm();
+    if (!validate) return;
+
+    if (!requestId.value) {
+      ElMessage.error('缺少必要参数');
+      return;
+    }
+
+    try {
+      await updateSupplyRequest({
+        id: requestId.value,
+        purchaseDate: ruleFormData.purchaseDate,
+        status: SUPPLY_REQUEST_STATUS.PURCHASING,
+      });
+      ElMessage.success('发起采购成功');
+      basicDialogRef.value?.closeDialog();
+      // 清理数据
+      requestId.value = undefined;
+      ruleFormData.purchaseDate = '';
+      // 触发父组件刷新列表
+      emits('success');
+    } catch (error) {
+      console.error('发起采购失败:', error);
+      ElMessage.error('发起采购失败');
+    }
+  };
+
+  // 刷新表单数据(在对话框打开时调用,用于清除验证状态)
+  const refreshFormData = () => {
+    basicFormRef.value?.clearValidate();
+  };
+
+  defineExpose({
+    openDialog,
+  });
+</script>
+
+<style scoped lang="scss"></style>

+ 109 - 0
src/views/emergency/emergency-supplies/src/components/SupplyRequestForm.vue

@@ -0,0 +1,109 @@
+<template>
+  <BasicDialog ref="basicDialogRef" :title="dialogTitle" @refresh="refreshFormData">
+    <template #form>
+      <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig" />
+    </template>
+    <template #footer>
+      <el-button type="primary" @click="handleSubmit">提交</el-button>
+      <el-button @click="basicDialogRef?.closeDialog">取消</el-button>
+    </template>
+  </BasicDialog>
+</template>
+
+<script setup lang="ts">
+  import { ref, computed } 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 { SUPPLY_REQUEST_FORM_CONFIG, SUPPLY_REQUEST_FORM_DATA, SUPPLY_REQUEST_FORM_RULES } from '../config';
+  import { createSupplyRequest, updateSupplyRequest } from '@/api/emergency-supplier';
+
+  const emits = defineEmits<{
+    (e: 'success'): void;
+  }>();
+
+  const basicDialogRef = ref<InstanceType<typeof BasicDialog>>();
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const isEditMode = ref(false);
+  const editId = ref<number | undefined>(undefined);
+
+  const { ruleFormConfig, ruleFormData, formRules } = useFormConfigHook(
+    SUPPLY_REQUEST_FORM_CONFIG,
+    SUPPLY_REQUEST_FORM_DATA,
+    SUPPLY_REQUEST_FORM_RULES,
+  );
+
+  // 对话框标题
+  const dialogTitle = computed(() => {
+    return isEditMode.value ? '编辑申领计划' : '创建申领计划';
+  });
+
+  // 打开对话框(创建模式)
+  const openDialog = () => {
+    isEditMode.value = false;
+    editId.value = undefined;
+    basicDialogRef.value?.openDialog();
+  };
+
+  // 打开对话框(编辑模式)
+  const openEditDialog = async (info: { id: number; planName?: string; purchaseDate?: string }) => {
+    isEditMode.value = true;
+    editId.value = info.id;
+    ruleFormData.planName = info.planName || '';
+    ruleFormData.purchaseDate = info.purchaseDate || '';
+    basicDialogRef.value?.openDialog();
+  };
+
+  // 提交表单
+  const handleSubmit = async () => {
+    const validate = await basicFormRef.value?.validateForm();
+    if (!validate) return;
+
+    try {
+      const submitData = ruleFormData.purchaseDate
+        ? {
+            planName: ruleFormData.planName,
+            purchaseDate: ruleFormData.purchaseDate,
+          }
+        : {
+            planName: ruleFormData.planName,
+          };
+
+      if (isEditMode.value && editId.value) {
+        await updateSupplyRequest({
+          id: editId.value,
+          ...submitData,
+        });
+        ElMessage.success('编辑成功');
+      } else {
+        await createSupplyRequest(submitData);
+        ElMessage.success('创建成功');
+      }
+
+      basicDialogRef.value?.closeDialog();
+      // 触发父组件刷新列表
+      emits('success');
+    } catch (error) {
+      console.error('提交失败:', error);
+      ElMessage.error(isEditMode.value ? '编辑失败' : '创建失败');
+    }
+  };
+
+  // 刷新表单数据
+  const refreshFormData = () => {
+    basicFormRef.value?.clearValidate();
+    if (!isEditMode.value) {
+      // 重置表单数据
+      ruleFormData.planName = '';
+      ruleFormData.purchaseDate = '';
+    }
+  };
+
+  defineExpose({
+    openDialog,
+    openEditDialog,
+  });
+</script>
+
+<style scoped lang="scss"></style>

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

@@ -327,3 +327,62 @@ export const SUPPLIES_DISCARD_FROM_DATA = {
 export const SUPPLIES_DISCARD_FROM_RULES = {
   quantity: [{ required: true, message: '请输入报废数量', trigger: 'blur' }],
 };
+
+// 物资申领计划表单配置
+export const SUPPLY_REQUEST_FORM_CONFIG: FormConfig[] = [
+  {
+    label: '物资申领计划:',
+    prop: 'planName',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入物资申领计划',
+    },
+  },
+  {
+    label: '采购日期:',
+    prop: 'purchaseDate',
+    component: 'ElDatePicker',
+    componentProps: {
+      placeholder: '请选择采购日期',
+      type: 'date',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+];
+
+// 物资申领计划表单数据
+export const SUPPLY_REQUEST_FORM_DATA = {
+  planName: '',
+  purchaseDate: '',
+};
+
+// 物资申领计划表单规则
+export const SUPPLY_REQUEST_FORM_RULES = {
+  planName: [{ required: true, message: '请输入物资申领计划', trigger: 'blur' }],
+};
+
+// 发起采购表单配置
+export const START_PURCHASE_FORM_CONFIG: FormConfig[] = [
+  {
+    label: '采购日期:',
+    prop: 'purchaseDate',
+    component: 'ElDatePicker',
+    componentProps: {
+      placeholder: '请选择采购日期',
+      type: 'date',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+];
+
+// 发起采购表单数据
+export const START_PURCHASE_FORM_DATA = {
+  purchaseDate: '',
+};
+
+// 发起采购表单规则
+export const START_PURCHASE_FORM_RULES = {
+  purchaseDate: [{ required: true, message: '请选择采购日期', trigger: 'change' }],
+};

+ 22 - 1
src/views/emergency/emergency-supplies/src/config/index.ts

@@ -1,4 +1,4 @@
-import { SUPPLY_LIST_SEARCH_CONFIG, INVENTORY_LIST_SEARCH_CONFIG } from './search';
+import { SUPPLY_LIST_SEARCH_CONFIG, INVENTORY_LIST_SEARCH_CONFIG, SUPPLY_REQUEST_SEARCH_CONFIG } from './search';
 import {
   SUPPLY_LIST_TABLE_COLUMNS,
   SUPPLY_LIST_TABLE_OPTIONS,
@@ -10,6 +10,10 @@ import {
   INVENTORY_CHECK_TABLE_OPTIONS,
   INVENTORY_CHECK_TABLE_MAX_HEIGHT_DEFAULT,
   INVENTORY_CHECK_TABLE_MAX_HEIGHT_PERMISSION,
+  SUPPLY_REQUEST_TABLE_COLUMNS,
+  SUPPLY_REQUEST_TABLE_OPTIONS,
+  SUPPLY_REQUEST_TABLE_MAX_HEIGHT_DEFAULT,
+  SUPPLY_REQUEST_TABLE_MAX_HEIGHT_PERMISSION,
 } from './table';
 import {
   INVENTORY_TASK_FROM_CONFIG,
@@ -29,11 +33,18 @@ import {
   SUPPLIES_DISCARD_FROM_CONFIG,
   SUPPLIES_DISCARD_FROM_DATA,
   SUPPLIES_DISCARD_FROM_RULES,
+  SUPPLY_REQUEST_FORM_CONFIG,
+  SUPPLY_REQUEST_FORM_DATA,
+  SUPPLY_REQUEST_FORM_RULES,
+  START_PURCHASE_FORM_CONFIG,
+  START_PURCHASE_FORM_DATA,
+  START_PURCHASE_FORM_RULES,
 } from './form';
 
 export {
   SUPPLY_LIST_SEARCH_CONFIG,
   INVENTORY_LIST_SEARCH_CONFIG,
+  SUPPLY_REQUEST_SEARCH_CONFIG,
   SUPPLY_LIST_TABLE_COLUMNS,
   SUPPLY_LIST_TABLE_OPTIONS,
   SUPPLY_LIST_TABLE_MAX_HEIGHT_DEFAULT,
@@ -61,4 +72,14 @@ export {
   SUPPLIES_DISCARD_FROM_CONFIG,
   SUPPLIES_DISCARD_FROM_DATA,
   SUPPLIES_DISCARD_FROM_RULES,
+  SUPPLY_REQUEST_TABLE_COLUMNS,
+  SUPPLY_REQUEST_TABLE_OPTIONS,
+  SUPPLY_REQUEST_TABLE_MAX_HEIGHT_DEFAULT,
+  SUPPLY_REQUEST_TABLE_MAX_HEIGHT_PERMISSION,
+  SUPPLY_REQUEST_FORM_CONFIG,
+  SUPPLY_REQUEST_FORM_DATA,
+  SUPPLY_REQUEST_FORM_RULES,
+  START_PURCHASE_FORM_CONFIG,
+  START_PURCHASE_FORM_DATA,
+  START_PURCHASE_FORM_RULES,
 };

+ 19 - 1
src/views/emergency/emergency-supplies/src/config/search.ts

@@ -1,5 +1,5 @@
 import type { SearchConfig } from '@/types/basic-search';
-import { EMERGENCY_SUPPLY_STATUS_OPTIONS, INVENTORY_RESULT_OPTIONS } from '../constant';
+import { EMERGENCY_SUPPLY_STATUS_OPTIONS, INVENTORY_RESULT_OPTIONS, SUPPLY_REQUEST_STATUS_OPTIONS } from '../constant';
 
 const BASIC_SEARCH_CONFIG = {
   EMERGENCY_TYPE: {
@@ -79,3 +79,21 @@ export const INVENTORY_LIST_SEARCH_CONFIG: SearchConfig[] = [
     },
   },
 ];
+
+// 物资申领搜索配置
+export const SUPPLY_REQUEST_SEARCH_CONFIG: SearchConfig[] = [
+  {
+    label: '物资申领计划:',
+    prop: 'planName',
+    slot: 'planName',
+  },
+  {
+    label: '状态:',
+    prop: 'status',
+    component: 'ElSelect',
+    selectOptions: SUPPLY_REQUEST_STATUS_OPTIONS,
+    componentProps: {
+      placeholder: '请选择状态',
+    },
+  },
+];

+ 37 - 2
src/views/emergency/emergency-supplies/src/config/table.ts

@@ -228,7 +228,7 @@ export const INVENTORY_CHECK_TABLE_COLUMNS: TableColumnProps[] = [
     slot: 'imageList',
     minWidth: '120px',
     align: 'center',
-    fixed: 'right'
+    fixed: 'right',
   },
 ];
 
@@ -236,5 +236,40 @@ 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
+  ...TABLE_OPTIONS,
+};
+
+// 物资申领表格样式配置
+export const SUPPLY_REQUEST_TABLE_OPTIONS = {
+  ...TABLE_OPTIONS,
 };
+
+export const SUPPLY_REQUEST_TABLE_MAX_HEIGHT_DEFAULT = 'calc(70vh - 100px)';
+export const SUPPLY_REQUEST_TABLE_MAX_HEIGHT_PERMISSION = 'calc(70vh - 150px)';
+
+// 物资申领表格列配置
+export const SUPPLY_REQUEST_TABLE_COLUMNS: TableColumnProps[] = [
+  BASIC_TABLE_COLUMNS.INDEX,
+  {
+    label: '物资申领计划',
+    prop: 'planName',
+    minWidth: '200px',
+  },
+  {
+    label: '采购日期',
+    prop: 'purchaseDate',
+    width: '140px',
+  },
+  {
+    label: '总价(元)',
+    prop: 'totalPrice',
+    minWidth: '140px',
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    slot: 'status',
+    minWidth: '120px',
+  },
+  BASIC_TABLE_COLUMNS.ACTION,
+];

+ 27 - 0
src/views/emergency/emergency-supplies/src/constant/index.ts

@@ -60,3 +60,30 @@ export const INVENTORY_RESULT_OPTIONS = [
   { label: INVENTORY_RESULT_MAP[INVENTORY_RESULT.DAMAGED], value: INVENTORY_RESULT.DAMAGED },
   { label: INVENTORY_RESULT_MAP[INVENTORY_RESULT.OVERDUE], value: INVENTORY_RESULT.OVERDUE },
 ];
+
+/**
+ * 物资申领状态
+ */
+export enum SUPPLY_REQUEST_STATUS {
+  ALL = 0,
+  APPLYING = 1, // 申请中
+  PURCHASING = 2, // 采购中
+  RECEIVING = 3, // 领用中
+  RECEIVED = 4, // 已领用
+}
+
+export const SUPPLY_REQUEST_STATUS_MAP = {
+  [SUPPLY_REQUEST_STATUS.ALL]: '全部',
+  [SUPPLY_REQUEST_STATUS.APPLYING]: '申请中',
+  [SUPPLY_REQUEST_STATUS.PURCHASING]: '采购中',
+  [SUPPLY_REQUEST_STATUS.RECEIVING]: '领用中',
+  [SUPPLY_REQUEST_STATUS.RECEIVED]: '已领用',
+};
+
+export const SUPPLY_REQUEST_STATUS_OPTIONS = [
+  { label: SUPPLY_REQUEST_STATUS_MAP[SUPPLY_REQUEST_STATUS.ALL], value: SUPPLY_REQUEST_STATUS.ALL },
+  { label: SUPPLY_REQUEST_STATUS_MAP[SUPPLY_REQUEST_STATUS.APPLYING], value: SUPPLY_REQUEST_STATUS.APPLYING },
+  { label: SUPPLY_REQUEST_STATUS_MAP[SUPPLY_REQUEST_STATUS.PURCHASING], value: SUPPLY_REQUEST_STATUS.PURCHASING },
+  { label: SUPPLY_REQUEST_STATUS_MAP[SUPPLY_REQUEST_STATUS.RECEIVING], value: SUPPLY_REQUEST_STATUS.RECEIVING },
+  { label: SUPPLY_REQUEST_STATUS_MAP[SUPPLY_REQUEST_STATUS.RECEIVED], value: SUPPLY_REQUEST_STATUS.RECEIVED },
+];