WXL
5 天以前 2140c5aae2032673cef7055df9738b9bacf37816
pagesSub/ethicalReview/index.vue
@@ -14,12 +14,12 @@
      <view class="divider"></view>
      <view class="stat-item">
        <text class="count">{{ stats.rejectCount }}</text>
        <text class="label">审查驳回</text>
        <text class="label">不同意</text>
      </view>
      <view class="divider"></view>
      <view class="stat-item">
        <text class="count">{{ stats.waiveCount }}</text>
        <text class="label">已放弃</text>
        <text class="label">弃权</text>
      </view>
    </view>
@@ -66,7 +66,7 @@
        <view class="review-header">
          <view class="case-info">
            <view class="hospital-badge">
              <u-icon name="order" size="16" color="#fff" />
              <up-icon name="order" size="16" color="#fff" />
            </view>
            <view class="info-content">
              <text class="donor-name">{{ review.name || "未填写姓名" }}</text>
@@ -112,19 +112,19 @@
        <!-- 审查详情 -->
        <view class="review-details">
          <view class="detail-item" v-if="review.createTime">
            <u-icon name="clock" size="14" color="#909399" />
            <up-icon name="clock" size="14" color="#909399" />
            <text class="detail-text"
              >创建时间:{{ formatDate(review.createTime) }}</text
            >
          </view>
          <view class="detail-item" v-if="review.conclusiontime">
            <u-icon name="checkmark-circle" size="14" color="#909399" />
            <up-icon name="checkmark-circle" size="14" color="#909399" />
            <text class="detail-text"
              >审查时间:{{ formatDate(review.conclusiontime) }}</text
            >
          </view>
          <view class="detail-item" v-if="review.expertname">
            <u-icon name="account" size="14" color="#909399" />
            <up-icon name="account" size="14" color="#909399" />
            <text class="detail-text">审查人:{{ review.expertname }}</text>
          </view>
        </view>
@@ -132,7 +132,7 @@
        <!-- 审查结论 -->
        <view
          class="conclusion-section"
          v-if="review.expertconclusion && review.expertconclusion !== 2"
          v-if="review.expertconclusion !== '' && review.expertconclusion != null && review.expertconclusion != 2"
        >
          <text class="conclusion-label">审查结论:</text>
          <text class="conclusion-content">{{
@@ -149,7 +149,7 @@
        <!-- 操作按钮 -->
        <view class="action-buttons">
          <button class="action-btn detail-btn" @tap.stop="viewDetail(review)">
            <u-icon name="eye" size="14" color="#747CF9" />
            <up-icon name="eye" size="14" color="#747CF9" />
            <text>查看详情</text>
          </button>
@@ -158,25 +158,25 @@
            class="action-btn download-btn"
            @tap.stop="downloadReport(review)"
          >
            <u-icon name="download" size="14" color="#52c41a" />
            <up-icon name="download" size="14" color="#52c41a" />
            <text>下载报告</text>
          </button>
          <button
            v-if="review.expertconclusion == 2"
            v-if="review.expertconclusion == 0"
            class="action-btn appeal-btn"
            @tap.stop="submitAppeal(review)"
          >
            <u-icon name="arrow-up" size="14" color="#fa8c16" />
            <up-icon name="arrow-up" size="14" color="#fa8c16" />
            <text>提起申诉</text>
          </button>
          <button
            v-if="review.expertconclusion == 2 || review.expertconclusion == 3"
            v-if="review.expertconclusion == 0 || review.expertconclusion == 3"
            class="action-btn restart-btn"
            @tap.stop="restartReview(review)"
          >
            <u-icon name="play-circle" size="14" color="#747CF9" />
            <up-icon name="play-circle" size="14" color="#747CF9" />
            <text>重新开始</text>
          </button>
        </view>
@@ -187,8 +187,7 @@
      <!-- 空状态 -->
      <view class="empty-state" v-if="!loading && reviewList.length == 0">
        <view> {{ loading }}-{{ reviewList.length }} </view>
        <u-icon name="file-remove" size="80" color="#C0C4CC" />
        <!-- <up-icon name="more-dot-fill" size="80" color="#C0C4CC" /> -->
        <text class="empty-text">暂无审查记录</text>
        <text class="empty-desc">当前筛选条件下没有找到相关记录</text>
        <button class="empty-action" @tap="resetFilters">
@@ -213,6 +212,7 @@
  onReachBottom,
} from "@dcloudio/uni-app";
import { useDict } from "@/utils/dict";
import { useUserStore } from "@/stores/user";
// 响应式数据
const loading = ref(false);
@@ -221,6 +221,7 @@
const pageNum = ref(1);
const pageSize = ref(10);
const dict = ref({});
const userStore = useUserStore();
// 筛选条件
const currentStatus = ref("all");
@@ -243,24 +244,24 @@
  { label: "全部", value: "all" },
  { label: "待审查", value: "pending" },
  { label: "审查通过", value: "approved" },
  { label: "审查驳回", value: "rejected" },
  { label: "已放弃", value: "abandoned" },
  { label: "不同意", value: "rejected" },
  { label: "弃权", value: "abandoned" },
]);
// 字典映射
const statusDict = {
  pending: "待审查",
  approved: "审查通过",
  rejected: "审查驳回",
  abandoned: "已放弃",
  rejected: "不同意",
  abandoned: "弃权",
};
// 结论映射
const conclusionDict = {
  0: "未审核",
  1: "审查通过",
  2: "审查驳回",
  3: "放弃",
  0: "不同意",
  1: "同意",
  2: "审查中",
  3: "弃权",
  4: "修改后同意",
};
@@ -361,10 +362,10 @@
        params.expertconclusion = 1; // 同意
        break;
      case "rejected":
        params.expertconclusion = 2; // 不同意
        params.expertconclusion = 0; // 不同意
        break;
      case "abandoned":
        params.expertconclusion = 3; // 放弃
        params.expertconclusion = 3; // 弃权
        break;
    }
  }
