From 84bf005a674d3c49e66a67b9a55824dedb0d7120 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期四, 05 二月 2026 16:56:22 +0800
Subject: [PATCH] 安全培训模块开发
---
src/pages/index.vue | 10
src/pages/safeProduction/safetyTrainingAssessment/fileList.vue | 567 ++++++++++++
src/api/safeProduction/safetyTrainingAssessment.js | 120 ++
src/pages.json | 42
src/pages/safeProduction/safetyTrainingAssessment/view.vue | 171 +++
src/pages/safeProduction/safetyTrainingAssessment/record.vue | 546 +++++++++++
src/pages/safeProduction/safetyTrainingAssessment/index.vue | 448 +++++++++
src/pages/safeProduction/safetyTrainingAssessment/detail.vue | 430 +++++++++
src/pages/safeProduction/safetyTrainingAssessment/resultDetail.vue | 388 ++++++++
9 files changed, 2,722 insertions(+), 0 deletions(-)
diff --git a/src/api/safeProduction/safetyTrainingAssessment.js b/src/api/safeProduction/safetyTrainingAssessment.js
new file mode 100644
index 0000000..7fdc956
--- /dev/null
+++ b/src/api/safeProduction/safetyTrainingAssessment.js
@@ -0,0 +1,120 @@
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function safeTrainingListPage(query) {
+ return request({
+ url: "/safeTraining/page",
+ method: "get",
+ params: query,
+ });
+}
+
+
+// 鏂板瀹夊叏鍩硅璇勪及
+export function safeTrainingAdd(query) {
+ return request({
+ url: '/safeTraining',
+ method: 'post',
+ data: query
+ })
+}
+
+// 淇敼瀹夊叏鍩硅璇勪及
+export function safeTrainingUpdate(query) {
+ return request({
+ url: '/safeTraining',
+ method: 'put',
+ data: query
+ })
+}
+
+// 鍒犻櫎瀹夊叏鍩硅璇勪及
+export function safeTrainingDel(ids) {
+ return request({
+ url: '/safeTraining/' + ids,
+ method: 'delete',
+ data: ids
+ })
+}
+
+// 瀵煎嚭
+export function safeTrainingExport(query) {
+ return request({
+ url: '/safeTraining/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob'
+ })
+}
+
+// 鏌ヨ闄勪欢鍒楄〃
+export function safeTrainingFileListPage(query) {
+ return request({
+ url: "/safeTrainingFile/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 娣诲姞闄勪欢
+export function safeTrainingFileAdd(query) {
+ return request({
+ url: '/safeTrainingFile/add',
+ method: 'post',
+ data: query
+ })
+}
+
+// 鍒犻櫎闄勪欢
+export function safeTrainingFileDel(ids) {
+ return request({
+ url: '/safeTrainingFile/del',
+ method: 'delete',
+ data: ids
+ })
+}
+
+// 绛惧埌
+export function safeTrainingSign(query) {
+ return request({
+ url: '/safeTraining/sign',
+ method: 'post',
+ data: query
+ })
+}
+
+// 鏌ヨ璇︽儏
+export function safeTrainingGet(query) {
+ return request({
+ url: '/safeTraining/getSafeTraining',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鎻愪氦
+export function safeTrainingSave(query) {
+ return request({
+ url: '/safeTraining/saveSafeTraining',
+ method: 'post',
+ data: query
+ })
+}
+
+export function safeTrainingDetailListPage(query) {
+ return request({
+ url: "/safeTrainingDetails/page",
+ method: "get",
+ params: query,
+ });
+}
+
+// 瀵煎嚭
+export function safeTrainingDetailExport(query) {
+ return request({
+ url: '/safeTrainingDetails/export',
+ method: 'post',
+ data: query,
+ responseType: 'blob'
+ })
+}
\ No newline at end of file
diff --git a/src/pages.json b/src/pages.json
index 2e1cabe..9bf4888 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -814,6 +814,48 @@
"navigationBarTitleText": "搴旀�ラ妗堣鎯�",
"navigationStyle": "custom"
}
+ },
+ {
+ "path": "pages/safeProduction/safetyTrainingAssessment/index",
+ "style": {
+ "navigationBarTitleText": "瀹夊叏鍩硅璇勪及",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/safeProduction/safetyTrainingAssessment/detail",
+ "style": {
+ "navigationBarTitleText": "鍩硅璇︽儏",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/safeProduction/safetyTrainingAssessment/view",
+ "style": {
+ "navigationBarTitleText": "鍩硅璇︽儏",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/safeProduction/safetyTrainingAssessment/fileList",
+ "style": {
+ "navigationBarTitleText": "鍩硅闄勪欢",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/safeProduction/safetyTrainingAssessment/resultDetail",
+ "style": {
+ "navigationBarTitleText": "缁撴灉鏄庣粏",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/safeProduction/safetyTrainingAssessment/record",
+ "style": {
+ "navigationBarTitleText": "鍩硅璁板綍",
+ "navigationStyle": "custom"
+ }
}
],
"subPackages": [
diff --git a/src/pages/index.vue b/src/pages/index.vue
index aa08cb3..7f0408c 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -331,6 +331,10 @@
icon: "/static/images/icon/guzhangfenxi@2x.png",
label: "浜嬫晠涓婃姤",
},
+ {
+ icon: "/static/images/icon/guzhangfenxi@2x.png",
+ label: "瀹夊叏鍩硅",
+ },
]);
// 鍗忓悓鍔炲叕鍔熻兘鏁版嵁
const collaborationItems = reactive([
@@ -724,6 +728,12 @@
url: "/pages/safeProduction/accidentReportingRecord/index",
});
break;
+ case "瀹夊叏鍩硅":
+ uni.navigateTo({
+ url: "/pages/safeProduction/safetyTrainingAssessment/index",
+ });
+ break;
+
default:
uni.showToast({
title: `鐐瑰嚮浜�${item.label}`,
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/detail.vue b/src/pages/safeProduction/safetyTrainingAssessment/detail.vue
new file mode 100644
index 0000000..d55c892
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/detail.vue
@@ -0,0 +1,430 @@
+<template>
+ <view class="danger-investigation-detail">
+ <PageHeader :title="isEdit ? '缂栬緫鍩硅' : '鏂板鍩硅'"
+ @back="goBack" />
+ <u-form @submit="handleSubmit"
+ ref="formRef"
+ label-width="110">
+ <!-- 鍩硅淇℃伅 -->
+ <u-cell-group title="鍩硅淇℃伅">
+ <u-form-item label="璇剧▼缂栧彿"
+ prop="courseCode"
+ border-bottom>
+ <u-input v-model="form.courseCode"
+ placeholder="绯荤粺鑷姩鐢熸垚"
+ readonly />
+ </u-form-item>
+ <u-form-item label="鍩硅鏃ユ湡"
+ prop="trainingDate"
+ required
+ border-bottom>
+ <u-input v-model="form.trainingDate"
+ placeholder="璇烽�夋嫨鍩硅鏃ユ湡"
+ @click="showTrainingDatePicker"
+ readonly />
+ <template #right>
+ <up-icon name="arrow-right"
+ @click="showTrainingDatePicker"></up-icon>
+ </template>
+ </u-form-item>
+ <u-form-item label="寮�濮嬫椂闂�"
+ prop="openingTime"
+ required
+ border-bottom>
+ <u-input v-model="form.openingTime"
+ placeholder="璇烽�夋嫨寮�濮嬫椂闂�"
+ @click="showOpeningTimePicker"
+ readonly />
+ <template #right>
+ <up-icon name="arrow-right"
+ @click="showOpeningTimePicker"></up-icon>
+ </template>
+ </u-form-item>
+ <u-form-item label="缁撴潫鏃堕棿"
+ prop="endTime"
+ required
+ border-bottom>
+ <u-input v-model="form.endTime"
+ placeholder="璇烽�夋嫨缁撴潫鏃堕棿"
+ @click="showEndTimePicker"
+ readonly />
+ <template #right>
+ <up-icon name="arrow-right"
+ @click="showEndTimePicker"></up-icon>
+ </template>
+ </u-form-item>
+ <u-form-item label="鍩硅鐩爣"
+ prop="trainingObjectives"
+ border-bottom>
+ <u-input v-model="form.trainingObjectives"
+ placeholder="璇疯緭鍏ュ煿璁洰鏍�" />
+ </u-form-item>
+ <u-form-item label="鍙傚姞瀵硅薄"
+ prop="participants"
+ border-bottom>
+ <u-input v-model="form.participants"
+ placeholder="璇疯緭鍏ュ弬鍔犲璞�" />
+ </u-form-item>
+ <u-form-item label="鍩硅鍐呭"
+ prop="trainingContent"
+ required
+ border-bottom>
+ <u-textarea v-model="form.trainingContent"
+ placeholder="璇疯緭鍏ュ煿璁唴瀹�"
+ :maxlength="200"
+ count
+ :autoHeight="true" />
+ </u-form-item>
+ <u-form-item label="鍩硅璁插笀"
+ prop="trainingLecturer"
+ required
+ border-bottom>
+ <u-input v-model="form.trainingLecturer"
+ placeholder="璇疯緭鍏ュ煿璁甯�" />
+ </u-form-item>
+ <u-form-item label="椤圭洰瀛﹀垎"
+ prop="projectCredits"
+ border-bottom>
+ <u-input v-model="form.projectCredits"
+ placeholder="璇疯緭鍏ラ」鐩鍒�"
+ type="number" />
+ </u-form-item>
+ <u-form-item label="鍩硅鏂瑰紡"
+ prop="trainingMode"
+ border-bottom>
+ <u-input v-model="trainingModeName"
+ placeholder="璇烽�夋嫨鍩硅鏂瑰紡"
+ @click="showTrainingModeSheet"
+ readonly />
+ <template #right>
+ <up-icon name="arrow-right"
+ @click="showTrainingModeSheet"></up-icon>
+ </template>
+ </u-form-item>
+ <u-form-item label="鍩硅鍦扮偣"
+ prop="placeTraining"
+ border-bottom>
+ <u-input v-model="form.placeTraining"
+ placeholder="璇疯緭鍏ュ煿璁湴鐐�" />
+ </u-form-item>
+ <u-form-item label="璇炬椂"
+ prop="classHour"
+ required
+ border-bottom>
+ <u-input v-model="form.classHour"
+ placeholder="璇疯緭鍏ヨ鏃�"
+ type="number" />
+ </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-datetime-picker :show="trainingDateVisible"
+ mode="date"
+ v-model="nowDate"
+ @confirm="handleTrainingDateConfirm"
+ @cancel="trainingDateVisible = false"
+ title="閫夋嫨鍩硅鏃ユ湡" />
+ <u-datetime-picker :show="openingTimeVisible"
+ mode="time"
+ @confirm="handleOpeningTimeConfirm"
+ @cancel="openingTimeVisible = false"
+ title="閫夋嫨寮�濮嬫椂闂�" />
+ <u-datetime-picker :show="endTimeVisible"
+ mode="time"
+ @confirm="handleEndTimeConfirm"
+ @cancel="endTimeVisible = false"
+ title="閫夋嫨缁撴潫鏃堕棿" />
+ <!-- 鍩硅鏂瑰紡閫夋嫨鍣� -->
+ <up-action-sheet :show="trainingModeSheetVisible"
+ :actions="trainingModeOptions"
+ @select="handleTrainingModeSelect"
+ @close="trainingModeSheetVisible = false"
+ title="閫夋嫨鍩硅鏂瑰紡" />
+ </view>
+</template>
+
+<script setup>
+ // 鏇挎崲 toast 鏂规硶
+ defineOptions({ name: "danger-investigation-detail" });
+ const showToast = message => {
+ uni.showToast({ title: message, icon: "none" });
+ };
+
+ import { ref, onMounted } from "vue";
+ import PageHeader from "@/components/PageHeader.vue";
+ import {
+ safeTrainingAdd,
+ safeTrainingUpdate,
+ } from "@/api/safeProduction/safetyTrainingAssessment";
+ import { onLoad } from "@dcloudio/uni-app";
+ import { useDict } from "@/utils/dict";
+ import dayjs from "dayjs";
+
+ // 鑾峰彇瀛楀吀鏁版嵁
+ const { safe_training_methods } = useDict("safe_training_methods");
+
+ // 琛ㄥ崟鏁版嵁
+ const form = ref({
+ courseCode: "", // 璇剧▼缂栧彿
+ trainingDate: "", // 鍩硅鏃ユ湡
+ openingTime: "", // 寮�濮嬫椂闂�
+ endTime: "", // 缁撴潫鏃堕棿
+ trainingObjectives: "", // 鍩硅鐩爣
+ participants: "", // 鍙傚姞瀵硅薄
+ trainingContent: "", // 鍩硅鍐呭
+ trainingLecturer: "", // 鍩硅璁插笀
+ projectCredits: "", // 椤圭洰瀛﹀垎
+ trainingMode: "", // 鍩硅鏂瑰紡
+ placeTraining: "", // 鍩硅鍦扮偣
+ classHour: "", // 璇炬椂
+ });
+
+ // 椤甸潰鐘舵��
+ const loading = ref(false);
+ const formRef = ref(null);
+ const isEdit = ref(false);
+
+ // 鍩硅鏂瑰紡閫夋嫨鍣�
+ const trainingModeSheetVisible = ref(false);
+ const trainingModeName = ref("");
+ const trainingModeOptions = ref([]);
+
+ // 鏃堕棿閫夋嫨鍣�
+ const trainingDateVisible = ref(false);
+ const openingTimeVisible = ref(false);
+ const endTimeVisible = ref(false);
+ const nowDate = ref(new Date());
+
+ const showTrainingDatePicker = () => {
+ trainingDateVisible.value = true;
+ };
+
+ const showOpeningTimePicker = () => {
+ openingTimeVisible.value = true;
+ };
+
+ const showEndTimePicker = () => {
+ endTimeVisible.value = true;
+ };
+
+ const handleTrainingDateConfirm = e => {
+ form.value.trainingDate = dayjs(e.value).format("YYYY-MM-DD");
+ nowDate.value = e.value;
+ trainingDateVisible.value = false;
+ };
+
+ const handleOpeningTimeConfirm = e => {
+ console.log(e);
+ form.value.openingTime = e.value;
+ openingTimeVisible.value = false;
+ };
+
+ const handleEndTimeConfirm = e => {
+ form.value.endTime = e.value;
+ endTimeVisible.value = false;
+ };
+
+ // 鏄剧ず鍩硅鏂瑰紡閫夋嫨鍣�
+ const showTrainingModeSheet = () => {
+ trainingModeSheetVisible.value = true;
+ };
+
+ // 澶勭悊鍩硅鏂瑰紡閫夋嫨
+ const handleTrainingModeSelect = item => {
+ form.value.trainingMode = item.value;
+ trainingModeName.value = item.name;
+ trainingModeSheetVisible.value = false;
+ };
+
+ // 杩斿洖涓婁竴椤�
+ const goBack = () => {
+ uni.removeStorageSync("safetyTraining");
+ uni.navigateBack();
+ };
+
+ // 鎻愪氦琛ㄥ崟
+ const handleSubmit = async () => {
+ if (!form.value.trainingDate) {
+ showToast("璇烽�夋嫨鍩硅鏃ユ湡");
+ return;
+ }
+
+ if (!form.value.openingTime) {
+ showToast("璇烽�夋嫨寮�濮嬫椂闂�");
+ return;
+ }
+
+ if (!form.value.endTime) {
+ showToast("璇烽�夋嫨缁撴潫鏃堕棿");
+ return;
+ }
+ if (!form.value.trainingContent) {
+ showToast("璇疯緭鍏ュ煿璁唴瀹�");
+ return;
+ }
+
+ if (!form.value.trainingLecturer) {
+ showToast("璇疯緭鍏ュ煿璁甯�");
+ return;
+ }
+
+ if (!form.value.classHour) {
+ showToast("璇疯緭鍏ヨ鏃�");
+ return;
+ }
+ if (
+ form.value.projectCredits &&
+ (isNaN(Number(form.value.projectCredits)) ||
+ Number(form.value.projectCredits) <= 0)
+ ) {
+ showToast("瀛﹀垎蹇呴』鏄ぇ浜�0鐨勬暟瀛�");
+ return;
+ }
+ form.value.openingTime = form.value.openingTime + ":00";
+ form.value.endTime = form.value.endTime + ":00";
+
+ if (
+ form.value.classHour &&
+ (isNaN(Number(form.value.classHour)) || Number(form.value.classHour) <= 0)
+ ) {
+ showToast("璇炬椂蹇呴』鏄ぇ浜�0鐨勬暟瀛�");
+ 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 safeTrainingAdd(submitData);
+ if (code === 200) {
+ showToast("淇敼鎴愬姛");
+ setTimeout(() => {
+ goBack();
+ }, 500);
+ } else {
+ loading.value = false;
+ showToast("淇敼澶辫触锛岃閲嶈瘯");
+ }
+ } else {
+ const { code } = await safeTrainingAdd(submitData);
+ if (code === 200) {
+ showToast("鏂板鎴愬姛");
+ setTimeout(() => {
+ goBack();
+ }, 500);
+ } else {
+ loading.value = false;
+ showToast("鏂板澶辫触锛岃閲嶈瘯");
+ }
+ }
+ } catch (e) {
+ loading.value = false;
+ console.error("鎻愪氦澶辫触:", e);
+ showToast("鎻愪氦澶辫触锛岃閲嶈瘯");
+ }
+ };
+
+ onLoad(() => {
+ // 缂栬緫鍩硅鏃讹紝浠庢湰鍦板瓨鍌ㄨ幏鍙栨暟鎹�
+ const safetyTraining = uni.getStorageSync("safetyTraining");
+ if (safetyTraining.id) {
+ form.value = safetyTraining;
+ nowDate.value = dayjs(form.value.trainingDate).toDate();
+ form.value.openingTime = form.value.openingTime
+ ? form.value.openingTime.slice(0, 5)
+ : "";
+ form.value.endTime = form.value.endTime
+ ? form.value.endTime.slice(0, 5)
+ : "";
+ isEdit.value = true;
+ } else {
+ isEdit.value = false;
+ // 榛樿鍩硅鏃ユ湡涓轰粖澶�
+ form.value.trainingDate = dayjs().format("YYYY-MM-DD");
+ }
+ });
+
+ onMounted(() => {
+ // 鍒濆鍖栧煿璁柟寮忛�夐」
+ if (safe_training_methods && Array.isArray(safe_training_methods.value)) {
+ trainingModeOptions.value =
+ safe_training_methods.value.map(item => ({
+ value: item.value,
+ name: item.label,
+ })) || [];
+ } else {
+ trainingModeOptions.value = [];
+ }
+
+ // 璁剧疆宸查�夊�肩殑鏄剧ず鏂囨湰
+ if (form.value.trainingMode) {
+ const modeItem = trainingModeOptions.value.find(
+ item => String(item.value) === String(form.value.trainingMode)
+ );
+ trainingModeName.value = modeItem ? modeItem.name : "";
+ }
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/static/scss/form-common.scss";
+
+ .danger-investigation-detail {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 100px;
+ }
+
+ .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>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/fileList.vue b/src/pages/safeProduction/safetyTrainingAssessment/fileList.vue
new file mode 100644
index 0000000..9797f21
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/fileList.vue
@@ -0,0 +1,567 @@
+<template>
+ <view class="file-list-page">
+ <!-- 椤甸潰澶撮儴 -->
+ <PageHeader title="闄勪欢绠$悊"
+ @back="goBack" />
+ <!-- 闄勪欢鍒楄〃 -->
+ <view class="file-list-container">
+ <view v-if="fileList.length > 0"
+ class="file-list">
+ <view v-for="(file, index) in fileList"
+ :key="file.id || index"
+ class="file-item">
+ <!-- 鏂囦欢鍥炬爣 -->
+ <!-- <view class="file-icon"
+ :class="getFileIconClass(file.fileType)">
+ <up-icon :name="getFileIcon(file.fileType)"
+ size="24"
+ color="#ffffff" />
+ </view> -->
+ <!-- 鏂囦欢淇℃伅 -->
+ <view class="file-info">
+ <text class="file-name">{{ file.name }}</text>
+ <!-- <text class="file-meta">{{ formatFileSize(file.fileSize) }} 路 {{ file.uploadTime || file.createTime }}</text> -->
+ </view>
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <view class="file-actions">
+ <!-- <u-button size="small"
+ type="primary"
+ plain
+ @click="previewFile(file)">棰勮</u-button> -->
+ <u-button size="small"
+ type="info"
+ plain
+ @click="downloadFile(file)">涓嬭浇骞堕瑙�</u-button>
+ <u-button size="small"
+ type="error"
+ plain
+ @click="confirmDelete(file, index)">鍒犻櫎</u-button>
+ </view>
+ </view>
+ </view>
+ <!-- 绌虹姸鎬� -->
+ <view v-else
+ class="empty-state">
+ <up-icon name="document"
+ size="64"
+ color="#c0c4cc" />
+ <text class="empty-text">鏆傛棤闄勪欢</text>
+ </view>
+ </view>
+ <!-- <a rel="nofollow"
+ id="downloadLink"
+ href="#"
+ style="display:none;">涓嬭浇鏂囨湰鏂囦欢</a> -->
+ <!-- 涓婁紶鎸夐挳 -->
+ <view class="upload-button"
+ @click="chooseFile">
+ <up-icon name="plus"
+ size="24"
+ color="#ffffff" />
+ <text class="upload-text">涓婁紶闄勪欢</text>
+ </view>
+ </view>
+</template>
+
+<script setup>
+ import { ref, onMounted } from "vue";
+ import PageHeader from "@/components/PageHeader.vue";
+ import config from "@/config";
+ import { getToken } from "@/utils/auth";
+ // import { saveAs } from "file-saver";
+ import {
+ listRuleFiles,
+ delRuleFile,
+ } from "@/api/managementMeetings/rulesRegulationsManagement";
+ import {
+ safeTrainingFileListPage,
+ safeTrainingFileAdd,
+ safeTrainingFileDel,
+ } from "@/api/safeProduction/safetyTrainingAssessment";
+ import { blobValidate } from "@/utils/ruoyi";
+
+ // 闄勪欢鍒楄〃
+ const fileList = ref([]);
+
+ // 杩斿洖涓婁竴椤�
+ const goBack = () => {
+ uni.navigateBack();
+ };
+ // const request = axios.create({
+ // baseURL: "URL.com",
+ // adapter: axiosAdapterUniapp,
+ // });
+ // 鑾峰彇鏂囦欢鍥炬爣
+ const getFileIcon = fileType => {
+ const iconMap = {
+ doc: "document",
+ docx: "document",
+ xls: "grid",
+ xlsx: "grid",
+ pdf: "document",
+ ppt: "copy",
+ pptx: "copy",
+ txt: "document",
+ jpg: "image",
+ jpeg: "image",
+ png: "image",
+ gif: "image",
+ zip: "folder",
+ rar: "folder",
+ };
+ return iconMap[fileType.toLowerCase()] || "document";
+ };
+
+ // 鑾峰彇鏂囦欢鍥炬爣鏍峰紡绫�
+ const getFileIconClass = fileType => {
+ const colorMap = {
+ doc: "blue",
+ docx: "blue",
+ xls: "green",
+ xlsx: "green",
+ pdf: "red",
+ ppt: "orange",
+ pptx: "orange",
+ txt: "gray",
+ jpg: "purple",
+ jpeg: "purple",
+ png: "purple",
+ gif: "purple",
+ zip: "yellow",
+ rar: "yellow",
+ };
+ return colorMap[fileType.toLowerCase()] || "gray";
+ };
+
+ // 鏍煎紡鍖栨枃浠跺ぇ灏�
+ const formatFileSize = bytes => {
+ if (bytes === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+ };
+
+ // 閫夋嫨鏂囦欢
+ const chooseFile = () => {
+ uni.chooseImage({
+ count: 9,
+ sizeType: ["original", "compressed"],
+ sourceType: ["album", "camera"],
+ success: res => {
+ console.log(res, "閫夋嫨鍥剧墖鎴愬姛");
+ uploadFiles(res.tempFiles);
+ },
+ fail: err => {
+ console.error("閫夋嫨鍥剧墖澶辫触:", err);
+ showToast("閫夋嫨鏂囦欢澶辫触");
+ },
+ });
+ // uni.chooseFile({
+ // count: 9,
+ // extension: [
+ // ".doc",
+ // ".docx",
+ // ".xls",
+ // ".xlsx",
+ // ".pdf",
+ // ".ppt",
+ // ".pptx",
+ // ".txt",
+ // ".jpg",
+ // ".jpeg",
+ // ".png",
+ // ".gif",
+ // ".zip",
+ // ".rar",
+ // ],
+ // success: res => {
+ // console.log(res, "閫夋嫨鏂囦欢鎴愬姛");
+ // uploadFiles(res.tempFiles);
+ // },
+ // fail: err => {
+ // showToast("閫夋嫨鏂囦欢澶辫触");
+ // },
+ // });
+ };
+
+ // 涓婁紶鏂囦欢
+ const uploadFiles = tempFiles => {
+ console.log(tempFiles, "涓婁紶鏂囦欢1");
+ tempFiles.forEach((tempFile, index) => {
+ // 鏄剧ず涓婁紶涓彁绀�
+ uni.showLoading({
+ title: "涓婁紶涓�...",
+ mask: true,
+ });
+ console.log(tempFile, "涓婁紶鏂囦欢2");
+ // 1. 鐩存帴浣跨敤 uni.uploadFile 涓婁紶鏂囦欢
+ uni.uploadFile({
+ url: config.baseUrl + "/file/upload",
+ filePath: tempFile.path,
+ name: "file",
+ header: {
+ Authorization: "Bearer " + getToken(),
+ },
+ success: uploadRes => {
+ uni.hideLoading();
+ console.log(uploadRes, "涓婁紶鏂囦欢3");
+
+ try {
+ const res = JSON.parse(uploadRes.data);
+ console.log(res, "涓婁紶鏂囦欢4");
+ if (res.code === 200) {
+ // 2. 鎻愬彇鏂囦欢淇℃伅
+ const fileName = tempFile.name
+ ? tempFile.name
+ : tempFile.path.split("/").pop();
+ // const fileType = fileName.split(".").pop();
+ // 3. 鏋勯�犱繚瀛樻枃浠朵俊鎭殑鍙傛暟
+ const saveData = {
+ name: fileName,
+ safeTrainingId: rulesRegulationsManagementId.value,
+ url: res.data.tempPath || "",
+ };
+ console.log(saveData, "淇濆瓨鏂囦欢淇℃伅鍙傛暟");
+ // 4. 璋冪敤 addRuleFile 鎺ュ彛淇濆瓨鏂囦欢淇℃伅
+ safeTrainingFileAdd(saveData)
+ .then(addRes => {
+ if (addRes.code === 200) {
+ // 5. 娣诲姞鍒版枃浠跺垪琛�
+ const newFile = {
+ ...addRes.data,
+ uploadTime: new Date().toLocaleString(),
+ };
+ // fileList.value.push(newFile);
+ getFileList();
+ showToast("涓婁紶鎴愬姛");
+ } else {
+ showToast("淇濆瓨鏂囦欢淇℃伅澶辫触");
+ }
+ })
+ .catch(err => {
+ console.error("淇濆瓨鏂囦欢淇℃伅澶辫触:", err);
+ showToast("淇濆瓨鏂囦欢淇℃伅澶辫触");
+ });
+ } else {
+ showToast("鏂囦欢涓婁紶澶辫触");
+ }
+ } catch (e) {
+ console.error("瑙f瀽涓婁紶缁撴灉澶辫触:", e);
+ showToast("涓婁紶澶辫触");
+ }
+ },
+ fail: err => {
+ uni.hideLoading();
+ console.error("涓婁紶澶辫触:", err);
+ showToast("涓婁紶澶辫触");
+ },
+ });
+ });
+ };
+ // 涓嬭浇鏂囦欢
+ const downloadFile = file => {
+ var url =
+ config.baseUrl +
+ "/common/download?fileName=" +
+ encodeURIComponent(file.url) +
+ "&delete=true";
+ console.log(url, "url");
+
+ uni
+ .downloadFile({
+ url: url,
+ responseType: "blob",
+ header: { Authorization: "Bearer " + getToken() },
+ })
+ .then(res => {
+ console.log(res, "涓嬭浇鏂囦欢");
+ let osType = uni.getStorageSync("deviceInfo").osName;
+ let filePath = res.tempFilePath;
+ if (osType === "ios") {
+ uni.openDocument({
+ filePath: filePath,
+ showMenu: true,
+ success: res => {
+ resolve(res);
+ },
+ fail: err => {
+ console.log("uni.openDocument--fail");
+ reject(err);
+ },
+ });
+ } else {
+ uni.saveFile({
+ tempFilePath: filePath,
+ success: fileRes => {
+ uni.showToast({
+ icon: "none",
+ mask: true,
+ title:
+ "鏂囦欢宸蹭繚瀛橈細Android/data/uni.UNI720216F/apps/__UNI__720216F/" +
+ fileRes.savedFilePath, //淇濆瓨璺緞
+ duration: 3000,
+ });
+ setTimeout(() => {
+ //鎵撳紑鏂囨。鏌ョ湅
+ uni.openDocument({
+ filePath: fileRes.savedFilePath,
+ success: function (res) {
+ resolve(fileRes);
+ },
+ });
+ }, 3000);
+ },
+ fail: err => {
+ console.log("uni.save--fail");
+ reject(err);
+ },
+ });
+ }
+ // const isBlob = blobValidate(res.data);
+ // if (isBlob) {
+ // const blob = new Blob([res.data], { type: "text/plain" });
+ // const url = URL.createObjectURL(blob);
+ // const downloadLink = document.getElementById("downloadLink");
+ // downloadLink.href = url;
+ // downloadLink.download = file.name;
+ // downloadLink.click();
+ // showToast("涓嬭浇鎴愬姛");
+ // } else {
+ // showToast("涓嬭浇澶辫触");
+ // }
+ })
+ .catch(err => {
+ console.error("涓嬭浇澶辫触:", err);
+ showToast("涓嬭浇澶辫触");
+ });
+ };
+
+ // 纭鍒犻櫎
+ const confirmDelete = (file, index) => {
+ uni.showModal({
+ title: "鍒犻櫎纭",
+ content: `纭畾瑕佸垹闄ら檮浠� "${file.name}" 鍚楋紵`,
+ success: res => {
+ if (res.confirm) {
+ deleteFile(file.id, index);
+ }
+ },
+ });
+ };
+
+ // 鍒犻櫎鏂囦欢
+ const deleteFile = (fileId, index) => {
+ uni.showLoading({
+ title: "鍒犻櫎涓�...",
+ mask: true,
+ });
+
+ safeTrainingFileDel([fileId])
+ .then(res => {
+ uni.hideLoading();
+ if (res.code === 200) {
+ // fileList.value.splice(index, 1);
+ getFileList();
+ showToast("鍒犻櫎鎴愬姛");
+ } else {
+ showToast("鍒犻櫎澶辫触");
+ }
+ })
+ .catch(err => {
+ uni.hideLoading();
+ showToast("鍒犻櫎澶辫触");
+ });
+ };
+
+ // 鏄剧ず鎻愮ず
+ const showToast = message => {
+ uni.showToast({
+ title: message,
+ icon: "none",
+ });
+ };
+ const rulesRegulationsManagementId = ref("");
+ // 椤甸潰鍔犺浇鏃�
+ onMounted(() => {
+ rulesRegulationsManagementId.value = uni.getStorageSync(
+ "safetyTrainingFileId"
+ );
+ // 浠� API 鑾峰彇闄勪欢鍒楄〃
+ getFileList();
+ // 浠庢湰鍦板瓨鍌ㄨ幏鍙� rulesRegulationsManagementId
+ });
+
+ // 鑾峰彇闄勪欢鍒楄〃
+ const getFileList = () => {
+ uni.showLoading({
+ title: "鍔犺浇涓�...",
+ mask: true,
+ });
+
+ safeTrainingFileListPage({
+ safeTrainingId: rulesRegulationsManagementId.value,
+ current: -1,
+ size: -1,
+ })
+ .then(res => {
+ uni.hideLoading();
+ if (res.code === 200) {
+ fileList.value = res.data.records || [];
+ } else {
+ showToast("鑾峰彇闄勪欢鍒楄〃澶辫触");
+ }
+ })
+ .catch(err => {
+ uni.hideLoading();
+ showToast("鑾峰彇闄勪欢鍒楄〃澶辫触");
+ });
+ };
+</script>
+
+<style scoped lang="scss">
+ @import "../../../styles/sales-common.scss";
+
+ .file-list-page {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 100rpx;
+ }
+
+ .file-list-container {
+ padding: 20rpx;
+ }
+
+ .file-list {
+ background: #ffffff;
+ border-radius: 8rpx;
+ overflow: hidden;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+ }
+
+ .file-item {
+ display: flex;
+ align-items: center;
+ padding: 20rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+
+ .file-icon {
+ width: 56rpx;
+ height: 56rpx;
+ border-radius: 8rpx;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-right: 20rpx;
+
+ &.blue {
+ background: #409eff;
+ }
+
+ &.green {
+ background: #67c23a;
+ }
+
+ &.red {
+ background: #f56c6c;
+ }
+
+ &.orange {
+ background: #e6a23c;
+ }
+
+ &.gray {
+ background: #909399;
+ }
+
+ &.purple {
+ background: #909399;
+ }
+
+ &.yellow {
+ background: #e6a23c;
+ }
+ }
+
+ .file-info {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .file-name {
+ display: block;
+ font-size: 16px;
+ color: #303133;
+ margin-bottom: 8rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .file-meta {
+ display: block;
+ font-size: 12px;
+ color: #909399;
+ }
+
+ .file-actions {
+ display: flex;
+ gap: 12rpx;
+ }
+
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 0;
+ background: #ffffff;
+ border-radius: 8rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+ }
+
+ .empty-text {
+ font-size: 14px;
+ color: #909399;
+ margin-top: 20rpx;
+ }
+
+ .upload-button {
+ position: fixed;
+ bottom: 40rpx;
+ right: 40rpx;
+ width: 130rpx;
+ height: 130rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4);
+ z-index: 1000;
+ }
+
+ .upload-text {
+ font-size: 10px;
+ color: #ffffff;
+ margin-top: 4rpx;
+ }
+
+ .upload-progress {
+ padding: 40rpx 0;
+ }
+
+ .upload-progress-text {
+ display: block;
+ text-align: center;
+ margin-top: 20rpx;
+ font-size: 14px;
+ color: #606266;
+ }
+</style>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/index.vue b/src/pages/safeProduction/safetyTrainingAssessment/index.vue
new file mode 100644
index 0000000..f2107ae
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/record.vue b/src/pages/safeProduction/safetyTrainingAssessment/record.vue
new file mode 100644
index 0000000..825b7d6
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/record.vue
@@ -0,0 +1,546 @@
+<template>
+ <view class="training-record">
+ <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+ <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="searchName"
+ clearable />
+ </view>
+ <view class="filter-button"
+ @click="searchName">
+ <u-icon name="search"
+ size="24"
+ color="#999"></u-icon>
+ </view>
+ </view>
+ </view>
+ <!-- 浜哄憳鍗$墖鍒楄〃 -->
+ <view class="user-card-list"
+ v-if="userList.length > 0">
+ <view v-for="(user, index) in userList"
+ :key="index"
+ class="user-card">
+ <!-- 鍗$墖澶撮儴 -->
+ <view class="card-header"
+ @click="toggleUserCard(index)">
+ <view class="header-left">
+ <text class="user-name">{{ user.nickName }}</text>
+ <text class="user-dept">鎵�灞烇細{{ user.deptNames || '-' }}</text>
+ <text class="user-dept">鑱旂郴鏂瑰紡锛歿{ user.phonenumber || '-' }}</text>
+ <!-- 鍩硅缁熻淇℃伅 -->
+ </view>
+ <u-icon :name="expandedUsers[index] ? 'arrow-up' : 'arrow-down'"
+ size="20"
+ color="#999"></u-icon>
+ </view>
+ <!-- 鍗$墖鍐呭锛堟姌鍙犻儴鍒嗭級 -->
+ <view class="card-content"
+ v-if="expandedUsers[index]">
+ <!-- 骞翠唤绛涢�� -->
+ <view class="year-filter-section">
+ <!-- <text class="filter-label">骞翠唤绛涢��</text> -->
+ <view class="year-options">
+ <u-tag v-for="year in yearOptions"
+ :key="year"
+ :text="year"
+ :type="userYearFilters[user.userId] === year.toString() ? 'primary' : 'info'"
+ @click="() => {
+ userYearFilters[user.userId] = year.toString();
+ filterUserCourses(user.userId);
+ }"
+ :class="{ active: userYearFilters[user.userId] === year.toString() }"
+ style="margin-right: 8px; margin-bottom: 8px;"></u-tag>
+ </view>
+ </view>
+ <!-- 鍩硅璇剧▼鍒楄〃 -->
+ <view class="course-list"
+ v-if="userCourses[user.userId] && userCourses[user.userId].length > 0">
+ <view class="user-stats"
+ v-if="userStats[user.userId]">
+ <text class="stat-item">鍩硅娆℃暟: {{ userStats[user.userId].total }}</text>
+ <text class="stat-item success">鍚堟牸: {{ userStats[user.userId].qualified }}</text>
+ <text class="stat-item danger">涓嶅悎鏍�: {{ userStats[user.userId].unqualified }}</text>
+ </view>
+ <view v-for="(course, courseIndex) in userCourses[user.userId]"
+ :key="courseIndex">
+ <view class="course-item"
+ v-if="userYearFilters[user.userId] === '鍏ㄩ儴' || course.trainingDate.includes(userYearFilters[user.userId])">
+ <view class="course-header">
+ <text class="course-date">{{ course.trainingDate || '-' }}</text>
+ <u-tag :type="course.examinationResults === '鍚堟牸' ? 'success' : 'error'">
+ {{ course.examinationResults || '-' }}
+ </u-tag>
+ </view>
+ <view class="course-info">
+ <text class="info-label">鍩硅鍐呭锛�</text>
+ <text class="info-value">{{ course.trainingContent || '-' }}</text>
+ </view>
+ <view class="course-info">
+ <text class="info-label">鍩硅璇炬椂锛�</text>
+ <text class="info-value">{{ course.classHour || '-' }}</text>
+ </view>
+ </view>
+ </view>
+ <!-- 绛涢�夊悗鏃犳暟鎹� -->
+ <view v-if="userYearFilters[user.userId] === '鍏ㄩ儴' ? userCourses[user.userId].length === 0 : userCourses[user.userId].filter(c => c.trainingDate.includes(userYearFilters[user.userId])).length === 0"
+ class="empty-course">
+ <text class="empty-text">{{ userYearFilters[user.userId] === '鍏ㄩ儴' ? '鏆傛棤鍩硅璁板綍' : '璇ュ勾浠芥殏鏃犲煿璁褰�' }}</text>
+ </view>
+ </view>
+ <!-- 绌虹姸鎬� -->
+ <view v-else
+ class="empty-course">
+ <text class="empty-text">鏆傛棤鍩硅璁板綍</text>
+ </view>
+ </view>
+ <!-- 瀵煎嚭鎸夐挳 -->
+ <!-- <view class="course-export">
+ <u-button type="primary"
+ size="small"
+ @click="exportUserRecord(user.userId)">瀵煎嚭璁板綍</u-button>
+ </view> -->
+ </view>
+ </view>
+ <view v-else
+ class="empty-state">
+ <up-icon name="people"
+ size="64"
+ color="#c0c4cc"></up-icon>
+ <text class="empty-text">鏆傛棤浜哄憳鏁版嵁</text>
+ </view>
+ <!-- 绌虹姸鎬� -->
+ <!-- 骞翠唤閫夋嫨鍣� -->
+ <up-datetime-picker :show="yearPickerVisible"
+ mode="year"
+ @confirm="handleYearConfirm"
+ @cancel="yearPickerVisible = false"
+ title="閫夋嫨骞翠唤" />
+ </view>
+</template>
+
+<script setup>
+ import { ref, onMounted, reactive } from "vue";
+ import { onShow } from "@dcloudio/uni-app";
+ import PageHeader from "@/components/PageHeader.vue";
+ import { safeTrainingDetailListPage } from "@/api/safeProduction/safetyTrainingAssessment";
+ import { userListNoPage } from "@/api/system/user.js";
+ import { getToken } from "@/utils/auth";
+ import config from "@/config";
+
+ // 椤甸潰鐘舵��
+ const userList = ref([]);
+ const userCourses = ref({}); // 瀛樺偍姣忎釜鐢ㄦ埛鐨勫煿璁绋�
+ const userStats = ref({}); // 瀛樺偍姣忎釜鐢ㄦ埛鐨勫煿璁粺璁′俊鎭�
+ const expandedUsers = ref([]); // 鎺у埗鐢ㄦ埛鍗$墖灞曞紑鐘舵��
+ const loading = ref(false);
+ const courseLoading = ref({}); // 鎺у埗姣忎釜鐢ㄦ埛鐨勮绋嬪姞杞界姸鎬�
+ const userYearFilters = ref({}); // 瀛樺偍姣忎釜鐢ㄦ埛鐨勫勾浠界瓫閫夋潯浠�
+ const yearOptions = ref([]); // 骞翠唤閫夐」锛堜粖骞村拰杩囧幓涓夊勾锛�
+
+ // 鎼滅储琛ㄥ崟
+ const searchForm = reactive({
+ searchText: "",
+ invoiceDate: "",
+ });
+
+ // 骞翠唤閫夋嫨鍣ㄧ姸鎬�
+ const yearPickerVisible = ref(false);
+
+ // 杩斿洖涓婁竴椤�
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ // 鐢熸垚骞翠唤閫夐」
+ const generateYearOptions = () => {
+ const currentYear = new Date().getFullYear();
+ const options = [];
+ // 娣诲姞"鍏ㄩ儴"閫夐」
+ options.push("鍏ㄩ儴");
+ for (let i = 0; i < 4; i++) {
+ options.push(currentYear - i);
+ }
+ yearOptions.value = options;
+ };
+
+ // 鎼滅储浜哄憳鍚嶇О
+ const searchName = () => {
+ getUserList();
+ };
+
+ // 鏄剧ず骞翠唤閫夋嫨鍣�
+ const showYearPicker = () => {
+ yearPickerVisible.value = true;
+ };
+
+ // 澶勭悊骞翠唤閫夋嫨纭
+ const handleYearConfirm = e => {
+ searchForm.invoiceDate = e.value;
+ yearPickerVisible.value = false;
+ };
+
+ // 鎸夊勾浠芥悳绱�
+ const searchDate = () => {
+ // 閬嶅巻鎵�鏈夊睍寮�鐨勭敤鎴峰崱鐗囷紝閲嶆柊鍔犺浇鍩硅璁板綍
+ userList.value.forEach((user, index) => {
+ if (expandedUsers.value[index]) {
+ getUserCourses(user.userId, index);
+ }
+ });
+ };
+
+ // 鑾峰彇浜哄憳鍒楄〃
+ const getUserList = () => {
+ loading.value = true;
+ userListNoPage()
+ .then(res => {
+ loading.value = false;
+ if (res.data && res.data.length > 0) {
+ let users = res.data;
+ // 濡傛灉鏈夋悳绱㈠叧閿瘝锛岃繘琛岀瓫閫�
+ if (searchForm.searchText) {
+ users = users.filter(user =>
+ user.nickName.includes(searchForm.searchText)
+ );
+ }
+ userList.value = users;
+ // 鍒濆鍖栨墍鏈夊崱鐗囦负鏀惰捣鐘舵��
+ expandedUsers.value = new Array(userList.value.length).fill(false);
+ } else {
+ userList.value = [];
+ expandedUsers.value = [];
+ }
+ })
+ .catch(() => {
+ loading.value = false;
+ uni.showToast({ title: "鑾峰彇浜哄憳鍒楄〃澶辫触", icon: "none" });
+ });
+ };
+
+ // 鍒囨崲鐢ㄦ埛鍗$墖灞曞紑鐘舵��
+ const toggleUserCard = index => {
+ const user = userList.value[index];
+ expandedUsers.value[index] = !expandedUsers.value[index];
+
+ // 濡傛灉灞曞紑鍗$墖锛屽姞杞藉煿璁褰�
+ if (expandedUsers.value[index]) {
+ // 鍒濆鍖栧勾浠界瓫閫夋潯浠朵负"鍏ㄩ儴"
+ userYearFilters.value[user.userId] = "鍏ㄩ儴";
+ getUserCourses(user.userId, index);
+ }
+ };
+
+ // 绛涢�夌敤鎴峰煿璁绋�
+ const filterUserCourses = userId => {
+ if (!userYearFilters.value[userId]) return;
+ console.log("userYearFilters", userYearFilters.value);
+ const year = userYearFilters.value[userId];
+ const allCourses = userCourses.value[userId] || [];
+
+ // 绛涢�夋寚瀹氬勾浠界殑鍩硅璁板綍
+ let filteredCourses = allCourses;
+ if (year !== "鍏ㄩ儴") {
+ filteredCourses = allCourses.filter(
+ course => course.trainingDate && course.trainingDate.includes(year)
+ );
+ }
+ console.log("filteredCourses", filteredCourses);
+
+ // 鏇存柊缁熻淇℃伅
+ const total = filteredCourses.length;
+ const qualified = filteredCourses.filter(
+ course => course.examinationResults === "鍚堟牸"
+ ).length;
+ const unqualified = filteredCourses.filter(
+ course => course.examinationResults === "涓嶅悎鏍�"
+ ).length;
+ userStats.value[userId] = {
+ total,
+ qualified,
+ unqualified,
+ };
+ };
+
+ // 鑾峰彇鐢ㄦ埛鍩硅璇剧▼
+ const getUserCourses = (userId, index) => {
+ courseLoading.value[userId] = true;
+
+ const params = {
+ userId,
+ // 濡傛灉鏈夊勾浠界瓫閫夛紝娣诲姞骞翠唤鍙傛暟
+ // 杩欓噷闇�瑕佹牴鎹悗绔帴鍙g殑瀹為檯鍙傛暟鍚嶈繘琛岃皟鏁�
+ };
+
+ safeTrainingDetailListPage(params)
+ .then(res => {
+ courseLoading.value[userId] = false;
+ if (res.data && res.data.records) {
+ let courses = res.data.records;
+ // 濡傛灉鏈夊勾浠界瓫閫夛紝杩涜绛涢��
+ if (searchForm.invoiceDate) {
+ const year = searchForm.invoiceDate.substring(0, 4);
+ courses = courses.filter(
+ course => course.trainingDate && course.trainingDate.includes(year)
+ );
+ }
+ userCourses.value[userId] = courses;
+
+ // 璁$畻鍩硅缁熻淇℃伅
+ const total = courses.length;
+ const qualified = courses.filter(
+ course => course.examinationResults === "鍚堟牸"
+ ).length;
+ const unqualified = courses.filter(
+ course => course.examinationResults === "涓嶅悎鏍�"
+ ).length;
+
+ userStats.value[userId] = {
+ total,
+ qualified,
+ unqualified,
+ };
+ } else {
+ userCourses.value[userId] = [];
+ userStats.value[userId] = {
+ total: 0,
+ qualified: 0,
+ unqualified: 0,
+ };
+ }
+ })
+ .catch(() => {
+ courseLoading.value[userId] = false;
+ uni.showToast({ title: "鑾峰彇鍩硅璁板綍澶辫触", icon: "none" });
+ });
+ };
+
+ // 椤甸潰鍔犺浇
+ onMounted(() => {
+ // 鐢熸垚骞翠唤閫夐」
+ generateYearOptions();
+ getUserList();
+ });
+
+ // 椤甸潰鏄剧ず鏃跺埛鏂�
+ onShow(() => {
+ // 鍙互鍦ㄨ繖閲屾坊鍔犲埛鏂伴�昏緫
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "../../../styles/sales-common.scss";
+ .training-record {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 20px;
+ }
+
+ // 骞翠唤閫夋嫨鍣�
+ .year-picker {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 15px;
+ background: #f8f9fa;
+ border-radius: 4px;
+ margin-right: 10px;
+ }
+
+ .picker-text {
+ font-size: 14px;
+ color: #333;
+ }
+
+ // 浜哄憳鍗$墖鍒楄〃
+ .user-card-list {
+ padding: 10px;
+ }
+
+ // 浜哄憳鍗$墖
+ .user-card {
+ background: #fff;
+ border-radius: 8px;
+ margin-bottom: 12px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
+ overflow: hidden;
+ border: 1px solid #e8e8e8;
+ }
+
+ // 鍗$墖澶撮儴
+ .card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px;
+ background: #f5f7fa;
+ border-bottom: 1px solid #e8e8e8;
+ cursor: pointer;
+ }
+
+ .header-left {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .user-name {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 20rpx;
+ }
+
+ .user-dept {
+ font-size: 14px;
+ color: #666;
+ margin-bottom: 8px;
+ }
+
+ // 鍩硅缁熻淇℃伅
+ .user-stats {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-bottom: 8px;
+ }
+
+ .stat-item {
+ font-size: 12px;
+ color: #555;
+ padding: 5px 10px;
+ background: #f0f2f5;
+ border-radius: 4px;
+ border: 1px solid #e0e0e0;
+ font-weight: 500;
+ }
+
+ .stat-item.success {
+ background: #e6f7ff;
+ color: #1890ff;
+ border-color: #91d5ff;
+ }
+
+ .stat-item.danger {
+ background: #fff1f0;
+ color: #ff4d4f;
+ border-color: #ffccc7;
+ }
+
+ // 骞翠唤绛涢�夊尯鍩�
+ .year-filter-section {
+ margin-bottom: 16px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #e8e8e8;
+ }
+
+ .filter-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 8px;
+ display: block;
+ }
+
+ .year-options {
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ // 鍗$墖鍐呭
+ .card-content {
+ padding: 16px;
+ }
+
+ // 鍩硅璇剧▼鍒楄〃
+ .course-list {
+ margin-bottom: 16px;
+ }
+
+ // 璇剧▼椤�
+ .course-item {
+ background: #fafafa;
+ border-radius: 6px;
+ padding: 14px;
+ margin-bottom: 12px;
+ border: 1px solid #e8e8e8;
+ }
+
+ // 璇剧▼澶撮儴
+ .course-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #f0f0f0;
+ }
+
+ .course-date {
+ font-size: 14px;
+ font-weight: 600;
+ color: #333;
+ }
+
+ // 璇剧▼淇℃伅
+ .course-info {
+ display: flex;
+ margin-bottom: 8px;
+ align-items: flex-start;
+ }
+
+ .info-label {
+ font-size: 14px;
+ color: #666;
+ width: 80px;
+ flex-shrink: 0;
+ }
+
+ .info-value {
+ font-size: 14px;
+ color: #333;
+ flex: 1;
+ line-height: 1.4;
+ }
+
+ // 璇剧▼瀵煎嚭鎸夐挳
+ .course-export {
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ // 绌虹姸鎬�
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 0;
+ }
+
+ .empty-course {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 30px 0;
+ color: #999;
+ font-size: 14px;
+ }
+
+ .empty-text {
+ font-size: 14px;
+ color: #999;
+ margin-top: 16px;
+ }
+ :deep(.u-tag--info) {
+ background-color: #c1c3c8;
+ border-width: 1px;
+ border-color: #c1c3c8;
+ }
+</style>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/resultDetail.vue b/src/pages/safeProduction/safetyTrainingAssessment/resultDetail.vue
new file mode 100644
index 0000000..9fd10a1
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/resultDetail.vue
@@ -0,0 +1,388 @@
+<template>
+ <view class="result-detail">
+ <!-- 椤甸潰澶撮儴 -->
+ <PageHeader title="缁撴灉鏄庣粏"
+ @back="goBack" />
+ <!-- 鍐呭鍖哄煙 -->
+ <view class="content">
+ <!-- 璇剧▼璇︽儏 -->
+ <view class="section">
+ <view class="section-title">璇剧▼璇︽儏</view>
+ <view class="info-list">
+ <view class="info-item">
+ <text class="info-label">璇剧▼缂栧彿</text>
+ <text class="info-value">{{ currentTraining.courseCode || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鍐呭</text>
+ <text class="info-value">{{ currentTraining.trainingContent || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鐘舵��</text>
+ <text class="info-value">
+ <u-tag :type="currentTraining.state === 0 ? 'success' : (currentTraining.state === 1 ? 'warning' : 'info')">
+ {{ currentTraining.state === 0 ? '鏈紑濮�' : (currentTraining.state === 1 ? '杩涜涓�' : '宸茬粨鏉�') }}
+ </u-tag>
+ </text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅璁插笀</text>
+ <text class="info-value">{{ currentTraining.trainingLecturer || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅寮�濮嬫椂闂�</text>
+ <text class="info-value">{{ currentTraining.trainingDate + ' ' + currentTraining.openingTime || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅缁撴潫鏃堕棿</text>
+ <text class="info-value">{{ currentTraining.trainingDate + ' ' + currentTraining.endTime || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鐩爣</text>
+ <text class="info-value">{{ currentTraining.trainingObjectives || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍙傚姞瀵硅薄</text>
+ <text class="info-value">{{ currentTraining.participants || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鏂瑰紡</text>
+ <text class="info-value">{{ getTrainingModeLabel(currentTraining.trainingMode) || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鍦扮偣</text>
+ <text class="info-value">{{ currentTraining.placeTraining || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">璇炬椂</text>
+ <text class="info-value">{{ currentTraining.classHour || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">璇剧▼瀛﹀垎</text>
+ <text class="info-value">{{ currentTraining.projectCredits || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鎶ュ悕浜烘暟</text>
+ <text class="info-value">{{ currentTraining.nums || '-' }}</text>
+ </view>
+ </view>
+ </view>
+ <!-- 璇剧▼璇勪环 -->
+ <view class="section">
+ <view class="section-title">璇剧▼璇勪环</view>
+ <u-form ref="formRef"
+ label-width="90"
+ :model="endform">
+ <u-form-item label="璇勪环浜�">
+ <u-input v-model="endform.assessmentUserName"
+ disabled
+ placeholder="璇烽�夋嫨璇勪环浜�" />
+ </u-form-item>
+ <u-form-item label="璇勪环鏃堕棿">
+ <u-input v-model="endform.assessmentDate"
+ disabled
+ placeholder="璇烽�夋嫨璇勪环鏃堕棿" />
+ </u-form-item>
+ <u-form-item label="鑰冩牳鏂瑰紡">
+ <u-input v-model="endform.assessmentMethod"
+ placeholder="璇疯緭鍏ヨ�冩牳鏂瑰紡" />
+ </u-form-item>
+ <u-form-item label="缁煎悎璇勪环">
+ <u-input v-model="endform.comprehensiveAssessment"
+ placeholder="璇疯緭鍏ユ湰娆¤绋嬬患鍚堣瘎浠�" />
+ </u-form-item>
+ <u-form-item label="鍩硅鎽樿">
+ <u-textarea v-model="endform.trainingAbstract"
+ :rows="4"
+ placeholder="璇疯緭鍏ュ煿璁憳瑕�" />
+ </u-form-item>
+ </u-form>
+ </view>
+ <!-- 鑰冩牳鍒楄〃 -->
+ <view class="section">
+ <view class="section-title">鑰冩牳鍒楄〃</view>
+ <view class="assessment-list">
+ <view v-for="(item, index) in endform.safeTrainingDetailsDtoList"
+ :key="index"
+ class="assessment-item">
+ <view class="assessment-info">
+ <view class="info-row">
+ <text class="label">濮撳悕锛�</text>
+ <text class="value">{{ item.nickName || '-' }}</text>
+ </view>
+ <view class="info-row">
+ <text class="label">鐢佃瘽鍙风爜锛�</text>
+ <text class="value">{{ item.phonenumber || '-' }}</text>
+ </view>
+ </view>
+ <view class="assessment-result">
+ <text class="result-label">鑰冩牳缁撴灉锛�</text>
+ <u-radio-group v-model="endform.safeTrainingDetailsDtoList[index].examinationResults"
+ :key="index"
+ size="default">
+ <u-radio label="鍚堟牸"
+ name="鍚堟牸"></u-radio>
+ <u-radio label="涓嶅悎鏍�"
+ name="涓嶅悎鏍�"></u-radio>
+ </u-radio-group>
+ </view>
+ </view>
+ </view>
+ </view>
+ <!-- 鎻愪氦鎸夐挳 -->
+ <view class="submit-btn">
+ <u-button type="primary"
+ @click="submitForm"
+ :loading="loading">鎻愪氦</u-button>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+ import { ref, onMounted } from "vue";
+ import PageHeader from "@/components/PageHeader.vue";
+ import { onLoad } from "@dcloudio/uni-app";
+ import { useDict } from "@/utils/dict";
+ import dayjs from "dayjs";
+ import useUserStore from "@/store/modules/user";
+ import {
+ safeTrainingGet,
+ safeTrainingSave,
+ } from "@/api/safeProduction/safetyTrainingAssessment";
+
+ // 鑾峰彇瀛楀吀鏁版嵁
+ const { safe_training_methods } = useDict("safe_training_methods");
+
+ // 椤甸潰鐘舵��
+ const loading = ref(false);
+ const currentTraining = ref({});
+ const endform = ref({
+ assessmentUserId: "",
+ assessmentUserName: "",
+ assessmentMethod: "",
+ assessmentDate: "",
+ comprehensiveAssessment: "",
+ trainingAbstract: "",
+ safeTrainingFileList: [],
+ safeTrainingDetailsDtoList: [],
+ });
+
+ // 鑾峰彇鍩硅鏂瑰紡鏍囩
+ const getTrainingModeLabel = val => {
+ if (!safe_training_methods || !Array.isArray(safe_training_methods.value)) {
+ return val;
+ }
+ const item = safe_training_methods.value.find(
+ i => String(i.value) === String(val)
+ );
+ return item ? item.label : val;
+ };
+
+ // 杩斿洖涓婁竴椤�
+ const goBack = () => {
+ uni.navigateBack();
+ };
+ // 鎻愪氦琛ㄥ崟
+ const submitForm = () => {
+ // 楠岃瘉鑰冩牳缁撴灉
+ for (let i = 0; i < endform.value.safeTrainingDetailsDtoList.length; i++) {
+ const item = endform.value.safeTrainingDetailsDtoList[i];
+ if (!item.examinationResults) {
+ uni.showToast({
+ title: `璇烽�夋嫨${item.nickName}鐨勮�冩牳缁撴灉`,
+ icon: "none",
+ });
+ return;
+ }
+ }
+
+ loading.value = true;
+ safeTrainingSave(endform.value)
+ .then(res => {
+ loading.value = false;
+ if (res.code === 200) {
+ uni.showToast({ title: "鎻愪氦鎴愬姛", icon: "success" });
+ setTimeout(() => {
+ goBack();
+ }, 500);
+ } else {
+ uni.showToast({ title: res.msg || "鎻愪氦澶辫触", icon: "none" });
+ }
+ })
+ .catch(() => {
+ loading.value = false;
+ uni.showToast({ title: "鎻愪氦澶辫触锛岃閲嶈瘯", icon: "none" });
+ });
+ };
+
+ // 椤甸潰鍔犺浇
+ onLoad(() => {
+ const trainingId = uni.getStorageSync("safetyTrainingResultId");
+ const trainingNums = uni.getStorageSync("safetyTrainingResultNums");
+
+ if (trainingId) {
+ getTrainingDetail(trainingId, trainingNums);
+ }
+ });
+ const userStore = useUserStore();
+ const getuserInfo = () => {
+ const userInfo = {
+ id: "",
+ nickName: "",
+ };
+ userStore.getInfo().then(res => {
+ userInfo.id = res.user.userId;
+ userInfo.nickName = res.user.nickName;
+ endform.value.assessmentUserName = res.user.nickName;
+ endform.value.assessmentUserId = res.user.userId;
+ });
+ return userInfo;
+ };
+
+ // 鑾峰彇鍩硅璇︽儏
+ const getTrainingDetail = (id, trainingNums) => {
+ loading.value = true;
+ safeTrainingGet({ id })
+ .then(res => {
+ loading.value = false;
+ if (res.code === 200) {
+ currentTraining.value = res.data;
+ currentTraining.value.nums = trainingNums;
+
+ endform.value = { ...res.data };
+ // 璁剧疆榛樿鍊�
+ if (!endform.value.assessmentUserId) {
+ getuserInfo();
+ }
+
+ endform.value.assessmentDate = endform.value.assessmentDate
+ ? dayjs(endform.value.assessmentDate).format("YYYY-MM-DD")
+ : dayjs().format("YYYY-MM-DD");
+ endform.value.safeTrainingDetailsDtoList =
+ endform.value.safeTrainingDetailsDtoList || [];
+ } else {
+ uni.showToast({ title: "鑾峰彇璇︽儏澶辫触", icon: "none" });
+ }
+ })
+ .catch(() => {
+ loading.value = false;
+ uni.showToast({ title: "鑾峰彇璇︽儏澶辫触", icon: "none" });
+ });
+ };
+</script>
+
+<style scoped lang="scss">
+ .result-detail {
+ min-height: 100vh;
+ background: #f8f9fa;
+ }
+
+ .content {
+ padding: 16px;
+ }
+
+ .section {
+ background: #fff;
+ border-radius: 8px;
+ padding: 16px;
+ margin-bottom: 16px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+ }
+
+ .section-title {
+ font-size: 16px;
+ font-weight: 600;
+ margin-bottom: 16px;
+ color: #333;
+ }
+
+ .info-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .info-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f0f0f0;
+ }
+
+ .info-item:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+ }
+
+ .info-label {
+ font-size: 14px;
+ color: #666;
+ width: 100px;
+ }
+
+ .info-value {
+ flex: 1;
+ font-size: 14px;
+ color: #333;
+ text-align: right;
+ }
+
+ .assessment-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .assessment-item {
+ background: #f8f9fa;
+ border-radius: 8px;
+ padding: 16px;
+ border: 1px solid #e8e8e8;
+ }
+
+ .assessment-info {
+ margin-bottom: 12px;
+ }
+
+ .info-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ }
+
+ .info-row:last-child {
+ margin-bottom: 0;
+ }
+
+ .label {
+ font-size: 14px;
+ color: #666;
+ min-width: 80px;
+ }
+
+ .value {
+ font-size: 14px;
+ color: #333;
+ flex: 1;
+ }
+
+ .assessment-result {
+ display: flex;
+ align-items: center;
+ padding-top: 8px;
+ border-top: 1px solid #e8e8e8;
+ }
+
+ .result-label {
+ font-size: 14px;
+ color: #666;
+ min-width: 80px;
+ }
+
+ .submit-btn {
+ margin-top: 24px;
+ margin-bottom: 32px;
+ }
+</style>
\ No newline at end of file
diff --git a/src/pages/safeProduction/safetyTrainingAssessment/view.vue b/src/pages/safeProduction/safetyTrainingAssessment/view.vue
new file mode 100644
index 0000000..df8b991
--- /dev/null
+++ b/src/pages/safeProduction/safetyTrainingAssessment/view.vue
@@ -0,0 +1,171 @@
+<template>
+ <view class="danger-investigation-view">
+ <PageHeader title="鍩硅璇︽儏"
+ @back="goBack" />
+ <!-- 鍐呭瀹瑰櫒 -->
+ <view class="detail-content">
+ <!-- 鍩硅淇℃伅 -->
+ <view class="info-section">
+ <!-- <view class="section-title">鍩硅淇℃伅</view> -->
+ <view class="info-grid">
+ <view class="info-item">
+ <text class="info-label">璇剧▼缂栧彿</text>
+ <text class="info-value">{{ form.courseCode || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鏃ユ湡</text>
+ <text class="info-value">{{ form.trainingDate || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">寮�濮嬫椂闂�</text>
+ <text class="info-value">{{ form.openingTime || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">缁撴潫鏃堕棿</text>
+ <text class="info-value">{{ form.endTime || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鐩爣</text>
+ <text class="info-value">{{ form.trainingObjectives || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍙傚姞瀵硅薄</text>
+ <text class="info-value">{{ form.participants || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鍐呭</text>
+ <text class="info-value">{{ form.trainingContent || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅璁插笀</text>
+ <text class="info-value">{{ form.trainingLecturer || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">椤圭洰瀛﹀垎</text>
+ <text class="info-value">{{ form.projectCredits || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鏂瑰紡</text>
+ <text class="info-value">{{ getTrainingModeLabel(form.trainingMode) || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">鍩硅鍦扮偣</text>
+ <text class="info-value">{{ form.placeTraining || '-' }}</text>
+ </view>
+ <view class="info-item">
+ <text class="info-label">璇炬椂</text>
+ <text class="info-value">{{ form.classHour || '-' }}</text>
+ </view>
+ </view>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+ import { ref, onMounted } from "vue";
+ import PageHeader from "@/components/PageHeader.vue";
+ import { onLoad } from "@dcloudio/uni-app";
+ import { useDict } from "@/utils/dict";
+ // 鏇挎崲 toast 鏂规硶
+ defineOptions({ name: "safety-training-view" });
+ const showToast = message => {
+ uni.showToast({ title: message, icon: "none" });
+ };
+
+ // 鑾峰彇瀛楀吀鏁版嵁
+ const { safe_training_methods } = useDict("safe_training_methods");
+
+ // 鑾峰彇鍩硅鏂瑰紡鏍囩
+ const getTrainingModeLabel = val => {
+ if (!safe_training_methods || !Array.isArray(safe_training_methods.value)) {
+ return val;
+ }
+ const item = safe_training_methods.value.find(
+ i => String(i.value) === String(val)
+ );
+ return item ? item.label : val;
+ };
+
+ // 鍩硅淇℃伅
+ const form = ref({});
+
+ // 杩斿洖涓婁竴椤�
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ onLoad(() => {
+ // 浠庢湰鍦板瓨鍌ㄨ幏鍙栧煿璁俊鎭�
+ const safetyTraining = uni.getStorageSync("safetyTraining");
+ if (safetyTraining) {
+ form.value = safetyTraining;
+ }
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "../../../styles/sales-common.scss";
+
+ .danger-investigation-view {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 2rem;
+ }
+
+ .detail-content {
+ padding: 20px;
+ }
+
+ .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 {
+ padding: 1rem;
+ font-size: 1rem;
+ font-weight: 500;
+ color: #303133;
+ background: #f5f5f5;
+ border-bottom: 1px solid #e4e7ed;
+ }
+
+ .info-content {
+ padding: 1rem;
+ }
+ .info-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+ }
+ .info-item {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+ .info-item:last-child {
+ margin-bottom: 0;
+ }
+
+ .info-label {
+ font-size: 14px;
+ color: #909399;
+ }
+
+ .info-value {
+ font-size: 14px;
+ color: #303133;
+ word-break: break-all;
+ }
+
+ .description-content {
+ padding: 1rem;
+ font-size: 0.875rem;
+ color: #303133;
+ line-height: 1.5;
+ }
+</style>
\ No newline at end of file
--
Gitblit v1.9.3