董国庆
2026-01-27 ff777e5eee376ebe3b5559cba9fed0d88387b750
项目列表
4个文件已删除
1个文件已添加
4个文件已修改
1632 ■■■■■ 已修改文件
screen/.env.development 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/.env.production 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/Word文档在线预览实现说明.md 339 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/docx-preview配置说明.md 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/accidentCase/api.txt 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/planOfAudit/service.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectList/index.vue 794 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectList/service.js 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/报告功能优化说明.md 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/.env.development
@@ -1,3 +1,7 @@
VUE_APP_BASE_URL=http://192.168.110.111:9908
# VUE_APP_BASE_URL=http://221.182.45.100:8180
# 开发环境配置
# 主API地址
VUE_APP_BASE_URL=http://221.182.45.100:8180
# 图片回显地址
VUE_APP_IMAGE_BASE_URL=http://221.182.45.100:8180
screen/.env.production
@@ -1,2 +1,8 @@
VUE_APP_BASE_URL=https://rhljsq.pzhsoar.com/manage
VUE_APP_IMAGE_BASE_URL=http://47.108.113.190:8181/project/download/
# 生产环境配置
# 主API地址
VUE_APP_BASE_URL=http://221.182.45.100:8180
# 图片回显地址
VUE_APP_IMAGE_BASE_URL=http://221.182.45.100:8180
screen/Word文档在线预览实现说明.md
File was deleted
screen/docx-preview配置说明.md
File was deleted
screen/src/view/accidentCase/api.txt
File was deleted
screen/src/view/planOfAudit/service.js
@@ -3,7 +3,7 @@
// 创建专门用于文件流下载的 axios 实例
const reportRequest = axios.create({
  baseURL: 'http://221.182.45.100:8180',
  baseURL: process.env.VUE_APP_IMAGE_BASE_URL,
  withCredentials: false,
  timeout: 60000, // 文件下载可能需要更长时间
})
screen/src/view/projectList/index.vue
@@ -12,20 +12,23 @@
      </div>
      <!-- 搜索 -->
      <div class="flex a-center gap--16 mt--24 mb--20">
        <!-- 项目选择(仅委托方显示) -->
        <div
          v-if="isClientMode"
          class="flex a-center j-between pl--18 pr--15 h--40 relative w--300 bgcolor1 searchBox"
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="searchProjectId"
            placeholder="请选择项目"
            class="flex1"
            clearable
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in projectOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
@@ -39,15 +42,16 @@
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="projectType"
            placeholder="请选择项目类型"
            class="flex1"
            clearable
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in projectTypeOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
@@ -61,15 +65,16 @@
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="riskLevel"
            placeholder="请选择项目风险"
            class="flex1"
            clearable
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in riskLevelOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
@@ -83,24 +88,25 @@
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="startTime"
            ref="datetime1"
            type="datetime"
            placeholder=""
            clearable
          >
          </el-date-picker>
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="startTimeTxt"
          >
            {{ dateTxt }}
            {{ startTimeTxt }}
          </div>
          <div
            v-else
            class="absolute w100 h100 flex a-center c-fff fs--14 SHSR"
          >
            请选择日期
            请选择开始日期
          </div>
          <img
            src="../../assets/img/home/Shape@2x (6).png"
@@ -112,24 +118,25 @@
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="endTime"
            ref="datetime2"
            type="datetime"
            placeholder=""
            clearable
          >
          </el-date-picker>
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="endTimeTxt"
          >
            {{ dateTxt }}
            {{ endTimeTxt }}
          </div>
          <div
            v-else
            class="absolute w100 h100 flex a-center c-fff fs--14 SHSR"
          >
            请选择日期
            请选择结束日期
          </div>
          <img
            src="../../assets/img/home/Shape@2x (6).png"