@@ -460,45 +461,75 @@
  });
};
// 下载报告
const downloadReport = async (review) => {
  if (!review.conclusionannex) {
    uni.showToast({
      title: "暂无报告可下载",
      icon: "none",
    });
    return;
  }
// 获取文件下载的基础地址:
// H5 端走同源地址(由部署该页面的 nginx 反向代理 /profile/ 到后端,规避跨域);
// 小程序 / App 端使用 userStore 中的文件服务器地址
const getDownloadBaseUrl = () => {
  // #ifdef H5
  try {
    const annexes = review.conclusionannex
      .split(";")
      .filter((item) => item.trim());
    return window.location.origin;
  } catch {
    return userStore.baseUrlHt;
  }
  // #endif
  // #ifndef H5
  return userStore.baseUrlHt;
  // #endif
};
    if (annexes.length == 0) {
// 下载报告:调用后端档案下载接口获取下载地址,再下载文件
const downloadReport = async (review) => {
  try {
    // 先调用后端接口获取档案下载地址(与 web 端一致:参数 nitiateId 对应伦理审查发起 id)
    const res = await uni.$uapi.get(
      "/project/ethicalreviewopinions/download",
      { nitiateId: review.nitiateId || review.fcid || review.id },
    );
    if (res.code != 200 || !res.data || !res.data.downloadUrl) {
      uni.showToast({
        title: "暂无报告可下载",
        title: res.msg || "暂无报告可下载",
        icon: "none",
      });
      return;
    }
    const downloadUrl = res.data.downloadUrl;
    const downloadName =
      res.data.downloadName || downloadUrl.split("/").pop() || "伦理审查报告.doc";
    // 拼接完整下载地址:base + /profile + downloadUrl(后端 downloadUrl 缺少 /profile 前缀)
    const fullUrl = getDownloadBaseUrl() + "/profile" + downloadUrl;
    // #ifdef H5
    // H5 端:直接触发浏览器下载
    const link = document.createElement("a");
    link.href = fullUrl;
    link.download = downloadName;
    link.target = "_blank";
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    return;
    // #endif
    // #ifndef H5
    // 小程序 / App 端:下载后保存并打开
    uni.showLoading({
      title: "下载中...",
      mask: true,
    });
    // 下载第一个附件
    const fileUrl = annexes[0];
    const token = uni.getStorageSync("token") || "";
    const downloadTask = uni.downloadFile({
      url: fileUrl,
      url: fullUrl,
      header: {
        Authorization: token ? `Bearer ${token}` : "",
      },
      success: (downloadRes) => {
        if (downloadRes.statusCode == 200) {
          const tempFilePath = downloadRes.tempFilePath;
          // 保存到本地
          uni.saveFile({
            tempFilePath: tempFilePath,
            tempFilePath: downloadRes.tempFilePath,
            success: (saveRes) => {
              uni.hideLoading();
              uni.showToast({
@@ -507,21 +538,18 @@
                duration: 2000,
              });
              // 在微信小程序中可以打开文件
              if (uni.getSystemInfoSync().platform == "weixin") {
                uni.openDocument({
                  filePath: saveRes.savedFilePath,
                  showMenu: true,
                  success: () => {
                    console.log("打开文档成功");
                  },
                  fail: (err) => {
                    console.error("打开文档失败", err);
                  },
                });
              }
              uni.openDocument({
                filePath: saveRes.savedFilePath,
                showMenu: true,
                success: () => {
                  console.log("打开文档成功");
                },
                fail: (err) => {
                  console.error("打开文档失败", err);
                },
              });
            },
            fail: (saveErr) => {
            fail: () => {
              uni.hideLoading();
              uni.showToast({
                title: "保存文件失败",
@@ -554,6 +582,7 @@
    downloadTask.onProgressUpdate((res) => {
      console.log("下载进度:", res.progress);
    });
    // #endif
  } catch (error) {
    uni.hideLoading();
    console.error("下载报告失败:", error);