已添加8个文件
已修改13个文件
3500 ■■■■ 文件已修改
src/api/safeProduction/accidentReportingRecord.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safeProduction/emergencyPlanReview.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/receiptPayment.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementLedger/detail.vue 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/accidentReportingRecord/detail.vue 411 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/accidentReportingRecord/index.vue 357 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/accidentReportingRecord/view.vue 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/emergencyPlanReview/detail.vue 724 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/emergencyPlanReview/index.vue 373 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/safeProduction/emergencyPlanReview/view.vue 372 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoiceLedger/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoicingRegistration/add.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPayment/add.vue 477 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPayment/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPaymentLedger/index.vue 285 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/detail.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/view.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/safeProduction/accidentReportingRecord.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function safeAccidentListPage(query) {
  return request({
    url: "/safeAccident/page",
    method: "get",
    params: query,
  });
}
export function safeAccidentAdd(query) {
    return request({
        url: '/safeAccident',
        method: 'post',
        data: query
    })
}
export function safeAccidentUpdate(query) {
    return request({
        url: '/safeAccident',
        method: 'put',
        data: query
    })
}
export function safeAccidentDel(ids) {
    return request({
        url: '/safeAccident/' + ids,
        method: 'delete',
        data: ids
    })
}
src/api/safeProduction/emergencyPlanReview.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
// åº”急预案审核页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function safeContingencyPlanListPage(query) {
  return request({
    url: "/safeContingencyPlan/page",
    method: "get",
    params: query,
  });
}
// æ–°å¢žåº”急预案
export function safeContingencyPlanAdd(query) {
    return request({
        url: '/safeContingencyPlan',
        method: 'post',
        data: query
    })
}
// ä¿®æ”¹åº”急预案
export function safeContingencyPlanUpdate(query) {
    return request({
        url: '/safeContingencyPlan',
        method: 'put',
        data: query
    })
}
// åˆ é™¤åº”急预案
export function safeContingencyPlanDel(ids) {
    return request({
        url: '/safeContingencyPlan/' + ids,
        method: 'delete',
        data: ids
    })
}
src/api/salesManagement/receiptPayment.js
@@ -40,7 +40,7 @@
// æŸ¥è¯¢å·²ç»ç»‘定发票的开票台账
export function bindInvoiceNoRegPage(query) {
    return request({
        url: '/receiptPayment/bindInvoiceNoRegPage',
        url: '/sales/product/listPageSalesLedger',
        method: 'get',
        params: query
    })
src/pages.json
@@ -711,6 +711,27 @@
      }
    },
    {
      "path": "pages/safeProduction/accidentReportingRecord/index",
      "style": {
        "navigationBarTitleText": "事故报告记录",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/accidentReportingRecord/detail",
      "style": {
        "navigationBarTitleText": "事故报告详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/accidentReportingRecord/view",
      "style": {
        "navigationBarTitleText": "事故报告详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index8",
      "style": {
        "navigationBarTitleText": "危险作业审批",
@@ -772,6 +793,27 @@
        "navigationBarTitleText": "危险物料详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/emergencyPlanReview/index",
      "style": {
        "navigationBarTitleText": "应急预案审核",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/emergencyPlanReview/detail",
      "style": {
        "navigationBarTitleText": "应急预案详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/emergencyPlanReview/view",
      "style": {
        "navigationBarTitleText": "应急预案详情",
        "navigationStyle": "custom"
      }
    }
  ],
  "subPackages": [
src/pages/cooperativeOffice/collaborativeApproval/index.vue
@@ -124,6 +124,7 @@
    </view>
    <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
    <view class="fab-button"
          v-if="props.approveType != 5 && props.approveType != 6 && props.approveType != 7"
          @click="handleAdd">
      <up-icon name="plus"
               size="24"
src/pages/index.vue
@@ -323,6 +323,14 @@
      icon: "/static/images/icon/guzhangfenxi@2x.png",
      label: "危险物料",
    },
    {
      icon: "/static/images/icon/guzhangfenxi@2x.png",
      label: "应急预案",
    },
    {
      icon: "/static/images/icon/guzhangfenxi@2x.png",
      label: "事故上报",
    },
  ]);
  // ååŒåŠžå…¬åŠŸèƒ½æ•°æ®
  const collaborationItems = reactive([
@@ -706,6 +714,16 @@
          url: "/pages/safeProduction/hazardousMaterialsControl/index",
        });
        break;
      case "应急预案":
        uni.navigateTo({
          url: "/pages/safeProduction/emergencyPlanReview/index",
        });
        break;
      case "事故上报":
        uni.navigateTo({
          url: "/pages/safeProduction/accidentReportingRecord/index",
        });
        break;
      default:
        uni.showToast({
          title: `点击了${item.label}`,
src/pages/procurementManagement/procurementLedger/detail.vue
@@ -190,7 +190,7 @@
                       size="small"
                       @click="addProduct"
                       class="add-btn"
                       v-if="operationType !== 'view'">
                       v-if="canEditProducts">
              æ–°å¢ž
            </up-button>
          </view>
@@ -208,7 +208,7 @@
            </view>
            <!-- æ“ä½œæŒ‰é’® -->
            <view class="product-actions"
                  v-if="operationType !== 'view'">
                  v-if="canEditProducts">
              <up-button type="error"
                         size="mini"
                         @click="removeProduct(idx)"
@@ -227,9 +227,11 @@
              <up-input v-model="product.productCategory"
                        readonly
                        placeholder="请选择"
                        :disabled="!canEditProducts"
                        @click="openCategoryPicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         v-if="canEditProducts"
                         @click="showCategoryPicker = true"></up-icon>
              </template>
            </up-form-item>
@@ -241,9 +243,11 @@
              <up-input v-model="product.specificationModel"
                        readonly
                        placeholder="请选择"
                        :disabled="!canEditProducts"
                        @click="openSpecificationPicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         v-if="canEditProducts"
                         @click="showSpecificationPicker = true"></up-icon>
              </template>
            </up-form-item>
@@ -253,6 +257,7 @@
                          required
                          :rules="productRules">
              <up-input v-model="product.unit"
                        :disabled="!canEditProducts"
                        placeholder="请输入" />
            </up-form-item>
            <!-- ç¨Žçއ -->
@@ -263,9 +268,11 @@
              <up-input v-model="product.taxRate"
                        readonly
                        placeholder="请选择"
                        :disabled="!canEditProducts"
                        @click="openTaxRatePicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         v-if="canEditProducts"
                         @click="showTaxRatePicker = true"></up-icon>
              </template>
            </up-form-item>
@@ -276,6 +283,7 @@
                          :rules="productRules">
              <up-input v-model="product.taxInclusiveUnitPrice"
                        type="number"
                        :disabled="!canEditProducts"
                        placeholder="请输入"
                        @blur="formatTaxPrice(idx)" />
            </up-form-item>
@@ -286,6 +294,7 @@
                          :rules="productRules">
              <up-input v-model="product.quantity"
                        type="number"
                        :disabled="!canEditProducts"
                        placeholder="请输入"
                        @blur="formatAmount(idx)" />
            </up-form-item>
@@ -296,6 +305,7 @@
                          :rules="productRules">
              <up-input v-model="product.taxInclusiveTotalPrice"
                        type="number"
                        :disabled="!canEditProducts"
                        placeholder="请输入"
                        @blur="formatTaxTotal(idx)" />
            </up-form-item>
@@ -306,6 +316,7 @@
                          :rules="productRules">
              <up-input v-model="product.taxExclusiveTotalPrice"
                        type="number"
                        :disabled="!canEditProducts"
                        placeholder="请输入"
                        @blur="formatNoTaxTotal(idx)" />
            </up-form-item>
@@ -317,9 +328,11 @@
              <up-input v-model="product.invoiceType"
                        readonly
                        placeholder="请选择"
                        :disabled="!canEditProducts"
                        @click="openInvoiceTypePicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         v-if="canEditProducts"
                         @click="showInvoiceTypePicker = true"></up-icon>
              </template>
            </up-form-item>
@@ -330,6 +343,7 @@
                          :rules="productRules">
              <up-input v-model="product.warnNum"
                        type="number"
                        :disabled="!canEditProducts"
                        placeholder="请输入" />
            </up-form-item>
            <up-form-item label="是否质检"
@@ -337,13 +351,16 @@
                          required
                          :rules="productRules">
              <u-radio-group v-model="product.isChecked"
                             :disabled="!canEditProducts"
                             placement="row"
                             @change="groupChange">
                <u-radio :customStyle="{marginRight: '40rpx'}"
                         label="是"
                         :disabled="!canEditProducts"
                         :name="true">
                </u-radio>
                <u-radio label="否"
                         :disabled="!canEditProducts"
                         :name="false">
                </u-radio>
              </u-radio-group>
@@ -352,7 +369,7 @@
        </view>
      </view>
      <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
      <FooterButtons :show="operationType !== 'view'"
      <FooterButtons :show="operationType !== 'view' && !isApprovalPassed"
                     cancelText="取消"
                     confirmText="保存"
                     @cancel="goBack"
@@ -375,6 +392,7 @@
    getSalesNo,
    approveProcessGetInfo,
  } from "@/api/procurementManagement/procurementLedger";
  import { delProduct } from "@/api/salesManagement/salesLedger";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import { userListNoPageByTenantId } from "@/api/system/user";
@@ -382,6 +400,14 @@
  const operationType = ref("");
  const editData = ref(null);
  const formRef = ref(null);
  // å®¡æ‰¹é€šè¿‡ï¼ˆapprovalStatus === 3)后,禁止编辑/删除产品
  const isApprovalPassed = computed(() => {
    const status = editData.value?.approvalStatus ?? form.value?.approvalStatus;
    return Number(status) === 3;
  });
  const canEditProducts = computed(() => {
    return operationType.value !== "view" && !isApprovalPassed.value;
  });
  const userStore = useUserStore();
  const form = ref({
@@ -413,12 +439,14 @@
    }));
  });
  // è®¡ç®—供应商选择列表
  // è®¡ç®—供应商选择列表(只保留 isWhite === 0 çš„供应商)
  const supplierActionList = computed(() => {
    return supplierList.value.map(item => ({
      name: item.text,
      value: item.value,
    }));
    return supplierList.value
      .filter(item => item.isWhite === 0)
      .map(item => ({
        name: item.text,
        value: item.value,
      }));
  });
  // é€‰æ‹©å™¨ç›¸å…³å˜é‡
@@ -528,6 +556,7 @@
  };
  const addProduct = () => {
    if (!canEditProducts.value) return;
    if (productData.value === null) {
      productData.value = [];
    }
@@ -574,26 +603,69 @@
  };
  const removeProduct = idx => {
    productData.value.splice(idx, 1);
    if (!canEditProducts.value) return;
    const row = productData.value[idx];
    // æ–°å¢žæ¨¡å¼æˆ–还未落库的产品,直接前端删除
    if (operationType.value === "add" || !row?.id) {
      productData.value.splice(idx, 1);
      return;
    }
    uni.showModal({
      title: "提示",
      content: "选中的产品将被删除,是否确认删除?",
      confirmText: "确认",
      cancelText: "取消",
      success: res => {
        if (!res.confirm) {
          uni.showToast({
            title: "已取消",
            icon: "none",
          });
          return;
        }
        const ids = [row.id];
        delProduct(ids).then(() => {
          uni.showToast({
            title: "删除成功",
            icon: "success",
          });
          const currentId = form.value.id || editData.value?.id;
          if (currentId) {
            getPurchaseById({ id: currentId, type: 2 }).then(res2 => {
              productData.value = res2.productData || [];
            });
          } else {
            // å›žé€€å¤„理:如果没有当前ID,则本地删除
            productData.value.splice(idx, 1);
          }
        });
      },
    });
  };
  // æ˜¾ç¤ºé€‰æ‹©å™¨
  const openCategoryPicker = idx => {
    if (!canEditProducts.value) return;
    currentProductIndex.value = idx;
    showCategoryPicker.value = true;
  };
  const openSpecificationPicker = idx => {
    if (!canEditProducts.value) return;
    currentProductIndex.value = idx;
    showSpecificationPicker.value = true;
  };
  const openTaxRatePicker = idx => {
    if (!canEditProducts.value) return;
    currentProductIndex.value = idx;
    showTaxRatePicker.value = true;
  };
  const openInvoiceTypePicker = idx => {
    if (!canEditProducts.value) return;
    currentProductIndex.value = idx;
    showInvoiceTypePicker.value = true;
  };