@@ -139,34 +146,32 @@
        <div
          class="flex a-center j-between w--300 pl--20 h--40 relative bgcolor1 searchBox"
        >
          <el-select
            filterable
            v-model="projectId"
          <el-input
            v-model="projectName"
            placeholder="请输入项目名称"
            class="flex1"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
            >
            </el-option>
          </el-select>
          <div class="btn SHSR">搜索</div>
            clearable
            @keyup.enter.native="handleSearch"
          />
          <div class="btn SHSR" @click="handleSearch">搜索</div>
        </div>
      </div>
      <!-- 表格 -->
      <CustomTable
        :data="[{ projectName: '项目777' }, { projectName: '项目2' }]"
        :data="tableData"
        height="800"
        style="border: 2px solid #3d7fff"
        v-loading="loading"
      >
        <el-table-column label="项目名称" prop="projectName" />
        <el-table-column label="项目类型" prop="projectName" />
        <el-table-column label="单位集合" prop="projectName">
        <el-table-column label="项目名称" prop="name" min-width="150" />
        <el-table-column label="项目类型" min-width="120" prop="type">
          <template #default="{ row }">
            <div class="flex j-center" @click="companyVisible = true">
            {{ formatProjectType(row.type) }}
          </template>
        </el-table-column>
        <el-table-column label="单位集合" min-width="120">
          <template #default="{ row }">
            <div class="flex j-center" @click="openCompanyDialog(row)">
              <div
                class="w--70 lh--30 txt-center j-center bgColor2 SHSR cursor-pointer"
              >
@@ -175,31 +180,52 @@
            </div>
          </template>
        </el-table-column>
        <el-table-column label="项目地址" prop="projectName" />
        <el-table-column label="本月检查情况" prop="projectName" />
        <el-table-column label="最后一次检查时间" prop="projectName" />
        <el-table-column label="本月检查风险" prop="projectName">
          <template #default="{ row, $index }">
        <el-table-column label="项目地址" min-width="200">
          <template #default="{ row }">
            {{ formatAddress(row.addressInfo) }}
          </template>
        </el-table-column>
        <el-table-column label="本月检查情况" prop="checkResult" min-width="120">
          <template #default="{ row }">
            <div class="flex a-center j-center">
              <div class="ripple">
              <div class="ripple" v-if="row.checkResult">
                <div
                  class="ripple-wave wave-1"
                  :class="{ bgColor3: $index === 0 }"
                  :class="{
                    bgColor3: isCheckResultGood(row.checkResult),
                  }"
                ></div>
                <div
                  class="ripple-wave wave-2"
                  :class="{ bgColor4: $index === 0 }"
                  :class="{
                    bgColor4: isCheckResultGood(row.checkResult),
                  }"
                ></div>
                <div
                  class="ripple-wave wave-3"
                  :class="{ bgColor5: $index === 0 }"
                  :class="{
                    bgColor5: isCheckResultGood(row.checkResult),
                  }"
                ></div>
              </div>
              <div class="ml--6 SHSR">{{ $index }}</div>
              <div :class="row.checkResult ? 'ml--6' : ''" class="SHSR">
                {{ row.checkResult || '-' }}
              </div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="操作">
        <el-table-column label="最后一次检查时间" min-width="180">
          <template #default="{ row }">
            {{ formatTime(row.checkTime) }}
          </template>
        </el-table-column>
        <el-table-column label="本月检查风险" prop="risk" min-width="120">
          <template #default="{ row }">
            <div class="SHSR">{{ row.risk || '-' }}</div>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right">
          <template #default="{ row }">
            <el-button
              @click="toProjectDetail(row)"
@@ -216,7 +242,7 @@
            <el-button
              type="text"
              class="btnColor"
              @click="projectVisible = true"
              @click="openReportDialog(row)"
              >报告</el-button
            >
          </template>
