zhangwencui
9 小时以前 263b4a60670671cef5445b102e670c40ba34a162
特种设备管理模块开发
已添加5个文件
已修改2个文件
1391 ■■■■■ 文件已修改
src/api/safeProduction/specialEquipmentManagement.js 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/specialEquipmentManagement/index.vue 516 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/specialEquipmentManagement/ledgerEdit.vue 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/specialEquipmentManagement/recordEdit.vue 275 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/specialEquipmentManagement/rectificationEdit.vue 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safeProduction/specialEquipmentManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
import request from "@/utils/request";
export function specialEquipmentLedgerListPage(query) {
  return request({
    url: "/safe/specialEquipment/ledger/listPage",
    method: "get",
    params: query,
  });
}
export function specialEquipmentLedgerList(query) {
  return request({
    url: "/safe/specialEquipment/ledger/list",
    method: "get",
    params: query,
  });
}
export function specialEquipmentLedgerAdd(data) {
  return request({
    url: "/safe/specialEquipment/ledger/add",
    method: "post",
    data,
  });
}
export function specialEquipmentLedgerUpdate(data) {
  return request({
    url: "/safe/specialEquipment/ledger/update",
    method: "post",
    data,
  });
}
export function specialEquipmentLedgerDel(ids) {
  return request({
    url: "/safe/specialEquipment/ledger/delete",
    method: "delete",
    data: ids,
  });
}
export function specialEquipmentRecordListPage(query) {
  return request({
    url: "/safe/specialEquipment/record/listPage",
    method: "get",
    params: query,
  });
}
export function specialEquipmentRecordAdd(data) {
  return request({
    url: "/safe/specialEquipment/record/add",
    method: "post",
    data,
  });
}
export function specialEquipmentRecordUpdate(data) {
  return request({
    url: "/safe/specialEquipment/record/update",
    method: "post",
    data,
  });
}
export function specialEquipmentRecordDel(ids) {
  return request({
    url: "/safe/specialEquipment/record/delete",
    method: "delete",
    data: ids,
  });
}
export function specialEquipmentRectificationListPage(query) {
  return request({
    url: "/safe/specialEquipment/rectification/listPage",
    method: "get",
    params: query,
  });
}
export function specialEquipmentRectificationAdd(data) {
  return request({
    url: "/safe/specialEquipment/rectification/add",
    method: "post",
    data,
  });
}
export function specialEquipmentRectificationClose(data) {
  return request({
    url: "/safe/specialEquipment/rectification/close",
    method: "post",
    data,
  });
}
export function specialEquipmentRectificationDel(ids) {
  return request({
    url: "/safe/specialEquipment/rectification/delete",
    method: "delete",
    data: ids,
  });
}
src/pages.json
@@ -1020,6 +1020,34 @@
      }
    },
    {
      "path": "pages/safeProduction/specialEquipmentManagement/index",
      "style": {
        "navigationBarTitleText": "特种设备管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/specialEquipmentManagement/ledgerEdit",
      "style": {
        "navigationBarTitleText": "台账",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/specialEquipmentManagement/recordEdit",
      "style": {
        "navigationBarTitleText": "检验维保",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/specialEquipmentManagement/rectificationEdit",
      "style": {
        "navigationBarTitleText": "隐患整改",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/accidentReportingRecord/index",
      "style": {
        "navigationBarTitleText": "事故报告记录",
@@ -1596,4 +1624,4 @@
    "navigationBarTitleText": "RuoYi",
    "navigationBarBackgroundColor": "#FFFFFF"
  }
}
}
src/pages/safeProduction/specialEquipmentManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,516 @@
<template>
  <view class="special-equipment">
    <PageHeader title="特种设备管理"
                @back="goBack">
    </PageHeader>
    <view class="tabs-section">
      <up-tabs v-model="activeTabIndex"
               :list="tabList"
               itemStyle="width: 33.33%;height: 80rpx;"
               @change="onTabChange" />
    </view>
    <scroll-view scroll-y
                 class="list-scroll"
                 @scrolltolower="loadMore"
                 v-if="list.length > 0">
      <up-checkbox-group v-if="selectMode"
                         v-model="selectedIds"
                         placement="column">
        <view v-for="row in list"
              :key="row.id"
              class="card"
              @click="toggleSelect(row)">
          <view class="card-header">
            <text class="title">{{ getCardTitle(row) }}</text>
            <view class="right">
              <up-checkbox :name="String(row.id)"
                           :label="''"
                           @click.stop></up-checkbox>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="card-body">
            <template v-if="activeTab === 'ledger'">
              <view class="row"><text class="label">设备编号</text><text class="value">{{ row.equipmentNo || '-' }}</text></view>
              <view class="row"><text class="label">设备类别</text><text class="value">{{ row.equipmentType || '-' }}</text></view>
              <view class="row"><text class="label">安装位置</text><text class="value">{{ row.installLocation || '-' }}</text></view>
              <view class="row"><text class="label">负责人</text><text class="value">{{ row.managerName || '-' }}</text></view>
            </template>
            <template v-else-if="activeTab === 'record'">
              <view class="row"><text class="label">记录类型</text><text class="value">{{ recordTypeLabel(row.recordType) }}</text></view>
              <view class="row"><text class="label">记录日期</text><text class="value">{{ row.recordDate || '-' }}</text></view>
              <view class="row"><text class="label">执行人</text><text class="value">{{ row.executorName || '-' }}</text></view>
              <view class="row"><text class="label">下次到期日期</text><text class="value">{{ row.nextDueDate || '-' }}</text></view>
            </template>
            <template v-else>
              <view class="row"><text class="label">风险等级</text><text class="value">{{ row.riskLevel || '-' }}</text></view>
              <view class="row"><text class="label">发现日期</text><text class="value">{{ row.discoveredDate || '-' }}</text></view>
              <view class="row"><text class="label">责任人</text><text class="value">{{ row.responsibleUserName || '-' }}</text></view>
              <view class="row"><text class="label">整改状态</text><text class="value">{{ rectStatusLabel(row.rectificationStatus) }}</text></view>
            </template>
          </view>
        </view>
      </up-checkbox-group>
      <view v-else>
        <view v-for="row in list"
              :key="row.id"
              class="card">
          <view class="card-header">
            <text class="title">{{ getCardTitle(row) }}</text>
            <up-tag v-if="activeTab === 'record'"
                    :text="recordTypeLabel(row.recordType)"
                    type="primary"
                    size="mini" />
            <up-tag v-else-if="activeTab === 'rectification'"
                    :text="rectStatusLabel(row.rectificationStatus)"
                    type="info"
                    size="mini" />
          </view>
          <up-divider></up-divider>
          <view class="card-body">
            <template v-if="activeTab === 'ledger'">
              <view class="row"><text class="label">设备编号</text><text class="value">{{ row.equipmentNo || '-' }}</text></view>
              <view class="row"><text class="label">设备类别</text><text class="value">{{ row.equipmentType || '-' }}</text></view>
              <view class="row"><text class="label">安装位置</text><text class="value">{{ row.installLocation || '-' }}</text></view>
              <view class="row"><text class="label">负责人</text><text class="value">{{ row.managerName || '-' }}</text></view>
            </template>
            <template v-else-if="activeTab === 'record'">
              <view class="row"><text class="label">关联设备</text><text class="value">{{ row.equipmentName || '-' }}</text></view>
              <view class="row"><text class="label">记录日期</text><text class="value">{{ row.recordDate || '-' }}</text></view>
              <view class="row"><text class="label">执行人</text><text class="value">{{ row.executorName || '-' }}</text></view>
              <view class="row"><text class="label">下次到期日期</text><text class="value">{{ row.nextDueDate || '-' }}</text></view>
            </template>
            <template v-else>
              <view class="row"><text class="label">隐患描述</text><text class="value">{{ row.hazardDescription || '-' }}</text></view>
              <view class="row"><text class="label">风险等级</text><text class="value">{{ row.riskLevel || '-' }}</text></view>
              <view class="row"><text class="label">整改期限</text><text class="value">{{ row.rectificationDeadline || '-' }}</text></view>
              <view class="row"><text class="label">责任人</text><text class="value">{{ row.responsibleUserName || '-' }}</text></view>
            </template>
          </view>
          <view class="actions">
            <u-button v-if="activeTab !== 'rectification'"
                      size="small"
                      class="action-btn"
                      @click.stop="goEdit(row)">编辑</u-button>
            <u-button size="small"
                      class="action-btn"
                      type="error"
                      @click.stop="deleteSingle(row)">删除</u-button>
          </view>
        </view>
        <up-loadmore :status="loadStatus" />
      </view>
    </scroll-view>
    <view v-else
          class="no-data">
      <up-empty mode="data"
                text="暂无数据"></up-empty>
    </view>
    <FooterButtons v-if="selectMode"
                   cancelText="取消"
                   confirmText="删除"
                   :confirmDisabled="selectedIds.length === 0"
                   @cancel="exitSelectMode"
                   @confirm="deleteSelected" />
    <view class="fab-button"
          @click="goAdd">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
    <up-action-sheet :show="showRecordTypeSheet"
                     title="选择记录类型"
                     :actions="recordTypeActions"
                     @select="onSelectRecordType"
                     @close="showRecordTypeSheet = false" />
    <up-action-sheet :show="showRectStatusSheet"
                     title="选择整改状态"
                     :actions="rectStatusActions"
                     @select="onSelectRectStatus"
                     @close="showRectStatusSheet = false" />
  </view>
</template>
<script setup>
  import { computed, onMounted, reactive, ref } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import {
    specialEquipmentLedgerDel,
    specialEquipmentLedgerListPage,
    specialEquipmentRecordDel,
    specialEquipmentRecordListPage,
    specialEquipmentRectificationDel,
    specialEquipmentRectificationListPage,
  } from "@/api/safeProduction/specialEquipmentManagement";
  const tabList = reactive([
    { name: "电子台账", value: "ledger" },
    { name: "检验维保", value: "record" },
    { name: "隐患整改", value: "rectification" },
  ]);
  const activeTabIndex = ref(0);
  const activeTab = computed(
    () => tabList[activeTabIndex.value]?.value || "ledger"
  );
  const ledgerSearch = reactive({ equipmentName: "" });
  const recordSearch = reactive({ recordType: "" });
  const rectSearch = reactive({ rectificationStatus: "" });
  const page = reactive({ current: 1, size: 10, total: 0 });
  const list = ref([]);
  const loadStatus = ref("loadmore");
  const loading = ref(false);
  const selectMode = ref(false);
  const selectedIds = ref([]);
  const showRecordTypeSheet = ref(false);
  const showRectStatusSheet = ref(false);
  const recordTypeActions = [
    { name: "检验", value: 1 },
    { name: "维保", value: 2 },
  ];
  const rectStatusActions = [
    { name: "待整改", value: 1 },
    { name: "整改中", value: 2 },
    { name: "待复核", value: 3 },
    { name: "已闭环", value: 4 },
  ];
  const recordTypeText = computed(() => {
    const v = recordSearch.recordType;
    if (!v) return "";
    return v === 1 ? "检验" : "维保";
  });
  const rectStatusText = computed(() =>
    rectStatusLabel(rectSearch.rectificationStatus)
  );
  const recordTypeLabel = v => {
    if (String(v) === "1") return "检验";
    if (String(v) === "2") return "维保";
    return "-";
  };
  const rectStatusLabel = v => {
    const map = { 1: "待整改", 2: "整改中", 3: "待复核", 4: "已闭环" };
    return map[Number(v)] || "";
  };
  const goBack = () => {
    uni.navigateBack();
  };
  const getCardTitle = row => {
    if (activeTab.value === "ledger") return row.equipmentName || "-";
    if (activeTab.value === "record") return row.recordNo || "-";
    return row.hazardDescription || "-";
  };
  const resetAndQuery = () => {
    page.current = 1;
    page.total = 0;
    list.value = [];
    loadStatus.value = "loadmore";
    exitSelectMode();
    fetchList();
  };
  const fetchList = () => {
    if (loading.value) return;
    loading.value = true;
    loadStatus.value = "loading";
    const reqPage = { current: page.current, size: page.size };
    let req;
    if (activeTab.value === "ledger") {
      req = specialEquipmentLedgerListPage({ ...ledgerSearch, ...reqPage });
    } else if (activeTab.value === "record") {
      req = specialEquipmentRecordListPage({ ...recordSearch, ...reqPage });
    } else {
      req = specialEquipmentRectificationListPage({ ...rectSearch, ...reqPage });
    }
    req
      .then(res => {
        const records = res?.data?.records || res?.data?.rows || [];
        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 ledgerQuery = () => {
    resetAndQuery();
  };
  const enterSelectMode = () => {
    selectMode.value = true;
    selectedIds.value = [];
  };
  const exitSelectMode = () => {
    selectMode.value = false;
    selectedIds.value = [];
  };
  const toggleSelect = row => {
    if (!row?.id) return;
    const id = String(row.id);
    const idx = selectedIds.value.indexOf(id);
    if (idx >= 0) selectedIds.value.splice(idx, 1);
    else selectedIds.value.push(id);
  };
  const deleteApi = ids => {
    if (activeTab.value === "ledger") return specialEquipmentLedgerDel(ids);
    if (activeTab.value === "record") return specialEquipmentRecordDel(ids);
    return specialEquipmentRectificationDel(ids);
  };
  const deleteSelected = () => {
    const ids = selectedIds.value.map(i => Number(i));
    if (!ids.length) {
      uni.showToast({ title: "请选择要删除的数据", icon: "none" });
      return;
    }
    uni.showModal({
      title: "删除确认",
      content: "确认删除所选数据吗?",
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "删除中...", mask: true });
        deleteApi(ids)
          .then(() => {
            uni.showToast({ title: "删除成功", icon: "success" });
            exitSelectMode();
            resetAndQuery();
          })
          .catch(() => {
            uni.showToast({ title: "删除失败", icon: "none" });
          })
          .finally(() => {
            uni.hideLoading();
          });
      },
    });
  };
  const deleteSingle = row => {
    if (!row?.id) return;
    uni.showModal({
      title: "删除确认",
      content: "确认删除该条数据吗?",
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "删除中...", mask: true });
        deleteApi([row.id])
          .then(() => {
            uni.showToast({ title: "删除成功", icon: "success" });
            resetAndQuery();
          })
          .catch(() => {
            uni.showToast({ title: "删除失败", icon: "none" });
          })
          .finally(() => {
            uni.hideLoading();
          });
      },
    });
  };
  const goAdd = () => {
    if (activeTab.value === "ledger") {
      uni.navigateTo({
        url: "/pages/safeProduction/specialEquipmentManagement/ledgerEdit",
      });
      return;
    }
    if (activeTab.value === "record") {
      uni.navigateTo({
        url: "/pages/safeProduction/specialEquipmentManagement/recordEdit",
      });
      return;
    }
    uni.navigateTo({
      url: "/pages/safeProduction/specialEquipmentManagement/rectificationEdit",
    });
  };
  const goEdit = row => {
    if (!row?.id) return;
    const data = encodeURIComponent(JSON.stringify(row));
    if (activeTab.value === "ledger") {
      uni.navigateTo({
        url: `/pages/safeProduction/specialEquipmentManagement/ledgerEdit?data=${data}`,
      });
      return;
    }
    if (activeTab.value === "record") {
      uni.navigateTo({
        url: `/pages/safeProduction/specialEquipmentManagement/recordEdit?data=${data}`,
      });
      return;
    }
  };
  const onSelectRecordType = action => {
    recordSearch.recordType = action.value;
    showRecordTypeSheet.value = false;
    resetAndQuery();
  };
  const onSelectRectStatus = action => {
    rectSearch.rectificationStatus = action.value;
    showRectStatusSheet.value = false;
    resetAndQuery();
  };
  onMounted(() => {
    resetAndQuery();
  });
  onShow(() => {
    resetAndQuery();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .special-equipment {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 140rpx;
  }
  .tabs-section {
    background: #fff;
    padding: 0 12px 8px 12px;
  }
  .header-right {
    display: flex;
    align-items: center;
    padding-right: 12rpx;
  }
  .list-scroll {
    flex: 1;
    min-height: 0;
    padding: 0 0 20rpx 0;
  }
  .card {
    background: #fff;
    border-radius: 16rpx;
    padding: 24rpx;
    margin: 20rpx;
    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  }
  .card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12rpx;
  }
  .title {
    font-size: 28rpx;
    font-weight: 600;
    color: #333;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .card-body {
    padding: 10rpx 0;
  }
  .row {
    display: flex;
    justify-content: space-between;
    margin-bottom: 12rpx;
  }
  .label {
    font-size: 26rpx;
    color: #999;
  }
  .value {
    font-size: 26rpx;
    color: #333;
    text-align: right;
    max-width: 70%;
    word-break: break-all;
  }
  .actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 16rpx;
    padding-top: 16rpx;
    border-top: 1rpx solid #f0f0f0;
  }
  .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;
  }
  .card-header .right :deep(.up-checkbox__label) {
    display: none;
  }
  .u-border,
  .up-border {
    border: none !important;
  }
