From eea8cb3bbe6379755410dffb774bd14e389612c2 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 04 二月 2026 13:54:26 +0800
Subject: [PATCH] 应急预案查阅模块开发

---
 src/api/safeProduction/emergencyPlanReview.js           |   38 +
 src/pages/index.vue                                     |    9 
 src/pages.json                                          |   21 
 src/pages/safeProduction/emergencyPlanReview/view.vue   |  372 ++++++++++++++
 src/pages/safeProduction/emergencyPlanReview/index.vue  |  373 ++++++++++++++
 src/pages/safeProduction/emergencyPlanReview/detail.vue |  724 +++++++++++++++++++++++++++
 6 files changed, 1,537 insertions(+), 0 deletions(-)

diff --git a/src/api/safeProduction/emergencyPlanReview.js b/src/api/safeProduction/emergencyPlanReview.js
new file mode 100644
index 0000000..5634bb7
--- /dev/null
+++ b/src/api/safeProduction/emergencyPlanReview.js
@@ -0,0 +1,38 @@
+// 搴旀�ラ妗堝鏍搁〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function safeContingencyPlanListPage(query) {
+  return request({
+    url: "/safeContingencyPlan/page",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板搴旀�ラ妗�
+export function safeContingencyPlanAdd(query) {
+    return request({
+        url: '/safeContingencyPlan',
+        method: 'post',
+        data: query
+    })
+}
+
+// 淇敼搴旀�ラ妗�
+export function safeContingencyPlanUpdate(query) {
+    return request({
+        url: '/safeContingencyPlan',
+        method: 'put',
+        data: query
+    })
+}
+
+// 鍒犻櫎搴旀�ラ妗�
+export function safeContingencyPlanDel(ids) {
+    return request({
+        url: '/safeContingencyPlan/' + ids,
+        method: 'delete',
+        data: ids
+    })
+}
diff --git a/src/pages.json b/src/pages.json
index c3210ff..5588424 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -772,6 +772,27 @@
         "navigationBarTitleText": "鍗遍櫓鐗╂枡璇︽儏",
         "navigationStyle": "custom"
       }
+    },
+    {
+      "path": "pages/safeProduction/emergencyPlanReview/index",
+      "style": {
+        "navigationBarTitleText": "搴旀�ラ妗堝鏍�",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/safeProduction/emergencyPlanReview/detail",
+      "style": {
+        "navigationBarTitleText": "搴旀�ラ妗堣鎯�",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/safeProduction/emergencyPlanReview/view",
+      "style": {
+        "navigationBarTitleText": "搴旀�ラ妗堣鎯�",
+        "navigationStyle": "custom"
+      }
     }
   ],
   "subPackages": [
diff --git a/src/pages/index.vue b/src/pages/index.vue
index 2d6bf1c..36c285a 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -323,6 +323,10 @@
       icon: "/static/images/icon/guzhangfenxi@2x.png",
       label: "鍗遍櫓鐗╂枡",
     },
+    {
+      icon: "/static/images/icon/guzhangfenxi@2x.png",
+      label: "搴旀�ラ妗�",
+    },
   ]);
   // 鍗忓悓鍔炲叕鍔熻兘鏁版嵁
   const collaborationItems = reactive([
@@ -706,6 +710,11 @@
           url: "/pages/safeProduction/hazardousMaterialsControl/index",
         });
         break;
