zhangwencui
10 小时以前 34af9a2cd41fb88985d05d57e9fa425bc98990cc
线路巡检、安全设施巡检模块开发
已添加13个文件
已修改2个文件
2912 ■■■■■ 文件已修改
src/api/safeProduction/lineInspection.js 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safeProduction/safetyFacility.js 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/lineInspection/edit.vue 274 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/lineInspection/hazardEdit.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/lineInspection/hazardList.vue 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/lineInspection/hazardRectify.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/lineInspection/index.vue 313 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/lineInspection/recordEdit.vue 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/lineInspection/recordList.vue 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/safetyFacility/index.vue 488 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/safetyFacility/inspectionEdit.vue 262 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/safetyFacility/ledgerEdit.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/safetyFacility/rectificationEdit.vue 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safeProduction/lineInspection.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,129 @@
import request from "@/utils/request";
export function getLineInspectionList(params) {
  return request({
    url: "/safeLineInspection/page",
    method: "get",
    params,
  });
}
export function getLineInspectionById(id) {
  return request({
    url: `/safeLineInspection/${id}`,
    method: "get",
  });
}
export function addLineInspection(data) {
  return request({
    url: "/safeLineInspection",
    method: "post",
    data,
  });
}
export function updateLineInspection(data) {
  return request({
    url: "/safeLineInspection",
    method: "put",
    data,
  });
}
export function deleteLineInspection(ids) {
  const joined = Array.isArray(ids) ? ids.join(",") : String(ids || "");
  return request({
    url: `/safeLineInspection/${joined}`,
    method: "delete",
    data: ids,
  });
}
export function completeLineInspection(id) {
  return request({
    url: `/safeLineInspection/complete/${id}`,
    method: "put",
  });
}
export function getInspectionRecords(inspectionId) {
  return request({
    url: `/safeLineInspectionRecord/list/${inspectionId}`,
    method: "get",
  });
}
export function addInspectionRecord(data) {
  return request({
    url: "/safeLineInspectionRecord",
    method: "post",
    data,
  });
}
export function updateInspectionRecord(data) {
  return request({
    url: "/safeLineInspectionRecord",
    method: "put",
    data,
  });
}
export function deleteInspectionRecord(ids) {
  const joined = Array.isArray(ids) ? ids.join(",") : String(ids || "");
  return request({
    url: `/safeLineInspectionRecord/${joined}`,
    method: "delete",
    data: ids,
  });
}
export function batchAddInspectionRecords(data) {
  return request({
    url: "/safeLineInspectionRecord/batch",
    method: "post",
    data,
  });
}
export function getInspectionHazards(params) {
  return request({
    url: "/safeLineInspectionHazard/page",
    method: "get",
    params,
  });
}
export function getHazardsByInspectionId(inspectionId) {
  return request({
    url: `/safeLineInspectionHazard/list/${inspectionId}`,
    method: "get",
  });
}
export function addInspectionHazard(data) {
  return request({
    url: "/safeLineInspectionHazard",
    method: "post",
    data,
  });
}
export function updateInspectionHazard(data) {
  return request({
    url: "/safeLineInspectionHazard",
    method: "put",
    data,
  });
}
export function deleteInspectionHazard(ids) {
  const joined = Array.isArray(ids) ? ids.join(",") : String(ids || "");
  return request({
    url: `/safeLineInspectionHazard/${joined}`,
    method: "delete",
    data: ids,
  });
}
src/api/safeProduction/safetyFacility.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,122 @@
import request from "@/utils/request";
export function getFacilityLedgerList(params) {
  return request({
    url: "/safeFacilityLedger/page",
    method: "get",
    params,
  });
}
export function getFacilityLedgerById(id) {
  return request({
    url: `/safeFacilityLedger/${id}`,
    method: "get",
  });
}
export function addFacilityLedger(data) {
  return request({
    url: "/safeFacilityLedger",
    method: "post",
    data,
  });
}
export function updateFacilityLedger(data) {
  return request({
    url: "/safeFacilityLedger",
    method: "put",
    data,
  });
}
export function deleteFacilityLedger(ids) {
  const joined = Array.isArray(ids) ? ids.join(",") : String(ids || "");
  return request({
    url: `/safeFacilityLedger/${joined}`,
    method: "delete",
    data: ids,
  });
}
export function getFacilityInspectionList(params) {
  return request({
    url: "/safeFacilityInspection/page",
    method: "get",
    params,
  });
}
export function getFacilityInspectionById(id) {
  return request({
    url: `/safeFacilityInspection/${id}`,
    method: "get",
  });
}
export function addFacilityInspection(data) {
  return request({
    url: "/safeFacilityInspection",
    method: "post",
    data,
  });
}
export function updateFacilityInspection(data) {
  return request({
    url: "/safeFacilityInspection",
    method: "put",
    data,
  });
}
export function deleteFacilityInspection(ids) {
  const joined = Array.isArray(ids) ? ids.join(",") : String(ids || "");
  return request({
    url: `/safeFacilityInspection/${joined}`,
    method: "delete",
    data: ids,
  });
}
export function getFacilityRectificationList(params) {
  return request({
    url: "/safeFacilityRectification/page",
    method: "get",
    params,
  });
}
export function getFacilityRectificationById(id) {
  return request({
    url: `/safeFacilityRectification/${id}`,
    method: "get",
  });
}
export function addFacilityRectification(data) {
  return request({
    url: "/safeFacilityRectification",
    method: "post",
    data,
  });
}
export function updateFacilityRectification(data) {
  return request({
    url: "/safeFacilityRectification",
    method: "put",
    data,
  });
}
export function deleteFacilityRectification(ids) {
  const joined = Array.isArray(ids) ? ids.join(",") : String(ids || "");
  return request({
    url: `/safeFacilityRectification/${joined}`,
    method: "delete",
    data: ids,
  });
}
src/pages.json
@@ -1048,6 +1048,83 @@
      }
    },
    {
      "path": "pages/safeProduction/lineInspection/index",
      "style": {
        "navigationBarTitleText": "线路巡检",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/lineInspection/edit",
      "style": {
        "navigationBarTitleText": "巡检任务",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/lineInspection/recordList",
      "style": {
        "navigationBarTitleText": "巡检记录",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/lineInspection/recordEdit",
      "style": {
        "navigationBarTitleText": "巡检记录",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/lineInspection/hazardList",
      "style": {
        "navigationBarTitleText": "隐患管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/lineInspection/hazardEdit",
      "style": {
        "navigationBarTitleText": "隐患",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/lineInspection/hazardRectify",
      "style": {
        "navigationBarTitleText": "整改",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/safetyFacility/index",
      "style": {
        "navigationBarTitleText": "安全设施巡检",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/safetyFacility/ledgerEdit",
      "style": {
        "navigationBarTitleText": "设施台账",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/safetyFacility/inspectionEdit",
      "style": {
        "navigationBarTitleText": "设施巡检",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/safetyFacility/rectificationEdit",
      "style": {
        "navigationBarTitleText": "整改",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/accidentReportingRecord/index",
      "style": {
        "navigationBarTitleText": "事故报告记录",
src/pages/safeProduction/lineInspection/edit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,274 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle" @back="goBack" />
    <up-form :model="form" label-width="110">
      <up-form-item label="巡检编号" required>
        <up-input v-model="form.inspectionCode" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="巡检名称" required>
        <up-input v-model="form.inspectionName" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="线路名称" required>
        <up-input v-model="form.lineName" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="巡检类型">
        <up-input :model-value="inspectionTypeText" placeholder="请选择" readonly @click="showTypeSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showTypeSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="计划巡检时间">
        <up-input :model-value="form.planTime" placeholder="请选择" readonly @click="showPlanTimePicker = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showPlanTimePicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="巡检人" required>
        <up-input :model-value="inspectorText" placeholder="请选择(可多选)" readonly @click="openInspectorPopup" />
        <template #right>
          <up-icon name="arrow-right" @click="openInspectorPopup"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="备注">
        <up-textarea v-model="form.remark" placeholder="请输入" auto-height />
      </up-form-item>
    </up-form>
    <FooterButtons :loading="loading" confirmText="保存" @cancel="goBack" @confirm="handleSubmit" />
    <up-action-sheet
      :show="showTypeSheet"
      title="选择巡检类型"
      :actions="typeActions"
      @select="onSelectType"
      @close="showTypeSheet = false"
    />
    <up-datetime-picker
      :show="showPlanTimePicker"
      mode="datetime"
      :value="planTimeValue"
      @confirm="onPlanTimeConfirm"
      @cancel="showPlanTimePicker = false"
    />
    <up-popup :show="showInspectorPopup" mode="bottom" @close="showInspectorPopup = false">
      <view class="popup-container">
        <view class="popup-header">
          <text class="popup-title">选择巡检人</text>
        </view>
        <scroll-view scroll-y class="popup-scroll">
          <up-checkbox-group v-model="inspectorIdsTemp">
            <up-checkbox
              v-for="u in userOptions"
              :key="u.value"
              :name="u.value"
              :label="u.label"
            ></up-checkbox>
          </up-checkbox-group>
        </scroll-view>
        <view class="popup-footer">
          <u-button size="small" @click="showInspectorPopup = false">取消</u-button>
          <u-button type="primary" size="small" @click="confirmInspector">确定</u-button>
        </view>
      </view>
    </up-popup>
  </view>
</template>
<script setup>
  import { computed, onMounted, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import dayjs from "dayjs";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import { addLineInspection, updateLineInspection } from "@/api/safeProduction/lineInspection";
  import { userListNoPage } from "@/api/system/user";
  const loading = ref(false);
  const form = ref({
    id: null,
    inspectionCode: "",
    inspectionName: "",
    lineName: "",
    inspectionType: "",
    planTime: "",
    inspectorId: "",
    inspectorIds: [],
    remark: "",
  });
  const userOptions = ref([]);
  const showTypeSheet = ref(false);
  const showPlanTimePicker = ref(false);
  const showInspectorPopup = ref(false);
  const inspectorIdsTemp = ref([]);
  const typeActions = [
    { name: "定期巡检", value: "定期巡检" },
    { name: "临时巡检", value: "临时巡检" },
  ];
  const pageTitle = computed(() => (form.value.id ? "编辑巡检任务" : "新增巡检任务"));
  const inspectionTypeText = computed(() => form.value.inspectionType || "");
  const inspectorText = computed(() => {
    const ids = Array.isArray(form.value.inspectorIds) ? form.value.inspectorIds : [];
    if (ids.length === 0) return "";
    const map = new Map(userOptions.value.map(x => [x.value, x.label]));
    const names = ids.map(id => map.get(id)).filter(Boolean);
    return names.join(",");
  });
  const planTimeValue = computed(() => {
    if (!form.value.planTime) return Date.now();
    const ts = dayjs(form.value.planTime).valueOf();
    return Number.isFinite(ts) ? ts : Date.now();
  });
  const goBack = () => {
    uni.navigateBack();
  };
  const onSelectType = action => {
    form.value.inspectionType = action.value;
    showTypeSheet.value = false;
  };
  const onPlanTimeConfirm = e => {
    const ts = e?.value;
    form.value.planTime = dayjs(ts).format("YYYY-MM-DD HH:mm:ss");
    showPlanTimePicker.value = false;
  };
  const openInspectorPopup = () => {
    inspectorIdsTemp.value = [...(form.value.inspectorIds || [])];
    showInspectorPopup.value = true;
  };
  const confirmInspector = () => {
    form.value.inspectorIds = [...(inspectorIdsTemp.value || [])];
    showInspectorPopup.value = false;
  };
  const handleSubmit = async () => {
    const inspectionCode = String(form.value.inspectionCode || "").trim();
    if (!inspectionCode) {
      uni.showToast({ title: "请输入巡检编号", icon: "none" });
      return;
    }
    const inspectionName = String(form.value.inspectionName || "").trim();
    if (!inspectionName) {
      uni.showToast({ title: "请输入巡检名称", icon: "none" });
      return;
    }
    const lineName = String(form.value.lineName || "").trim();
    if (!lineName) {
      uni.showToast({ title: "请输入线路名称", icon: "none" });
      return;
    }
    const inspectorIds = Array.isArray(form.value.inspectorIds) ? form.value.inspectorIds : [];
    if (inspectorIds.length === 0) {
      uni.showToast({ title: "请选择巡检人", icon: "none" });
      return;
    }
    const payload = {
      ...form.value,
      inspectionCode,
      inspectionName,
      lineName,
      inspectorId: inspectorIds.join(","),
    };
    delete payload.inspectorIds;
    loading.value = true;
    const api = payload.id ? updateLineInspection : addLineInspection;
    api(payload)
      .then(() => {
        uni.showToast({ title: "保存成功", icon: "success" });
        uni.$emit("lineInspection:refresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "保存失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  const initUsers = () => {
    userListNoPage()
      .then(res => {
        const list = res?.data || [];
        userOptions.value = list.map(u => ({
          label: u.nickName,
          value: u.userId,
        }));
      })
      .catch(() => {});
  };
  onMounted(() => {
    initUsers();
  });
  onLoad(options => {
    if (options?.data) {
      try {
        const row = JSON.parse(decodeURIComponent(options.data));
        const inspectorIds = row?.inspectorId
          ? String(row.inspectorId)
              .split(",")
              .map(x => Number(String(x).trim()))
              .filter(n => Number.isFinite(n))
          : [];
        form.value = {
          ...form.value,
          ...row,
          inspectorIds,
        };
      } catch (e) {}
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .popup-container {
    background: #fff;
    border-top-left-radius: 20rpx;
    border-top-right-radius: 20rpx;
    padding: 24rpx;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
  }
  .popup-header {
    padding-bottom: 16rpx;
  }
  .popup-title {
    font-size: 30rpx;
    font-weight: 600;
    color: #333;
  }
  .popup-scroll {
    flex: 1;
    min-height: 0;
    padding: 10rpx 0;
  }
  .popup-footer {
    display: flex;
    justify-content: flex-end;
    gap: 16rpx;
    padding-top: 16rpx;
  }
</style>
src/pages/safeProduction/lineInspection/hazardEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle" @back="goBack" />
    <up-form :model="form" label-width="110">
      <up-form-item label="隐患描述" required>
        <up-textarea v-model="form.hazardDesc" placeholder="请输入" auto-height />
      </up-form-item>
      <up-form-item label="隐患等级" required>
        <up-input :model-value="form.hazardLevel" placeholder="请选择" readonly @click="showLevelSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showLevelSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="隐患位置" required>
        <up-input v-model="form.hazardLocation" placeholder="请输入" clearable />
      </up-form-item>
    </up-form>
    <FooterButtons :loading="loading" confirmText="保存" @cancel="goBack" @confirm="handleSubmit" />
    <up-action-sheet
      :show="showLevelSheet"
      title="选择隐患等级"
      :actions="levelActions"
      @select="onSelectLevel"
      @close="showLevelSheet = false"
    />
  </view>
</template>
<script setup>
  import { computed, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import { addInspectionHazard, updateInspectionHazard } from "@/api/safeProduction/lineInspection";
  const loading = ref(false);
  const inspectionId = ref(null);
  const form = ref({
    id: null,
    inspectionId: null,
    hazardDesc: "",
    hazardLevel: "",
    hazardLocation: "",
  });
  const showLevelSheet = ref(false);
  const levelActions = [
    { name: "一般", value: "一般" },
    { name: "重大", value: "重大" },
  ];
  const pageTitle = computed(() => (form.value.id ? "编辑隐患" : "上报隐患"));
  const goBack = () => {
    uni.navigateBack();
  };
  const onSelectLevel = action => {
    form.value.hazardLevel = action.value;
    showLevelSheet.value = false;
  };
  const handleSubmit = () => {
    const hazardDesc = String(form.value.hazardDesc || "").trim();
    if (!hazardDesc) {
      uni.showToast({ title: "请输入隐患描述", icon: "none" });
      return;
    }
    const hazardLevel = String(form.value.hazardLevel || "").trim();
    if (!hazardLevel) {
      uni.showToast({ title: "请选择隐患等级", icon: "none" });
      return;
    }
    const hazardLocation = String(form.value.hazardLocation || "").trim();
    if (!hazardLocation) {
      uni.showToast({ title: "请输入隐患位置", icon: "none" });
      return;
    }
    const payload = {
      ...form.value,
      inspectionId: inspectionId.value,
      hazardDesc,
      hazardLevel,
      hazardLocation,
    };
    loading.value = true;
    const api = payload.id ? updateInspectionHazard : addInspectionHazard;
    api(payload)
      .then(() => {
        uni.showToast({ title: "保存成功", icon: "success" });
        uni.$emit("lineInspection:hazardsRefresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "保存失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  onLoad(options => {
    if (options?.data) {
      try {
        const obj = JSON.parse(decodeURIComponent(options.data));
        inspectionId.value = obj.inspectionId;
        if (obj?.row) {
          form.value = { ...form.value, ...(obj.row || {}) };
        }
      } catch (e) {}
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/safeProduction/lineInspection/hazardList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
<template>
  <view class="hazard-list">
    <PageHeader title="隐患管理"
                @back="goBack" />
    <view class="top-actions">
      <u-button type="primary"
                size="small"
                :disabled="isCompleted"
                @click="goAdd">上报隐患</u-button>
    </view>
    <scroll-view scroll-y
                 class="list-scroll ledger-list"
                 v-if="hazards.length > 0">
      <view v-for="row in hazards"
            :key="row.id"
            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-id">{{ row.hazardCode || "隐患" }}</text>
          </view>
          <view class="item-right">
            <up-tag :text="row.status || '-'"
                    :type="statusType(row.status)"
                    size="mini" />
          </view>
        </view>
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">隐患描述</text>
            <text class="detail-value">{{ row.hazardDesc || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">隐患等级</text>
            <text class="detail-value">{{ row.hazardLevel || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">隐患位置</text>
            <text class="detail-value">{{ row.hazardLocation || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">整改责任人</text>
            <text class="detail-value">{{ row.rectifyUserName || "-" }}</text>
          </view>
        </view>
        <view class="action-buttons">
          <u-button size="small"
                    class="action-btn fixed-btn"
                    :disabled="isCompleted"
                    @click.stop="goEdit(row)">编辑</u-button>
          <u-button v-if="String(row.status) !== '已整改'"
                    size="small"
                    class="action-btn fixed-btn"
                    type="success"
                    :disabled="isCompleted"
                    @click.stop="goRectify(row)">
            æ•´æ”¹
          </u-button>
        </view>
      </view>
    </scroll-view>
    <view v-else
          class="no-data">
      <up-empty mode="data"
                text="暂无隐患"></up-empty>
    </view>
  </view>
</template>
<script setup>
  import { computed, onMounted, ref } from "vue";
  import { onLoad, onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { getHazardsByInspectionId } from "@/api/safeProduction/lineInspection";
  const inspectionId = ref(null);
  const inspectionStatus = ref("");
  const hazards = ref([]);
  const isCompleted = computed(() => String(inspectionStatus.value) === "已完成");
  const statusType = status => {
    const map = {
      å¾…整改: "error",
      æ•´æ”¹ä¸­: "warning",
      å·²æ•´æ”¹: "success",
    };
    return map[status] || "info";
  };
  const goBack = () => {
    uni.navigateBack();
  };
  const fetchHazards = () => {
    if (!inspectionId.value) return;
    uni.showLoading({ title: "加载中...", mask: true });
    getHazardsByInspectionId(inspectionId.value)
      .then(res => {
        hazards.value = res?.data || [];
      })
      .catch(() => {
        uni.showToast({ title: "加载失败", icon: "none" });
      })
      .finally(() => {
        uni.hideLoading();
      });
  };
  const goAdd = () => {
    const data = encodeURIComponent(
      JSON.stringify({ inspectionId: inspectionId.value })
    );
    uni.navigateTo({
      url: `/pages/safeProduction/lineInspection/hazardEdit?data=${data}`,
    });
  };
  const goEdit = row => {
    const data = encodeURIComponent(
      JSON.stringify({ inspectionId: inspectionId.value, row })
    );
    uni.navigateTo({
      url: `/pages/safeProduction/lineInspection/hazardEdit?data=${data}`,
    });
  };
  const goRectify = row => {
    const data = encodeURIComponent(JSON.stringify({ row }));
    uni.navigateTo({
      url: `/pages/safeProduction/lineInspection/hazardRectify?data=${data}`,
    });
  };
  onMounted(() => {
    uni.$on("lineInspection:hazardsRefresh", fetchHazards);
  });
  onLoad(options => {
    if (options?.data) {
      try {
        const obj = JSON.parse(decodeURIComponent(options.data));
        inspectionId.value = obj.inspectionId;
        inspectionStatus.value = obj.inspectionStatus || "";
      } catch (e) {}
    }
  });
  onShow(() => {
    fetchHazards();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .hazard-list {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 40rpx;
  }
  .top-actions {
    padding: 20rpx;
    display: flex;
    justify-content: flex-end;
  }
  .action-buttons {
    display: flex;
    justify-content: flex-end;
    gap: 20rpx;
    padding-bottom: 30rpx;
  }
  .fixed-btn {
    width: 160rpx;
    margin: 0 !important;
  }
  .no-data {
    padding-top: 200rpx;
  }
</style>
src/pages/safeProduction/lineInspection/hazardRectify.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
<template>
  <view class="account-detail">
    <PageHeader title="整改" @back="goBack" />
    <up-form :model="form" label-width="110">
      <up-form-item label="整改说明" required>
        <up-textarea v-model="form.rectifyDesc" placeholder="请输入" auto-height />
      </up-form-item>
    </up-form>
    <FooterButtons :loading="loading" confirmText="保存" @cancel="goBack" @confirm="handleSubmit" />
  </view>
</template>
<script setup>
  import { ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import { updateInspectionHazard } from "@/api/safeProduction/lineInspection";
  const loading = ref(false);
  const hazardId = ref(null);
  const form = ref({
    rectifyDesc: "",
  });
  const goBack = () => {
    uni.navigateBack();
  };
  const handleSubmit = () => {
    const rectifyDesc = String(form.value.rectifyDesc || "").trim();
    if (!rectifyDesc) {
      uni.showToast({ title: "请输入整改说明", icon: "none" });
      return;
    }
    if (!hazardId.value) return;
    loading.value = true;
    updateInspectionHazard({ id: hazardId.value, status: "已整改", rectifyDesc })
      .then(() => {
        uni.showToast({ title: "整改成功", icon: "success" });
        uni.$emit("lineInspection:hazardsRefresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "整改失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  onLoad(options => {
    if (options?.data) {
      try {
        const obj = JSON.parse(decodeURIComponent(options.data));
        hazardId.value = obj?.row?.id;
      } catch (e) {}
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/safeProduction/lineInspection/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,313 @@
<template>
  <view class="line-inspection">
    <PageHeader title="线路巡检"
                @back="goBack" />
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入巡检编号/巡检名称/线路名称"
                    v-model="keyword"
                    @change="handleQuery"
                    clearable />
        </view>
        <view class="filter-button"
              @click="handleQuery">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <scroll-view scroll-y
                 class="list-scroll ledger-list"
                 @scrolltolower="loadMore"
                 v-if="list.length > 0">
      <view v-for="row in list"
            :key="row.id"
            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-id">{{ row.inspectionCode || "-" }}</text>
          </view>
          <view class="item-right">
            <up-tag :text="row.status || '-'"
                    :type="statusType(row.status)"
                    size="mini" />
          </view>
        </view>
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">巡检名称</text>
            <text class="detail-value">{{ row.inspectionName || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">线路名称</text>
            <text class="detail-value">{{ row.lineName || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">巡检类型</text>
            <text class="detail-value">{{ row.inspectionType || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">计划巡检时间</text>
            <text class="detail-value">{{ row.planTime || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">巡检人</text>
            <text class="detail-value">{{ row.inspectorName || "-" }}</text>
          </view>
        </view>
        <view class="action-buttons action-buttons-bottom">
          <u-button size="small"
                    class="action-btn fixed-btn"
                    :disabled="!canEdit(row)"
                    @click.stop="goEdit(row)">编辑</u-button>
          <u-button size="small"
                    class="action-btn fixed-btn"
                    type="primary"
                    @click.stop="goRecords(row)">巡检记录</u-button>
          <u-button size="small"
                    class="action-btn fixed-btn"
                    type="primary"
                    @click.stop="goHazards(row)">隐患管理</u-button>
          <u-button v-if="String(row.status) === '巡检中'"
                    size="small"
                    class="action-btn fixed-btn"
                    type="success"
                    @click.stop="handleComplete(row)">
            å®Œæˆå·¡æ£€
          </u-button>
          <u-button size="small"
                    class="action-btn fixed-btn"
                    type="error"
                    :disabled="!canDelete(row)"
                    @click.stop="handleDelete(row)">删除</u-button>
        </view>
      </view>
      <up-loadmore :status="loadStatus" />
    </scroll-view>
    <view v-else
          class="no-data">
      <up-empty mode="data"
                text="暂无数据"></up-empty>
    </view>
    <view class="fab-button"
          @click="goAdd">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
  import { onMounted, reactive, ref } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    completeLineInspection,
    deleteLineInspection,
    getLineInspectionList,
  } from "@/api/safeProduction/lineInspection";
  const keyword = ref("");
  const page = reactive({ current: 1, size: 20, total: 0 });
  const list = ref([]);
  const loadStatus = ref("loadmore");
  const loading = ref(false);
  const goBack = () => {
    uni.navigateBack();
  };
  const statusType = status => {
    const map = {
      å¾…巡检: "info",
      å·¡æ£€ä¸­: "warning",
      å·²å®Œæˆ: "success",
    };
    return map[status] || "info";
  };
  const canEdit = row => String(row?.status) === "待巡检";
  const canDelete = row => String(row?.status) === "待巡检";
  const fetchList = () => {
    if (loading.value) return;
    loading.value = true;
    loadStatus.value = "loading";
    const kw = String(keyword.value || "").trim();
    getLineInspectionList({
      inspectionCode: kw,
      inspectionName: kw,
      lineName: kw,
      ...page,
    })
      .then(res => {
        const records = res?.data?.records || [];
        const total = Number(res?.data?.total || 0);
        page.total = total;
        list.value = page.current === 1 ? records : [...list.value, ...records];
        loadStatus.value =
          list.value.length >= page.total ? "nomore" : "loadmore";
      })
      .catch(() => {
        loadStatus.value = "loadmore";
        uni.showToast({ title: "加载失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  const handleQuery = () => {
    page.current = 1;
    list.value = [];
    fetchList();
  };
  const loadMore = () => {
    if (loadStatus.value !== "loadmore") return;
    page.current++;
    fetchList();
  };
  const goAdd = () => {
    uni.navigateTo({ url: "/pages/safeProduction/lineInspection/edit" });
  };
  const goEdit = row => {
    const data = encodeURIComponent(JSON.stringify(row || {}));
    uni.navigateTo({
      url: `/pages/safeProduction/lineInspection/edit?data=${data}`,
    });
  };
  const goRecords = row => {
    const params = encodeURIComponent(
      JSON.stringify({ inspectionId: row.id, inspectionStatus: row.status || "" })
    );
    uni.navigateTo({
      url: `/pages/safeProduction/lineInspection/recordList?data=${params}`,
    });
  };
  const goHazards = row => {
    const params = encodeURIComponent(
      JSON.stringify({ inspectionId: row.id, inspectionStatus: row.status || "" })
    );
    uni.navigateTo({
      url: `/pages/safeProduction/lineInspection/hazardList?data=${params}`,
    });
  };
  const handleDelete = row => {
    if (!row?.id) return;
    uni.showModal({
      title: "删除",
      content: "确认删除该巡检任务吗?",
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "删除中...", mask: true });
        deleteLineInspection([row.id])
          .then(() => {
            uni.showToast({ title: "删除成功", icon: "success" });
            handleQuery();
          })
          .catch(() => {
            uni.showToast({ title: "删除失败", icon: "none" });
          })
          .finally(() => {
            uni.hideLoading();
          });
      },
    });
  };
  const handleComplete = row => {
    if (!row?.id) return;
    uni.showModal({
      title: "完成巡检",
      content: "确认完成该巡检任务吗?",
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "提交中...", mask: true });
        completeLineInspection(row.id)
          .then(() => {
            uni.showToast({ title: "巡检已完成", icon: "success" });
            handleQuery();
          })
          .catch(() => {
            uni.showToast({ title: "操作失败", icon: "none" });
          })
          .finally(() => {
            uni.hideLoading();
          });
      },
    });
  };
  onMounted(() => {
    handleQuery();
  });
  onShow(() => {
    handleQuery();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .line-inspection {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 140rpx;
  }
  .list-scroll {
    flex: 1;
    min-height: 0;
  }
  .action-buttons {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 20rpx;
    padding-bottom: 30rpx;
  }
  .fixed-btn {
    width: 160rpx;
    margin: 0 !important;
  }
  .fab-button {
    position: fixed;
    right: 30rpx;
    bottom: 140rpx;
    width: 96rpx;
    height: 96rpx;
    background: #3c9cff;
    border-radius: 48rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 8rpx 24rpx rgba(60, 156, 255, 0.3);
    z-index: 1001;
  }
  .no-data {
    padding-top: 200rpx;
  }
</style>
src/pages/safeProduction/lineInspection/recordEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle" @back="goBack" />
    <up-form :model="form" label-width="110">
      <up-form-item label="检查点" required>
        <up-input v-model="form.checkPoint" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="检查结果" required>
        <up-input :model-value="form.checkResult" placeholder="请选择" readonly @click="showResultSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showResultSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="检查内容" required>
        <up-textarea v-model="form.checkContent" placeholder="请输入" auto-height />
      </up-form-item>
      <up-form-item label="检查说明">
        <up-textarea v-model="form.checkDesc" placeholder="请输入" auto-height />
      </up-form-item>
      <up-form-item label="检查时间">
        <up-input :model-value="form.checkTime" placeholder="请选择" readonly @click="showTimePicker = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showTimePicker = true"></up-icon>
        </template>
      </up-form-item>
    </up-form>
    <FooterButtons :loading="loading" confirmText="保存" @cancel="goBack" @confirm="handleSubmit" />
    <up-action-sheet
      :show="showResultSheet"
      title="选择检查结果"
      :actions="resultActions"
      @select="onSelectResult"
      @close="showResultSheet = false"
    />
    <up-datetime-picker
      :show="showTimePicker"
      mode="datetime"
      :value="checkTimeValue"
      @confirm="onTimeConfirm"
      @cancel="showTimePicker = false"
    />
  </view>
</template>
<script setup>
  import { computed, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import dayjs from "dayjs";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import { addInspectionRecord, updateInspectionRecord } from "@/api/safeProduction/lineInspection";
  const loading = ref(false);
  const inspectionId = ref(null);
  const form = ref({
    id: null,
    inspectionId: null,
    checkPoint: "",
    checkContent: "",
    checkResult: "正常",
    checkDesc: "",
    checkTime: "",
  });
  const showResultSheet = ref(false);
  const showTimePicker = ref(false);
  const resultActions = [
    { name: "正常", value: "正常" },
    { name: "异常", value: "异常" },
  ];
  const pageTitle = computed(() => (form.value.id ? "编辑巡检记录" : "新增巡检记录"));
  const checkTimeValue = computed(() => {
    if (!form.value.checkTime) return Date.now();
    const ts = dayjs(form.value.checkTime).valueOf();
    return Number.isFinite(ts) ? ts : Date.now();
  });
  const goBack = () => {
    uni.navigateBack();
  };
  const onSelectResult = action => {
    form.value.checkResult = action.value;
    showResultSheet.value = false;
  };
  const onTimeConfirm = e => {
    const ts = e?.value;
    form.value.checkTime = dayjs(ts).format("YYYY-MM-DD HH:mm:ss");
    showTimePicker.value = false;
  };
  const handleSubmit = () => {
    const checkPoint = String(form.value.checkPoint || "").trim();
    if (!checkPoint) {
      uni.showToast({ title: "请输入检查点", icon: "none" });
      return;
    }
    const checkContent = String(form.value.checkContent || "").trim();
    if (!checkContent) {
      uni.showToast({ title: "请输入检查内容", icon: "none" });
      return;
    }
    const checkResult = String(form.value.checkResult || "").trim();
    if (!checkResult) {
      uni.showToast({ title: "请选择检查结果", icon: "none" });
      return;
    }
    const payload = {
      ...form.value,
      inspectionId: inspectionId.value,
      checkPoint,
      checkContent,
      checkResult,
      checkDesc: String(form.value.checkDesc || "").trim(),
      checkTime: form.value.checkTime || "",
    };
    loading.value = true;
    const api = payload.id ? updateInspectionRecord : addInspectionRecord;
    api(payload)
      .then(() => {
        uni.showToast({ title: "保存成功", icon: "success" });
        uni.$emit("lineInspection:recordsRefresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "保存失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  onLoad(options => {
    if (options?.data) {
      try {
        const obj = JSON.parse(decodeURIComponent(options.data));
        inspectionId.value = obj.inspectionId;
        if (obj?.row) {
          form.value = { ...form.value, ...(obj.row || {}) };
        }
      } catch (e) {}
    }
    if (!form.value.checkTime) form.value.checkTime = dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss");
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/safeProduction/lineInspection/recordList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,197 @@
<template>
  <view class="record-list">
    <PageHeader title="巡检记录"
                @back="goBack" />
    <view class="top-actions">
      <u-button type="primary"
                size="small"
                :disabled="isCompleted"
                @click="goAdd">新增记录</u-button>
    </view>
    <scroll-view scroll-y
                 class="list-scroll ledger-list"
                 v-if="records.length > 0">
      <view v-for="row in records"
            :key="row.id"
            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-id">{{ row.checkPoint || "-" }}</text>
          </view>
          <view class="item-right">
            <up-tag :text="row.checkResult || '-'"
                    :type="row.checkResult === '正常' ? 'success' : 'error'"
                    size="mini" />
          </view>
        </view>
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">检查内容</text>
            <text class="detail-value">{{ row.checkContent || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">检查说明</text>
            <text class="detail-value">{{ row.checkDesc || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">检查时间</text>
            <text class="detail-value">{{ row.checkTime || "-" }}</text>
          </view>
        </view>
        <view class="action-buttons">
          <u-button size="small"
                    class="action-btn fixed-btn"
                    :disabled="isCompleted"
                    @click.stop="goEdit(row)">编辑</u-button>
          <u-button size="small"
                    class="action-btn fixed-btn"
                    type="error"
                    :disabled="isCompleted"
                    @click.stop="handleDelete(row)">删除</u-button>
        </view>
      </view>
    </scroll-view>
    <view v-else
          class="no-data">
      <up-empty mode="data"
                text="暂无记录"></up-empty>
    </view>
  </view>
</template>
<script setup>
  import { computed, onMounted, ref } from "vue";
  import { onLoad, onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    deleteInspectionRecord,
    getInspectionRecords,
  } from "@/api/safeProduction/lineInspection";
  const inspectionId = ref(null);
  const inspectionStatus = ref("");
  const records = ref([]);
  const isCompleted = computed(() => String(inspectionStatus.value) === "已完成");
  const goBack = () => {
    uni.navigateBack();
  };
  const fetchRecords = () => {
    if (!inspectionId.value) return;
    uni.showLoading({ title: "加载中...", mask: true });
    getInspectionRecords(inspectionId.value)
      .then(res => {
        records.value = res?.data || [];
      })
      .catch(() => {
        uni.showToast({ title: "加载失败", icon: "none" });
      })
      .finally(() => {
        uni.hideLoading();
      });
  };
  const goAdd = () => {
    const data = encodeURIComponent(
      JSON.stringify({ inspectionId: inspectionId.value })
    );
    uni.navigateTo({
      url: `/pages/safeProduction/lineInspection/recordEdit?data=${data}`,
    });
  };
  const goEdit = row => {
    const data = encodeURIComponent(
      JSON.stringify({ inspectionId: inspectionId.value, row })
    );
    uni.navigateTo({
      url: `/pages/safeProduction/lineInspection/recordEdit?data=${data}`,
    });
  };
  const handleDelete = row => {
    if (!row?.id) return;
    uni.showModal({
      title: "删除",
      content: "确认删除该巡检记录吗?",
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "删除中...", mask: true });
        deleteInspectionRecord([row.id])
          .then(() => {
            uni.showToast({ title: "删除成功", icon: "success" });
            fetchRecords();
          })
          .catch(() => {
            uni.showToast({ title: "删除失败", icon: "none" });
          })
          .finally(() => {
            uni.hideLoading();
          });
      },
    });
  };
  onMounted(() => {
    uni.$on("lineInspection:recordsRefresh", fetchRecords);
  });
  onLoad(options => {
    if (options?.data) {
      try {
        const obj = JSON.parse(decodeURIComponent(options.data));
        inspectionId.value = obj.inspectionId;
        inspectionStatus.value = obj.inspectionStatus || "";
      } catch (e) {}
    }
  });
  onShow(() => {
    fetchRecords();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .record-list {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 40rpx;
  }
  .top-actions {
    padding: 20rpx;
    display: flex;
    justify-content: flex-end;
  }
  .list-scroll {
    flex: 1;
    min-height: 0;
  }
  .action-buttons {
    display: flex;
    justify-content: flex-end;
    gap: 20rpx;
    padding-bottom: 30rpx;
  }
  .fixed-btn {
    width: 160rpx;
    margin: 0 !important;
  }
  .no-data {
    padding-top: 200rpx;
  }
</style>
src/pages/safeProduction/safetyFacility/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,488 @@
<template>
  <view class="safety-facility">
    <PageHeader title="安全设施巡检"
                @back="goBack" />
    <view class="tabs-section">
      <up-tabs v-model="activeTabIndex"
               :list="tabList"
               itemStyle="width: 33.33%;height: 80rpx;"
               @change="onTabChange" />
    </view>
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    v-model="keyword"
                    :placeholder="searchPlaceholder"
                    @change="handleSearch"
                    clearable />
        </view>
        <view class="filter-button"
              @click="handleSearch">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <scroll-view scroll-y
                 class="list-scroll ledger-list"
                 @scrolltolower="loadMore"
                 v-if="list.length > 0">
      <view v-for="row in list"
            :key="row.id"
            class="ledger-item">
        <view class="item-header"
              @click="handleInspectionInfoClick(row)">
          <view class="item-left">
            <view class="document-icon">
              <up-icon name="setting-fill"
                       size="16"
                       color="#ffffff"></up-icon>
            </view>
            <text class="item-id">{{ getCardTitle(row) }}</text>
          </view>
          <view class="item-right">
            <text v-if="activeTab === 'ledger'"
                  class="item-index">{{ row.facilityCode || "-" }}</text>
            <up-tag v-else
                    :text="row.status || '-'"
                    :type="activeTab === 'inspection' ? (row.status === '待巡检' ? 'info' : 'success') : rectStatusType(row.status)"
                    size="mini" />
          </view>
        </view>
        <up-divider></up-divider>
        <view class="item-details"
              @click="handleInspectionInfoClick(row)">
          <template v-if="activeTab === 'ledger'">
            <view class="detail-row">
              <text class="detail-label">设施类型</text>
              <text class="detail-value">{{ row.facilityType || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">规格型号</text>
              <text class="detail-value">{{ row.facilitySpec || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">安装位置</text>
              <text class="detail-value">{{ row.installLocation || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">安装时间</text>
              <text class="detail-value">{{ row.installTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">下次检查时间</text>
              <text class="detail-value">{{ row.nextCheckTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">状态</text>
              <up-tag :text="row.status || '-'"
                      :type="facilityStatusType(row.status)"
                      size="mini" />
            </view>
          </template>
          <template v-else-if="activeTab === 'inspection'">
            <view class="detail-row">
              <text class="detail-label">设施名称</text>
              <text class="detail-value">{{ row.facilityName || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">设施编号</text>
              <text class="detail-value">{{ row.facilityCode || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">巡检类型</text>
              <text class="detail-value">{{ row.inspectionType || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">计划巡检时间</text>
              <text class="detail-value">{{ row.planTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">检查结果</text>
              <text class="detail-value">{{ row.checkResult || '-' }}</text>
            </view>
          </template>
          <template v-else>
            <view class="detail-row">
              <text class="detail-label">设施名称</text>
              <text class="detail-value">{{ row.facilityName || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">问题描述</text>
              <text class="detail-value">{{ row.problemDesc || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">问题等级</text>
              <text class="detail-value">{{ row.problemLevel || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">整改责任人</text>
              <text class="detail-value">{{ row.rectifyUserName || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">计划整改时间</text>
              <text class="detail-value">{{ row.planTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">验收人</text>
              <text class="detail-value">{{ row.verifyUserName || '-' }}</text>
            </view>
          </template>
        </view>
        <view class="action-buttons">
          <template v-if="activeTab === 'ledger'">
            <u-button size="small"
                      class="action-btn fixed-btn"
                      @click.stop="goLedgerEdit(row)">编辑</u-button>
            <u-button size="small"
                      class="action-btn fixed-btn"
                      type="primary"
                      @click.stop="goInspectionAdd(row)">发起巡检</u-button>
            <u-button size="small"
                      class="action-btn fixed-btn"
                      type="error"
                      @click.stop="deleteLedger(row)">删除</u-button>
          </template>
          <template v-else-if="activeTab === 'inspection'">
            <u-button v-if="String(row.status) === '待巡检'"
                      size="small"
                      class="action-btn fixed-btn"
                      type="primary"
                      @click.stop="goInspectionDo(row)">巡检</u-button>
            <u-button v-if="String(row.checkResult) === '异常'"
                      size="small"
                      class="action-btn fixed-btn"
                      type="success"
                      @click.stop="goRectificationAdd(row)">整改</u-button>
            <u-button size="small"
                      class="action-btn fixed-btn"
                      type="error"
                      :disabled="String(row.status) !== '待巡检'"
                      @click.stop="deleteInspection(row)">删除</u-button>
          </template>
          <template v-else>
            <u-button v-if="String(row.status) === '待整改' || String(row.status) === '整改中'"
                      size="small"
                      class="action-btn fixed-btn"
                      type="success"
                      @click.stop="goRectificationDo(row)">整改</u-button>
            <u-button v-if="String(row.status) === '已整改'"
                      size="small"
                      class="action-btn fixed-btn"
                      type="primary"
                      @click.stop="goRectificationVerify(row)">验收</u-button>
            <u-button size="small"
                      class="action-btn fixed-btn"
                      @click.stop="goRectificationView(row)">查看</u-button>
          </template>
        </view>
      </view>
      <up-loadmore :status="loadStatus" />
    </scroll-view>
    <view v-else
          class="no-data">
      <up-empty mode="data"
                text="暂无数据"></up-empty>
    </view>
    <view v-if="activeTab === 'ledger'"
          class="fab-button"
          @click="goLedgerAdd">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
  import { computed, onMounted, reactive, ref } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    deleteFacilityInspection,
    deleteFacilityLedger,
    getFacilityInspectionList,
    getFacilityLedgerList,
    getFacilityRectificationList,
  } from "@/api/safeProduction/safetyFacility";
  const tabList = reactive([
    { name: "设施台账", value: "ledger" },
    { name: "设施巡检", value: "inspection" },
    { name: "整改跟踪", value: "rectification" },
  ]);
  const activeTabIndex = ref(0);
  const activeTab = computed(
    () => tabList[activeTabIndex.value]?.value || "ledger"
  );
  const keyword = ref("");
  const page = reactive({ current: 1, size: 10, total: 0 });
  const list = ref([]);
  const loadStatus = ref("loadmore");
  const loading = ref(false);
  const searchPlaceholder = computed(() => {
    if (activeTab.value === "ledger") return "设施编号/设施名称";
    if (activeTab.value === "inspection") return "巡检编号";
    return "状态(待整改/整改中/已整改/已验收)";
  });
  const facilityStatusType = status => {
    const map = { æ­£å¸¸: "success", å¼‚常: "error", æŠ¥åºŸ: "info" };
    return map[status] || "info";
  };
  const rectStatusType = status => {
    const map = {
      å¾…整改: "error",
      æ•´æ”¹ä¸­: "warning",
      å·²æ•´æ”¹: "success",
      å·²éªŒæ”¶: "info",
    };
    return map[status] || "info";
  };
  const goBack = () => {
    uni.navigateBack();
  };
  const getCardTitle = row => {
    if (activeTab.value === "ledger") return row.facilityName || "-";
    if (activeTab.value === "inspection") return row.inspectionCode || "-";
    return row.facilityName || "-";
  };
  const handleInspectionInfoClick = row => {
    if (activeTab.value !== "inspection") return;
    if (String(row?.status) === "待巡检") return;
    goInspectionView(row);
  };
  const resetAndQuery = () => {
    page.current = 1;
    page.total = 0;
    list.value = [];
    loadStatus.value = "loadmore";
    fetchList();
  };
  const fetchList = () => {
    if (loading.value) return;
    loading.value = true;
    loadStatus.value = "loading";
    const reqPage = { current: page.current, size: page.size };
    let req;
    const kw = String(keyword.value || "").trim();
    if (activeTab.value === "ledger") {
      req = getFacilityLedgerList({
        facilityCode: kw,
        facilityName: kw,
        ...reqPage,
      });
    } else if (activeTab.value === "inspection") {
      req = getFacilityInspectionList({ inspectionCode: kw, ...reqPage });
    } else {
      req = getFacilityRectificationList({ status: kw, ...reqPage });
    }
    req
      .then(res => {
        const records = res?.data?.records || [];
        const total = Number(res?.data?.total || 0);
        page.total = total;
        list.value = page.current === 1 ? records : [...list.value, ...records];
        loadStatus.value =
          list.value.length >= page.total ? "nomore" : "loadmore";
      })
      .catch(() => {
        loadStatus.value = "loadmore";
        uni.showToast({ title: "加载失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  const handleSearch = () => {
    resetAndQuery();
  };
  const loadMore = () => {
    if (loadStatus.value !== "loadmore") return;
    page.current++;
    fetchList();
  };
  const onTabChange = val => {
    activeTabIndex.value = val.index;
    resetAndQuery();
  };
  const goLedgerAdd = () => {
    uni.navigateTo({ url: "/pages/safeProduction/safetyFacility/ledgerEdit" });
  };
  const goLedgerEdit = row => {
    const data = encodeURIComponent(JSON.stringify(row || {}));
    uni.navigateTo({
      url: `/pages/safeProduction/safetyFacility/ledgerEdit?data=${data}`,
    });
  };
  const goInspectionAdd = row => {
    const data = encodeURIComponent(
      JSON.stringify({ mode: "add", facility: row })
    );
    uni.navigateTo({
      url: `/pages/safeProduction/safetyFacility/inspectionEdit?data=${data}`,
    });
  };
  const goInspectionDo = row => {
    const data = encodeURIComponent(JSON.stringify({ mode: "do", row }));
    uni.navigateTo({
      url: `/pages/safeProduction/safetyFacility/inspectionEdit?data=${data}`,
    });
  };
  const goInspectionView = row => {
    const data = encodeURIComponent(JSON.stringify({ mode: "view", row }));
    uni.navigateTo({
      url: `/pages/safeProduction/safetyFacility/inspectionEdit?data=${data}`,
    });
  };
  const goRectificationAdd = row => {
    const data = encodeURIComponent(
      JSON.stringify({ mode: "add", inspection: row })
    );
    uni.navigateTo({
      url: `/pages/safeProduction/safetyFacility/rectificationEdit?data=${data}`,
    });
  };
  const goRectificationDo = row => {
    const data = encodeURIComponent(JSON.stringify({ mode: "do", row }));
    uni.navigateTo({
      url: `/pages/safeProduction/safetyFacility/rectificationEdit?data=${data}`,
    });
  };
  const goRectificationVerify = row => {
    const data = encodeURIComponent(JSON.stringify({ mode: "verify", row }));
    uni.navigateTo({
      url: `/pages/safeProduction/safetyFacility/rectificationEdit?data=${data}`,
    });
  };
  const goRectificationView = row => {
    const data = encodeURIComponent(JSON.stringify({ mode: "view", row }));
    uni.navigateTo({
      url: `/pages/safeProduction/safetyFacility/rectificationEdit?data=${data}`,
    });
  };
  const deleteLedger = row => {
    if (!row?.id) return;
    uni.showModal({
      title: "删除",
      content: "确认删除该设施吗?",
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "删除中...", mask: true });
        deleteFacilityLedger([row.id])
          .then(() => {
            uni.showToast({ title: "删除成功", icon: "success" });
            resetAndQuery();
          })
          .catch(() => {
            uni.showToast({ title: "删除失败", icon: "none" });
          })
          .finally(() => {
            uni.hideLoading();
          });
      },
    });
  };
  const deleteInspection = row => {
    if (!row?.id) return;
    if (String(row.status) !== "待巡检") return;
    uni.showModal({
      title: "删除",
      content: "确认删除该巡检记录吗?",
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "删除中...", mask: true });
        deleteFacilityInspection([row.id])
          .then(() => {
            uni.showToast({ title: "删除成功", icon: "success" });
            resetAndQuery();
          })
          .catch(() => {
            uni.showToast({ title: "删除失败", icon: "none" });
          })
          .finally(() => {
            uni.hideLoading();
          });
      },
    });
  };
  onMounted(() => {
    uni.$on("safetyFacility:refresh", resetAndQuery);
  });
  onShow(() => {
    resetAndQuery();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .safety-facility {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 140rpx;
  }
  .tabs-section {
    background: #fff;
  }
  .list-scroll {
    flex: 1;
    min-height: 0;
  }
  .action-buttons {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 20rpx;
    padding-bottom: 30rpx;
  }
  .fixed-btn {
    width: 160rpx;
    margin: 0 !important;
  }
  .fab-button {
    position: fixed;
    right: 30rpx;
    bottom: 140rpx;
    width: 96rpx;
    height: 96rpx;
    background: #3c9cff;
    border-radius: 48rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 8rpx 24rpx rgba(60, 156, 255, 0.3);
    z-index: 1001;
  }
  .no-data {
    padding-top: 200rpx;
  }
</style>
src/pages/safeProduction/safetyFacility/inspectionEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,262 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle"
                @back="goBack" />
    <up-form :model="form"
             label-width="110">
      <up-form-item label="设施名称">
        <up-input :model-value="facilityName"
                  disabled
                  placeholder="自动带出" />
      </up-form-item>
      <up-form-item label="设施编号">
        <up-input :model-value="facilityCode"
                  disabled
                  placeholder="自动带出" />
      </up-form-item>
      <up-form-item label="巡检编号"
                    required>
        <up-input v-model="form.inspectionCode"
                  :disabled="isView"
                  placeholder="请输入"
                  clearable />
      </up-form-item>
      <up-form-item label="巡检类型">
        <up-input :model-value="form.inspectionType"
                  placeholder="请选择"
                  readonly
                  :disabled="isView"
                  @click="openTypeSheet" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="openTypeSheet"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="计划巡检时间">
        <up-input :model-value="form.planTime"
                  placeholder="请选择"
                  readonly
                  :disabled="isView"
                  @click="openPlanPicker" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="openPlanPicker"></up-icon>
        </template>
      </up-form-item>
      <template v-if="mode === 'do' || mode === 'view'">
        <up-form-item label="实际巡检时间">
          <up-input :model-value="form.actualTime"
                    disabled
                    placeholder="自动填充" />
        </up-form-item>
        <up-form-item label="检查结果"
                      :required="mode === 'do'">
          <up-input :model-value="form.checkResult"
                    placeholder="请选择"
                    readonly
                    :disabled="isView"
                    @click="openResultSheet" />
          <template #right>
            <up-icon name="arrow-right"
                     @click="openResultSheet"></up-icon>
          </template>
        </up-form-item>
        <up-form-item label="检查说明">
          <up-textarea v-model="form.checkDesc"
                       :disabled="isView"
                       placeholder="请输入"
                       auto-height />
        </up-form-item>
      </template>
    </up-form>
    <FooterButtons :loading="loading"
                   :confirmText="isView ? '返回' : '保存'"
                   @cancel="goBack"
                   @confirm="handleSubmit" />
    <up-action-sheet :show="showTypeSheet"
                     title="选择巡检类型"
                     :actions="typeActions"
                     @select="onSelectType"
                     @close="showTypeSheet = false" />
    <up-action-sheet :show="showResultSheet"
                     title="选择检查结果"
                     :actions="resultActions"
                     @select="onSelectResult"
                     @close="showResultSheet = false" />
    <up-datetime-picker :show="showPlanPicker"
                        mode="datetime"
                        :value="planValue"
                        @confirm="onPlanConfirm"
                        @cancel="showPlanPicker = false" />
  </view>
</template>
<script setup>
  import { computed, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import dayjs from "dayjs";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import {
    addFacilityInspection,
    updateFacilityInspection,
  } from "@/api/safeProduction/safetyFacility";
  const loading = ref(false);
  const mode = ref("add");
  const facilityName = ref("");
  const facilityCode = ref("");
  const form = ref({
    id: null,
    facilityId: null,
    inspectionCode: "",
    inspectionType: "定期巡检",
    planTime: "",
    checkResult: "",
    checkDesc: "",
    actualTime: "",
    status: "",
  });
  const showTypeSheet = ref(false);
  const showResultSheet = ref(false);
  const showPlanPicker = ref(false);
  const typeActions = [
    { name: "定期巡检", value: "定期巡检" },
    { name: "临时巡检", value: "临时巡检" },
  ];
  const resultActions = [
    { name: "正常", value: "正常" },
    { name: "异常", value: "异常" },
  ];
  const isView = computed(() => mode.value === "view");
  const pageTitle = computed(() => {
    if (mode.value === "add") return "发起巡检";
    if (mode.value === "do") return "巡检";
    return "查看巡检";
  });
  const planValue = computed(() => {
    if (!form.value.planTime) return Date.now();
    const ts = dayjs(form.value.planTime).valueOf();
    return Number.isFinite(ts) ? ts : Date.now();
  });
  const goBack = () => {
    uni.navigateBack();
  };
  const openTypeSheet = () => {
    if (isView.value) return;
    showTypeSheet.value = true;
  };
  const openPlanPicker = () => {
    if (isView.value) return;
    showPlanPicker.value = true;
  };
  const openResultSheet = () => {
    if (isView.value) return;
    showResultSheet.value = true;
  };
  const onSelectType = action => {
    if (isView.value) return;
    form.value.inspectionType = action.value;
    showTypeSheet.value = false;
  };
  const onSelectResult = action => {
    if (isView.value) return;
    form.value.checkResult = action.value;
    showResultSheet.value = false;
  };
  const onPlanConfirm = e => {
    if (isView.value) return;
    form.value.planTime = dayjs(e?.value).format("YYYY-MM-DD HH:mm:ss");
    showPlanPicker.value = false;
  };
  const handleSubmit = () => {
    if (isView.value) {
      goBack();
      return;
    }
    const inspectionCode = String(form.value.inspectionCode || "").trim();
    if (!inspectionCode) {
      uni.showToast({ title: "请输入巡检编号", icon: "none" });
      return;
    }
    if (!form.value.facilityId) {
      uni.showToast({ title: "缺少设施信息", icon: "none" });
      return;
    }
    if (!form.value.planTime) {
      uni.showToast({ title: "请选择计划巡检时间", icon: "none" });
      return;
    }
    if (mode.value === "do") {
      const checkResult = String(form.value.checkResult || "").trim();
      if (!checkResult) {
        uni.showToast({ title: "请选择检查结果", icon: "none" });
        return;
      }
      form.value.status = "已巡检";
      form.value.actualTime = dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss");
    }
    const payload = {
      ...form.value,
      inspectionCode,
      checkDesc: String(form.value.checkDesc || "").trim(),
    };
    loading.value = true;
    const api = payload.id ? updateFacilityInspection : addFacilityInspection;
    api(payload)
      .then(() => {
        uni.showToast({ title: "操作成功", icon: "success" });
        uni.$emit("safetyFacility:refresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "操作失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  onLoad(options => {
    if (options?.data) {
      try {
        const obj = JSON.parse(decodeURIComponent(options.data));
        mode.value = obj.mode || "add";
        if (obj.facility) {
          const f = obj.facility;
          form.value.facilityId = f.id;
          facilityName.value = f.facilityName || "";
          facilityCode.value = f.facilityCode || "";
        }
        if (obj.row) {
          const r = obj.row;
          form.value = { ...form.value, ...(r || {}) };
          facilityName.value = r.facilityName || facilityName.value;
          facilityCode.value = r.facilityCode || facilityCode.value;
        }
      } catch (e) {}
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/safeProduction/safetyFacility/ledgerEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,211 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle" @back="goBack" />
    <up-form :model="form" label-width="110">
      <up-form-item label="设施编号" required>
        <up-input v-model="form.facilityCode" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="设施名称" required>
        <up-input v-model="form.facilityName" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="设施类型" required>
        <up-input :model-value="form.facilityType" placeholder="请选择" readonly @click="showTypeSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showTypeSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="规格型号">
        <up-input v-model="form.facilitySpec" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="安装位置" required>
        <up-input v-model="form.installLocation" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="安装时间">
        <up-input :model-value="form.installTime" placeholder="请选择" readonly @click="showInstallPicker = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showInstallPicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="生产日期">
        <up-input :model-value="form.productionDate" placeholder="请选择" readonly @click="showProductionPicker = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showProductionPicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="有效期(天)">
        <up-input v-model="form.validPeriod" type="number" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="下次检查时间">
        <up-input :model-value="nextCheckTime" placeholder="根据安装时间+有效期自动计算" disabled />
      </up-form-item>
      <up-form-item label="状态">
        <up-input :model-value="form.status" placeholder="请选择" readonly @click="showStatusSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showStatusSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="备注">
        <up-textarea v-model="form.remark" placeholder="请输入" auto-height />
      </up-form-item>
    </up-form>
    <FooterButtons :loading="loading" confirmText="保存" @cancel="goBack" @confirm="handleSubmit" />
    <up-action-sheet :show="showTypeSheet" title="选择设施类型" :actions="typeActions" @select="onSelectType" @close="showTypeSheet = false" />
    <up-action-sheet :show="showStatusSheet" title="选择状态" :actions="statusActions" @select="onSelectStatus" @close="showStatusSheet = false" />
    <up-datetime-picker :show="showInstallPicker" mode="date" :value="installValue" @confirm="onInstallConfirm" @cancel="showInstallPicker = false" />
    <up-datetime-picker :show="showProductionPicker" mode="date" :value="productionValue" @confirm="onProductionConfirm" @cancel="showProductionPicker = false" />
  </view>
</template>
<script setup>
  import { computed, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import dayjs from "dayjs";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import { addFacilityLedger, updateFacilityLedger } from "@/api/safeProduction/safetyFacility";
  const loading = ref(false);
  const form = ref({
    id: null,
    facilityCode: "",
    facilityName: "",
    facilityType: "",
    facilitySpec: "",
    installLocation: "",
    installTime: "",
    productionDate: "",
    validPeriod: "",
    nextCheckTime: "",
    status: "正常",
    remark: "",
  });
  const showTypeSheet = ref(false);
  const showStatusSheet = ref(false);
  const showInstallPicker = ref(false);
  const showProductionPicker = ref(false);
  const typeActions = [
    { name: "消防器材", value: "消防器材" },
    { name: "安全设备", value: "安全设备" },
    { name: "防护用品", value: "防护用品" },
    { name: "监控设备", value: "监控设备" },
  ];
  const statusActions = [
    { name: "正常", value: "正常" },
    { name: "异常", value: "异常" },
    { name: "报废", value: "报废" },
  ];
  const pageTitle = computed(() => (form.value.id ? "编辑设施" : "新增设施"));
  const nextCheckTime = computed(() => {
    const installTime = form.value.installTime;
    const validPeriod = Number(form.value.validPeriod);
    if (!installTime || !Number.isFinite(validPeriod) || validPeriod <= 0) return "";
    const d = dayjs(installTime);
    if (!d.isValid()) return "";
    return d.add(validPeriod, "day").format("YYYY-MM-DD");
  });
  const installValue = computed(() => {
    if (!form.value.installTime) return Date.now();
    const ts = dayjs(form.value.installTime).valueOf();
    return Number.isFinite(ts) ? ts : Date.now();
  });
  const productionValue = computed(() => {
    if (!form.value.productionDate) return Date.now();
    const ts = dayjs(form.value.productionDate).valueOf();
    return Number.isFinite(ts) ? ts : Date.now();
  });
  const goBack = () => {
    uni.navigateBack();
  };
  const onSelectType = action => {
    form.value.facilityType = action.value;
    showTypeSheet.value = false;
  };
  const onSelectStatus = action => {
    form.value.status = action.value;
    showStatusSheet.value = false;
  };
  const onInstallConfirm = e => {
    form.value.installTime = dayjs(e?.value).format("YYYY-MM-DD");
    showInstallPicker.value = false;
  };
  const onProductionConfirm = e => {
    form.value.productionDate = dayjs(e?.value).format("YYYY-MM-DD");
    showProductionPicker.value = false;
  };
  const handleSubmit = () => {
    const facilityCode = String(form.value.facilityCode || "").trim();
    if (!facilityCode) {
      uni.showToast({ title: "请输入设施编号", icon: "none" });
      return;
    }
    const facilityName = String(form.value.facilityName || "").trim();
    if (!facilityName) {
      uni.showToast({ title: "请输入设施名称", icon: "none" });
      return;
    }
    const facilityType = String(form.value.facilityType || "").trim();
    if (!facilityType) {
      uni.showToast({ title: "请选择设施类型", icon: "none" });
      return;
    }
    const installLocation = String(form.value.installLocation || "").trim();
    if (!installLocation) {
      uni.showToast({ title: "请输入安装位置", icon: "none" });
      return;
    }
    const payload = {
      ...form.value,
      facilityCode,
      facilityName,
      facilityType,
      facilitySpec: String(form.value.facilitySpec || "").trim(),
      installLocation,
      validPeriod: form.value.validPeriod === "" ? null : Number(form.value.validPeriod),
      nextCheckTime: nextCheckTime.value || null,
      remark: String(form.value.remark || "").trim(),
    };
    loading.value = true;
    const api = payload.id ? updateFacilityLedger : addFacilityLedger;
    api(payload)
      .then(() => {
        uni.showToast({ title: "保存成功", icon: "success" });
        uni.$emit("safetyFacility:refresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "保存失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  onLoad(options => {
    if (options?.data) {
      try {
        const row = JSON.parse(decodeURIComponent(options.data));
        form.value = { ...form.value, ...(row || {}) };
      } catch (e) {}
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/safeProduction/safetyFacility/rectificationEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,281 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle" @back="goBack" />
    <up-form :model="form" label-width="110">
      <up-form-item label="设施名称">
        <up-input :model-value="facilityName" disabled placeholder="自动带出" />
      </up-form-item>
      <template v-if="mode !== 'verify'">
        <up-form-item label="问题描述" :required="mode === 'add'">
          <up-textarea v-model="form.problemDesc" :disabled="isReadOnlyProblem" placeholder="请输入" auto-height />
        </up-form-item>
        <up-form-item label="问题等级" :required="mode === 'add'">
          <up-input
            :model-value="form.problemLevel"
            placeholder="请选择"
            readonly
            :disabled="isReadOnlyProblem"
            @click="openLevelSheet"
          />
          <template #right>
            <up-icon name="arrow-right" @click="openLevelSheet"></up-icon>
          </template>
        </up-form-item>
        <up-form-item label="计划整改时间" :required="mode === 'add'">
          <up-input
            :model-value="form.planTime"
            placeholder="请选择"
            readonly
            :disabled="isReadOnlyProblem"
            @click="openPlanPicker"
          />
          <template #right>
            <up-icon name="arrow-right" @click="openPlanPicker"></up-icon>
          </template>
        </up-form-item>
      </template>
      <up-form-item v-if="mode === 'do'" label="整改说明" required>
        <up-textarea v-model="form.rectifyDesc" placeholder="请输入" auto-height />
      </up-form-item>
      <up-form-item v-if="mode === 'verify'" label="验收说明" required>
        <up-textarea v-model="form.verifyDesc" placeholder="请输入" auto-height />
      </up-form-item>
      <template v-if="mode === 'view'">
        <up-form-item label="整改说明">
          <up-textarea :model-value="form.rectifyDesc" disabled auto-height />
        </up-form-item>
        <up-form-item label="验收说明">
          <up-textarea :model-value="form.verifyDesc" disabled auto-height />
        </up-form-item>
      </template>
    </up-form>
    <FooterButtons :loading="loading" :confirmText="confirmText" @cancel="goBack" @confirm="handleSubmit" />
    <up-action-sheet
      :show="showLevelSheet"
      title="选择问题等级"
      :actions="levelActions"
      @select="onSelectLevel"
      @close="showLevelSheet = false"
    />
    <up-datetime-picker
      :show="showPlanPicker"
      mode="datetime"
      :value="planValue"
      @confirm="onPlanConfirm"
      @cancel="showPlanPicker = false"
    />
  </view>
</template>
<script setup>
  import { computed, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import dayjs from "dayjs";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import {
    addFacilityRectification,
    updateFacilityRectification,
  } from "@/api/safeProduction/safetyFacility";
  const loading = ref(false);
  const mode = ref("add");
  const facilityName = ref("");
  const form = ref({
    id: null,
    inspectionId: null,
    facilityId: null,
    problemDesc: "",
    problemLevel: "",
    planTime: "",
    rectifyDesc: "",
    verifyDesc: "",
    status: "",
    actualTime: "",
    verifyTime: "",
  });
  const showLevelSheet = ref(false);
  const showPlanPicker = ref(false);
  const levelActions = [
    { name: "一般", value: "一般" },
    { name: "重大", value: "重大" },
  ];
  const isReadOnlyProblem = computed(() => mode.value !== "add");
  const pageTitle = computed(() => {
    if (mode.value === "add") return "新增整改";
    if (mode.value === "do") return "整改";
    if (mode.value === "verify") return "验收";
    return "查看整改";
  });
  const confirmText = computed(() => {
    if (mode.value === "view") return "返回";
    return "保存";
  });
  const planValue = computed(() => {
    if (!form.value.planTime) return Date.now();
    const ts = dayjs(form.value.planTime).valueOf();
    return Number.isFinite(ts) ? ts : Date.now();
  });
  const goBack = () => {
    uni.navigateBack();
  };
  const openLevelSheet = () => {
    if (mode.value !== "add") return;
    showLevelSheet.value = true;
  };
  const openPlanPicker = () => {
    if (mode.value !== "add") return;
    showPlanPicker.value = true;
  };
  const onSelectLevel = action => {
    if (mode.value !== "add") return;
    form.value.problemLevel = action.value;
    showLevelSheet.value = false;
  };
  const onPlanConfirm = e => {
    if (mode.value !== "add") return;
    form.value.planTime = dayjs(e?.value).format("YYYY-MM-DD HH:mm:ss");
    showPlanPicker.value = false;
  };
  const handleSubmit = () => {
    if (mode.value === "view") {
      goBack();
      return;
    }
    if (mode.value === "add") {
      if (!form.value.inspectionId || !form.value.facilityId) {
        uni.showToast({ title: "缺少巡检信息", icon: "none" });
        return;
      }
      const problemDesc = String(form.value.problemDesc || "").trim();
      if (!problemDesc) {
        uni.showToast({ title: "请输入问题描述", icon: "none" });
        return;
      }
      const problemLevel = String(form.value.problemLevel || "").trim();
      if (!problemLevel) {
        uni.showToast({ title: "请选择问题等级", icon: "none" });
        return;
      }
      if (!form.value.planTime) {
        uni.showToast({ title: "请选择计划整改时间", icon: "none" });
        return;
      }
      const payload = { ...form.value, problemDesc, problemLevel };
      loading.value = true;
      addFacilityRectification(payload)
        .then(() => {
          uni.showToast({ title: "操作成功", icon: "success" });
          uni.$emit("safetyFacility:refresh");
          goBack();
        })
        .catch(() => {
          uni.showToast({ title: "操作失败", icon: "none" });
        })
        .finally(() => {
          loading.value = false;
        });
      return;
    }
    if (mode.value === "do") {
      if (!form.value.id) return;
      const rectifyDesc = String(form.value.rectifyDesc || "").trim();
      if (!rectifyDesc) {
        uni.showToast({ title: "请输入整改说明", icon: "none" });
        return;
      }
      const payload = {
        ...form.value,
        rectifyDesc,
        status: "已整改",
        actualTime: dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss"),
      };
      loading.value = true;
      updateFacilityRectification(payload)
        .then(() => {
          uni.showToast({ title: "操作成功", icon: "success" });
          uni.$emit("safetyFacility:refresh");
          goBack();
        })
        .catch(() => {
          uni.showToast({ title: "操作失败", icon: "none" });
        })
        .finally(() => {
          loading.value = false;
        });
      return;
    }
    if (mode.value === "verify") {
      if (!form.value.id) return;
      const verifyDesc = String(form.value.verifyDesc || "").trim();
      if (!verifyDesc) {
        uni.showToast({ title: "请输入验收说明", icon: "none" });
        return;
      }
      const payload = {
        ...form.value,
        verifyDesc,
        status: "已验收",
        verifyTime: dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss"),
      };
      loading.value = true;
      updateFacilityRectification(payload)
        .then(() => {
          uni.showToast({ title: "操作成功", icon: "success" });
          uni.$emit("safetyFacility:refresh");
          goBack();
        })
        .catch(() => {
          uni.showToast({ title: "操作失败", icon: "none" });
        })
        .finally(() => {
          loading.value = false;
        });
    }
  };
  onLoad(options => {
    if (options?.data) {
      try {
        const obj = JSON.parse(decodeURIComponent(options.data));
        mode.value = obj.mode || "add";
        if (obj.inspection) {
          const ins = obj.inspection;
          form.value.inspectionId = ins.id;
          form.value.facilityId = ins.facilityId;
          facilityName.value = ins.facilityName || "";
        }
        if (obj.row) {
          form.value = { ...form.value, ...(obj.row || {}) };
          facilityName.value = obj.row.facilityName || facilityName.value;
        }
      } catch (e) {}
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/works.vue
@@ -511,6 +511,14 @@
      label: "特种设备管理",
    },
    {
      icon: "/static/images/icon/xunjianshangchuan.svg",
      label: "线路巡检",
    },
    {
      icon: "/static/images/icon/xunjianshangchuan.svg",
      label: "安全设施巡检",
    },
    {
      icon: "/static/images/icon/weixianyuan.svg",
      label: "危险源台账",
    },
@@ -944,6 +952,16 @@
          url: "/pages/safeProduction/specialEquipmentManagement/index",
        });
        break;
      case "线路巡检":
        uni.navigateTo({
          url: "/pages/safeProduction/lineInspection/index",
        });
        break;
      case "安全设施巡检":
        uni.navigateTo({
          url: "/pages/safeProduction/safetyFacility/index",
        });
        break;
      case "危险源台账":
        uni.navigateTo({
          url: "/pages/safeProduction/hazardSourceLedger/index",