</style>
src/pages/safeProduction/specialEquipmentManagement/ledgerEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,145 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle" @back="goBack" />
    <up-form ref="formRef" :model="form" label-width="110">
      <up-form-item label="设备编号" required>
        <up-input v-model="form.equipmentNo" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="设备名称" required>
        <up-input v-model="form.equipmentName" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="设备类别">
        <up-input v-model="form.equipmentType" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="规格型号">
        <up-input v-model="form.modelSpec" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="安装位置">
        <up-input v-model="form.installLocation" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="使用单位">
        <up-input v-model="form.useUnit" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="负责人">
        <up-input v-model="managerName" placeholder="请选择" readonly @click="showManagerSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showManagerSheet = true"></up-icon>
        </template>
      </up-form-item>
    </up-form>
    <FooterButtons :loading="loading" confirmText="保存" @cancel="goBack" @confirm="handleSubmit" />
    <up-action-sheet
      :show="showManagerSheet"
      title="选择负责人"
      :actions="managerActions"
      @select="onSelectManager"
      @close="showManagerSheet = false"
    />
  </view>
</template>
<script setup>
  import { computed, onMounted, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import FooterButtons from "@/components/FooterButtons.vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { userListNoPageByTenantId } from "@/api/system/user";
  import {
    specialEquipmentLedgerAdd,
    specialEquipmentLedgerUpdate,
  } from "@/api/safeProduction/specialEquipmentManagement";
  const formRef = ref();
  const loading = ref(false);
  const showManagerSheet = ref(false);
  const managerActions = ref([]);
  const managerName = ref("");
  const form = ref({
    id: "",
    equipmentNo: "",
    equipmentName: "",
    equipmentType: "",
    modelSpec: "",
    installLocation: "",
    useUnit: "",
    managerId: "",
    managerName: "",
  });
  const pageTitle = computed(() => (form.value.id ? "编辑台账" : "新增台账"));
  const goBack = () => {
    uni.navigateBack();
  };
  const loadUsers = () => {
    userListNoPageByTenantId()
      .then(res => {
        const rows = Array.isArray(res?.data) ? res.data : [];
        managerActions.value = rows.map(u => ({
          name: u.nickName || u.userName || "",
          value: u.userId,
        }));
      })
      .catch(() => {
        managerActions.value = [];
      });
  };
  const onSelectManager = action => {
    form.value.managerId = action.value;
    form.value.managerName = action.name;
    managerName.value = action.name;
    showManagerSheet.value = false;
  };
  const handleSubmit = async () => {
    const equipmentNo = String(form.value.equipmentNo || "").trim();
    if (!equipmentNo) {
      uni.showToast({ title: "请输入设备编号", icon: "none" });
      return;
    }
    const equipmentName = String(form.value.equipmentName || "").trim();
    if (!equipmentName) {
      uni.showToast({ title: "请输入设备名称", icon: "none" });
      return;
    }
    loading.value = true;
    const api = form.value.id ? specialEquipmentLedgerUpdate : specialEquipmentLedgerAdd;
    api({ ...form.value, equipmentNo, equipmentName })
      .then(() => {
        uni.showToast({ title: "保存成功", icon: "success" });
        uni.$emit("specialEquipmentManagement:refresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "保存失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  onLoad(options => {
    if (!options?.data) return;
    try {
      const row = JSON.parse(decodeURIComponent(options.data));
      form.value = { ...form.value, ...(row || {}) };
      managerName.value = form.value.managerName || "";
    } catch {
      uni.showToast({ title: "数据加载失败", icon: "none" });
    }
  });
  onMounted(() => {
    loadUsers();
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/safeProduction/specialEquipmentManagement/recordEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,275 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle" @back="goBack" />
    <up-form ref="formRef" :model="form" label-width="110">
      <up-form-item label="关联设备" required>
        <up-input v-model="equipmentText" placeholder="请选择" readonly @click="showEquipmentSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showEquipmentSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="记录类型" required>
        <up-input v-model="recordTypeText" placeholder="请选择" readonly @click="showRecordTypeSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showRecordTypeSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="记录编号" required>
        <up-input v-model="form.recordNo" placeholder="请输入" clearable />
      </up-form-item>
      <up-form-item label="记录日期" required>
        <up-input v-model="form.recordDate" placeholder="请选择" readonly @click="showRecordDatePicker = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showRecordDatePicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="执行人" required>
        <up-input v-model="executorText" placeholder="请选择" readonly @click="showExecutorSheet = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showExecutorSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="下次到期日期">
        <up-input v-model="form.nextDueDate" placeholder="请选择" readonly @click="showNextDueDatePicker = true" />
        <template #right>
          <up-icon name="arrow-right" @click="showNextDueDatePicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="执行结果">
        <up-textarea v-model="form.executeResult" placeholder="请输入" auto-height />
      </up-form-item>
      <up-form-item label="问题描述">
        <up-textarea v-model="form.problemDescription" placeholder="请输入" auto-height />
      </up-form-item>
      <up-form-item label="处理措施">
        <up-textarea v-model="form.disposeMeasure" placeholder="请输入" auto-height />
      </up-form-item>
      <up-form-item label="附件说明">
        <up-textarea v-model="form.attachmentRemark" placeholder="请输入" auto-height />
      </up-form-item>
    </up-form>
    <FooterButtons :loading="loading" confirmText="保存" @cancel="goBack" @confirm="handleSubmit" />
    <up-action-sheet
      :show="showEquipmentSheet"
      title="选择设备"
      :actions="equipmentActions"
      @select="onSelectEquipment"
      @close="showEquipmentSheet = false"
    />
    <up-action-sheet
      :show="showRecordTypeSheet"
      title="选择记录类型"
      :actions="recordTypeActions"
      @select="onSelectRecordType"
      @close="showRecordTypeSheet = false"
    />
    <up-action-sheet
      :show="showExecutorSheet"
      title="选择执行人"
      :actions="executorActions"
      @select="onSelectExecutor"
      @close="showExecutorSheet = false"
    />
    <up-datetime-picker
      :show="showRecordDatePicker"
      v-model="recordDateValue"
      mode="date"
      @confirm="onRecordDateConfirm"
      @cancel="showRecordDatePicker = false"
    />
    <up-datetime-picker
      :show="showNextDueDatePicker"
      v-model="nextDueDateValue"
      mode="date"
      @confirm="onNextDueDateConfirm"
      @cancel="showNextDueDatePicker = false"
    />
  </view>
</template>
<script setup>
  import { computed, onMounted, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import FooterButtons from "@/components/FooterButtons.vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { formatDateToYMD } from "@/utils/ruoyi";
  import { userListNoPageByTenantId } from "@/api/system/user";
  import {
    specialEquipmentLedgerList,
    specialEquipmentRecordAdd,
    specialEquipmentRecordUpdate,
  } from "@/api/safeProduction/specialEquipmentManagement";
  const formRef = ref();
  const loading = ref(false);
  const showEquipmentSheet = ref(false);
  const showRecordTypeSheet = ref(false);
  const showExecutorSheet = ref(false);
  const equipmentActions = ref([]);
  const executorActions = ref([]);
  const showRecordDatePicker = ref(false);
  const showNextDueDatePicker = ref(false);
  const recordDateValue = ref(Date.now());
  const nextDueDateValue = ref(Date.now());
  const equipmentText = ref("");
  const recordTypeText = ref("");
  const executorText = ref("");
  const recordTypeActions = [
    { name: "检验", value: 1 },
    { name: "维保", value: 2 },
  ];
  const form = ref({
    id: "",
    equipmentId: "",
    recordType: "",
    recordNo: "",
    recordDate: "",
    executeResult: "",
    problemDescription: "",
    disposeMeasure: "",
    executorId: "",
    executorName: "",
    nextDueDate: "",
    attachmentRemark: "",
  });
  const pageTitle = computed(() => (form.value.id ? "编辑记录" : "新增记录"));
  const goBack = () => {
    uni.navigateBack();
  };
  const loadEquipmentOptions = () => {
    specialEquipmentLedgerList({})
      .then(res => {
        const rows = Array.isArray(res?.data) ? res.data : [];
        equipmentActions.value = rows.map(r => ({
          name: r.equipmentName || "",
          value: r.id,
          equipmentNo: r.equipmentNo,
        }));
      })
      .catch(() => {
        equipmentActions.value = [];
      });
  };
  const loadUsers = () => {
    userListNoPageByTenantId()
      .then(res => {
        const rows = Array.isArray(res?.data) ? res.data : [];
        executorActions.value = rows.map(u => ({
          name: u.nickName || u.userName || "",
          value: u.userId,
        }));
      })
      .catch(() => {
        executorActions.value = [];
      });
  };
  const onSelectEquipment = action => {
    form.value.equipmentId = action.value;
    equipmentText.value = action.equipmentNo ? `${action.name}(${action.equipmentNo})` : action.name;
    showEquipmentSheet.value = false;
  };
  const onSelectRecordType = action => {
    form.value.recordType = action.value;
    recordTypeText.value = action.name;
    showRecordTypeSheet.value = false;
  };
  const onSelectExecutor = action => {
    form.value.executorId = action.value;
    form.value.executorName = action.name;
    executorText.value = action.name;
    showExecutorSheet.value = false;
  };
  const onRecordDateConfirm = e => {
    const value = e?.value ?? recordDateValue.value;
    form.value.recordDate = formatDateToYMD(value);
    showRecordDatePicker.value = false;
  };
  const onNextDueDateConfirm = e => {
    const value = e?.value ?? nextDueDateValue.value;
    form.value.nextDueDate = formatDateToYMD(value);
    showNextDueDatePicker.value = false;
  };
  const normalizeTexts = () => {
    if (form.value.recordType) {
      recordTypeText.value = String(form.value.recordType) === "1" ? "检验" : "维保";
    }
    executorText.value = form.value.executorName || "";
  };
  const handleSubmit = async () => {
    if (!form.value.equipmentId) {
      uni.showToast({ title: "请选择关联设备", icon: "none" });
      return;
    }
    if (!form.value.recordType) {
      uni.showToast({ title: "请选择记录类型", icon: "none" });
      return;
    }
    const recordNo = String(form.value.recordNo || "").trim();
    if (!recordNo) {
      uni.showToast({ title: "请输入记录编号", icon: "none" });
      return;
    }
    if (!form.value.recordDate) {
      uni.showToast({ title: "请选择记录日期", icon: "none" });
      return;
    }
    if (!form.value.executorId) {
      uni.showToast({ title: "请选择执行人", icon: "none" });
      return;
    }
    loading.value = true;
    const api = form.value.id ? specialEquipmentRecordUpdate : specialEquipmentRecordAdd;
    api({ ...form.value, recordNo })
      .then(() => {
        uni.showToast({ title: "保存成功", icon: "success" });
        uni.$emit("specialEquipmentManagement:refresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "保存失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  onLoad(options => {
    if (!options?.data) return;
    try {
      const row = JSON.parse(decodeURIComponent(options.data));
      form.value = { ...form.value, ...(row || {}) };
      normalizeTexts();
    } catch {
      uni.showToast({ title: "数据加载失败", icon: "none" });
    }
  });
  onMounted(() => {
    loadEquipmentOptions();
    loadUsers();
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/safeProduction/specialEquipmentManagement/rectificationEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,310 @@
<template>
  <view class="account-detail">
    <PageHeader title="新增整改"
                @back="goBack" />
    <up-form ref="formRef"
             :model="form"
             label-width="110">
      <up-form-item label="关联设备"
                    required>
        <up-input v-model="equipmentText"
                  placeholder="请选择"
                  readonly
                  @click="showEquipmentSheet = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showEquipmentSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="风险等级"
                    required>
        <up-input v-model="riskLevelText"
                  placeholder="请选择"
                  readonly
                  @click="showRiskLevelSheet = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showRiskLevelSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="发现日期"
                    required>
        <up-input v-model="form.discoveredDate"
                  placeholder="请选择"
                  readonly
                  @click="showDiscoveredDatePicker = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showDiscoveredDatePicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="整改期限"
                    required>
        <up-input v-model="form.rectificationDeadline"
                  placeholder="请选择"
                  readonly
                  @click="showDeadlinePicker = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showDeadlinePicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="整改责任人">
        <up-input v-model="responsibleUserText"
                  placeholder="请选择"
                  readonly
                  @click="showResponsibleUserSheet = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showResponsibleUserSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="整改状态">
        <up-input v-model="rectStatusText"
                  placeholder="请选择"
                  readonly
                  @click="showRectStatusSheet = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showRectStatusSheet = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="隐患描述"
                    required>
        <up-textarea v-model="form.hazardDescription"
                     placeholder="请输入"
                     auto-height />
      </up-form-item>
      <up-form-item label="整改措施">
        <up-textarea v-model="form.rectificationMeasures"
                     placeholder="请输入"
                     auto-height />
      </up-form-item>
      <up-form-item label="整改结果">
        <up-textarea v-model="form.rectificationResult"
                     placeholder="请输入"
                     auto-height />
      </up-form-item>
      <up-form-item label="备注">
        <up-textarea v-model="form.remarks"
                     placeholder="请输入"
                     auto-height />
      </up-form-item>
    </up-form>
    <FooterButtons :loading="loading"
                   confirmText="保存"
                   @cancel="goBack"
                   @confirm="handleSubmit" />
    <up-action-sheet :show="showEquipmentSheet"
                     title="选择设备"
                     :actions="equipmentActions"
                     @select="onSelectEquipment"
                     @close="showEquipmentSheet = false" />
    <up-action-sheet :show="showRiskLevelSheet"
                     title="选择风险等级"
                     :actions="riskLevelActions"
                     @select="onSelectRiskLevel"
                     @close="showRiskLevelSheet = false" />
    <up-action-sheet :show="showResponsibleUserSheet"
                     title="选择整改责任人"
                     :actions="responsibleUserActions"
                     @select="onSelectResponsibleUser"
                     @close="showResponsibleUserSheet = false" />
    <up-action-sheet :show="showRectStatusSheet"
                     title="选择整改状态"
                     :actions="rectStatusActions"
                     @select="onSelectRectStatus"
                     @close="showRectStatusSheet = false" />
    <up-datetime-picker :show="showDiscoveredDatePicker"
                        v-model="discoveredDateValue"
                        mode="date"
                        @confirm="onDiscoveredDateConfirm"
                        @cancel="showDiscoveredDatePicker = false" />
    <up-datetime-picker :show="showDeadlinePicker"
                        v-model="deadlineValue"
                        mode="date"
                        @confirm="onDeadlineConfirm"
                        @cancel="showDeadlinePicker = false" />
  </view>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { formatDateToYMD } from "@/utils/ruoyi";
  import { userListNoPageByTenantId } from "@/api/system/user";
  import {
    specialEquipmentLedgerList,
    specialEquipmentRectificationAdd,
  } from "@/api/safeProduction/specialEquipmentManagement";
  const formRef = ref();
  const loading = ref(false);
  const equipmentActions = ref([]);
  const responsibleUserActions = ref([]);
  const showEquipmentSheet = ref(false);
  const showRiskLevelSheet = ref(false);
  const showResponsibleUserSheet = ref(false);
  const showRectStatusSheet = ref(false);
  const showDiscoveredDatePicker = ref(false);
  const showDeadlinePicker = ref(false);
  const discoveredDateValue = ref(Date.now());
  const deadlineValue = ref(Date.now());
  const equipmentText = ref("");
  const riskLevelText = ref("");
  const responsibleUserText = ref("");
  const rectStatusText = ref("待整改");
  const riskLevelActions = [
    { name: "低", value: "低" },
    { name: "中", value: "中" },
    { name: "高", value: "高" },
  ];
  const rectStatusActions = [
    { name: "待整改", value: 1 },
    { name: "整改中", value: 2 },
    { name: "待复核", value: 3 },
    { name: "已闭环", value: 4 },
  ];
  const form = ref({
    id: "",
    equipmentId: "",
    hazardDescription: "",
    riskLevel: "",
    discoveredDate: "",
    responsibleUserId: "",
    responsibleUserName: "",
    rectificationDeadline: "",
    rectificationStatus: 1,
    rectificationMeasures: "",
    rectificationResult: "",
    remarks: "",
  });
  const goBack = () => {
    uni.navigateBack();
  };
  const loadEquipmentOptions = () => {
    specialEquipmentLedgerList({})
      .then(res => {
        const rows = Array.isArray(res?.data) ? res.data : [];
        equipmentActions.value = rows.map(r => ({
          name: r.equipmentName || "",
          value: r.id,
          equipmentNo: r.equipmentNo,
        }));
      })
      .catch(() => {
        equipmentActions.value = [];
      });
  };
  const loadUsers = () => {
    userListNoPageByTenantId()
      .then(res => {
        const rows = Array.isArray(res?.data) ? res.data : [];
        responsibleUserActions.value = rows.map(u => ({
          name: u.nickName || u.userName || "",
          value: u.userId,
        }));
      })
      .catch(() => {
        responsibleUserActions.value = [];
      });
  };
  const onSelectEquipment = action => {
    form.value.equipmentId = action.value;
    equipmentText.value = action.equipmentNo
      ? `${action.name}(${action.equipmentNo})`
      : action.name;
    showEquipmentSheet.value = false;
  };
  const onSelectRiskLevel = action => {
    form.value.riskLevel = action.value;
    riskLevelText.value = action.name;
    showRiskLevelSheet.value = false;
  };
  const onSelectResponsibleUser = action => {
    form.value.responsibleUserId = action.value;
    form.value.responsibleUserName = action.name;
    responsibleUserText.value = action.name;
    showResponsibleUserSheet.value = false;
  };
  const onSelectRectStatus = action => {
    form.value.rectificationStatus = action.value;
    rectStatusText.value = action.name;
    showRectStatusSheet.value = false;
  };
  const onDiscoveredDateConfirm = e => {
    const value = e?.value ?? discoveredDateValue.value;
    form.value.discoveredDate = formatDateToYMD(value);
    showDiscoveredDatePicker.value = false;
  };
  const onDeadlineConfirm = e => {
    const value = e?.value ?? deadlineValue.value;
    form.value.rectificationDeadline = formatDateToYMD(value);
    showDeadlinePicker.value = false;
  };
  const handleSubmit = async () => {
    if (!form.value.equipmentId) {
      uni.showToast({ title: "请选择关联设备", icon: "none" });
      return;
    }
    if (!form.value.riskLevel) {
      uni.showToast({ title: "请选择风险等级", icon: "none" });
      return;
    }
    if (!form.value.discoveredDate) {
      uni.showToast({ title: "请选择发现日期", icon: "none" });
      return;
    }
    if (!form.value.rectificationDeadline) {
      uni.showToast({ title: "请选择整改期限", icon: "none" });
      return;
    }
    const hazardDescription = String(form.value.hazardDescription || "").trim();
    if (!hazardDescription) {
      uni.showToast({ title: "请输入隐患描述", icon: "none" });
      return;
    }
    loading.value = true;
    specialEquipmentRectificationAdd({ ...form.value, hazardDescription })
      .then(() => {
        uni.showToast({ title: "保存成功", icon: "success" });
        uni.$emit("specialEquipmentManagement:refresh");
        goBack();
      })
      .catch(() => {
        uni.showToast({ title: "保存失败", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
      });
  };
  onMounted(() => {
    loadEquipmentOptions();
    loadUsers();
    rectStatusText.value = "待整改";
    form.value.rectificationStatus = 1;
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
</style>
src/pages/works.vue
@@ -507,6 +507,10 @@
      label: "规程与资质",
    },
    {
      icon: "/static/images/icon/yunxingguanli.svg",
      label: "特种设备管理",
    },
    {
      icon: "/static/images/icon/weixianyuan.svg",
      label: "危险源台账",
    },
@@ -935,6 +939,11 @@
          url: "/pages/safeProduction/safeQualifications/index",
        });
        break;
      case "特种设备管理":
        uni.navigateTo({
          url: "/pages/safeProduction/specialEquipmentManagement/index",
        });
        break;
      case "危险源台账":
        uni.navigateTo({
          url: "/pages/safeProduction/hazardSourceLedger/index",