spring
2026-02-06 add06adc5d974ac685cb637c48f2455034c8a52f
src/pages/safeProduction/safetyTrainingAssessment/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,448 @@
<template>
  <view class="sales-accoun">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="安全培训考核"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view @click="selectDate"
              class="search-input">
          <view class="search-text">{{ searchKeyword? searchKeyword : '请选择培训日期' }}</view>
        </view>
        <view class="filter-button"
              @click="clearDate">
          <u-icon name="close-circle"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
      <!-- åŸ¹è®­è®°å½•按钮 -->
      <view class="record-button">
        <u-button type="info"
                  @click="viewTrainingRecord">培训记录</u-button>
      </view>
    </view>
    <!-- æ ‡ç­¾é¡µ -->
    <view class="tabs-section">
      <up-tabs v-model="searchForm.state"
               :list="tabList"
               itemStyle="width: 33%;height: 80rpx;"
               @change="tabhandleQuery">
      </up-tabs>
    </view>
    <!-- åŸ¹è®­è®°å½•列表 -->
    <view class="ledger-list"
          v-if="trainingList.length > 0">
      <view v-for="(item, index) in trainingList"
            :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.courseCode }}</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.courseCode || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">培训日期</text>
              <text class="detail-value">{{ item.trainingDate || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">开始时间</text>
              <text class="detail-value">{{ item.openingTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">结束时间</text>
              <text class="detail-value">{{ item.endTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">培训目标</text>
              <text class="detail-value">{{ item.trainingObjectives || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">参加对象</text>
              <text class="detail-value">{{ item.participants || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">培训内容</text>
              <text class="detail-value">{{ item.trainingContent || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">培训讲师</text>
              <text class="detail-value">{{ item.trainingLecturer || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">项目学分</text>
              <text class="detail-value">{{ item.projectCredits || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">培训方式</text>
              <text class="detail-value">{{ getTrainingModeLabel(item.trainingMode) || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">培训地点</text>
              <text class="detail-value">{{ item.placeTraining || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">课时</text>
              <text class="detail-value">{{ item.classHour || '-' }}</text>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      :disabled="item.state !== 0"
                      @click="editVisit(item)">
              ç¼–辑
            </u-button>
            <u-button type="info"
                      size="small"
                      class="action-btn"
                      @click="viewFileList(item)">
              é™„ä»¶
            </u-button>
            <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="deleteVisit(item)">
              åˆ é™¤
            </u-button>
          </view>
          <view class="action-buttons">
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      :disabled="item.state !== 1"
                      @click="signIn(item)">
              ç­¾åˆ°
            </u-button>
            <u-button type="success"
                      size="small"
                      class="action-btn"
                      :disabled="item.state === 0"
                      @click="viewResultDetail(item)">
              ç»“果明细
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无培训记录</text>
    </view>
    <up-datetime-picker :show="trainingDateVisible"
                        mode="date"
                        @confirm="handleDateConfirm"
                        @cancel="handleDateCancel"
                        title="选择培训日期" />
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button"
          @click="addVisit">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, reactive } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    safeTrainingListPage,
    safeTrainingDel,
    safeTrainingSign,
    safeTrainingGet,
  } from "@/api/safeProduction/safetyTrainingAssessment";
  import useUserStore from "@/store/modules/user";
  import { useDict } from "@/utils/dict";
  import dayjs from "dayjs";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "safety-training-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  const userStore = useUserStore();
  // èŽ·å–å­—å…¸æ•°æ®
  const { safe_training_methods } = useDict("safe_training_methods");
  // æœç´¢å…³é”®è¯
  const searchKeyword = ref("");
  // æ—¥æœŸé€‰æ‹©å™¨çŠ¶æ€
  const trainingDateVisible = ref(false);
  const tabList = reactive([
    { name: "未开始", value: 0 },
    { name: "进行中", value: 1 },
    { name: "已结束", value: 2 },
  ]);
  // æœç´¢è¡¨å•
  const searchForm = ref({
    state: 0, // é»˜è®¤æ˜¾ç¤ºå·²ç»“束
    trainingDate: "",
  });
  const tabhandleQuery = val => {
    searchForm.value.state = val.value;
    getList();
  };
  const getTrainingModeLabel = mode => {
    if (!safe_training_methods || !Array.isArray(safe_training_methods.value)) {
      return mode || "-";
    }
    const dictItem = safe_training_methods.value.find(
      item => String(item.value) === String(mode)
    );
    return dictItem ? dictItem.label : mode || "-";
  };
  // åŸ¹è®­è®°å½•数据
  const trainingList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const viewFileList = item => {
    uni.setStorageSync("safetyTrainingFileId", item.id);
    uni.navigateTo({
      url: "/pages/safeProduction/safetyTrainingAssessment/fileList",
    });
  };
  const currentUserId = ref("");
  // ç­¾åˆ°åŠŸèƒ½
  const signIn = item => {
    uni.showModal({
      title: "提示",
      content: "确认签到吗?",
      success: function (res) {
        if (res.confirm) {
          safeTrainingSign({
            safeTrainingId: item.id,
            userId: currentUserId.value,
          })
            .then(res => {
              if (res.code === 200) {
                uni.showToast({ title: "签到成功", icon: "success" });
                setTimeout(() => {}, 1000);
              } else {
                uni.showToast({ title: res.msg || "签到失败", icon: "none" });
              }
            })
            .catch(() => {
              uni.showToast({ title: "签到失败,请重试", icon: "none" });
            });
        }
      },
    });
  };
  // æŸ¥çœ‹ç»“果明细
  const viewResultDetail = item => {
    uni.setStorageSync("safetyTrainingResultId", item.id);
    uni.setStorageSync("safetyTrainingResultNums", item.nums);
    uni.navigateTo({
      url: "/pages/safeProduction/safetyTrainingAssessment/resultDetail",
    });
  };
  // æŸ¥çœ‹åŸ¹è®­è®°å½•
  const viewTrainingRecord = () => {
    uni.navigateTo({
      url: "/pages/safeProduction/safetyTrainingAssessment/record",
    });
  };
  // æ¸…除日期选择
  const clearDate = () => {
    searchKeyword.value = "";
    searchForm.value.trainingDate = "";
    getList();
  };
  // æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
  const selectDate = () => {
    trainingDateVisible.value = true;
  };
  // å¤„理日期选择确认
  const handleDateConfirm = e => {
    searchKeyword.value = dayjs(e.value).format("YYYY-MM-DD");
    searchForm.value.trainingDate = dayjs(e.value).format("YYYY-MM-DD");
    trainingDateVisible.value = false;
    getList();
  };
  // å¤„理日期选择取消
  const handleDateCancel = () => {
    trainingDateVisible.value = false;
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      trainingDate: searchForm.value.trainingDate,
      state: searchForm.value.state,
    };
    safeTrainingListPage(params)
      .then(res => {
        trainingList.value = res.records || res.data?.records || [];
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ–°å¢žåŸ¹è®­
  const addVisit = () => {
    uni.setStorageSync("safetyTraining", {});
    uni.navigateTo({
      url: "/pages/safeProduction/safetyTrainingAssessment/detail",
    });
  };
  // ç¼–辑培训
  const editVisit = item => {
    uni.setStorageSync("safetyTraining", item);
    uni.navigateTo({
      url: "/pages/safeProduction/safetyTrainingAssessment/detail",
    });
  };
  // åˆ é™¤åŸ¹è®­
  const deleteVisit = item => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除该培训记录吗?`,
      success: res => {
        if (res.confirm) {
          deleteClientVisit(item.id);
        }
      },
    });
  };
  // åˆ é™¤åŸ¹è®­è®°å½•
  const deleteClientVisit = id => {
    showLoadingToast("删除中...");
    safeTrainingDel([id])
      .then(() => {
        closeToast();
        showToast("删除成功");
        getList();
      })
      .catch(() => {
        closeToast();
        showToast("删除失败");
      });
  };
  // æŸ¥çœ‹è¯¦æƒ…
  const viewDetail = item => {
    uni.setStorageSync("safetyTraining", item);
    uni.navigateTo({
      url: "/pages/safeProduction/safetyTrainingAssessment/view",
    });
  };
  onMounted(() => {
    userStore.getInfo().then(res => {
      currentUserId.value = res.user.userId;
    });
    // currentUserId
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .sales-accoun {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 80px;
  }
  // åŸ¹è®­è®°å½•按钮
  .record-button {
    margin-top: 10px;
    text-align: center;
  }
  // ç‰¹å®šçš„图标样式
  .document-icon {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
  }
  // ç‰¹æœ‰æ ·å¼
  .visit-status {
    display: flex;
    align-items: center;
  }
  .detail-value {
    word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
  }
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .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;
  }
  .tabs-section {
    background: #fff;
    margin-bottom: 1rem;
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.05);
  }
  .search-text {
    // font-size: 24rpx;
    color: #a6a6a6;
    height: 70rpx;
    line-height: 70rpx;
    margin-left: 20rpx;
  }
</style>