@@ -226,42 +252,80 @@
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="currentPage"
        :page-sizes="[100, 200, 300, 400]"
        :page-size="10"
        :total="400"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="pageSize"
        :total="total"
      />
    </div>
    <!-- 项目列表弹窗 -->
    <CustomDialog title="项目" :visible.sync="projectVisible" width="1420px">
      <CustomTable
        :data="[{ projectName: '项目777' }, { projectName: '项目2' }]"
        :data="checkList"
        height="630"
        v-loading="checkLoading"
      >
        <el-table-column label="检查计划" prop="projectName" />
        <el-table-column label="计划类型" prop="projectName" />
        <el-table-column label="检测时间" prop="projectName" />
        <el-table-column label="分数" prop="projectName" />
        <el-table-column label="风险类型" prop="projectName" />
        <el-table-column label="操作">
        <el-table-column label="检查计划" prop="planName" min-width="150" />
        <el-table-column label="计划类型" min-width="120">
          <template #default="{ row }">
            <el-popover trigger="click" popper-class="custom-popper">
            {{ formatPlanType(row.planType) }}
          </template>
        </el-table-column>
        <el-table-column label="检测时间" min-width="180">
          <template #default="{ row }">
            {{ formatTime(row.checkTime) }}
          </template>
        </el-table-column>
        <el-table-column label="分数" prop="score" min-width="100">
          <template #default="{ row }">
            {{ row.score || '-' }}
          </template>
        </el-table-column>
        <el-table-column label="风险类型" min-width="120">
          <template #default="{ row }">
            {{ formatRiskLevel(row.riskLevel) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="150">
          <template #default="{ row }">
            <el-popover
              trigger="click"
              popper-class="custom-popper"
              :disabled="!hasAnyReport(row)"
            >
              <template #reference>
                <el-button type="text" class="btnColor">查看报告</el-button>
                <el-button
                  type="text"
                  class="btnColor"
                  :disabled="!hasAnyReport(row)"
                  @click="checkReportAvailability(row)"
                >
                  查看报告
                </el-button>
              </template>
              <div
                class="h100 flex flex-column a-center j-between c-fff fs--16 SHSBold"
              >
                <div
                  @click="openReport('safe')"
                  class="py--12 w100 txt-center br--10 bgcolor6 cursor-pointer"
                  @click="openReport('safe', row)"
                  class="py--12 w100 txt-center br--10 bgcolor6 report-btn"
                  :class="{
                    'report-disabled': row.containSystemRes !== 1,
                    'report-enabled': row.containSystemRes === 1
                  }"
                >
                  安全报告
                  <span>安全报告</span>
                  <span v-if="row.containSystemRes !== 1" class="no-report-tag">(暂无)</span>
                </div>
                <div
                  @click="openReport('device')"
                  class="py--12 w100 txt-center br--10 bgcolor7 cursor-pointer"
                  @click="openReport('device', row)"
                  class="py--12 w100 txt-center br--10 bgcolor7 report-btn"
                  :class="{
                    'report-disabled': row.containDeviceRes !== 1,
                    'report-enabled': row.containDeviceRes === 1
                  }"
                >
                  设备报告
                  <span>设备报告</span>
                  <span v-if="row.containDeviceRes !== 1" class="no-report-tag">(暂无)</span>
                </div>
              </div>
            </el-popover>