@@ -932,6 +1004,7 @@
      supplierList.value = res.data.map(item => ({
        text: item.supplierName,
        value: item.id,
        isWhite: item.isWhite,
      }));
    });
  };
src/pages/safeProduction/accidentReportingRecord/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,411 @@
<template>
  <view class="accident-detail">
    <PageHeader :title="isEdit ? '编辑事故报告' : '新增事故报告'"
                @back="goBack" />
    <u-form @submit="handleSubmit"
            ref="formRef"
            label-width="110">
      <!-- äº‹æ•…信息 -->
      <u-cell-group title="基本信息">
        <u-form-item label="事故名称"
                     prop="accidentName"
                     required
                     border-bottom>
          <u-input v-model="form.accidentName"
                   placeholder="请输入事故名称" />
        </u-form-item>
        <u-form-item label="事故编码"
                     prop="accidentCode"
                     required
                     border-bottom>
          <u-input v-model="form.accidentCode"
                   placeholder="请输入事故编码" />
        </u-form-item>
        <u-form-item label="事故类型"
                     prop="accidentType"
                     required
                     border-bottom>
          <u-input v-model="accidentTypeName"
                   placeholder="请选择事故类型"
                   @click="showTypeSheet"
                   readonly />
          <template #right>
            <up-icon name="arrow-right"
                     @click="showTypeSheet"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="事故等级"
                     prop="accidentGrade"
                     required
                     border-bottom>
          <u-input v-model="accidentGradeName"
                   placeholder="请选择事故等级"
                   @click="showGradeSheet"
                   readonly />
          <template #right>
            <up-icon name="arrow-right"
                     @click="showGradeSheet"></up-icon>
          </template>
        </u-form-item>
      </u-cell-group>
      <u-cell-group title="详细信息">
        <u-form-item label="发生时间"
                     prop="happenTime"
                     required
                     border-bottom>
          <u-input v-model="form.happenTime"
                   placeholder="请选择发生时间"
                   @click="showTimePicker"
                   readonly />
          <template #right>
            <up-icon name="arrow-right"
                     @click="showTimePicker"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="事故地点"
                     prop="happenLocation"
                     required
                     border-bottom>
          <u-input v-model="form.happenLocation"
                   placeholder="请输入事故地点" />
        </u-form-item>
        <u-form-item label="直接财产损失"
                     prop="assetLoss"
                     border-bottom>
          <u-input v-model="form.assetLoss"
                   placeholder="请输入直接财产损失"
                   type="number" />
          <template #right>
            <view class="unit">元</view>
          </template>
        </u-form-item>
        <u-form-item label="事故直接原因"
                     prop="accidentCause"
                     border-bottom>
          <u-textarea v-model="form.accidentCause"
                      placeholder="请输入事故直接原因"
                      :maxlength="200"
                      count
                      :autoHeight="true" />
        </u-form-item>
        <u-form-item label="事故根本原因"
                     prop="rootCause"
                     border-bottom>
          <u-textarea v-model="form.rootCause"
                      placeholder="请输入事故根本原因"
                      :maxlength="200"
                      count
                      :autoHeight="true" />
        </u-form-item>
        <u-form-item label="生产影响情况"
                     prop="productionLoss"
                     border-bottom>
          <u-textarea v-model="form.productionLoss"
                      placeholder="请输入生产影响情况"
                      :maxlength="200"
                      count
                      :autoHeight="true" />
        </u-form-item>
        <u-form-item label="现场应急处置措施"
                     prop="handleMeasures"
                     border-bottom>
          <u-textarea v-model="form.handleMeasures"
                      placeholder="请输入现场应急处置措施"
                      :maxlength="200"
                      count
                      :autoHeight="true" />
        </u-form-item>
        <u-form-item label="备注"
                     prop="remark"
                     border-bottom>
          <u-textarea v-model="form.remark"
                      placeholder="请输入备注"
                      :maxlength="200"
                      count
                      :autoHeight="true" />
        </u-form-item>
      </u-cell-group>
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="sign-btn"
                  type="primary"
                  @click="handleSubmit"
                  :loading="loading">{{ isEdit ? '保存修改' : '提交' }}</u-button>
      </view>
    </u-form>
    <!-- äº‹æ•…类型选择器 -->
    <up-action-sheet :show="typeSheetVisible"
                     :actions="typeOptions"
                     @select="handleTypeSelect"
                     @close="typeSheetVisible = false"
                     title="选择事故类型" />
    <!-- äº‹æ•…等级选择器 -->
    <up-action-sheet :show="gradeSheetVisible"
                     :actions="gradeOptions"
                     @select="handleGradeSelect"
                     @close="gradeSheetVisible = false"
                     title="选择事故等级" />
    <!-- æ—¶é—´é€‰æ‹©å™¨ -->
    <up-datetime-picker :show="timePickerVisible"
                        mode="datetime"
                        v-model="currentTime"
                        @confirm="handleTimeConfirm"
                        @cancel="timePickerVisible = false"
                        title="选择发生时间" />
  </view>