+      case "搴旀�ラ妗�":
+        uni.navigateTo({
+          url: "/pages/safeProduction/emergencyPlanReview/index",
+        });
+        break;
       default:
         uni.showToast({
           title: `鐐瑰嚮浜�${item.label}`,
diff --git a/src/pages/safeProduction/emergencyPlanReview/detail.vue b/src/pages/safeProduction/emergencyPlanReview/detail.vue
new file mode 100644
index 0000000..6117254
--- /dev/null
+++ b/src/pages/safeProduction/emergencyPlanReview/detail.vue
@@ -0,0 +1,724 @@
+<template>
+  <view class="emergency-plan-detail">
+    <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+    <PageHeader :title="isEdit ? '缂栬緫搴旀�ラ妗�' : '鏂板搴旀�ラ妗�'"
+                @back="goBack" />
+    <!-- 琛ㄥ崟鍖哄煙 -->
+    <u-form :model="form"
+            label-width="110"
+            :rules="rules"
+            ref="formRef">
+      <!-- 搴旀�ラ妗堢紪鐮� -->
+      <u-form-item label="搴旀�ラ妗堢紪鐮�"
+                   border-bottom
+                   required
+                   prop="planCode">
+        <up-input v-model="form.planCode"
+                  placeholder="璇疯緭鍏ュ簲鎬ラ妗堢紪鐮�"
+                  clearable />
+      </u-form-item>
+      <!-- 搴旀�ラ妗堝悕绉� -->
+      <u-form-item label="搴旀�ラ妗堝悕绉�"
+                   required
+                   border-bottom
+                   prop="planName">
+        <up-input v-model="form.planName"
+                  placeholder="璇疯緭鍏ュ簲鎬ラ妗堝悕绉�"
+                  clearable />
+      </u-form-item>
+      <!-- 鍙戝竷鐢熸晥鏃堕棿 -->
+      <u-form-item label="鍙戝竷鐢熸晥鏃堕棿"
+                   required
+                   border-bottom
+                   prop="publishTime">
+        <up-input v-model="form.publishTime"
+                  placeholder="璇烽�夋嫨鍙戝竷鐢熸晥鏃堕棿"
+                  readonly
+                  @click="showTime = true" />
+        <template #right>
+          <up-icon name="arrow-right"
+                   @click="showTime = true"></up-icon>
+        </template>
+      </u-form-item>
+      <!-- 棰勬绫诲瀷 -->
+      <u-form-item label="棰勬绫诲瀷"
+                   prop="planType"
+                   required
+                   border-bottom>
+        <u-input v-model="emergencyPlanTypeLabel"
+                 placeholder="璇烽�夋嫨棰勬绫诲瀷"
+                 @click="showPlanTypeActionSheet = true"
+                 readonly />
+        <template #right>
+          <up-icon name="arrow-right"
+                   @click="showPlanTypeActionSheet = true"></up-icon>
+        </template>
+      </u-form-item>
+      <u-form-item label="鏍稿績璐d换浜�"
+                   prop="coreResponsorUserId"
+                   required
+                   border-bottom>
+        <u-input v-model="form.coreResponsorUserName"
+                 placeholder="璇烽�夋嫨鏍稿績璐d换浜�"
+                 @click="showUserActionSheet = true"
+                 readonly />
+        <template #right>
+          <up-icon name="arrow-right"
+                   @click="showUserActionSheet = true"></up-icon>
+        </template>
+      </u-form-item>
+      <!-- 澶囨敞 -->
+      <u-form-item label="澶囨敞"
+                   prop="remark">
+        <up-input v-model="form.remark"
+                  placeholder="璇疯緭鍏ュ娉�"
+                  type="textarea"
+                  rows="3"
+                  clearable />
+      </u-form-item>
+      <!-- 閫傜敤鑼冨洿 -->
+      <u-form-item label="閫傜敤鑼冨洿"
+                   required
+                   prop="applyScope">
+        <view class="checkbox-group">
+          <u-checkbox-group v-model="form.applyScope"
+                            @change="handleApplyScopeChange">
+            <u-checkbox shape="circle"
+                        size="32rpx"
+                        class="checkbox-item"
+                        v-for="(item, index) in applyScopeOptions"
+                        :key="index"
+                        :label="item.label"
+                        :name="item.value">
+            </u-checkbox>
+          </u-checkbox-group>
+        </view>
+      </u-form-item>
+      <!-- 搴旀�ュ缃楠� -->
+      <view class="exec-steps-container">
+        <view class="steps-header">
+          <text class="steps-title">澶勭疆姝ラ鍒楄〃</text>
+          <text class="steps-count">鍏� {{ execStepsList.length }} 涓楠�</text>
+        </view>
+        <view class="steps-list">
+          <view v-for="(step, index) in execStepsList"
+                :key="index"
+                class="exec-step-item">
+            <view class="delete-btn"
+                  @click="removeExecStep(index)">
+              <u-icon name="close"
+                      color="#fff"
+                      size="16" />
+            </view>
+            <view class="step-number">
+              {{ index + 1 }}
+            </view>
+            <view class="step-content">
+              <view class="step-row">
+                <text class="step-label">姝ラ鍚嶇О锛�</text>
+                <u-textarea v-model="step.step"
+                            placeholder="璇疯緭鍏ユ楠ゅ悕绉�"
+                            clearable
+                            border-bottom
+                            class="step-input" />
+              </view>
+              <view class="step-row">
+                <text class="step-label">澶勭疆鎺柦锛�</text>
+                <u-textarea v-model="step.description"
+                            placeholder="璇疯緭鍏ュ叿浣撶殑搴旀�ュ缃帾鏂�"
+                            type="textarea"
+                            clearable
+                            class="step-textarea" />
+              </view>
+            </view>
+          </view>
+        </view>
+        <u-button type="primary"
+                  @click="addExecStep"
+                  class="add-step-btn">
+          <text>娣诲姞姝ラ</text>
+        </u-button>
+      </view>
+    </u-form>
+    <!-- 鍙戝竷鐢熸晥鏃堕棿閫夋嫨鍣� -->
+    <up-datetime-picker :show="showTime"
+                        v-model="currentTime"
+                        @confirm="handleDateConfirm"
+                        @cancel="showTime = false"
+                        mode="date" />
+    <!--棰勬绫诲瀷閫夋嫨鍣� -->
+    <up-action-sheet :show="showPlanTypeActionSheet"
+                     :actions="emergencyPlanTypeOptions"
+                     @select="handlePlanTypeConfirm"
+                     title="閫夋嫨棰勬绫诲瀷" />
+    <!--鏍稿績璐d换浜洪�夋嫨鍣� -->
+    <up-action-sheet :show="showUserActionSheet"
+                     :actions="userList"
+                     @select="handleUserConfirm"
+                     title="閫夋嫨鏍稿績璐d换浜�" />
+  </view>
+  <!-- 搴曢儴鎸夐挳 -->
+  <view class="bottom-buttons">
+    <u-button type="default"
+              size="default"
+              @click="goBack"
+              class="bottom-btn">
+      鍙栨秷
+    </u-button>
+    <u-button type="primary"
+              size="default"
+              @click="submitForm"
+              class="bottom-btn">
+      淇濆瓨
+    </u-button>
+  </view>
+</template>
+
+<script setup>
+  import { ref, onMounted, computed } from "vue";
+  import { onShow } from "@dcloudio/uni-app";
+  import PageHeader from "@/components/PageHeader.vue";
+  import {
+    safeContingencyPlanAdd,
+    safeContingencyPlanUpdate,
+  } from "@/api/safeProduction/emergencyPlanReview";
+  import { userListNoPage } from "@/api/system/user";
+  import { useDict } from "@/utils/dict";
+  import dayjs from "dayjs";
+  // 鏇挎崲 toast 鏂规硶
+  defineOptions({ name: "emergency-plan-detail" });
+  const showToast = message => {
+    uni.showToast({
+      title: message,
+      icon: "none",
+    });
+  };
+
+  // 琛ㄥ崟寮曠敤
+  const formRef = ref();
+
+  // 琛ㄥ崟鏁版嵁
+  const form = ref({
+    id: "",
+    planCode: "",
+    planName: "",
+    publishTime: "",
+    planType: "",
+    coreResponsorUserId: "",
+    coreResponsorUserName: "",
+    remark: "",
+    applyScope: [],
+    execSteps: "",
+  });
+
+  // 搴旀�ュ缃楠ゅ垪琛�
+  const execStepsList = ref([]);
+
+  // 鏃ユ湡鑼冨洿
+  const minDate = new Date("2000-01-01");
+  const maxDate = new Date("2030-12-31");
+  const currentTime = ref(Date.now());
+  // 鐢ㄦ埛鍒楄〃
+  const userList = ref([]);
+
+  // 搴旀�ラ妗堢被鍨嬮�夐」
+  const { emergency_plan_type } = useDict("emergency_plan_type");
+  const emergencyPlanTypeOptions = computed(() => {
+    return (
+      emergency_plan_type?.value.map(item => ({
+        value: item.value,
+        name: item.label,
+      })) || []
+    );
+  });
+
+  // 搴旀�ラ妗堢被鍨嬫爣绛�
+  const emergencyPlanTypeLabel = ref("");
+
+  // 閫傜敤鑼冨洿閫夐」
+  const applyScopeOptions = [
+    { value: "all", label: "鍏ㄤ綋鍛樺伐" },
+    { value: "manager", label: "绠$悊灞�" },
+    { value: "hr", label: "浜轰簨閮ㄩ棬" },
+    { value: "finance", label: "璐㈠姟閮ㄩ棬" },
+    { value: "tech", label: "鎶�鏈儴闂�" },
+  ];
+
+  // 鏄惁涓虹紪杈戞ā寮�
+  const isEdit = ref(false);
+
+  // ActionSheet 鏄剧ず鐘舵��
+  const showPlanTypeActionSheet = ref(false);
+  const showUserActionSheet = ref(false);
+  const showTime = ref(false);
+
+  // 鍒濆鍖栨暟鎹�
+  const initData = () => {
+    const emergencyPlan = uni.getStorageSync("emergencyPlan") || {};
+    if (emergencyPlan.id) {
+      // 缂栬緫妯″紡
+      isEdit.value = true;
+      form.value = {
+        id: emergencyPlan.id,
+        planCode: emergencyPlan.planCode || "",
+        planName: emergencyPlan.planName || "",
+        publishTime: emergencyPlan.publishTime || "",
+        planType: emergencyPlan.planType || "",
+        coreResponsorUserId: emergencyPlan.coreResponsorUserId || "",
+        coreResponsorUserName: emergencyPlan.coreResponsorUserName || "",
+        remark: emergencyPlan.remark || "",
+        applyScope: emergencyPlan.applyScope
+          ? emergencyPlan.applyScope.split(",")
+          : [],
+        execSteps: emergencyPlan.execSteps || "",
+      };
+      currentTime.value = new Date(emergencyPlan.publishTime).getTime();
+      // 璁剧疆棰勬绫诲瀷鏍囩
+      const planTypeItem = emergencyPlanTypeOptions.value.find(
+        item => item.value === emergencyPlan.planType
+      );
+      emergencyPlanTypeLabel.value = planTypeItem ? planTypeItem.name : "";
+      console.log(form.value.applyScope, form.value.applyScope);
+      // 鍒濆鍖栧簲鎬ュ缃楠�
+      initExecSteps(emergencyPlan.execSteps);
+    } else {
+      // 鏂板妯″紡
+      isEdit.value = false;
+      form.value = {
+        planCode: "",
+        planName: "",
+        publishTime: new Date().toISOString().split("T")[0],
+        planType: "",
+        coreResponsorUserId: "",
+        coreResponsorUserName: "",
+        remark: "",
+        applyScope: [],
+        execSteps: "",
+      };
+      emergencyPlanTypeLabel.value = "";
+      execStepsList.value = [];
+      addExecStep();
+    }
+  };
+  const handleApplyScopeChange = e => {
+    // form.value.applyScope = e;
+    console.log(e, "e");
+    console.log(form.value.applyScope, "form.value.applyScope");
+  };
+
+  // 鍒濆鍖栧簲鎬ュ缃楠�
+  const initExecSteps = execSteps => {
+    if (execSteps) {
+      try {
+        execStepsList.value = JSON.parse(execSteps);
+      } catch (e) {
+        execStepsList.value = [];
+      }
+    } else {
+      execStepsList.value = [];
+    }
+    if (execStepsList.value.length === 0) {
+      addExecStep();
+    }
+  };
+
+  // 娣诲姞搴旀�ュ缃楠�
+  const addExecStep = () => {
+    const stepNumber = execStepsList.value.length + 1;
+    execStepsList.value.push({
+      step: `姝ラ${stepNumber}`,
+      description: "",
+    });
+  };
+
+  // 鍒犻櫎搴旀�ュ缃楠�
+  const removeExecStep = index => {
+    if (execStepsList.value.length > 1) {
+      execStepsList.value.splice(index, 1);
+    } else {
+      showToast("鑷冲皯淇濈暀涓�涓楠�");
+    }
+  };
+
+  // 鑾峰彇鐢ㄦ埛鍒楄〃
+  const getUserList = () => {
+    userListNoPage()
+      .then(res => {
+        userList.value = res.data.map(item => ({
+          value: item.userId,
+          name: item.nickName,
+        }));
+      })
+      .catch(() => {
+        showToast("鑾峰彇鐢ㄦ埛鍒楄〃澶辫触");
+      });
+  };
+
+  // 鏃ユ湡閫夋嫨纭
+  const handleDateConfirm = e => {
+    form.value.publishTime = dayjs(e.value).format("YYYY-MM-DD");
+    showTime.value = false;
+  };
+
+  // 棰勬绫诲瀷閫夋嫨纭
+  const handlePlanTypeConfirm = e => {
+    form.value.planType = e.value;
+    const selectedType = emergencyPlanTypeOptions.value.find(
+      item => item.value === e.value
+    );
+    if (selectedType) {
+      emergencyPlanTypeLabel.value = selectedType.name;
+    }
+    showPlanTypeActionSheet.value = false;
+  };
+
+  // 鐢ㄦ埛閫夋嫨纭
+  const handleUserConfirm = e => {
+    form.value.coreResponsorUserId = e.value;
+    const selectedUser = userList.value.find(user => user.value === e.value);
+    if (selectedUser) {
+      form.value.coreResponsorUserName = selectedUser.name;
+    }
+    showUserActionSheet.value = false;
+  };
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+
+  // 琛ㄥ崟楠岃瘉瑙勫垯
+  const rules = {
+    planCode: [
+      {
+        required: true,
+        message: "璇疯緭鍏ュ簲鎬ラ妗堢紪鐮�",
+        trigger: ["submit", "blur"],
+      },
+    ],
+    planName: [
+      {
+        required: true,
+        message: "璇疯緭鍏ュ簲鎬ラ妗堝悕绉�",
+        trigger: ["submit", "blur"],
+      },
+    ],
+    publishTime: [
+      {
+        required: true,
+        message: "璇烽�夋嫨鍙戝竷鐢熸晥鏃堕棿",
+        trigger: ["submit", "change"],
+      },
+    ],
+    planType: [
+      {
+        required: true,
+        message: "璇烽�夋嫨棰勬绫诲瀷",
+        trigger: ["submit", "change"],
+      },
+    ],
+    coreResponsorUserId: [
+      {
+        required: true,
+        message: "璇烽�夋嫨鏍稿績璐d换浜�",
+        trigger: ["submit", "change"],
+      },
+    ],
+    applyScope: [
+      {
+        required: true,
+        message: "璇烽�夋嫨閫傜敤鑼冨洿",
+        trigger: ["submit", "change"],
+      },
+    ],
+  };
+
+  // 鎻愪氦琛ㄥ崟
+  const submitForm = async () => {
+    // 楠岃瘉琛ㄥ崟蹇呭~椤�
+    if (!formRef.value) return;
+
+    const valid = await formRef.value.validate();
+    if (!valid) {
+      return;
+    }
+
+    // 楠岃瘉搴旀�ュ缃楠�
+    for (let i = 0; i < execStepsList.value.length; i++) {
+      const step = execStepsList.value[i];
+      if (!step.step || !step.step.trim()) {
+        showToast(`绗�${i + 1}鏉℃楠ょ殑"姝ラ"涓嶈兘涓虹┖`);
+        return;
+      }
+      if (!step.description || !step.description.trim()) {
+        showToast(`绗�${i + 1}鏉℃楠ょ殑"鎺柦"涓嶈兘涓虹┖`);
+        return;
+      }
+    }
+
+    // 灏嗗簲鎬ュ缃楠よ浆鎹负JSON瀛楃涓�
+    form.value.execSteps = JSON.stringify(execStepsList.value);
+
+    // 澶勭悊閫傜敤鑼冨洿
+    form.value.applyScope = form.value.applyScope.join(",");
+
+    showLoadingToast("淇濆瓨涓�...");
+
+    try {
+      if (isEdit.value) {
+        // 缂栬緫妯″紡
+        const res = await safeContingencyPlanUpdate(form.value);
+        if (res.code === 200) {
+          showToast("鏇存柊鎴愬姛");
+          setTimeout(() => {
+            uni.navigateBack();
+          }, 1000);
+        } else {
+          showToast(res.msg || "鏇存柊澶辫触");
+        }
+      } else {
+        // 鏂板妯″紡
+        const res = await safeContingencyPlanAdd(form.value);
+        if (res.code === 200) {
+          showToast("娣诲姞鎴愬姛");
+          setTimeout(() => {
+            uni.navigateBack();
+          }, 1000);
+        } else {
+          showToast(res.msg || "娣诲姞澶辫触");
+        }
+      }
+    } catch (error) {
+      console.error("鎻愪氦澶辫触:", error);
+      showToast("鎻愪氦澶辫触锛岃閲嶈瘯");
+    } finally {
+      closeToast();
+    }
+  };
+
+  // 鏄剧ず鍔犺浇鎻愮ず
+  const showLoadingToast = message => {
+    uni.showLoading({
+      title: message,
+      mask: true,
+    });
+  };
+
+  // 鍏抽棴鎻愮ず
+  const closeToast = () => {
+    uni.hideLoading();
+  };
+
+  onMounted(() => {
+    initData();
+    getUserList();
+  });
+
+  onShow(() => {
+    initData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "@/static/scss/form-common.scss";
+  .emergency-plan-detail {
+    min-height: 100vh;
+    background: #f8f9fa;
+    padding-bottom: 100px;
+  }
+
+  .form-section {
+  }
+
+  .checkbox-group {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 16px;
+  }
+
+  .checkbox-item {
+    margin-right: 16px;
+  }
+
+  .select-container {
+    position: relative;
+    width: 100%;
+  }
+
+  .select-container .up-input {
+    width: 100%;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    padding: 12px 16px;
+    background-color: #ffffff;
+  }
+
+  .exec-steps-container {
+    padding: 20px;
+    background-color: #fff;
+  }
+
+  .steps-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #e4e7ed;
+  }
+
+  .steps-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+  }
+
+  .steps-count {
+    font-size: 14px;
+    color: #909399;
+  }
+
+  .steps-list {
+    margin-bottom: 20px;
+  }
+
+  .exec-step-item {
+    position: relative;
+    display: flex;
+    margin-bottom: 16px;
+    padding: 16px;
+    background-color: #ffffff;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    transition: all 0.3s ease;
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+  }
+
+  .exec-step-item:hover {
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+    border-color: #409eff;
+    transform: translateY(-1px);
+  }
+
+  .delete-btn {
+    position: absolute;
+    top: -25rpx;
+    right: -25rpx;
+    width: 50rpx;
+    height: 50rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+    font-size: 20px;
+    border-radius: 50%;
+    background-color: red;
+    border: none;
+    z-index: 10;
+  }
+
+  .delete-btn:hover {
+    transform: scale(1.1);
+    box-shadow: 0 3px 6px rgba(245, 108, 108, 0.4);
+  }
+
+  .step-number {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 32px;
+    height: 32px;
+    margin-right: 16px;
+    background-color: #ecf5ff;
+    color: #409eff;
+    font-size: 14px;
+    font-weight: 600;
+    border-radius: 50%;
+    flex-shrink: 0;
+  }
+
+  .step-content {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .step-row {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: 12px;
+  }
+
+  .step-row:last-child {
+    margin-bottom: 0;
+  }
+
+  .step-label {
+    display: inline-block;
+    width: 80px;
+    font-size: 14px;
+    color: #606266;
+    margin-right: 12px;
+    flex-shrink: 0;
+    line-height: 36px;
+  }
+
+  .step-input {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .step-input input {
+    font-size: 14px;
+    color: #303133;
+  }
+
+  .step-textarea {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .step-textarea textarea {
+    font-size: 14px;
+    color: #303133;
+    min-height: 80px;
+    line-height: 1.5;
+  }
+
+  .add-step-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 44px;
+    line-height: 44px;
+    font-size: 14px;
+    border-radius: 8px;
+    transition: all 0.3s ease;
+    gap: 8px;
+  }
+
+  .add-step-btn:hover {
+    transform: translateY(-1px);
+    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+  }
+
+  .add-step-btn text {
+    font-size: 14px;
+  }
+
+  .bottom-buttons {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    display: flex;
+    padding: 16px 20px;
+    background: #ffffff;
+    border-top: 1px solid #f0f0f0;
+    gap: 16px;
+  }
+
+  .bottom-btn {
+    flex: 1;
+  }
+</style>
diff --git a/src/pages/safeProduction/emergencyPlanReview/index.vue b/src/pages/safeProduction/emergencyPlanReview/index.vue
new file mode 100644
index 0000000..f575002
--- /dev/null
+++ b/src/pages/safeProduction/emergencyPlanReview/index.vue
@@ -0,0 +1,373 @@
+<template>
+  <view class="emergency-plan-review">
+    <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+    <PageHeader title="搴旀�ラ妗堝鏍�"
+                @back="goBack" />
+    <!-- 鎼滅储鍜岀瓫閫夊尯鍩� -->
+    <view class="search-section">
+      <view class="search-bar">
+        <view class="search-input">
+          <up-input class="search-text"
+                    placeholder="璇疯緭鍏ュ簲鎬ラ妗堝悕绉�"
+                    v-model="searchForm.planName"
+                    @change="searchChange"
+                    clearable />
+        </view>
+        <view class="filter-button"
+              @click="getList">
+          <u-icon name="search"
+                  size="24"
+                  color="#999"></u-icon>
+        </view>
+      </view>
+    </view>
+    <!-- 搴旀�ラ妗堝垪琛� -->
+    <view class="ledger-list"
+          v-if="planList.length > 0">
+      <view v-for="(item, index) in planList"
+            :key="index">
+        <view class="ledger-item">
+          <view class="item-header">
+            <view class="item-left">
+              <view class="document-icon">
+                <up-icon name="file-text"
+                         size="16"
+                         color="#ffffff"></up-icon>
+              </view>
+              <text class="item-title">{{ item.planName }}</text>
+            </view>
+          </view>
+          <up-divider></up-divider>
+          <view class="item-details"
+                @click="viewDetail(item)">
+            <view class="detail-row">
+              <text class="detail-label">棰勬缂栫爜</text>
+              <text class="detail-value">{{ item.planCode || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">棰勬绫诲瀷</text>
+              <u-tag :type="getPlanTypeTagType(item.planType)">
+                {{ emergencyPlanTypeLabel(item.planType) }}
+              </u-tag>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍙戝竷鐢熸晥鏃堕棿</text>
+              <text class="detail-value">{{ item.publishTime || '-' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鏍稿績璐d换浜�</text>
+              <text class="detail-value">{{ item.coreResponsorUserName || '-' }}</text>
+            </view>
+            <view class="detail-row"
+                  v-if="item.remark">
+              <text class="detail-label">澶囨敞</text>
+              <text class="detail-value">{{ item.remark }}</text>
+            </view>
+          </view>
+          <!-- 鎸夐挳鍖哄煙 -->
+          <view class="action-buttons">
+            <u-button type="primary"
+                      size="small"
+                      class="action-btn"
+                      @click="editPlan(item)">
+              缂栬緫
+            </u-button>
+            <u-button type="info"
+                      size="small"
+                      class="action-btn"
+                      @click="viewDetail(item)">
+              鏌ョ湅璇︽儏
+            </u-button>
+            <u-button type="error"
+                      size="small"
+                      class="action-btn"
+                      @click="deletePlan(item)">
+              鍒犻櫎
+            </u-button>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view v-else
+          class="no-data">
+      <text>鏆傛棤搴旀�ラ妗�</text>
+    </view>
+    <!-- 娴姩鏂板鎸夐挳 -->
+    <view class="fab-button"
+          @click="addPlan">
+      <up-icon name="plus"
+               size="24"
+               color="#ffffff"></up-icon>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, onMounted, computed } from "vue";
+  import { onShow } from "@dcloudio/uni-app";
+  import PageHeader from "@/components/PageHeader.vue";
+  import {
+    safeContingencyPlanListPage,
+    safeContingencyPlanDel,
+  } from "@/api/safeProduction/emergencyPlanReview";
+  import { useDict } from "@/utils/dict";
+  import useUserStore from "@/store/modules/user";
+
+  // 鏇挎崲 toast 鏂规硶
+  defineOptions({ name: "emergency-plan-review-index" });
+  const showToast = message => {
+    uni.showToast({
+      title: message,
+      icon: "none",
+    });
+  };
+
+  const userStore = useUserStore();
+
+  // 鎼滅储琛ㄥ崟
+  const searchForm = ref({
+    planName: "",
+  });
+
+  // 搴旀�ラ妗堟暟鎹�
+  const planList = ref([]);
+
+  // 搴旀�ラ妗堢被鍨嬮�夐」
+  const { emergency_plan_type } = useDict("emergency_plan_type");
+  const emergencyPlanTypeOptions = computed(
+    () => emergency_plan_type?.value || []
+  );
+
+  // 鑾峰彇棰勬绫诲瀷鏍囩绫诲瀷
+  const getPlanTypeTagType = planType => {
+    const typeMap = {
+      emergency: "warning",
+      fire: "danger",
+      natural: "info",
+      accident: "error",
+    };
+    return typeMap[planType] || "info";
+  };
+
+  // 鑾峰彇棰勬绫诲瀷鏍囩鏂囨湰
+  const emergencyPlanTypeLabel = val => {
+    const item = emergencyPlanTypeOptions.value.find(
+      i => String(i.value) === String(val)
+    );
+    return item ? item.label : val;
+  };
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+  const searchChange = val => {
+    searchForm.value.planName = val;
+    getList();
+  };
+
+  // 鏌ヨ鍒楄〃
+  const getList = () => {
+    showLoadingToast("鍔犺浇涓�...");
+    const params = {
+      current: -1,
+      size: -1,
+      ...searchForm.value,
+    };
+    safeContingencyPlanListPage(params)
+      .then(res => {
+        planList.value = res.records || res.data?.records || [];
+        closeToast();
+      })
+      .catch(() => {
+        closeToast();
+        showToast("鑾峰彇鏁版嵁澶辫触");
+      });
+  };
+
+  // 鏄剧ず鍔犺浇鎻愮ず
+  const showLoadingToast = message => {
+    uni.showLoading({
+      title: message,
+      mask: true,
+    });
+  };
+
+  // 鍏抽棴鎻愮ず
+  const closeToast = () => {
+    uni.hideLoading();
+  };
+
+  // 鏂板搴旀�ラ妗�
+  const addPlan = () => {
+    uni.setStorageSync("emergencyPlan", {});
+    uni.navigateTo({
+      url: "/pages/safeProduction/emergencyPlanReview/detail",
+    });
+  };
+
+  // 缂栬緫搴旀�ラ妗�
+  const editPlan = item => {
+    uni.setStorageSync("emergencyPlan", item);
+    uni.navigateTo({
+      url: "/pages/safeProduction/emergencyPlanReview/detail",
+    });
+  };
+
+  // 鍒犻櫎搴旀�ラ妗�
+  const deletePlan = item => {
+    uni.showModal({
+      title: "鍒犻櫎纭",
+      content: `纭畾瑕佸垹闄よ搴旀�ラ妗堝悧锛焋,
+      success: res => {
+        if (res.confirm) {
+          deleteEmergencyPlan(item.id);
+        }
+      },
+    });
+  };
+
+  // 鍒犻櫎搴旀�ラ妗堣褰�
+  const deleteEmergencyPlan = id => {
+    showLoadingToast("鍒犻櫎涓�...");
+    safeContingencyPlanDel([id])
+      .then(() => {
+        closeToast();
+        showToast("鍒犻櫎鎴愬姛");
+        getList();
+      })
+      .catch(() => {
+        closeToast();
+        showToast("鍒犻櫎澶辫触");
+      });
+  };
+
+  // 鏌ョ湅璇︽儏
+  const viewDetail = item => {
+    uni.setStorageSync("emergencyPlan", item);
+    uni.navigateTo({
+      url: "/pages/safeProduction/emergencyPlanReview/view",
+    });
+  };
+
+  onMounted(() => {
+    getList();
+  });
+
+  onShow(() => {
+    getList();
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "../../../styles/sales-common.scss";
+
+  // 椤甸潰鐗瑰畾鐨勬牱寮忚鐩�
+  .emergency-plan-review {
+    min-height: 100vh;
+    background: #f8f9fa;
+    position: relative;
+  }
+
+  // 鐗瑰畾鐨勫浘鏍囨牱寮�
+  .document-icon {
+    background: #2979ff; // 涓庨攢鍞ā鍧椾繚鎸佷竴鑷寸殑鑳屾櫙鑹�
+  }
+
+  // 鐗规湁鏍峰紡
+  .detail-value {
+    word-break: break-all; // 淇濈暀椤甸潰鐗规湁鐨勬枃鏈崲琛屾牱寮�
+  }
+
+  // 鐗瑰畾鐨勬诞鍔ㄦ寜閽牱寮�
+  .fab-button {
+    background: #2979ff; // 涓庨攢鍞ā鍧椾繚鎸佷竴鑷寸殑鑳屾櫙鑹�
+    box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3); // 涓庨攢鍞ā鍧椾繚鎸佷竴鑷寸殑闃村奖鏁堟灉
+  }
+
+  // 鎿嶄綔鎸夐挳鏍峰紡
+  .action-buttons {
+    display: flex;
+    gap: 12px;
+    padding: 0 0 16px 0;
+    justify-content: space-between;
+  }
+
+  .action-btn {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+  }
+
+  // 鍒楄〃瀹瑰櫒鏍峰紡
+  .plan-list {
+    padding: 20px;
+  }
+
+  // 鍒楄〃椤规牱寮�
+  .plan-item {
+    background: #ffffff;
+    border-radius: 12px;
+    margin-bottom: 16px;
+    overflow: hidden;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+    padding: 0 16px;
+
+    &:active {
+      transform: scale(0.98);
+      box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+    }
+  }
+
+  // 椤圭洰澶撮儴鏍峰紡
+  .item-header {
+    padding: 16px 0;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .item-left {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+
+  .item-title {
+    font-size: 14px;
+    color: #333;
+    font-weight: 500;
+  }
+
+  // 璇︽儏鍖哄煙鏍峰紡
+  .item-details {
+    padding: 16px 0;
+  }
+
+  .detail-row {
+    display: flex;
+    align-items: flex-end;
+    justify-content: space-between;
+    margin-bottom: 8px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  .detail-label {
+    font-size: 12px;
+    color: #777777;
+    min-width: 60px;
+  }
+
+  .detail-value {
+    font-size: 12px;
+    color: #000000;
+    text-align: right;
+    flex: 1;
+    margin-left: 16px;
+  }
+</style>
diff --git a/src/pages/safeProduction/emergencyPlanReview/view.vue b/src/pages/safeProduction/emergencyPlanReview/view.vue
new file mode 100644
index 0000000..45a8124
--- /dev/null
+++ b/src/pages/safeProduction/emergencyPlanReview/view.vue
@@ -0,0 +1,372 @@
+<template>
+  <view class="emergency-plan-view">
+    <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+    <PageHeader title="搴旀�ラ妗堣鎯�"
+                @back="goBack" />
+    <!-- 璇︽儏鍐呭 -->
+    <view class="detail-content"
+          v-if="currentPlan">
+      <!-- 鍩烘湰淇℃伅 -->
+      <view class="info-section">
+        <!-- <view class="section-title">
+          <text class="title-text">{{ currentPlan.planName }}</text>
+        </view> -->
+        <view class="info-grid">
+          <view class="info-item">
+            <text class="info-label">搴旀�ラ妗堢紪鐮�</text>
+            <text class="info-value">{{ currentPlan.planCode || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">搴旀�ラ妗堝悕绉�</text>
+            <text class="info-value">{{ currentPlan.planName || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">棰勬绫诲瀷</text>
+            <u-tag :type="getPlanTypeTagType(currentPlan.planType)">
+              {{ emergencyPlanTypeLabel(currentPlan.planType) }}
+            </u-tag>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鍙戝竷鐢熸晥鏃堕棿</text>
+            <text class="info-value">{{ currentPlan.publishTime || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">鏍稿績璐d换浜�</text>
+            <text class="info-value">{{ currentPlan.coreResponsorUserName || '-' }}</text>
+          </view>
+          <view class="info-item">
+            <text class="info-label">澶囨敞</text>
+            <text class="info-value">{{ currentPlan.remark || '-' }}</text>
+          </view>
+        </view>
+      </view>
+      <!-- 閫傜敤鑼冨洿 -->
+      <view class="scope-section">
+        <view class="section-header">
+          <text class="section-heading">閫傜敤鑼冨洿</text>
+        </view>
+        <view class="scope-tags">
+          <u-tag v-for="(scope, index) in applyScopeList"
+                 :key="index"
+                 type="primary"
+                 size="small"
+                 class="scope-tag">
+            {{ scope }}
+          </u-tag>
+        </view>
+      </view>
+      <!-- 搴旀�ュ缃楠� -->
+      <view class="steps-section">
+        <view class="section-header">
+          <text class="section-heading">搴旀�ュ缃楠�</text>
+        </view>
+        <view class="steps-list"
+              v-if="execStepsList.length > 0">
+          <view v-for="(step, index) in execStepsList"
+                :key="index"
+                class="step-item">
+            <view class="step-number">{{ index + 1 }}</view>
+            <view class="step-content">
+              <text class="step-title">{{ step.step }}</text>
+              <text class="step-description">{{ step.description }}</text>
+            </view>
+          </view>
+        </view>
+        <view class="no-steps"
+              v-else>
+          <text>鏆傛棤搴旀�ュ缃楠�</text>
+        </view>
+      </view>
+    </view>
+    <!-- 绌虹姸鎬� -->
+    <view class="empty-state"
+          v-else>
+      <text>鏆傛棤搴旀�ラ妗堜俊鎭�</text>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, onMounted, computed } from "vue";
+  import { onShow } from "@dcloudio/uni-app";
+  import PageHeader from "@/components/PageHeader.vue";
+  import { useDict } from "@/utils/dict";
+
+  // 鏇挎崲 toast 鏂规硶
+  defineOptions({ name: "emergency-plan-view" });
+  const showToast = message => {
+    uni.showToast({
+      title: message,
+      icon: "none",
+    });
+  };
+
+  // 褰撳墠搴旀�ラ妗�
+  const currentPlan = ref(null);
+
+  // 搴旀�ュ缃楠ゅ垪琛�
+  const execStepsList = ref([]);
+
+  // 閫傜敤鑼冨洿鍒楄〃
+  const applyScopeList = ref([]);
+
+  // 搴旀�ラ妗堢被鍨嬮�夐」
+  const { emergency_plan_type } = useDict("emergency_plan_type");
+  const emergencyPlanTypeOptions = computed(
+    () => emergency_plan_type?.value || []
+  );
+
+  // 鑾峰彇棰勬绫诲瀷鏍囩绫诲瀷
+  const getPlanTypeTagType = planType => {
+    const typeMap = {
+      emergency: "warning",
+      fire: "danger",
+      natural: "info",
+      accident: "error",
+    };
+    return typeMap[planType] || "info";
+  };
+
+  // 鑾峰彇棰勬绫诲瀷鏍囩鏂囨湰
+  const emergencyPlanTypeLabel = val => {
+    const item = emergencyPlanTypeOptions.value.find(
+      i => String(i.value) === String(val)
+    );
+    return item ? item.label : val;
+  };
+
+  // 鍒濆鍖栨暟鎹�
+  const initData = () => {
+    const emergencyPlan = uni.getStorageSync("emergencyPlan") || {};
+    if (emergencyPlan.id) {
+      currentPlan.value = emergencyPlan;
+
+      // 澶勭悊閫傜敤鑼冨洿
+      if (emergencyPlan.applyScope) {
+        const scopes = emergencyPlan.applyScope.split(",");
+        applyScopeList.value = scopes.map(scope => {
+          const scopeMap = {
+            all: "鍏ㄤ綋鍛樺伐",
+            manager: "绠$悊灞�",
+            hr: "浜轰簨閮ㄩ棬",
+            finance: "璐㈠姟閮ㄩ棬",
+            tech: "鎶�鏈儴闂�",
+          };
+          return scopeMap[scope] || scope;
+        });
+      } else {
+        applyScopeList.value = [];
+      }
+
+      // 澶勭悊搴旀�ュ缃楠�
+      if (emergencyPlan.execSteps) {
+        try {
+          execStepsList.value = JSON.parse(emergencyPlan.execSteps);
+        } catch (e) {
+          execStepsList.value = [];
+        }
+      } else {
+        execStepsList.value = [];
+      }
+    } else {
+      currentPlan.value = null;
+      applyScopeList.value = [];
+      execStepsList.value = [];
+      showToast("鏈壘鍒板簲鎬ラ妗堜俊鎭�");
+    }
+  };
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+
+  onMounted(() => {
+    initData();
+  });
+
+  onShow(() => {
+    initData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "../../../styles/sales-common.scss";
+
+  .emergency-plan-view {
+    min-height: 100vh;
+    background: #f8f9fa;
+    padding-bottom: 32px;
+  }
+
+  .detail-content {
+    padding: 20px;
+  }
+
+  // 淇℃伅 section
+  .info-section {
+    background: #ffffff;
+    border-radius: 12px;
+    padding: 24px;
+    margin-bottom: 24px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  }
+
+  .section-title {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 24px;
+  }
+
+  .title-text {
+    font-size: 18px;
+    font-weight: 600;
+    color: #303133;
+    flex: 1;
+  }
+
+  .type-tag {
+    margin-left: 16px;
+  }
+
+  .info-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 20px;
+  }
+
+  .info-item {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .info-label {
+    font-size: 14px;
+    color: #909399;
+  }
+
+  .info-value {
+    font-size: 14px;
+    color: #303133;
+    word-break: break-all;
+  }
+
+  // 閫傜敤鑼冨洿 section
+  .scope-section {
+    background: #ffffff;
+    border-radius: 12px;
+    padding: 24px;
+    margin-bottom: 24px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  }
+
+  .section-header {
+    margin-bottom: 16px;
+  }
+
+  .section-heading {
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+    border-left: 4px solid #2979ff;
+    padding-left: 16px;
+  }
+
+  .scope-tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+  }
+
+  .scope-tag {
+    margin-bottom: 8px;
+  }
+
+  // 搴旀�ュ缃楠� section
+  .steps-section {
+    background: #ffffff;
+    border-radius: 12px;
+    padding: 24px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  }
+
+  .steps-list {
+    margin-top: 16px;
+  }
+
+  .step-item {
+    display: flex;
+    margin-bottom: 24px;
+    position: relative;
+  }
+
+  .step-item::before {
+    content: "";
+    position: absolute;
+    left: 15px;
+    top: 40px;
+    bottom: -24px;
+    width: 2px;
+    background-color: #eaeaea;
+  }
+
+  .step-item:last-child::before {
+    display: none;
+  }
+
+  .step-number {
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
+    background-color: #2979ff;
+    color: #ffffff;
+    font-size: 14px;
+    font-weight: 600;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 16px;
+    flex-shrink: 0;
+    margin-top: 4px;
+  }
+
+  .step-content {
+    flex: 1;
+  }
+
+  .step-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+    display: block;
+    margin-bottom: 8px;
+  }
+
+  .step-description {
+    font-size: 14px;
+    color: #606266;
+    line-height: 1.5;
+    word-break: break-all;
+  }
+
+  .no-steps {
+    padding: 40px;
+    text-align: center;
+    color: #909399;
+    font-size: 14px;
+    background-color: #f8f9fa;
+    border-radius: 8px;
+  }
+
+  // 绌虹姸鎬�
+  .empty-state {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 60vh;
+    font-size: 16px;
+    color: #909399;
+  }
+</style>

--
Gitblit v1.9.3