@@ -275,34 +339,64 @@
      :visible.sync="companyVisible"
      width="1420px"
    >
      <el-descriptions>
        <el-descriptions-item label="施工单位">成都喜望</el-descriptions-item>
        <el-descriptions-item label="项目经理">张三</el-descriptions-item>
        <el-descriptions-item label="联系方式"
          >18100000000</el-descriptions-item
        >
        <el-descriptions-item label="监理单位">成都喜望</el-descriptions-item>
        <el-descriptions-item label="总监">张三</el-descriptions-item>
        <el-descriptions-item label="联系方式"
          >18100000000</el-descriptions-item
        >
        <el-descriptions-item label="建设单位">成都喜望</el-descriptions-item>
        <el-descriptions-item label="项目负责人">张三</el-descriptions-item>
        <el-descriptions-item label="联系方式"
          >18100000000</el-descriptions-item
        >
      <el-descriptions :column="3">
        <el-descriptions-item label="施工单位">
          {{ companyInfo.construction?.name || '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="项目经理">
          {{ companyInfo.construction?.leader || '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="联系方式">
          {{ companyInfo.construction?.phone || '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="监理单位">
          {{ companyInfo.supervisor?.name || '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="总监">
          {{ companyInfo.supervisor?.leader || '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="联系方式">
          {{ companyInfo.supervisor?.phone || '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="建设单位">
          {{ companyInfo.development?.name || '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="项目负责人">
          {{ companyInfo.development?.leader || '-' }}
        </el-descriptions-item>
        <el-descriptions-item label="联系方式">
          {{ companyInfo.development?.phone || '-' }}
        </el-descriptions-item>
      </el-descriptions>
    </CustomDialog>
    <!-- 检查报告弹窗 -->
    <CustomDialog title="检查报告" :visible.sync="reportVisible" width="1420px">
      <div style="height: 691px" class="oa">
        <iframe
          v-if="reportUrl"
          :src="reportUrl"
          width="100%"
          height="100%"
          frameborder="0"
        ></iframe>
    <CustomDialog
      title="检查报告"
      :visible.sync="reportVisible"
      width="1420px"
      @close="closeReportDialog"
    >
      <div style="height: 691px;" class="flex flex-column" v-loading="reportLoading">
        <!-- PDF预览容器 -->
        <div
          v-if="showPreview && pdfUrl"
          style="width: 100%; height: 100%; overflow: auto; background: #525659;"
        >
          <iframe
            :src="pdfUrl"
            style="width: 100%; height: 100%; border: none;"
            frameborder="0"
          ></iframe>
        </div>
        <!-- 加载提示 -->
        <div v-else class="flex flex-column a-center j-center h100">
          <div class="c-fff fs--18 mb--20">正在加载报告...</div>
        </div>
        <!-- 底部操作按钮 -->
        <div class="flex j-center gap--20 mt--20">
          <el-button type="primary" @click="downloadReport" :disabled="!reportBlobUrl">下载报告</el-button>
          <el-button @click="closeReportDialog">关闭</el-button>
        </div>
      </div>
    </CustomDialog>
  </div>
@@ -311,83 +405,494 @@
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import {
  getProjectList,
  projectList,
  getDeviceDetailCheckList,
  getFuzzyReportWord,
  getScoreReportWord,
  getFuzzyReportPdfUrl,
  getScoreReportPdfUrl,
} from "./service";
export default {
  components: { HeaderBox },
  props: {},
  data() {
    return {
      dateTime: null,
      projectId: null,
      dateTxt: "",
      // 搜索条件
      searchProjectId: null, // 项目选择
      projectType: null, // 项目类型
      riskLevel: null, // 项目风险
      startTime: null, // 开始时间
      endTime: null, // 结束时间
      startTimeTxt: "", // 开始时间显示文本
      endTimeTxt: "", // 结束时间显示文本
      projectName: "", // 项目名称搜索
      // 下拉选项
      projectOptions: [], // 项目下拉列表
      projectTypeOptions: [
        { label: "房建", value: 0 },
        { label: "市政", value: 1 },
        { label: "固定资产", value: 2 },
      ], // 项目类型下拉列表
      riskLevelOptions: [
        { label: "拒绝接受风险", value: 0 },
        { label: "不可接受风险", value: 1 },
        { label: "可接受风险", value: 2 },
        { label: "可容忍风险", value: 3 },
        { label: "可忽略风险", value: 4 },
      ],
      // 表格数据
      tableData: [],
      loading: false,
      total: 0,
      currentPage: 1,
      pageSize: 10,
      // 弹窗
      projectVisible: false,
      companyVisible: false,
      reportVisible: false,
      reportUrl: "",
      safeReportUrl:
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
      deviceReportUrl:
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
      // 检查列表数据
      checkList: [],
      checkLoading: false,
      currentProjectId: null, // 当前查看报告的项目ID
      // 单位信息
      companyInfo: {},
      // 用户信息
      userInfo: {},
      isClientMode: false, // 是否是委托方模式
      // 报告相关
      reportUrl: "", // 已废弃,保留兼容性
      reportBlobUrl: "", // Blob URL用于下载
      pdfUrl: "", // PDF预览URL
      reportLoading: false,
      showPreview: false, // 是否显示预览
      currentRow: null, // 当前选中的行数据
    };
  },
  computed: {},
  watch: {
    dateTime: {
      handler(newVal, oldVal) {
    startTime: {
      handler(newVal) {
        if (newVal) {
          this.dateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.startTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.startTimeTxt = "";
        }
      },
      immediate: true,
    },
    endTime: {
      handler(newVal) {
        if (newVal) {
          this.endTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.endTimeTxt = "";
        }
      },
      immediate: true,
    },
  },
  created() {},
  created() {
    this.initUserInfo();
    this.initData();
  },
  mounted() {},
  methods: {
    // 初始化用户信息
    initUserInfo() {
      const userInfo = JSON.parse(localStorage.getItem("userInfo") || "{}");
      this.userInfo = userInfo;
      // 判断是否是委托方(type为1表示委托方)
      this.isClientMode = userInfo.type === 1;
    },
    // 初始化数据
    async initData() {
      await this.loadProjectOptions();
      await this.loadTableData();
    },
    // 加载项目下拉列表(仅委托方需要)
    async loadProjectOptions() {
      if (!this.isClientMode) return;
      try {
        const res = await getProjectList({
          clientId: this.userInfo.clientId,
        });
        if (res) {
          this.projectOptions = res.map((item) => ({
            label: item.name,
            value: item.id,
          }));
        }
      } catch (error) {
        console.error("加载项目列表失败:", error);
      }
    },
    // 加载表格数据
    async loadTableData() {
      this.loading = true;
      try {
        const params = {
          pageNum: this.currentPage,
          pageSize: this.pageSize,
        };
        // 添加搜索条件
        if (this.searchProjectId) params.projectId = this.searchProjectId;
        if (this.projectType) params.type = this.projectType;
        if (this.riskLevel !== null && this.riskLevel !== "")
          params.riskLevel = this.riskLevel;
        if (this.startTime)
          params.startTime = moment(this.startTime).format(
            "YYYY-MM-DD HH:mm:ss"
          );
        if (this.endTime)
          params.endTime = moment(this.endTime).format("YYYY-MM-DD HH:mm:ss");
        if (this.projectName) params.name = this.projectName;
        const res = await projectList(params);
        if (res) {
          this.tableData = res.records || [];
          this.total = res.total || 0;
        }
      } catch (error) {
        console.error("加载项目列表失败:", error);
        this.$message.error("加载项目列表失败");
      } finally {
        this.loading = false;
      }
    },
    // 搜索
    handleSearch() {
      this.currentPage = 1;
      this.loadTableData();
    },
    // 分页大小改变
    handleSizeChange(val) {
      this.pageSize = val;
      this.currentPage = 1;
      this.loadTableData();
    },
    // 当前页改变
    handleCurrentChange(val) {
      this.currentPage = val;
      this.loadTableData();
    },
    // 点击日期选择器
    handleClick(type) {
      this.$refs[`datetime${type}`].focus();
    },
    // 返回
    backFun() {
      this.$router.go(-1);
    },
    // 跳转到项目详情
    toProjectDetail(row) {
      this.$router.push({
        path: "/projectHome",
        query: {
          projectId: 111,
          projectName: "项目777",
          projectId: row.id,
          projectName: row.name,
        },
      });
    },
    // 跳转到隐患页面
    toHiddenDanger(row, type) {
      this.$router.push({
        path: "/hiddenDanger",
        query: {
          projectId: 111,
          projectId: row.id,
          activeTab: type,
        },
      });
    },
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
    // 打开报告弹窗
    async openReportDialog(row) {
      this.currentProjectId = row.id;
      this.projectVisible = true;
      await this.loadCheckList(row.id);
    },
    handleCurrentChange(val) {
      console.log(`当前页: ${val}`);
    },
    handleClick(type) {
      this.$refs[`datetime${type}`].focus();
    },
    backFun() {
      this.$router.go(-1);
    },
    openReport(type) {
      if (type === "safe") {
        this.reportUrl = this.safeReportUrl;
      } else if (type === "device") {
        this.reportUrl = this.deviceReportUrl;
      } else {
        this.reportUrl = "";
    // 加载检查列表
    async loadCheckList(projectId) {
      this.checkLoading = true;
      try {
        const res = await getDeviceDetailCheckList({
          projectId: projectId,
        });
        if (res) {
          this.checkList = res.records || [];
        }
      } catch (error) {
        console.error("加载检查列表失败:", error);
        this.$message.error("加载检查列表失败");
      } finally {
        this.checkLoading = false;
      }
      this.reportVisible = !!this.reportUrl;
    },
    // 打开单位信息弹窗
    openCompanyDialog(row) {
      // 解析单位信息
      this.companyInfo = {
        construction: this.parseCompanyInfo(row.constructionCompanyInfo),
        supervisor: this.parseCompanyInfo(row.supervisorCompanyInfo),
        development: this.parseCompanyInfo(row.developmentCompanyInfo),
      };
      this.companyVisible = true;
    },
    // 解析单位信息字符串
    parseCompanyInfo(infoStr) {
      if (!infoStr) return {};
      try {
        return JSON.parse(infoStr);
      } catch (error) {
        return {};
      }
    },
    // 检查是否有任何报告
    hasAnyReport(row) {
      return row.containSystemRes === 1 || row.containDeviceRes === 1;
    },
    // 检查报告可用性(当按钮被禁用时点击)
    checkReportAvailability(row) {
      if (!this.hasAnyReport(row)) {
        this.$message.warning("该计划暂无可查看的报告");
      }
    },
    // 打开报告
    async openReport(type, row) {
      // 根据类型检查是否有对应报告
      const hasReport = type === "safe"
        ? row.containSystemRes === 1
        : row.containDeviceRes === 1;
      if (!hasReport) {
        this.$message.warning(`暂无${type === "safe" ? "安全" : "设备"}报告`);
        return;
      }
      this.currentRow = row;
      this.reportVisible = true;
      this.showPreview = false;
      this.reportLoading = true;
      try {
        // 从 localStorage 获取模板 ID
        const deviceTemplateId = localStorage.getItem("deviceTemplateId");
        const safetyId = localStorage.getItem("safetyId");
        // 检查任务 ID
        const checkTaskId = row.id;
        if (!checkTaskId) {
          this.$message.error("缺少检查任务ID");
          this.reportLoading = false;
          return;
        }
        // 使用当前时间戳作为 uuid
        const uuid = Date.now().toString();
        // 第一步:调用接口生成Word文档(返回Word文档流)
        let wordBlob;
        if (type === "safe") {
          // 安全报告(模糊评价)
          if (!safetyId) {
            this.$message.error("缺少安全报告模板ID");
            this.reportLoading = false;
            return;
          }
          wordBlob = await getFuzzyReportWord({
            checkTaskId: checkTaskId,
            templateId: safetyId,
            uuid: uuid
          });
        } else {
          // 设备报告(打分评价)
          if (!deviceTemplateId) {
            this.$message.error("缺少设备报告模板ID");
            this.reportLoading = false;
            return;
          }
          wordBlob = await getScoreReportWord({
            checkTaskId: checkTaskId,
            templateId: deviceTemplateId,
            uuid: uuid
          });
        }
        // 检查 Word 文档是否生成成功
        console.log('Word文档生成成功:', wordBlob instanceof Blob, 'size:', wordBlob.size);
        if (!wordBlob || wordBlob.size === 0) {
          this.$message.error("报告生成失败");
          this.reportLoading = false;
          return;
        }
        // 第二步:使用uuid获取PDF链接
        let pdfUrlResponse;
        if (type === "safe") {
          pdfUrlResponse = await getFuzzyReportPdfUrl({ uuid: uuid });
        } else {
          pdfUrlResponse = await getScoreReportPdfUrl({ uuid: uuid });
        }
        console.log('PDF链接响应:', pdfUrlResponse);
        // 根据后端返回的数据结构获取PDF链接
        // 假设后端返回格式为 { data: "http://xxx.pdf" } 或直接返回字符串
        const pdfUrl = pdfUrlResponse.data || pdfUrlResponse;
        if (!pdfUrl) {
          this.$message.error("获取PDF链接失败");
          this.reportLoading = false;
          return;
        }
        // 保存PDF链接用于预览和下载
        this.pdfUrl = pdfUrl;
        this.reportBlobUrl = pdfUrl; // 用于下载
        this.showPreview = true;
        this.$message.success("报告加载成功");
      } catch (error) {
        console.error("获取报告失败:", error);
        if (error.response) {
          const status = error.response.status;
          if (status === 404) {
            this.$message.error("报告不存在");
          } else if (status === 500) {
            this.$message.error("服务器错误,请稍后重试");
          } else {
            this.$message.error(`获取报告失败: ${error.response.statusText || '未知错误'}`);
          }
        } else {
          this.$message.error("获取报告失败,请检查网络连接");
        }
        this.showPreview = false;
      } finally {
        this.reportLoading = false;
      }
    },
    // 下载报告
    downloadReport() {
      if (!this.reportBlobUrl) {
        this.$message.warning("报告未加载");
        return;
      }
      try {
        // 直接打开PDF链接进行下载
        window.open(this.reportBlobUrl, '_blank');
        this.$message.success("报告下载成功");
      } catch (error) {
        console.error("下载报告失败:", error);
        this.$message.error("下载报告失败");
      }
    },
    // 关闭报告弹窗
    closeReportDialog() {
      this.reportVisible = false;
      this.showPreview = false;
      // 清空PDF链接(不需要释放URL,因为是服务器链接)
      this.reportBlobUrl = "";
      this.pdfUrl = "";
      this.reportUrl = ""; // 保留兼容性
    },
    // 格式化项目类型
    formatProjectType(type) {
      const typeMap = {
        0: "房建",
        1: "市政",
        2: "建筑工程",
      };
      return typeMap[type] || "-";
    },
    // 格式化风险等级
    formatRiskLevel(level) {
      const levelMap = {
        0: "拒绝接受风险",
        1: "不可接受风险",
        2: "可接受风险",
        3: "可容忍风险",
        4: "可忽略风险",
      };
      return levelMap[level] || "-";
    },
    // 格式化时间
    formatTime(time) {
      return time ? moment(time).format("YYYY-MM-DD HH:mm:ss") : "-";
    },
    // 格式化计划类型
    formatPlanType(type) {
      const typeMap = {
        1: "日常检查",
        2: "专项检查",
        3: "综合检查",
      };
      return typeMap[type] || "-";
    },
    // 判断检查情况是否良好(用于控制图标颜色)
    isCheckResultGood(checkResult) {
      // 检查情况只有"已检查"和"未检查"两种状态
      // 已检查显示绿色,未检查显示红色
      if (!checkResult) return false;
      return checkResult.includes("已检查");
    },
    // 格式化地址信息
    formatAddress(addressInfo) {
      if (!addressInfo) return "-";
      try {
        const addressObj = JSON.parse(addressInfo);
        // 拼接完整地址:省 + 市 + 区 + 详细地址
        const { province, city, district, detail } = addressObj;
        const parts = [];
        if (province) parts.push(province);
        if (city) parts.push(city);
        if (district && district !== "其它区") parts.push(district);
        if (detail) parts.push(detail);
        return parts.join("") || "-";
      } catch (error) {
        // 如果解析失败,直接返回原始字符串
        return addressInfo || "-";
      }
    },
  },
};
@@ -500,6 +1005,31 @@
  color: #00ebfc;
}
.report-btn {
  cursor: pointer;
  transition: all 0.3s ease;
  position: relative;
}
.report-enabled {
  &:hover {
    opacity: 0.8;
    transform: translateY(-2px);
  }
}
.report-disabled {
  opacity: 0.4;
  cursor: not-allowed !important;
  pointer-events: none;
}
.no-report-tag {
  font-size: 12px;
  margin-left: 4px;
  opacity: 0.7;
}
::v-deep .el-descriptions {
  .el-descriptions__body {
    background-color: transparent;
screen/src/view/projectList/service.js
New file
@@ -0,0 +1,98 @@
import request from '@/utils/request'
import axios from 'axios'
// 创建专门用于文件流下载的 axios 实例
const reportRequest = axios.create({
  baseURL: process.env.VUE_APP_IMAGE_BASE_URL,
  withCredentials: false,
  timeout: 60000, // 文件下载可能需要更长时间
})
// 请求拦截 - 添加 token
reportRequest.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = token
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)
// 响应拦截 - 文件流直接返回 blob
reportRequest.interceptors.response.use(
  response => {
    // 对于 blob 类型的响应,直接返回 data
    if (response.config.responseType === 'blob') {
      return response.data
    }
    return response.data
  },
  error => {
    console.error('文件下载失败:', error)
    return Promise.reject(error)
  }
)
// 获取项目下拉列表
export const getProjectList = async (params) => {
  return request(`/project/list`, {
    method: 'get', params
  })
}
// 项目列表
export const projectList = async (data) => {
  return request(`/projectEntrust/projectList`, {
    method: 'post',
    data
  })
}
// 获取检查计划列表
export const getDeviceDetailCheckList = async (data) => {
  return request(`/project/getDeviceDetailCheckList`, {
    method: 'post', data
  })
}
// 获取安全报告Word文档流(模糊评价)- 生成Word文档
export const getFuzzyReportWord = async (params) => {
  return reportRequest({
    url: '/admin/stat/report/fuzzyReport',
    method: 'get',
    params,
    responseType: 'blob' // 返回Word文档流
  })
}
// 获取设备报告Word文档流(打分评价)- 生成Word文档
export const getScoreReportWord = async (params) => {
  return reportRequest({
    url: '/admin/stat/report/scoreReport',
    method: 'get',
    params,
    responseType: 'blob' // 返回Word文档流
  })
}
// 获取安全报告PDF链接(模糊评价)- 通过uuid获取PDF链接
export const getFuzzyReportPdfUrl = async (params) => {
  return reportRequest({
    url: '/admin/stat/report/fuzzyReportPdfNew',
    method: 'get',
    params
  })
}
// 获取设备报告PDF链接(打分评价)- 通过uuid获取PDF链接
export const getScoreReportPdfUrl = async (params) => {
  return reportRequest({
    url: '/admin/stat/report/fuzzyReportPdfNew',
    method: 'get',
    params
  })
}
screen/报告功能优化说明.md
File was deleted