</template>
<script setup>
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "accident-detail" });
  const showToast = message => {
    uni.showToast({ title: message, icon: "none" });
  };
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    safeAccidentAdd,
    safeAccidentUpdate,
  } from "@/api/safeProduction/accidentReportingRecord";
  import { onLoad } from "@dcloudio/uni-app";
  import { useDict } from "@/utils/dict";
  import dayjs from "dayjs";
  // èŽ·å–å­—å…¸æ•°æ®
  const { accident_type } = useDict("accident_type");
  // è¡¨å•数据
  const form = ref({
    accidentName: "",
    accidentCode: "",
    accidentType: "",
    accidentGrade: "",
    happenTime: "",
    happenLocation: "",
    createUserName: "",
    createTime: "",
    assetLoss: "",
    accidentCause: "",
    rootCause: "",
    productionLoss: "",
    handleMeasures: "",
    remark: "",
  });
  // é¡µé¢çŠ¶æ€
  const loading = ref(false);
  const formRef = ref(null);
  const isEdit = ref(false);
  const currentTime = ref(new Date());
  // äº‹æ•…类型选择器
  const typeSheetVisible = ref(false);
  const accidentTypeName = ref("");
  const typeOptions = ref([]);
  const showTypeSheet = () => {
    typeSheetVisible.value = true;
  };
  const handleTypeSelect = item => {
    form.value.accidentType = item.value;
    accidentTypeName.value = item.name;
    typeSheetVisible.value = false;
  };
  // äº‹æ•…等级选择器
  const gradeSheetVisible = ref(false);
  const accidentGradeName = ref("");
  const gradeOptions = ref([
    { value: "轻微事故", name: "轻微事故" },
    { value: "一般事故", name: "一般事故" },
    { value: "较大事故", name: "较大事故" },
    { value: "重大事故", name: "重大事故" },
  ]);
  const showGradeSheet = () => {
    gradeSheetVisible.value = true;
  };
  const handleGradeSelect = item => {
    form.value.accidentGrade = item.value;
    accidentGradeName.value = item.name;
    gradeSheetVisible.value = false;
  };
  // æ—¶é—´é€‰æ‹©å™¨
  const timePickerVisible = ref(false);
  const showTimePicker = () => {
    timePickerVisible.value = true;
  };
  const handleTimeConfirm = e => {
    form.value.happenTime = dayjs(e.value).format("YYYY-MM-DD HH:mm:ss");
    currentTime.value = e.value;
    timePickerVisible.value = false;
  };
  // è¿”回上一页
  const goBack = () => {
    uni.removeStorageSync("accidentReport");
    uni.navigateBack();
  };
  // æäº¤è¡¨å•
  const handleSubmit = async () => {
    if (!form.value.accidentName) {
      showToast("请输入事故名称");
      return;
    }
    if (!form.value.accidentCode) {
      showToast("请输入事故编码");
      return;
    }
    if (!form.value.accidentType) {
      showToast("请选择事故类型");
      return;
    }
    if (!form.value.accidentGrade) {
      showToast("请选择事故等级");
      return;
    }
    if (!form.value.happenTime) {
      showToast("请选择发生时间");
      return;
    }
    if (!form.value.happenLocation) {
      showToast("请输入事故地点");
      return;
    }
    try {
      loading.value = true;
      // ä½¿ç”¨å®‰å…¨æµ…拷贝
      const source =
        form.value && typeof form.value === "object" ? form.value : {};
      const submitData = {};
      Object.keys(source).forEach(k => {
        submitData[k] = source[k];
      });
      if (isEdit.value) {
        const { code } = await safeAccidentUpdate(submitData);
        if (code === 200) {
          showToast("修改成功");
          setTimeout(() => {
            goBack();
          }, 500);
        } else {
          loading.value = false;
          showToast("修改失败,请重试");
        }
      } else {
        const { code } = await safeAccidentAdd(submitData);
        if (code === 200) {
          showToast("新增成功");
          setTimeout(() => {
            goBack();
          }, 500);
        } else {
          loading.value = false;
          showToast("新增失败,请重试");
        }
      }
    } catch (e) {
      loading.value = false;
      console.error("提交失败:", e);
      showToast("提交失败,请重试");
    }
  };
  onLoad(() => {
    // ç¼–辑事故时,从本地存储获取数据
    const accidentReport = uni.getStorageSync("accidentReport");
    if (accidentReport.id) {
      form.value = accidentReport;
      currentTime.value = dayjs(accidentReport.happenTime).toDate();
      isEdit.value = true;
    } else {
      isEdit.value = false;
    }
  });
  onMounted(() => {
    // åˆå§‹åŒ–事故类型选项
    typeOptions.value =
      accident_type?.value.map(item => ({
        value: item.value,
        name: item.label,
      })) || [];
    // è®¾ç½®å·²é€‰å€¼çš„æ˜¾ç¤ºæ–‡æœ¬
    if (form.value.accidentType) {
      const typeItem = typeOptions.value.find(
        item => String(item.value) === String(form.value.accidentType)
      );
      accidentTypeName.value = typeItem ? typeItem.name : "";
    }
    if (form.value.accidentGrade) {
      const gradeItem = gradeOptions.value.find(
        item => item.value === form.value.accidentGrade
      );
      accidentGradeName.value = gradeItem ? gradeItem.name : "";
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .accident-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #666;
    background: #f5f5f5;
    border: 1px solid #ddd;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem;
  }
  .sign-btn {
    font-weight: 500;
    font-size: 1rem;
    color: #fff;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem;
  }
</style>
src/pages/safeProduction/accidentReportingRecord/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,357 @@
<template>
  <view class="accident-report">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="事故上报记录"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入事故名称"
                    v-model="accidentName"
                    @change="searchChange"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- äº‹æ•…记录列表 -->
    <view class="ledger-list"
          v-if="accidentList.length > 0">
      <view v-for="(item, index) in accidentList"
            :key="index">
        <view class="ledger-item">
          <!-- <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">事故名称:{{ item.accidentName }}</text>
            </view>
          </view> -->
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-title">事故名称:{{ item.accidentName }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">事故编码</text>
              <text class="detail-value">{{ item.accidentCode || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">事故类型</text>
              <text class="detail-value">{{ accidentTypeLabel(item.accidentType) || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">发生时间</text>
              <text class="detail-value">{{ item.happenTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">事故地点</text>
              <text class="detail-value">{{ item.happenLocation || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">事故等级</text>
              <u-tag :type="accidentGradeType(item.accidentGrade)">
                {{ item.accidentGrade || '-' }}
              </u-tag>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button type="info"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item)">
              æŸ¥çœ‹è¯¦æƒ…
            </u-button>
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="editAccident(item)">
              ç¼–辑
            </u-button>
            <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="deleteAccident(item)">
              åˆ é™¤
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无事故记录</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button"
          @click="addAccident">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    safeAccidentListPage,
    safeAccidentDel,
  } from "@/api/safeProduction/accidentReportingRecord";
  import { useDict } from "@/utils/dict";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "accident-report-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  // åº”急预案类型选项
  const { accident_type } = useDict("accident_type");
  const emergencyPlanTypeOptions = computed(() => accident_type?.value || []);
  // æœç´¢å…³é”®è¯
  const accidentName = ref("");
  // äº‹æ•…记录数据
  const accidentList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const accidentGradeType = val => {
    switch (val) {
      case "轻微事故":
        return "info";
      case "一般事故":
        return "info";
      case "较大事故":
        return "warning";
      case "重大事故":
        return "error";
      default:
        return "info";
    }
  };
  const accidentGradeOptions = [
    {
      label: "轻微事故",
      value: "轻微事故",
    },
    {
      label: "一般事故",
      value: "一般事故",
    },
    {
      label: "较大事故",
      value: "较大事故",
    },
    {
      label: "重大事故",
      value: "重大事故",
    },
  ];
  // èŽ·å–äº‹æ•…ç±»åž‹æ ‡ç­¾
  const accidentTypeLabel = val => {
    const item = emergencyPlanTypeOptions.value.find(
      i => String(i.value) === String(val)
    );
    return item ? item.label : val;
  };
  const searchChange = val => {
    accidentName.value = val;
    getList();
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      accidentName: accidentName.value,
    };
    safeAccidentListPage(params)
      .then(res => {
        accidentList.value = res.records || res.data?.records || [];
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ–°å¢žäº‹æ•…记录
  const addAccident = () => {
    uni.setStorageSync("accidentReport", {});
    uni.navigateTo({
      url: "/pages/safeProduction/accidentReportingRecord/detail",
    });
  };
  // ç¼–辑事故记录
  const editAccident = item => {
    uni.setStorageSync("accidentReport", item);
    uni.navigateTo({
      url: "/pages/safeProduction/accidentReportingRecord/detail",
    });
  };
  // åˆ é™¤äº‹æ•…记录
  const deleteAccident = item => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除该事故记录吗?`,
      success: res => {
        if (res.confirm) {
          deleteAccidentRecord(item.id);
        }
      },
    });
  };
  // åˆ é™¤äº‹æ•…记录
  const deleteAccidentRecord = id => {
    showLoadingToast("删除中...");
    safeAccidentDel([id])
      .then(() => {
        closeToast();
        showToast("删除成功");
        getList();
      })
      .catch(() => {
        closeToast();
        showToast("删除失败");
      });
  };
  // æŸ¥çœ‹è¯¦æƒ…
  const viewDetail = item => {
    uni.setStorageSync("accidentReport", item);
    uni.navigateTo({
      url: "/pages/safeProduction/accidentReportingRecord/view",
    });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .accident-report {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 80px;
  }
  // ç‰¹å®šçš„图标样式
  .document-icon {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
  }
  // ç‰¹æœ‰æ ·å¼
  .visit-status {
    display: flex;
    align-items: center;
  }
  .detail-value {
    word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
    white-space: normal;
    line-height: 1.4;
  }
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .fab-button {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // ä¿æŒé¡µé¢ç‰¹æœ‰çš„阴影效果
  }
  .action-buttons {
    gap: 4px;
  }
  .action-buttons {
    padding: 0 0 10rpx 0;
  }
  // è¶…期未整改的隐患样式
  .overdue {
    border-left: 8rpx solid #ff4d4f;
    background-color: rgba(255, 77, 79, 0.02);
  }
  .overdue .item-header {
    position: relative;
    padding-left: 20rpx;
  }
  .overdue .item-header::after {
    content: "超期";
    position: absolute;
    top: 32rpx;
    right: 20rpx;
    font-size: 24rpx;
    font-weight: 500;
    color: #ff4d4f;
    background-color: rgba(255, 77, 79, 0.1);
    padding: 4rpx 16rpx;
    border-radius: 16rpx;
    border: 1rpx solid rgba(255, 77, 79, 0.3);
  }
  .overdue .detail-row:nth-child(7) .detail-value {
    color: #ff4d4f;
    font-weight: 500;
  }
  .overdue .detail-row {
    padding-left: 20rpx;
  }
</style>
src/pages/safeProduction/accidentReportingRecord/view.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,223 @@
<template>
  <view class="accident-view">
    <PageHeader title="事故报告详情"
                @back="goBack" />
    <view class="detail-container">
      <!-- äº‹æ•…基本信息 -->
      <view class="info-section">
        <view class="info-title">基本信息</view>
        <view class="info-content">
          <view class="info-row">
            <view class="info-label">事故名称:</view>
            <view class="info-value">{{ accidentInfo.accidentName || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">事故编码:</view>
            <view class="info-value">{{ accidentInfo.accidentCode || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">事故类型:</view>
            <view class="info-value">{{ accidentTypeLabel(accidentInfo.accidentType) || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">事故等级:</view>
            <view class="info-value">
              <u-tag :type="getAccidentLevelType(accidentInfo.accidentGrade)">
                {{ accidentInfo.accidentGrade || '-' }}
              </u-tag>
            </view>
          </view>
        </view>
      </view>
      <!-- äº‹æ•…详细信息 -->
      <view class="info-section">
        <view class="info-title">详细信息</view>
        <view class="info-content">
          <view class="info-row">
            <view class="info-label">发生时间:</view>
            <view class="info-value">{{ accidentInfo.happenTime || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">事故地点:</view>
            <view class="info-value">{{ accidentInfo.happenLocation || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">上报人:</view>
            <view class="info-value">{{ accidentInfo.createUserName || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">上报时间:</view>
            <view class="info-value">{{ accidentInfo.createTime || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">直接财产损失:</view>
            <view class="info-value">{{ accidentInfo.assetLoss || '-' }}<span v-if="accidentInfo.assetLoss">元</span></view>
          </view>
          <view class="info-row">
            <view class="info-label">上报时间:</view>
            <view class="info-value">{{ accidentInfo.createTime || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">事故直接原因:</view>
            <view class="info-value">{{ accidentInfo.accidentCause || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">事故根本原因:</view>
            <view class="info-value">{{ accidentInfo.rootCause || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">生产影响情况:</view>
            <view class="info-value">{{ accidentInfo.productionLoss || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">现场应急处置措施:</view>
            <view class="info-value">{{ accidentInfo.handleMeasures || '-' }}</view>
          </view>
          <view class="info-row">
            <view class="info-label">备注:</view>
            <view class="info-value">{{ accidentInfo.remark || '-' }}</view>
          </view>
        </view>
      </view>
      <!-- äº‹æ•…描述 -->
      <view class="info-section"
            v-if="accidentInfo.accidentDescription">
        <view class="info-title">事故描述</view>
        <view class="description-content">
          {{ accidentInfo.accidentDescription }}
        </view>
      </view>
      <!-- å¤„理措施 -->
      <view class="info-section"
            v-if="accidentInfo.handlingMeasures">
        <view class="info-title">处理措施</view>
        <view class="description-content">
          {{ accidentInfo.handlingMeasures }}
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { onLoad } from "@dcloudio/uni-app";
  import { useDict } from "@/utils/dict";
  // åº”急预案类型选项
  const { accident_type } = useDict("accident_type");
  const emergencyPlanTypeOptions = computed(() => accident_type?.value || []);
  // èŽ·å–äº‹æ•…ç±»åž‹æ ‡ç­¾
  const accidentTypeLabel = val => {
    const item = emergencyPlanTypeOptions.value.find(
      i => String(i.value) === String(val)
    );
    return item ? item.label : val;
  };
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "accident-view" });
  const showToast = message => {
    uni.showToast({ title: message, icon: "none" });
  };
  // äº‹æ•…信息
  const accidentInfo = ref({});
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // èŽ·å–äº‹æ•…ç­‰çº§æ ‡ç­¾ç±»åž‹
  const getAccidentLevelType = val => {
    switch (val) {
      case "轻微事故":
        return "info";
      case "一般事故":
        return "info";
      case "较大事故":
        return "warning";
      case "重大事故":
        return "error";
      default:
        return "info";
    }
  };
  onLoad(() => {
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–äº‹æ•…ä¿¡æ¯
    const accidentReport = uni.getStorageSync("accidentReport");
    if (accidentReport) {
      accidentInfo.value = accidentReport;
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .accident-view {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 2rem;
  }
  .detail-container {
    padding: 1rem;
  }
  .info-section {
    background: #fff;
    border-radius: 0.5rem;
    margin-bottom: 1rem;
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.05);
    overflow: hidden;
  }
  .info-title {
    padding: 1rem;
    font-size: 1rem;
    font-weight: 500;
    color: #303133;
    background: #f5f5f5;
    border-bottom: 1px solid #e4e7ed;
  }
  .info-content {
    padding: 1rem;
  }
  .info-row {
    display: flex;
    margin-bottom: 0.75rem;
    align-items: flex-start;
  }
  .info-row:last-child {
    margin-bottom: 0;
  }
  .info-label {
    width: 240rpx;
    font-size: 0.875rem;
    color: #606266;
  }
  .info-value {
    flex: 1;
    font-size: 0.875rem;
    color: #303133;
    word-break: break-all;
    white-space: normal;
    line-height: 1.4;
  }
  .description-content {
    padding: 1rem;
    font-size: 0.875rem;
    color: #303133;
    line-height: 1.5;
  }
</style>
src/pages/safeProduction/emergencyPlanReview/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,724 @@
<template>
  <view class="emergency-plan-detail">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader :title="isEdit ? '编辑应急预案' : '新增应急预案'"
                @back="goBack" />
    <!-- è¡¨å•区域 -->
    <u-form :model="form"
            label-width="110"
            :rules="rules"
            ref="formRef">
      <!-- åº”急预案编码 -->
      <u-form-item label="应急预案编码"
                   border-bottom
                   required
                   prop="planCode">
        <up-input v-model="form.planCode"
                  placeholder="请输入应急预案编码"
                  clearable />
      </u-form-item>
      <!-- åº”急预案名称 -->
      <u-form-item label="应急预案名称"
                   required
                   border-bottom
                   prop="planName">
        <up-input v-model="form.planName"
                  placeholder="请输入应急预案名称"
                  clearable />
      </u-form-item>
      <!-- å‘布生效时间 -->
      <u-form-item label="发布生效时间"
                   required
                   border-bottom
                   prop="publishTime">
        <up-input v-model="form.publishTime"
                  placeholder="请选择发布生效时间"
                  readonly
                  @click="showTime = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showTime = true"></up-icon>
        </template>
      </u-form-item>
      <!-- é¢„案类型 -->
      <u-form-item label="预案类型"
                   prop="planType"
                   required
                   border-bottom>
        <u-input v-model="emergencyPlanTypeLabel"
                 placeholder="请选择预案类型"
                 @click="showPlanTypeActionSheet = true"
                 readonly />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showPlanTypeActionSheet = true"></up-icon>
        </template>
      </u-form-item>
      <u-form-item label="核心责任人"
                   prop="coreResponsorUserId"
                   required
                   border-bottom>
        <u-input v-model="form.coreResponsorUserName"
                 placeholder="请选择核心责任人"
                 @click="showUserActionSheet = true"
                 readonly />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showUserActionSheet = true"></up-icon>
        </template>
      </u-form-item>
      <!-- å¤‡æ³¨ -->
      <u-form-item label="备注"
                   prop="remark">
        <up-input v-model="form.remark"
                  placeholder="请输入备注"
                  type="textarea"
                  rows="3"
                  clearable />
      </u-form-item>
      <!-- é€‚用范围 -->
      <u-form-item label="适用范围"
                   required
                   prop="applyScope">
        <view class="checkbox-group">
          <u-checkbox-group v-model="form.applyScope"
                            @change="handleApplyScopeChange">
            <u-checkbox shape="circle"
                        size="32rpx"
                        class="checkbox-item"
                        v-for="(item, index) in applyScopeOptions"
                        :key="index"
                        :label="item.label"
                        :name="item.value">
            </u-checkbox>
          </u-checkbox-group>
        </view>
      </u-form-item>
      <!-- åº”急处置步骤 -->
      <view class="exec-steps-container">
        <view class="steps-header">
          <text class="steps-title">处置步骤列表</text>
          <text class="steps-count">共 {{ execStepsList.length }} ä¸ªæ­¥éª¤</text>
        </view>
        <view class="steps-list">
          <view v-for="(step, index) in execStepsList"
                :key="index"
                class="exec-step-item">
            <view class="delete-btn"
                  @click="removeExecStep(index)">
              <u-icon name="close"
                      color="#fff"
                      size="16" />
            </view>
            <view class="step-number">
              {{ index + 1 }}
            </view>
            <view class="step-content">
              <view class="step-row">
                <text class="step-label">步骤名称:</text>
                <u-textarea v-model="step.step"
                            placeholder="请输入步骤名称"
                            clearable
                            border-bottom
                            class="step-input" />
              </view>
              <view class="step-row">
                <text class="step-label">处置措施:</text>
                <u-textarea v-model="step.description"
                            placeholder="请输入具体的应急处置措施"
                            type="textarea"
                            clearable
                            class="step-textarea" />
              </view>
            </view>
          </view>
        </view>
        <u-button type="primary"
                  @click="addExecStep"
                  class="add-step-btn">
          <text>添加步骤</text>
        </u-button>
      </view>
    </u-form>
    <!-- å‘布生效时间选择器 -->
    <up-datetime-picker :show="showTime"
                        v-model="currentTime"
                        @confirm="handleDateConfirm"
                        @cancel="showTime = false"
                        mode="date" />
    <!--预案类型选择器 -->
    <up-action-sheet :show="showPlanTypeActionSheet"
                     :actions="emergencyPlanTypeOptions"
                     @select="handlePlanTypeConfirm"
                     title="选择预案类型" />
    <!--核心责任人选择器 -->
    <up-action-sheet :show="showUserActionSheet"
                     :actions="userList"
                     @select="handleUserConfirm"
                     title="选择核心责任人" />
  </view>
  <!-- åº•部按钮 -->
  <view class="bottom-buttons">
    <u-button type="default"
              size="default"
              @click="goBack"
              class="bottom-btn">
      å–消
    </u-button>
    <u-button type="primary"
              size="default"
              @click="submitForm"
              class="bottom-btn">
      ä¿å­˜
    </u-button>
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    safeContingencyPlanAdd,
    safeContingencyPlanUpdate,
  } from "@/api/safeProduction/emergencyPlanReview";
  import { userListNoPage } from "@/api/system/user";
  import { useDict } from "@/utils/dict";
  import dayjs from "dayjs";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "emergency-plan-detail" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  // è¡¨å•引用
  const formRef = ref();
  // è¡¨å•数据
  const form = ref({
    id: "",
    planCode: "",
    planName: "",
    publishTime: "",
    planType: "",
    coreResponsorUserId: "",
    coreResponsorUserName: "",
    remark: "",
    applyScope: [],
    execSteps: "",
  });
  // åº”急处置步骤列表
  const execStepsList = ref([]);
  // æ—¥æœŸèŒƒå›´
  const minDate = new Date("2000-01-01");
  const maxDate = new Date("2030-12-31");
  const currentTime = ref(Date.now());
  // ç”¨æˆ·åˆ—表
  const userList = ref([]);
  // åº”急预案类型选项
  const { emergency_plan_type } = useDict("emergency_plan_type");
  const emergencyPlanTypeOptions = computed(() => {
    return (
      emergency_plan_type?.value.map(item => ({
        value: item.value,
        name: item.label,
      })) || []
    );
  });
  // åº”急预案类型标签
  const emergencyPlanTypeLabel = ref("");
  // é€‚用范围选项
  const applyScopeOptions = [
    { value: "all", label: "全体员工" },
    { value: "manager", label: "管理层" },
    { value: "hr", label: "人事部门" },
    { value: "finance", label: "财务部门" },
    { value: "tech", label: "技术部门" },
  ];
  // æ˜¯å¦ä¸ºç¼–辑模式
  const isEdit = ref(false);
  // ActionSheet æ˜¾ç¤ºçŠ¶æ€
  const showPlanTypeActionSheet = ref(false);
  const showUserActionSheet = ref(false);
  const showTime = ref(false);
  // åˆå§‹åŒ–数据
  const initData = () => {
    const emergencyPlan = uni.getStorageSync("emergencyPlan") || {};
    if (emergencyPlan.id) {
      // ç¼–辑模式
      isEdit.value = true;
      form.value = {
        id: emergencyPlan.id,
        planCode: emergencyPlan.planCode || "",
        planName: emergencyPlan.planName || "",
        publishTime: emergencyPlan.publishTime || "",
        planType: emergencyPlan.planType || "",
        coreResponsorUserId: emergencyPlan.coreResponsorUserId || "",
        coreResponsorUserName: emergencyPlan.coreResponsorUserName || "",
        remark: emergencyPlan.remark || "",
        applyScope: emergencyPlan.applyScope
          ? emergencyPlan.applyScope.split(",")
          : [],
        execSteps: emergencyPlan.execSteps || "",
      };
      currentTime.value = new Date(emergencyPlan.publishTime).getTime();
      // è®¾ç½®é¢„案类型标签
      const planTypeItem = emergencyPlanTypeOptions.value.find(
        item => item.value === emergencyPlan.planType
      );
      emergencyPlanTypeLabel.value = planTypeItem ? planTypeItem.name : "";
      console.log(form.value.applyScope, form.value.applyScope);
      // åˆå§‹åŒ–应急处置步骤
      initExecSteps(emergencyPlan.execSteps);
    } else {
      // æ–°å¢žæ¨¡å¼
      isEdit.value = false;
      form.value = {
        planCode: "",
        planName: "",
        publishTime: new Date().toISOString().split("T")[0],
        planType: "",
        coreResponsorUserId: "",
        coreResponsorUserName: "",
        remark: "",
        applyScope: [],
        execSteps: "",
      };
      emergencyPlanTypeLabel.value = "";
      execStepsList.value = [];
      addExecStep();
    }
  };
  const handleApplyScopeChange = e => {
    // form.value.applyScope = e;
    console.log(e, "e");
    console.log(form.value.applyScope, "form.value.applyScope");
  };
  // åˆå§‹åŒ–应急处置步骤
  const initExecSteps = execSteps => {
    if (execSteps) {
      try {
        execStepsList.value = JSON.parse(execSteps);
      } catch (e) {
        execStepsList.value = [];
      }
    } else {
      execStepsList.value = [];
    }
    if (execStepsList.value.length === 0) {
      addExecStep();
    }
  };
  // æ·»åŠ åº”æ€¥å¤„ç½®æ­¥éª¤
  const addExecStep = () => {
    const stepNumber = execStepsList.value.length + 1;
    execStepsList.value.push({
      step: `步骤${stepNumber}`,
      description: "",
    });
  };
  // åˆ é™¤åº”急处置步骤
  const removeExecStep = index => {
    if (execStepsList.value.length > 1) {
      execStepsList.value.splice(index, 1);
    } else {
      showToast("至少保留一个步骤");
    }
  };
  // èŽ·å–ç”¨æˆ·åˆ—è¡¨
  const getUserList = () => {
    userListNoPage()
      .then(res => {
        userList.value = res.data.map(item => ({
          value: item.userId,
          name: item.nickName,
        }));
      })
      .catch(() => {
        showToast("获取用户列表失败");
      });
  };
  // æ—¥æœŸé€‰æ‹©ç¡®è®¤
  const handleDateConfirm = e => {
    form.value.publishTime = dayjs(e.value).format("YYYY-MM-DD");
    showTime.value = false;
  };
  // é¢„案类型选择确认
  const handlePlanTypeConfirm = e => {
    form.value.planType = e.value;
    const selectedType = emergencyPlanTypeOptions.value.find(
      item => item.value === e.value
    );
    if (selectedType) {
      emergencyPlanTypeLabel.value = selectedType.name;
    }
    showPlanTypeActionSheet.value = false;
  };
  // ç”¨æˆ·é€‰æ‹©ç¡®è®¤
  const handleUserConfirm = e => {
    form.value.coreResponsorUserId = e.value;
    const selectedUser = userList.value.find(user => user.value === e.value);
    if (selectedUser) {
      form.value.coreResponsorUserName = selectedUser.name;
    }
    showUserActionSheet.value = false;
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // è¡¨å•验证规则
  const rules = {
    planCode: [
      {
        required: true,
        message: "请输入应急预案编码",
        trigger: ["submit", "blur"],
      },
    ],
    planName: [
      {
        required: true,
        message: "请输入应急预案名称",
        trigger: ["submit", "blur"],
      },
    ],
    publishTime: [
      {
        required: true,
        message: "请选择发布生效时间",
        trigger: ["submit", "change"],
      },
    ],
    planType: [
      {
        required: true,
        message: "请选择预案类型",
        trigger: ["submit", "change"],
      },
    ],
    coreResponsorUserId: [
      {
        required: true,
        message: "请选择核心责任人",
        trigger: ["submit", "change"],
      },
    ],
    applyScope: [
      {
        required: true,
        message: "请选择适用范围",
        trigger: ["submit", "change"],
      },
    ],
  };
  // æäº¤è¡¨å•
  const submitForm = async () => {
    // éªŒè¯è¡¨å•必填项
    if (!formRef.value) return;
    const valid = await formRef.value.validate();
    if (!valid) {
      return;
    }
    // éªŒè¯åº”急处置步骤
    for (let i = 0; i < execStepsList.value.length; i++) {
      const step = execStepsList.value[i];
      if (!step.step || !step.step.trim()) {
        showToast(`第${i + 1}条步骤的"步骤"不能为空`);
        return;
      }
      if (!step.description || !step.description.trim()) {
        showToast(`第${i + 1}条步骤的"措施"不能为空`);
        return;
      }
    }
    // å°†åº”急处置步骤转换为JSON字符串
    form.value.execSteps = JSON.stringify(execStepsList.value);
    // å¤„理适用范围
    form.value.applyScope = form.value.applyScope.join(",");
    showLoadingToast("保存中...");
    try {
      if (isEdit.value) {
        // ç¼–辑模式
        const res = await safeContingencyPlanUpdate(form.value);
        if (res.code === 200) {
          showToast("更新成功");
          setTimeout(() => {
            uni.navigateBack();
          }, 1000);
        } else {
          showToast(res.msg || "更新失败");
        }
      } else {
        // æ–°å¢žæ¨¡å¼
        const res = await safeContingencyPlanAdd(form.value);
        if (res.code === 200) {
          showToast("添加成功");
          setTimeout(() => {
            uni.navigateBack();
          }, 1000);
        } else {
          showToast(res.msg || "添加失败");
        }
      }
    } catch (error) {
      console.error("提交失败:", error);
      showToast("提交失败,请重试");
    } finally {
      closeToast();
    }
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  onMounted(() => {
    initData();
    getUserList();
  });
  onShow(() => {
    initData();
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .emergency-plan-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 100px;
  }
  .form-section {
  }
  .checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
  }
  .checkbox-item {
    margin-right: 16px;
  }
  .select-container {
    position: relative;
    width: 100%;
  }
  .select-container .up-input {
    width: 100%;
    border: 1px solid #e4e7ed;
    border-radius: 8px;
    padding: 12px 16px;
    background-color: #ffffff;
  }
  .exec-steps-container {
    padding: 20px;
    background-color: #fff;
  }
  .steps-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    padding-bottom: 12px;
    border-bottom: 1px solid #e4e7ed;
  }
  .steps-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
  }
  .steps-count {
    font-size: 14px;
    color: #909399;
  }
  .steps-list {
    margin-bottom: 20px;
  }
  .exec-step-item {
    position: relative;
    display: flex;
    margin-bottom: 16px;
    padding: 16px;
    background-color: #ffffff;
    border: 1px solid #e4e7ed;
    border-radius: 8px;
    transition: all 0.3s ease;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
  }
  .exec-step-item:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
    border-color: #409eff;
    transform: translateY(-1px);
  }
  .delete-btn {
    position: absolute;
    top: -25rpx;
    right: -25rpx;
    width: 50rpx;
    height: 50rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    font-size: 20px;
    border-radius: 50%;
    background-color: red;
    border: none;
    z-index: 10;
  }
  .delete-btn:hover {
    transform: scale(1.1);
    box-shadow: 0 3px 6px rgba(245, 108, 108, 0.4);
  }
  .step-number {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    margin-right: 16px;
    background-color: #ecf5ff;
    color: #409eff;
    font-size: 14px;
    font-weight: 600;
    border-radius: 50%;
    flex-shrink: 0;
  }
  .step-content {
    flex: 1;
    min-width: 0;
  }
  .step-row {
    display: flex;
    align-items: flex-start;
    margin-bottom: 12px;
  }
  .step-row:last-child {
    margin-bottom: 0;
  }
  .step-label {
    display: inline-block;
    width: 80px;
    font-size: 14px;
    color: #606266;
    margin-right: 12px;
    flex-shrink: 0;
    line-height: 36px;
  }
  .step-input {
    flex: 1;
    min-width: 0;
  }
  .step-input input {
    font-size: 14px;
    color: #303133;
  }
  .step-textarea {
    flex: 1;
    min-width: 0;
  }
  .step-textarea textarea {
    font-size: 14px;
    color: #303133;
    min-height: 80px;
    line-height: 1.5;
  }
  .add-step-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 44px;
    line-height: 44px;
    font-size: 14px;
    border-radius: 8px;
    transition: all 0.3s ease;
    gap: 8px;
  }
  .add-step-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
  }
  .add-step-btn text {
    font-size: 14px;
  }
  .bottom-buttons {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    padding: 16px 20px;
    background: #ffffff;
    border-top: 1px solid #f0f0f0;
    gap: 16px;
  }
  .bottom-btn {
    flex: 1;
  }
</style>
src/pages/safeProduction/emergencyPlanReview/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,373 @@
<template>
  <view class="emergency-plan-review">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="应急预案审核"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入应急预案名称"
                    v-model="searchForm.planName"
                    @change="searchChange"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- åº”急预案列表 -->
    <view class="ledger-list"
          v-if="planList.length > 0">
      <view v-for="(item, index) in planList"
            :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-title">{{ item.planName }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details"
                @click="viewDetail(item)">
            <view class="detail-row">
              <text class="detail-label">预案编码</text>
              <text class="detail-value">{{ item.planCode || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">预案类型</text>
              <u-tag :type="getPlanTypeTagType(item.planType)">
                {{ emergencyPlanTypeLabel(item.planType) }}
              </u-tag>
            </view>
            <view class="detail-row">
              <text class="detail-label">发布生效时间</text>
              <text class="detail-value">{{ item.publishTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">核心责任人</text>
              <text class="detail-value">{{ item.coreResponsorUserName || '-' }}</text>
            </view>
            <view class="detail-row"
                  v-if="item.remark">
              <text class="detail-label">备注</text>
              <text class="detail-value">{{ item.remark }}</text>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="editPlan(item)">
              ç¼–辑
            </u-button>
            <u-button type="info"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item)">
              æŸ¥çœ‹è¯¦æƒ…
            </u-button>
            <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="deletePlan(item)">
              åˆ é™¤
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无应急预案</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button"
          @click="addPlan">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    safeContingencyPlanListPage,
    safeContingencyPlanDel,
  } from "@/api/safeProduction/emergencyPlanReview";
  import { useDict } from "@/utils/dict";
  import useUserStore from "@/store/modules/user";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "emergency-plan-review-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  const userStore = useUserStore();
  // æœç´¢è¡¨å•
  const searchForm = ref({
    planName: "",
  });
  // åº”急预案数据
  const planList = ref([]);
  // åº”急预案类型选项
  const { emergency_plan_type } = useDict("emergency_plan_type");
  const emergencyPlanTypeOptions = computed(
    () => emergency_plan_type?.value || []
  );
  // èŽ·å–é¢„æ¡ˆç±»åž‹æ ‡ç­¾ç±»åž‹
  const getPlanTypeTagType = planType => {
    const typeMap = {
      emergency: "warning",
      fire: "danger",
      natural: "info",
      accident: "error",
    };
    return typeMap[planType] || "info";
  };
  // èŽ·å–é¢„æ¡ˆç±»åž‹æ ‡ç­¾æ–‡æœ¬
  const emergencyPlanTypeLabel = val => {
    const item = emergencyPlanTypeOptions.value.find(
      i => String(i.value) === String(val)
    );
    return item ? item.label : val;
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const searchChange = val => {
    searchForm.value.planName = val;
    getList();
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      ...searchForm.value,
    };
    safeContingencyPlanListPage(params)
      .then(res => {
        planList.value = res.records || res.data?.records || [];
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ–°å¢žåº”急预案
  const addPlan = () => {
    uni.setStorageSync("emergencyPlan", {});
    uni.navigateTo({
      url: "/pages/safeProduction/emergencyPlanReview/detail",
    });
  };
  // ç¼–辑应急预案
  const editPlan = item => {
    uni.setStorageSync("emergencyPlan", item);
    uni.navigateTo({
      url: "/pages/safeProduction/emergencyPlanReview/detail",
    });
  };
  // åˆ é™¤åº”急预案
  const deletePlan = item => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除该应急预案吗?`,
      success: res => {
        if (res.confirm) {
          deleteEmergencyPlan(item.id);
        }
      },
    });
  };
  // åˆ é™¤åº”急预案记录
  const deleteEmergencyPlan = id => {
    showLoadingToast("删除中...");
    safeContingencyPlanDel([id])
      .then(() => {
        closeToast();
        showToast("删除成功");
        getList();
      })
      .catch(() => {
        closeToast();
        showToast("删除失败");
      });
  };
  // æŸ¥çœ‹è¯¦æƒ…
  const viewDetail = item => {
    uni.setStorageSync("emergencyPlan", item);
    uni.navigateTo({
      url: "/pages/safeProduction/emergencyPlanReview/view",
    });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .emergency-plan-review {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
  }
  // ç‰¹å®šçš„图标样式
  .document-icon {
    background: #2979ff; // ä¸Žé”€å”®æ¨¡å—保持一致的背景色
  }
  // ç‰¹æœ‰æ ·å¼
  .detail-value {
    word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
  }
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .fab-button {
    background: #2979ff; // ä¸Žé”€å”®æ¨¡å—保持一致的背景色
    box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3); // ä¸Žé”€å”®æ¨¡å—保持一致的阴影效果
  }
  // æ“ä½œæŒ‰é’®æ ·å¼
  .action-buttons {
    display: flex;
    gap: 12px;
    padding: 0 0 16px 0;
    justify-content: space-between;
  }
  .action-btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
  }
  // åˆ—表容器样式
  .plan-list {
    padding: 20px;
  }
  // åˆ—表项样式
  .plan-item {
    background: #ffffff;
    border-radius: 12px;
    margin-bottom: 16px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
    padding: 0 16px;
    &:active {
      transform: scale(0.98);
      box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
    }
  }
  // é¡¹ç›®å¤´éƒ¨æ ·å¼
  .item-header {
    padding: 16px 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .item-left {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .item-title {
    font-size: 14px;
    color: #333;
    font-weight: 500;
  }
  // è¯¦æƒ…区域样式
  .item-details {
    padding: 16px 0;
  }
  .detail-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    margin-bottom: 8px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .detail-label {
    font-size: 12px;
    color: #777777;
    min-width: 60px;
  }
  .detail-value {
    font-size: 12px;
    color: #000000;
    text-align: right;
    flex: 1;
    margin-left: 16px;
  }
</style>
src/pages/safeProduction/emergencyPlanReview/view.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,372 @@
<template>
  <view class="emergency-plan-view">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="应急预案详情"
                @back="goBack" />
    <!-- è¯¦æƒ…内容 -->
    <view class="detail-content"
          v-if="currentPlan">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <view class="info-section">
        <!-- <view class="section-title">
          <text class="title-text">{{ currentPlan.planName }}</text>
        </view> -->
        <view class="info-grid">
          <view class="info-item">
            <text class="info-label">应急预案编码</text>
            <text class="info-value">{{ currentPlan.planCode || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">应急预案名称</text>
            <text class="info-value">{{ currentPlan.planName || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">预案类型</text>
            <u-tag :type="getPlanTypeTagType(currentPlan.planType)">
              {{ emergencyPlanTypeLabel(currentPlan.planType) }}
            </u-tag>
          </view>
          <view class="info-item">
            <text class="info-label">发布生效时间</text>
            <text class="info-value">{{ currentPlan.publishTime || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">核心责任人</text>
            <text class="info-value">{{ currentPlan.coreResponsorUserName || '-' }}</text>
          </view>
          <view class="info-item">
            <text class="info-label">备注</text>
            <text class="info-value">{{ currentPlan.remark || '-' }}</text>
          </view>
        </view>
      </view>
      <!-- é€‚用范围 -->
      <view class="scope-section">
        <view class="section-header">
          <text class="section-heading">适用范围</text>
        </view>
        <view class="scope-tags">
          <u-tag v-for="(scope, index) in applyScopeList"
                 :key="index"
                 type="primary"
                 size="small"
                 class="scope-tag">
            {{ scope }}
          </u-tag>
        </view>
      </view>
      <!-- åº”急处置步骤 -->
      <view class="steps-section">
        <view class="section-header">
          <text class="section-heading">应急处置步骤</text>
        </view>
        <view class="steps-list"
              v-if="execStepsList.length > 0">
          <view v-for="(step, index) in execStepsList"
                :key="index"
                class="step-item">
            <view class="step-number">{{ index + 1 }}</view>
            <view class="step-content">
              <text class="step-title">{{ step.step }}</text>
              <text class="step-description">{{ step.description }}</text>
            </view>
          </view>
        </view>
        <view class="no-steps"
              v-else>
          <text>暂无应急处置步骤</text>
        </view>
      </view>
    </view>
    <!-- ç©ºçŠ¶æ€ -->
    <view class="empty-state"
          v-else>
      <text>暂无应急预案信息</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { useDict } from "@/utils/dict";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "emergency-plan-view" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  // å½“前应急预案
  const currentPlan = ref(null);
  // åº”急处置步骤列表
  const execStepsList = ref([]);
  // é€‚用范围列表
  const applyScopeList = ref([]);
  // åº”急预案类型选项
  const { emergency_plan_type } = useDict("emergency_plan_type");
  const emergencyPlanTypeOptions = computed(
    () => emergency_plan_type?.value || []
  );
  // èŽ·å–é¢„æ¡ˆç±»åž‹æ ‡ç­¾ç±»åž‹
  const getPlanTypeTagType = planType => {
    const typeMap = {
      emergency: "warning",
      fire: "danger",
      natural: "info",
      accident: "error",
    };
    return typeMap[planType] || "info";
  };
  // èŽ·å–é¢„æ¡ˆç±»åž‹æ ‡ç­¾æ–‡æœ¬
  const emergencyPlanTypeLabel = val => {
    const item = emergencyPlanTypeOptions.value.find(
      i => String(i.value) === String(val)
    );
    return item ? item.label : val;
  };
  // åˆå§‹åŒ–数据
  const initData = () => {
    const emergencyPlan = uni.getStorageSync("emergencyPlan") || {};
    if (emergencyPlan.id) {
      currentPlan.value = emergencyPlan;
      // å¤„理适用范围
      if (emergencyPlan.applyScope) {
        const scopes = emergencyPlan.applyScope.split(",");
        applyScopeList.value = scopes.map(scope => {
          const scopeMap = {
            all: "全体员工",
            manager: "管理层",
            hr: "人事部门",
            finance: "财务部门",
            tech: "技术部门",
          };
          return scopeMap[scope] || scope;
        });
      } else {
        applyScopeList.value = [];
      }
      // å¤„理应急处置步骤
      if (emergencyPlan.execSteps) {
        try {
          execStepsList.value = JSON.parse(emergencyPlan.execSteps);
        } catch (e) {
          execStepsList.value = [];
        }
      } else {
        execStepsList.value = [];
      }
    } else {
      currentPlan.value = null;
      applyScopeList.value = [];
      execStepsList.value = [];
      showToast("未找到应急预案信息");
    }
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  onMounted(() => {
    initData();
  });
  onShow(() => {
    initData();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .emergency-plan-view {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 32px;
  }
  .detail-content {
    padding: 20px;
  }
  // ä¿¡æ¯ section
  .info-section {
    background: #ffffff;
    border-radius: 12px;
    padding: 24px;
    margin-bottom: 24px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  .section-title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 24px;
  }
  .title-text {
    font-size: 18px;
    font-weight: 600;
    color: #303133;
    flex: 1;
  }
  .type-tag {
    margin-left: 16px;
  }
  .info-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
  }
  .info-item {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .info-label {
    font-size: 14px;
    color: #909399;
  }
  .info-value {
    font-size: 14px;
    color: #303133;
    word-break: break-all;
  }
  // é€‚用范围 section
  .scope-section {
    background: #ffffff;
    border-radius: 12px;
    padding: 24px;
    margin-bottom: 24px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  .section-header {
    margin-bottom: 16px;
  }
  .section-heading {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    border-left: 4px solid #2979ff;
    padding-left: 16px;
  }
  .scope-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
  }
  .scope-tag {
    margin-bottom: 8px;
  }
  // åº”急处置步骤 section
  .steps-section {
    background: #ffffff;
    border-radius: 12px;
    padding: 24px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  .steps-list {
    margin-top: 16px;
  }
  .step-item {
    display: flex;
    margin-bottom: 24px;
    position: relative;
  }
  .step-item::before {
    content: "";
    position: absolute;
    left: 15px;
    top: 40px;
    bottom: -24px;
    width: 2px;
    background-color: #eaeaea;
  }
  .step-item:last-child::before {
    display: none;
  }
  .step-number {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background-color: #2979ff;
    color: #ffffff;
    font-size: 14px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 16px;
    flex-shrink: 0;
    margin-top: 4px;
  }
  .step-content {
    flex: 1;
  }
  .step-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    display: block;
    margin-bottom: 8px;
  }
  .step-description {
    font-size: 14px;
    color: #606266;
    line-height: 1.5;
    word-break: break-all;
  }
  .no-steps {
    padding: 40px;
    text-align: center;
    color: #909399;
    font-size: 14px;
    background-color: #f8f9fa;
    border-radius: 8px;
  }
  // ç©ºçŠ¶æ€
  .empty-state {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 60vh;
    font-size: 16px;
    color: #909399;
  }
</style>
src/pages/sales/invoiceLedger/index.vue
@@ -91,7 +91,6 @@
            <up-button type="primary"
                       size="small"
                       class="action-btn"
                       :disabled="item.invoicePerson !== userStore.nickName"
                       @click="openEdit(item)">
              ç¼–辑
            </up-button>
@@ -99,7 +98,6 @@
                       size="small"
                       plain
                       class="action-btn"
                       :disabled="item.invoicePerson !== userStore.nickName"
                       @click="handleDelete(item)">
              åˆ é™¤
            </up-button>
src/pages/sales/invoicingRegistration/add.vue
@@ -386,10 +386,13 @@
      return
    }
    const submitData = {
      ...form.value,
      productList: productData.value
    }
    // åŽç«¯è¦æ±‚以数组形式提交:保持原来的对象结构不变,只在最外层包一层数组
    const submitData = [
      {
        ...form.value,
        productList: productData.value
      }
    ]
    await invoiceRegistrationSave(submitData)
    showToast('提交成功')
src/pages/sales/receiptPayment/add.vue
@@ -1,27 +1,29 @@
<template>
  <view class="account-detail">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="新增回款" @back="onClickLeft" />
    <PageHeader title="新增回款"
                @back="onClickLeft" />
    <!-- è¡¨å•内容 -->
    <u-form @submit="onSubmit" ref="formRef" label-width="110" input-align="right" error-message-align="right">
    <u-form @submit="onSubmit"
            ref="formRef"
            label-width="110"
            input-align="right"
            error-message-align="right">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <u-cell-group title="基本信息">
        <u-form-item label="销售合同号" border-bottom>
          <u-input
            v-model="form.salesContractNo"
            placeholder="自动填充"
            readonly
          />
        <u-form-item label="销售合同号"
                     border-bottom>
          <u-input v-model="form.salesContractNo"
                   placeholder="自动填充"
                   readonly />
        </u-form-item>
        <u-form-item label="客户名称" border-bottom>
          <u-input
            v-model="form.customerName"
            placeholder="自动填充"
            readonly
          />
        <u-form-item label="客户名称"
                     border-bottom>
          <u-input v-model="form.customerName"
                   placeholder="自动填充"
                   readonly />
        </u-form-item>
        <u-form-item label="发票号" border-bottom>
        <!-- <u-form-item label="发票号" border-bottom>
          <u-input
            v-model="form.invoiceNo"
            placeholder="自动填充"
@@ -34,255 +36,270 @@
            placeholder="自动填充"
            readonly
          />
        </u-form-item> -->
        <u-form-item label="税率"
                     border-bottom>
          <u-input v-model="form.taxRate"
                   placeholder="自动填充"
                   readonly />
        </u-form-item>
        <u-form-item label="税率" border-bottom>
          <u-input
            v-model="form.taxRate"
            placeholder="自动填充"
            readonly
          />
        <view class="tip-text">待回款金额:{{ form.pendingInvoiceTotal }} å…ƒ</view>
        <u-form-item label="本次回款金额"
                     prop="receiptPaymentAmount"
                     required
                     border-bottom>
          <u-input v-model="form.receiptPaymentAmount"
                   type="number"
                   placeholder="请输入"
                   @blur="changeNum"
                   clearable />
        </u-form-item>
        <view class="tip-text">待回款金额:{{ currentNoReceiptAmount }} å…ƒ</view>
        <u-form-item label="本次回款金额" prop="receiptPaymentAmount" required border-bottom>
          <u-input
            v-model="form.receiptPaymentAmount"
            type="number"
            placeholder="请输入"
            @blur="changeNum"
            clearable
          />
        </u-form-item>
        <u-form-item label="回款形式" prop="receiptPaymentTypeName" required border-bottom>
          <u-input
            v-model="form.receiptPaymentTypeName"
            placeholder="请选择"
            readonly
            @click="showPaymentTypePicker"
          />
        <u-form-item label="回款形式"
                     prop="receiptPaymentTypeName"
                     required
                     border-bottom>
          <u-input v-model="form.receiptPaymentTypeName"
                   placeholder="请选择"
                   readonly
                   @click="showPaymentTypePicker" />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPaymentTypePicker"
                    ></up-icon>
                </template>
            <up-icon name="arrow-right"
                     @click="showPaymentTypePicker"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="来款日期" prop="receiptPaymentDate" required border-bottom>
          <u-input
            v-model="form.receiptPaymentDate"
            placeholder="请选择"
            readonly
            @click="showDatePicker"
          />
        <u-form-item label="来款日期"
                     prop="receiptPaymentDate"
                     required
                     border-bottom>
          <u-input v-model="form.receiptPaymentDate"
                   placeholder="请选择"
                   readonly
                   @click="showDatePicker" />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showDatePicker"
                    ></up-icon>
                </template>
            <up-icon name="arrow-right"
                     @click="showDatePicker"></up-icon>
          </template>
        </u-form-item>
        <u-form-item label="登记人" border-bottom>
          <u-input
            v-model="form.registrant"
            placeholder="自动填充"
            readonly
          />
        <u-form-item label="登记人"
                     border-bottom>
          <u-input v-model="form.registrant"
                   placeholder="自动填充"
                   readonly />
        </u-form-item>
      </u-cell-group>
      <!-- æäº¤æŒ‰é’® -->
      <FooterButtons
        cancelText="取消"
        confirmText="保存"
        :loading="loading"
        @cancel="onClickLeft"
        @confirm="onSubmit"
      />
      <FooterButtons cancelText="取消"
                     confirmText="保存"
                     :loading="loading"
                     @cancel="onClickLeft"
                     @confirm="onSubmit" />
    </u-form>
    <!-- å›žæ¬¾æ–¹å¼é€‰æ‹©å™¨ -->
    <up-action-sheet
      :show="showPaymentType"
      :actions="receiptPaymentType"
      title="选择回款形式"
      @select="onPaymentTypeConfirm"
      @close="showPaymentType = false"
    />
    <up-action-sheet :show="showPaymentType"
                     :actions="receiptPaymentType"
                     title="选择回款形式"
                     @select="onPaymentTypeConfirm"
                     @close="showPaymentType = false" />
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <up-popup :show="showDate" mode="bottom" @close="showDate = false">
      <up-datetime-picker
        :show="true"
        v-model="currentDate"
        @confirm="onDateConfirm"
        @cancel="showDate = false"
        mode="date"
      />
    <up-popup :show="showDate"
              mode="bottom"
              @close="showDate = false">
      <up-datetime-picker :show="true"
                          v-model="currentDate"
                          @confirm="onDateConfirm"
                          @cancel="showDate = false"
                          mode="date" />
    </up-popup>
  </view>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import FooterButtons from '@/components/FooterButtons.vue'
import { receiptPaymentSaveOrUpdate, invoiceInfo } from '@/api/salesManagement/receiptPayment'
import useUserStore from '@/store/modules/user'
import { useDict } from '@/utils/dict'
import { formatDateToYMD } from '@/utils/ruoyi'
  import { ref, onMounted, computed } from "vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import {
    receiptPaymentSaveOrUpdate,
    invoiceInfo,
  } from "@/api/salesManagement/receiptPayment";
  import useUserStore from "@/store/modules/user";
  import { useDict } from "@/utils/dict";
  import { formatDateToYMD } from "@/utils/ruoyi";
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
  // æ˜¾ç¤ºæç¤ºä¿¡æ¯
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
  uni.showLoading({
    title: message,
    mask: true
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­åŠ è½½æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  const userStore = useUserStore();
  // è¡¨å•引用
  const formRef = ref();
  // å“åº”式数据
  const loading = ref(false);
  const showPaymentType = ref(false);
  const pickerValue = ref([]);
  const showDate = ref(false);
  const currentDate = ref([
    new Date().getFullYear(),
    new Date().getMonth() + 1,
    new Date().getDate(),
  ]);
  // è¡¨å•数据
  const form = ref({
    salesContractNo: "",
    customerName: "",
    invoiceNo: "",
    invoiceTotal: "",
    taxRate: "",
    receiptPaymentAmount: "",
    receiptPaymentType: "",
    receiptPaymentTypeName: "",
    registrant: "",
    receiptPaymentDate: "",
    invoiceLedgerId: "",
    salesLedgerProductId: "",
  });
};
  const currentNoReceiptAmount = ref(0);
// å…³é—­åŠ è½½æç¤º
const closeToast = () => {
  uni.hideLoading();
};
  // èŽ·å–å­—å…¸æ•°æ®
  const { receipt_payment_type: dictReceiptPaymentType } = useDict(
    "receipt_payment_type"
  );
const userStore = useUserStore()
  // è½¬æ¢å­—典数据格式为选择器需要的格式
  const receiptPaymentType = computed(() => {
    return dictReceiptPaymentType.value.map(item => ({
      name: item.label,
      value: item.value,
    }));
  });
// è¡¨å•引用
const formRef = ref()
  // è¿”回上一页
  const onClickLeft = () => {
    uni.removeStorageSync("invoiceLedgerEditRow");
    uni.navigateBack();
  };
// å“åº”式数据
const loading = ref(false)
const showPaymentType = ref(false)
const pickerValue = ref([])
const showDate = ref(false)
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
  // æ˜¾ç¤ºå›žæ¬¾æ–¹å¼é€‰æ‹©å™¨
  const showPaymentTypePicker = () => {
    showPaymentType.value = true;
  };
  const changeNum = () => {
    if (form.value.receiptPaymentAmount > form.value.pendingInvoiceTotal) {
      form.value.receiptPaymentAmount = form.value.pendingInvoiceTotal;
      showToast("不可大于待回款金额");
    }
  };
// è¡¨å•数据
const form = ref({
  salesContractNo: '',
  customerName: '',
  invoiceNo: '',
  invoiceTotal: '',
  taxRate: '',
  receiptPaymentAmount: '',
  receiptPaymentType: '',
    receiptPaymentTypeName: '',
  registrant: '',
  receiptPaymentDate: '',
  invoiceLedgerId: ''
})
const currentNoReceiptAmount = ref(0)
  // ç¡®è®¤å›žæ¬¾æ–¹å¼é€‰æ‹©
  const onPaymentTypeConfirm = action => {
    form.value.receiptPaymentType = action.value;
    form.value.receiptPaymentTypeName = action.name;
    showPaymentType.value = false;
  };
// èŽ·å–å­—å…¸æ•°æ®
const { receipt_payment_type: dictReceiptPaymentType } = useDict('receipt_payment_type')
  // æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
  const showDatePicker = () => {
    showDate.value = true;
  };
// è½¬æ¢å­—典数据格式为选择器需要的格式
const receiptPaymentType = computed(() => {
  return dictReceiptPaymentType.value.map(item => ({
    name: item.label,
    value: item.value
  }))
})
  // ç¡®è®¤æ—¥æœŸé€‰æ‹©
  const onDateConfirm = e => {
    form.value.receiptPaymentDate = formatDateToYMD(e.value);
    currentDate.value = formatDateToYMD(e.value);
    showDate.value = false;
  };
// è¿”回上一页
const onClickLeft = () => {
    uni.removeStorageSync('invoiceLedgerEditRow');
    uni.navigateBack()
}
  // æäº¤è¡¨å•
  const onSubmit = () => {
    // è¡¨å•验证
    if (!form.value.receiptPaymentAmount) {
      showToast("请输入回款金额");
      return;
    }
// æ˜¾ç¤ºå›žæ¬¾æ–¹å¼é€‰æ‹©å™¨
const showPaymentTypePicker = () => {
  showPaymentType.value = true
}
const changeNum = () => {
    if (form.value.receiptPaymentAmount > currentNoReceiptAmount.value) {
        form.value.receiptPaymentAmount = currentNoReceiptAmount.value
        showToast('不可大于待回款金额')
    }
}
    if (!form.value.receiptPaymentType) {
      showToast("请选择回款形式");
      return;
    }
// ç¡®è®¤å›žæ¬¾æ–¹å¼é€‰æ‹©
const onPaymentTypeConfirm = (action) => {
  form.value.receiptPaymentType = action.value
  form.value.receiptPaymentTypeName = action.name
  showPaymentType.value = false
}
    if (!form.value.receiptPaymentDate) {
      showToast("请选择来款日期");
      return;
    }
// æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
const showDatePicker = () => {
  showDate.value = true
}
    loading.value = true;
    form.value.receiptPaymentAmount = Number(form.value.receiptPaymentAmount);
    receiptPaymentSaveOrUpdate([form.value])
      .then(() => {
        showToast("提交成功");
        // é€šçŸ¥ä¸Šä¸€é¡µåˆ·æ–°æ•°æ®
        const pages = getCurrentPages();
        const prevPage = pages[pages.length - 2];
        if (prevPage && prevPage.$vm && prevPage.$vm.getList) {
          prevPage.$vm.getList();
        }
        uni.removeStorageSync("invoiceLedgerEditRow");
        uni.navigateBack();
      })
      .catch(error => {
        loading.value = false;
      });
  };
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = (e) => {
    form.value.receiptPaymentDate = formatDateToYMD(e.value)
    currentDate.value = formatDateToYMD(e.value)
    showDate.value = false;
}
  // åˆå§‹åŒ–数据
  const initData = () => {
    const rowStr = uni.getStorageSync("invoiceLedgerEditRow");
    const row = JSON.parse(rowStr);
    form.value = { ...row };
    form.value.invoiceLedgerId = form.value.id;
    form.value.salesLedgerProductId = form.value.id;
    form.value.id = "";
    currentNoReceiptAmount.value = row.noReceiptAmount;
    form.value.registrant = userStore.nickName;
// æäº¤è¡¨å•
const onSubmit = () => {
  // è¡¨å•验证
  if (!form.value.receiptPaymentAmount) {
    showToast('请输入回款金额')
    return
  }
  if (!form.value.receiptPaymentType) {
    showToast('请选择回款形式')
    return
  }
  if (!form.value.receiptPaymentDate) {
    showToast('请选择来款日期')
    return
  }
    // invoiceInfo({ id: row.id })
    //   .then(res => {
    //     const data = res.data;
    //     form.value = { ...data };
    //     form.value.invoiceLedgerId = form.value.id;
    //     form.value.id = "";
    //     currentNoReceiptAmount.value = row.noReceiptAmount;
    //     form.value.registrant = userStore.nickName;
    //   })
    //   .catch(() => {
    //     showToast("获取数据失败");
    //   });
  };
  loading.value = true
  receiptPaymentSaveOrUpdate(form.value)
    .then(() => {
      showToast('提交成功')
      // é€šçŸ¥ä¸Šä¸€é¡µåˆ·æ–°æ•°æ®
      const pages = getCurrentPages();
      const prevPage = pages[pages.length - 2];
      if (prevPage && prevPage.$vm && prevPage.$vm.getList) {
        prevPage.$vm.getList();
      }
      uni.removeStorageSync('invoiceLedgerEditRow');
      uni.navigateBack()
    })
    .catch((error) => {
            loading.value = false
    })
}
// åˆå§‹åŒ–数据
const initData = () => {
    const rowStr = uni.getStorageSync('invoiceLedgerEditRow')
    const row = JSON.parse(rowStr)
    invoiceInfo({ id: row.id }).then((res) => {
        const data = res.data
        form.value = { ...data}
        form.value.invoiceLedgerId = form.value.id;
        form.value.id = "";
        currentNoReceiptAmount.value = row.noReceiptAmount
        form.value.registrant = userStore.nickName
    }).catch(() => {
        showToast('获取数据失败')
    })
}
onMounted(() => {
  initData()
})
  onMounted(() => {
    initData();
  });
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
  @import "@/static/scss/form-common.scss";
  .tip-text {
    font-size: 14px;
    color: #999;
    margin-top: 20rpx;
    margin-bottom: 20rpx;
  }
</style>
src/pages/sales/receiptPayment/index.vue
@@ -54,8 +54,8 @@
              <text class="detail-value">{{ item.customerName }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">客户合同号</text>
              <text class="detail-value">{{ item.customerContractNo }}</text>
              <text class="detail-label">销售合同号</text>
              <text class="detail-value">{{ item.salesContractNo }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">项目名称</text>
@@ -66,24 +66,16 @@
              <text class="detail-value">{{ item.productCategory }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">发票号</text>
              <text class="detail-value">{{ item.invoiceNo || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">发票金额(元)</text>
              <text class="detail-value highlight">{{ formatNumber(item.invoiceTotal) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">税率(%)</text>
              <text class="detail-value">{{ item.taxRate }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">回款金额(元)</text>
              <text class="detail-value highlight">{{ formatNumber(item.receiptPaymentAmountTotal) }}</text>
              <text class="detail-value highlight">{{ formatNumber(item.invoiceTotal) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">待回款金额(元)</text>
              <text class="detail-value danger">{{ formatNumber(item.noReceiptAmount) }}</text>
              <text class="detail-value danger">{{ formatNumber(item.pendingInvoiceTotal) }}</text>
            </view>
          </view>
          <!-- æ“ä½œæŒ‰é’® -->
@@ -157,7 +149,7 @@
    if (!type) {
      return "info";
    }
    if (type == "未完成回款") {
    if (type == "未完成付款") {
      return "warning";
    } else {
      return "success";
src/pages/sales/receiptPaymentLedger/index.vue
@@ -1,160 +1,167 @@
<template>
    <view class="sales-account">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="客户往来" @back="goBack" />
        <!-- æœç´¢åŒºåŸŸ -->
        <view class="search-section">
            <view class="search-bar">
                <view class="search-input">
                    <up-input
                        class="search-text"
                        placeholder="请输入客户名称"
                        v-model="searchForm.customerName"
                        @change="handleQuery"
                        clearable
                    />
                </view>
                <view class="search-button" @click="handleQuery">
                    <up-icon name="search" size="24" color="#999"></up-icon>
                </view>
            </view>
        </view>
        <!-- å®¢æˆ·åˆ—表 -->
        <view class="customer-list-container">
            <view class="customer-list" v-if="tableData.length > 0">
                <view
                    v-for="(item, index) in tableData"
                    :key="item.id"
                    class="customer-item"
                    @click="rowClickMethod(item)"
                >
                    <view class="item-header">
                        <view class="item-left">
                            <view class="customer-icon">
                                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                            </view>
                            <text class="customer-name">{{ item.customerName }}</text>
                        </view>
                        <view class="item-right">
                            <up-icon name="arrow-right" size="16" color="#999"></up-icon>
                        </view>
                    </view>
                    <up-divider></up-divider>
                    <view class="item-details">
                        <view class="detail-row">
                            <text class="detail-label">开票金额(元)</text>
                            <text class="detail-value">{{ formattedNumber(item.invoiceTotal) }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">回款金额(元)</text>
                            <text class="detail-value">{{ formattedNumber(item.receiptPaymentAmount) }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">应收金额(元)</text>
                            <text class="detail-value highlight danger">{{ formattedNumber(item.unReceiptPaymentAmount) }}</text>
                        </view>
                    </view>
                </view>
            </view>
            <view v-else class="no-data">
                <text>暂无客户数据</text>
            </view>
        </view>
    </view>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="客户往来"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入客户名称"
                    v-model="searchForm.searchText"
                    @change="searchChange"
                    clearable />
        </view>
        <view class="search-button"
              @click="handleQuery">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- å®¢æˆ·åˆ—表 -->
    <view class="customer-list-container">
      <view class="customer-list"
            v-if="tableData.length > 0">
        <view v-for="(item, index) in tableData"
              :key="item.id"
              class="customer-item"
              @click="rowClickMethod(item)">
          <view class="item-header">
            <view class="item-left">
              <view class="customer-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="customer-name">{{ item.customerName }}</text>
            </view>
            <view class="item-right">
              <up-icon name="arrow-right"
                       size="16"
                       color="#999"></up-icon>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">开票金额(元)</text>
              <text class="detail-value">{{ formattedNumber(item.invoiceTotal) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">回款金额(元)</text>
              <text class="detail-value">{{ formattedNumber(item.receiptPaymentAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">应收金额(元)</text>
              <text class="detail-value highlight danger">{{ formattedNumber(item.unReceiptPaymentAmount) }}</text>
            </view>
          </view>
        </view>
      </view>
      <view v-else
            class="no-data">
        <text>暂无客户数据</text>
      </view>
    </view>
  </view>
</template>
<script setup>
import { onMounted, ref, reactive, toRefs } from "vue";
import { onShow } from '@dcloudio/uni-app';
import { invoiceLedgerSalesAccount } from "@/api/salesManagement/invoiceLedger";
  import { onMounted, ref, reactive, toRefs } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { invoiceLedgerSalesAccount } from "@/api/salesManagement/invoiceLedger";
  const tableData = ref([]);
const tableData = ref([]);
  const page = reactive({
    current: -1,
    size: -1,
  });
const page = reactive({
    current: -1,
    size: -1,
});
  const data = reactive({
    searchForm: {
      searchText: "",
      invoiceDate: "",
    },
  });
  const { searchForm } = toRefs(data);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æœç´¢æ¡†å˜åŒ–时触发
  const searchChange = val => {
    searchForm.value.searchText = val;
    getList();
  };
  // æŸ¥è¯¢åˆ—表
  const handleQuery = () => {
    getList();
  };
const data = reactive({
    searchForm: {
        customerName: "",
        invoiceDate: "",
    },
});
  const getList = () => {
    showLoadingToast("加载中...");
    invoiceLedgerSalesAccount({ ...searchForm.value, ...page })
      .then(res => {
        tableData.value = res.data.records;
        closeToast();
      })
      .catch(() => {
        closeToast();
        uni.showToast({
          title: "查询失败",
          icon: "error",
        });
      });
  };
const { searchForm } = toRefs(data);
  const formattedNumber = value => {
    return parseFloat(value || 0).toFixed(2);
  };
// è¿”回上一页
const goBack = () => {
    uni.navigateBack();
};
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
// æŸ¥è¯¢åˆ—表
const handleQuery = () => {
    getList();
};
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
const getList = () => {
    showLoadingToast('加载中...')
    invoiceLedgerSalesAccount({ ...searchForm.value, ...page }).then((res) => {
        tableData.value = res.data.records;
        closeToast()
    }).catch(() => {
        closeToast()
        uni.showToast({
            title: '查询失败',
            icon: 'error'
        });
    });
};
  const rowClickMethod = row => {
    // ä½¿ç”¨ uni.setStorageSync å­˜å‚¨å®¢æˆ·ä¿¡æ¯
    uni.setStorageSync("customerId", row.id);
    // è·³è½¬åˆ°å›žæ¬¾è®°å½•明细页面
    uni.navigateTo({
      url: "/pages/sales/receiptPaymentLedger/detail",
    });
  };
const formattedNumber = (value) => {
    return parseFloat(value || 0).toFixed(2);
};
  onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶åˆ·æ–°åˆ—表
    getList();
  });
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
    uni.showLoading({
        title: message,
        mask: true
    });
};
// å…³é—­æç¤º
const closeToast = () => {
    uni.hideLoading();
};
const rowClickMethod = (row) => {
    // ä½¿ç”¨ uni.setStorageSync å­˜å‚¨å®¢æˆ·ä¿¡æ¯
    uni.setStorageSync('customerId', row.id);
    // è·³è½¬åˆ°å›žæ¬¾è®°å½•明细页面
    uni.navigateTo({
        url: '/pages/sales/receiptPaymentLedger/detail'
    });
};
onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶åˆ·æ–°åˆ—表
    getList();
});
onMounted(() => {
    getList();
});
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
  @import "@/styles/sales-common.scss";
// å®¢æˆ·å¾€æ¥ç‰¹æœ‰æ ·å¼
.detail-value.danger {
    color: #ff4757; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ #ee0a24 ä¸åŒ
    font-weight: 500;
}
  // å®¢æˆ·å¾€æ¥ç‰¹æœ‰æ ·å¼
  .detail-value.danger {
    color: #ff4757; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ #ee0a24 ä¸åŒ
    font-weight: 500;
  }
</style>
src/pages/sales/salesAccount/detail.vue
@@ -28,12 +28,6 @@
                   @click="showPicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="客户合同号"
                    prop="customerContractNo"
                    required>
        <up-input v-model="form.customerContractNo"
                  placeholder="请输入客户合同号" />
      </up-form-item>
      <up-form-item label="客户名称"
                    prop="customerName"
                    required>
@@ -325,7 +319,6 @@
  const form = ref({
    id: "",
    salesContractNo: "",
    customerContractNo: "",
    customerId: "",
    customerName: "",
    projectName: "",
@@ -448,9 +441,6 @@
  // è¡¨å•校验规则
  const rules = {
    salesman: [{ required: true, message: "请选择业务员", trigger: "change" }],
    customerContractNo: [
      { required: true, message: "请输入客户合同号", trigger: "blur" },
    ],
    customerName: [
      { required: true, message: "请选择客户名称", trigger: "change" },
    ],
@@ -842,7 +832,6 @@
    console.log(editData.value);
    // å¡«å……基本信息
    form.value.salesContractNo = editData.value.salesContractNo || "";
    form.value.customerContractNo = editData.value.customerContractNo || "";
    form.value.customerName = editData.value.customerName || "";
    form.value.projectName = editData.value.projectName || "";
    form.value.executionDate = editData.value.executionDate || "";
src/pages/sales/salesAccount/index.vue
@@ -48,10 +48,6 @@
              <text class="detail-value">{{ item.customerName }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">客户合同号</text>
              <text class="detail-value">{{ item.customerContractNo }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">业务员</text>
              <text class="detail-value">{{ item.salesman }}</text>
            </view>
src/pages/sales/salesAccount/view.vue
@@ -12,10 +12,6 @@
          <text class="info-value">{{ form.salesContractNo }}</text>
        </view>
        <view class="info-item">
          <text class="info-label">客户合同号</text>
          <text class="info-value highlight">{{ form.customerContractNo }}</text>
        </view>
        <view class="info-item">
          <text class="info-label">客户名称</text>
          <text class="info-value">{{ form.customerName }}</text>
        </view>
@@ -125,7 +121,6 @@
  const form = ref({
    id: "",
    salesContractNo: "",
    customerContractNo: "",
    customerId: "",
    customerName: "",
    projectName: "",