From 4d9da000fbe74d344e0e4580b138e79d4ad98ede Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期一, 01 六月 2026 11:07:14 +0800
Subject: [PATCH] 维护

---
 pages/ethicalReview/ethicalInfo.vue | 1583 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 1,425 insertions(+), 158 deletions(-)

diff --git a/pages/ethicalReview/ethicalInfo.vue b/pages/ethicalReview/ethicalInfo.vue
index 271f2cd..954d588 100644
--- a/pages/ethicalReview/ethicalInfo.vue
+++ b/pages/ethicalReview/ethicalInfo.vue
@@ -4,32 +4,40 @@
     <view class="review-overview card">
       <view class="overview-header">
         <text class="title">浼︾悊瀹℃煡浠诲姟</text>
-        <view class="status-badge" :class="reviewStatus">
-          {{ statusText }}
+        <view
+          class="status-badge"
+          :class="getStatusClass(caseInfo.receiveStatus)"
+        >
+          {{ getStatusText(caseInfo.receiveStatus) }}
         </view>
       </view>
-      
+
       <!-- 绱у噾鍨嬪熀纭�淇℃伅甯冨眬 -->
       <view class="compact-info-grid">
         <view class="compact-info-item">
           <up-icon name="file-text" size="14" color="#909399" />
           <text class="compact-label">浣忛櫌鍙�</text>
-          <text class="compact-value">{{ caseInfo.hospitalNo }}</text>
+          <text class="compact-value">{{ caseInfo.inpatientno || "--" }}</text>
         </view>
         <view class="compact-info-item">
           <up-icon name="account" size="14" color="#909399" />
           <text class="compact-label">鎹愮尞鑰�</text>
-          <text class="compact-value">{{ caseInfo.donorName }}</text>
+          <text class="compact-value">{{ caseInfo.name || "--" }}</text>
         </view>
         <view class="compact-info-item">
           <up-icon name="man" size="14" color="#909399" />
           <text class="compact-label">鎬у埆/骞撮緞</text>
-          <text class="compact-value">{{ caseInfo.gender }}/{{ caseInfo.age }}宀�</text>
+          <text class="compact-value"
+            >{{ caseInfo.sex || "--" }}/{{ caseInfo.age || "--"
+            }}{{ caseInfo.ageunit || "宀�" }}</text
+          >
         </view>
         <view class="compact-info-item">
           <up-icon name="heart" size="14" color="#909399" />
           <text class="compact-label">鐤剧梾璇婃柇</text>
-          <text class="compact-value">{{ caseInfo.diagnosis }}</text>
+          <text class="compact-value">{{
+            caseInfo.diagnosisname || "--"
+          }}</text>
         </view>
       </view>
     </view>
@@ -42,8 +50,8 @@
       </view>
 
       <view class="compact-material-list">
-        <view 
-          v-for="material in materials" 
+        <view
+          v-for="material in materials"
           :key="material.id"
           class="compact-material-item"
           @tap="previewMaterial(material)"
@@ -51,6 +59,9 @@
           <view class="material-left">
             <up-icon :name="material.icon" :color="material.color" size="18" />
             <text class="file-name">{{ material.name }}</text>
+            <view v-if="material.type" class="file-meta">
+              <text class="file-type">{{ material.type || "鏂囦欢" }}</text>
+            </view>
           </view>
           <view class="material-right">
             <text class="file-size">{{ material.size }}</text>
@@ -64,17 +75,22 @@
     <view class="review-form card">
       <view class="section-header">
         <text class="section-title">瀹℃煡鎰忚</text>
+        <view v-if="isTimeout" class="timeout-badge">
+          <up-icon name="clock" size="16" />
+          <text>宸茶秴鏃�</text>
+        </view>
       </view>
 
       <view class="form-content">
         <!-- 浣跨敤uView鍗曢�夌粍浠舵浛浠h嚜瀹氫箟瀹炵幇 -->
         <view class="form-group">
           <text class="form-label">瀹℃煡缁撹</text>
-          <u-radio-group 
-            v-model="form.conclusion" 
+          <u-radio-group
+            v-model="form.expertconclusion"
             placement="column"
             activeColor="#007aff"
             @change="onConclusionChange"
+            :disabled="isReadonly"
           >
             <u-radio
               v-for="option in conclusionOptions"
@@ -90,42 +106,14 @@
         <view class="form-group">
           <text class="form-label">璇︾粏鎰忚</text>
           <u--textarea
-            v-model="form.opinion"
+            v-model="form.expertopinion"
             placeholder="璇疯緭鍏ヨ缁嗙殑瀹℃煡鎰忚鍜屾敼杩涘缓璁�..."
             maxlength="1000"
             count
             :height="120"
             border="surround"
+            :disabled="isReadonly"
           ></u--textarea>
-        </view>
-
-        <!-- 椋庨櫓璇勪及 -->
-        <view class="form-group">
-          <text class="form-label">椋庨櫓璇勪及</text>
-          <view class="risk-assessment">
-            <view class="risk-item">
-              <text class="risk-label">鍙楄瘯鑰呴闄╃瓑绾�</text>
-              <view class="risk-slider-compact">
-                <view class="risk-levels">
-                  <text class="level-label" :class="{ active: form.riskLevel >= 1 }">浣�</text>
-                  <text class="level-label" :class="{ active: form.riskLevel >= 2 }">涓綆</text>
-                  <text class="level-label" :class="{ active: form.riskLevel >= 3 }">涓�</text>
-                  <text class="level-label" :class="{ active: form.riskLevel >= 4 }">涓珮</text>
-                  <text class="level-label" :class="{ active: form.riskLevel >= 5 }">楂�</text>
-                </view>
-                <slider 
-                  v-model="form.riskLevel"
-                  min="1"
-                  max="5"
-                  step="1"
-                  activeColor="#f56c6c"
-                  backgroundColor="#e4e7ed"
-                  block-color="#f56c6c"
-                  block-size="20"
-                />
-              </view>
-            </view>
-          </view>
         </view>
 
         <!-- 绛惧悕纭 -->
@@ -142,31 +130,158 @@
       </view>
     </view>
 
+    <!-- 绛惧悕纭鍖哄煙 -->
+    <view class="signature-section card">
+      <view class="section-header">
+        <text class="section-title">涓撳绛惧悕纭</text>
+        <view v-if="isTimeout" class="timeout-badge">
+          <up-icon name="clock" size="16" />
+          <text>宸茶秴鏃讹紝涓嶅彲鎿嶄綔</text>
+        </view>
+      </view>
+
+      <view class="signature-content">
+        <!-- 鏄剧ず宸茬鍚嶅浘鐗� -->
+        <view v-if="signatureData.signatureUrl" class="signed-preview">
+          <image
+            :src="signatureData.signatureUrl"
+            mode="aspectFit"
+            class="signature-image"
+            @tap="previewSignature"
+          />
+          <view class="signature-info">
+            <text class="signature-name">绛惧悕浜猴細{{ expertInfo.name }}</text>
+            <text class="signature-time">{{
+              signatureData.signatureTime
+            }}</text>
+            <view class="signature-actions" v-if="!isReadonly">
+              <button class="re-sign-btn" @tap="removeSignature">
+                <u-icon name="photo" size="16" />
+                <text>閲嶆柊绛惧悕</text>
+              </button>
+            </view>
+          </view>
+        </view>
+
+        <!-- 娣诲姞绛惧悕鎸夐挳 -->
+        <view
+          v-else
+          class="signature-upload"
+          @tap="openSignaturePanel"
+          v-if="!isReadonly"
+        >
+          <view class="signature-upload-area">
+            <up-icon name="edit-pen" size="48" color="#c0c4cc" />
+            <text class="upload-hint">鐐瑰嚮杩涜鎵嬪啓绛惧悕</text>
+            <text class="upload-tip">绛惧悕灏嗕綔涓洪噸瑕佸嚟璇�</text>
+          </view>
+        </view>
+
+        <!-- 瓒呮椂鐘舵�佷笅鏄剧ず鎻愮ず -->
+        <view
+          v-if="isReadonly && !signatureData.signatureUrl"
+          class="signature-disabled"
+        >
+          <view class="signature-disabled-area">
+            <up-icon name="close-circle" size="48" color="#dcdfe6" />
+            <text class="disabled-hint">褰撳墠浠诲姟宸茶秴鏃�</text>
+            <text class="disabled-tip">鏃犳硶杩涜绛惧悕鎿嶄綔</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
     <!-- 鎿嶄綔鎸夐挳 -->
-    <view class="action-bar-compact">
-      <button 
-        class="action-btn save-btn"
-        @tap="saveDraft"
-        
-      >
+    <view class="action-bar-compact" v-if="!isReadonly">
+      <button class="action-btn save-btn" @tap="saveDraft" v-if="showSaveBtn">
         <up-icon name="file-text" size="16" color="#606266" />
         <text>淇濆瓨鑽夌</text>
       </button>
-      <button 
+      <button
         class="action-btn submit-btn"
         @tap="submitReview"
-        :disabled="!canSubmit"
+        v-if="showSubmitBtn"
       >
         <up-icon name="checkmark" size="16" color="#fff" />
-        <text>鎻愪氦瀹℃煡</text>
+        <text>{{ submitBtnText }}</text>
       </button>
     </view>
+
+    <!-- 鍙鐘舵�佹彁绀� -->
+    <view v-if="isReadonly" class="readonly-tip card">
+      <up-icon name="info-circle" size="20" color="#fa8c16" />
+      <text>褰撳墠浠诲姟宸茶秴鏃讹紝浠呭彲鏌ョ湅锛屼笉鍙搷浣�</text>
+    </view>
+
+    <!-- 浼樺寲鐨勭鍚嶅脊绐楃粍浠� -->
+    <u-popup
+      :show="showSignaturePanel"
+      @close="closeSignaturePanel"
+      mode="bottom"
+      :round="20"
+      :closeable="true"
+      closeIcon="close"
+    >
+      <view class="signature-modal">
+        <view class="modal-header">
+          <text class="modal-title">鎵嬪啓绛惧悕</text>
+          <text class="modal-subtitle">璇峰湪涓嬫柟鍖哄煙杩涜绛惧悕</text>
+        </view>
+
+        <view class="signature-canvas-container">
+          <!-- 鎵嬪啓绛惧悕鐢诲竷 -->
+          <canvas
+            canvas-id="signatureCanvas"
+            class="signature-canvas"
+            :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
+            @touchstart="onTouchStart"
+            @touchmove="onTouchMove"
+            @touchend="onTouchEnd"
+            disable-scroll
+          ></canvas>
+
+          <!-- 鎿嶄綔鎸夐挳 -->
+          <view class="canvas-actions">
+            <button class="action-btn clear-btn" @tap="clearCanvas">
+              <up-icon name="trash" size="20" />
+              <text>娓呯┖</text>
+            </button>
+            <button
+              class="action-btn redo-btn"
+              @tap="undoLastStroke"
+              :disabled="!canUndo"
+            >
+              <up-icon name="play-left" size="20" />
+              <text>鎾ら攢</text>
+            </button>
+            <button
+              class="action-btn confirm-btn"
+              @tap="confirmSignature"
+              :disabled="!hasSignature"
+            >
+              <up-icon name="checkmark" size="20" />
+              <text>纭绛惧悕</text>
+            </button>
+          </view>
+
+          <!-- 绛惧悕棰勮 -->
+          <view v-if="tempSignatureData" class="signature-preview">
+            <text class="preview-title">绛惧悕棰勮</text>
+            <image
+              :src="tempSignatureData"
+              mode="aspectFit"
+              class="preview-image"
+            />
+          </view>
+        </view>
+      </view>
+    </u-popup>
 
     <!-- 鎻愪氦纭寮圭獥 -->
     <u-modal
       :show="showSubmitModal"
-      title="纭鎻愪氦"
-      content="纭畾瑕佹彁浜ゅ鏌ユ剰瑙佸悧锛熸彁浜ゅ悗灏嗘棤娉曚慨鏀广��"
+      :title="modalTitle"
+      :content="modalContent"
       showCancelButton
       @confirm="confirmSubmit"
       @cancel="showSubmitModal = false"
@@ -175,114 +290,943 @@
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from "vue";
-import { onLoad } from "@dcloudio/uni-app";
+import { ref, computed, reactive, onMounted, nextTick } from "vue";
+import { onLoad, onShow } from "@dcloudio/uni-app";
+import dayjs from "dayjs";
+import { useUserStore } from "@/stores/user";
+const userStore = useUserStore();
 
+const dict = ref({});
 // 鍝嶅簲寮忔暟鎹�
 const caseInfo = ref({
-  hospitalNo: "D230415",
-  donorName: "寮犳煇鏌�",
-  gender: "鐢�",
-  age: "45",
-  diagnosis: "缁堟湯鏈熻倽鐥�"
+  hospitalNo: "",
+  donorName: "",
+  gender: "",
+  age: "",
+  diagnosis: "",
+  receiveStatus: "0",
+  endtime: "",
 });
 
-const materials = ref([
-  { id: 1, name: "鎹愮尞鑰呯煡鎯呭悓鎰忎功.pdf", icon: "file-text", color: "#f56c6c", size: "2.3MB" },
-  { id: 2, name: "鍖诲璇勪及鎶ュ憡.docx", icon: "file-text", color: "#1890ff", size: "1.1MB" },
-  { id: 3, name: "瀹為獙瀹ゆ鏌ョ粨鏋�.xlsx", icon: "file-text", color: "#52c41a", size: "0.8MB" },
-  { id: 4, name: "褰卞儚瀛﹁祫鏂�.jpg", icon: "photo", color: "#fa8c16", size: "3.2MB" }
-]);
-
+const id = ref(null);
+const fcid = ref(null);
+const materials = ref([]);
 const form = ref({
-  conclusion: "",
-  opinion: "",
-  riskLevel: 3
+  expertconclusion: "", // 涓撳缁撹
+  expertopinion: "", // 涓撳鎰忚
 });
 
 const expertInfo = ref({
-  name: "瀛斿績娑�",
-  title: "涓诲涓撳"
+  name: "",
+  title: "",
 });
 
-const reviewStatus = ref("pending");
 const showSubmitModal = ref(false);
+const modalTitle = ref("纭鎻愪氦");
+const modalContent = ref("纭畾瑕佹彁浜ゅ鏌ユ剰瑙佸悧锛熸彁浜ゅ悗灏嗘棤娉曚慨鏀广��");
 
 // 璁$畻灞炴��
-const statusText = computed(() => {
-  const statusMap = {
-    pending: "寰呭鏌�",
-    drafted: "鑽夌",
-    submitted: "宸叉彁浜�"
-  };
-  return statusMap[reviewStatus.value];
-});
-
-const canSubmit = computed(() => {
-  return form.value.conclusion !== "" && form.value.opinion.trim().length > 10;
-});
-
 const currentTime = computed(() => {
-  return new Date().toLocaleString('zh-CN', {
-    year: 'numeric',
-    month: '2-digit',
-    day: '2-digit',
-    hour: '2-digit',
-    minute: '2-digit'
+  return new Date().toLocaleString("zh-CN", {
+    year: "numeric",
+    month: "2-digit",
+    day: "2-digit",
+    hour: "2-digit",
+    minute: "2-digit",
   });
 });
 
 const conclusionOptions = ref([
-  { label: "鍚屾剰", value: "approved" },
-  { label: "淇敼鍚庡悓鎰�", value: "approved_with_modifications" },
-  { label: "淇敼鍚庨噸瀹�", value: "re-review" },
-  { label: "涓嶅悓鎰�", value: "disapproved" }
+  { label: "鍚屾剰", value: "1" }, // 瀵瑰簲receiveStatus 5-瀹屾垚
+  { label: "椹冲洖", value: "2" }, // 瀵瑰簲receiveStatus 6-椹冲洖
 ]);
 
-// 鏂规硶
-const loadReviewData = (reviewId) => {
-  console.log("鍔犺浇瀹℃煡鏁版嵁:", reviewId);
+// 绛惧悕鐩稿叧鏁版嵁
+const showSignaturePanel = ref(false);
+const signatureData = reactive({
+  signatureUrl: "",
+  signatureTime: "",
+  fileName: "",
+  serverData: null,
+});
+
+// 鎵嬪啓绛惧悕鐩稿叧
+const canvasWidth = 650;
+const canvasHeight = 300;
+let ctx = null;
+let isDrawing = false;
+let lastX = 0;
+let lastY = 0;
+let strokeHistory = [];
+const tempSignatureData = ref("");
+const canUndo = computed(() => strokeHistory.length > 0);
+const hasSignature = computed(() => tempSignatureData.value !== "");
+
+// 涓婁紶閰嶇疆
+const uploadConfig = reactive({
+  uploadUrl: "/api/common/upload",
+  extraParams: {
+    caseNo: "",
+    expertId: "",
+    expertName: "",
+    type: "ethics_review_signature",
+    bizType: "expert_review",
+  },
+});
+
+// 鎸夐挳鏄剧ず鎺у埗
+const showSaveBtn = ref(true);
+const showSubmitBtn = ref(true);
+const submitBtnText = ref("鎻愪氦瀹℃煡");
+
+// 鐘舵�佽浆鎹�
+const getStatusText = (status) => {
+  const statusMap = {
+    0: "寰呮帴鏀�",
+    1: "鏈帴鏀�",
+    2: "宸叉帴鏀�",
+    3: "瓒呮椂",
+    4: "涓",
+    5: "瀹屾垚",
+    6: "椹冲洖",
+  };
+  return statusMap[status] || "鏈煡鐘舵��";
 };
 
-const previewMaterial = (material) => {
-  uni.showToast({
-    title: `棰勮: ${material.name}`,
-    icon: "none"
+const getStatusClass = (status) => {
+  const classMap = {
+    0: "pending",
+    1: "pending",
+    2: "pending",
+    3: "submitted",
+    4: "submitted",
+    5: "success",
+    6: "error",
+  };
+  return classMap[status] || "pending";
+};
+
+// 鏄惁瓒呮椂
+const isTimeout = computed(() => {
+  return caseInfo.value.receiveStatus === "3";
+});
+
+// 鏄惁鍙妯″紡
+const isReadonly = computed(() => {
+  return isTimeout.value;
+});
+
+// 鏄惁鍙互鎻愪氦
+const canSubmit = computed(() => {
+  if (isReadonly.value) return false;
+  return (
+    form.value.expertconclusion !== "" &&
+    form.value.expertopinion.trim().length > 0 &&
+    signatureData.signatureUrl !== "" &&
+    signatureData.signatureUrl.startsWith("http") // 纭繚鏄畬鏁寸殑URL璺緞
+  );
+});
+
+// 鐢熷懡鍛ㄦ湡
+onLoad(async (options) => {
+  id.value = options.id;
+  fcid.value = options.fcid;
+  if (fcid.value) {
+    await loadCaseData(fcid.value);
+  }
+});
+
+onShow(() => {
+  // 椤甸潰鏄剧ず鏃舵鏌ョ敤鎴风櫥褰曠姸鎬�
+  const userInfo = uni.getStorageSync("userInfo");
+  if (userInfo) {
+    expertInfo.value.name = userInfo.nickName || userInfo.userName || "";
+  }
+});
+
+// 鍔犺浇妗堜欢鏁版嵁
+const loadCaseData = async (id) => {
+  try {
+    uni.showLoading({ title: "鍔犺浇涓�..." });
+
+    // 鏋勫缓鏌ヨ鍙傛暟
+    const params = {
+      fcid: id,
+      pageNum: 1,
+      pageSize: 1,
+    };
+
+    const res = await uni.$uapi.get(
+      `/project/ethicalreviewopinions/listnew`,
+      params,
+    );
+
+    if (res.code === 200 && res.rows && res.rows.length > 0) {
+      const data = res.rows[0];
+      console.log("鍔犺浇鐨勫鏌ユ暟鎹�:", data);
+
+      // 璁剧疆妗堜欢淇℃伅
+      caseInfo.value = {
+        ...caseInfo.value,
+        inpatientno: data.inpatientno,
+        name: data.name,
+        sex: data.sex,
+        age: data.age,
+        ageunit: data.ageunit,
+        expertType: data.expertType,
+        diagnosisname: data.diagnosisname,
+        receiveStatus: data.receiveStatus || "0",
+        endtime: data.endtime || "",
+        caseNo: data.caseNo || "",
+      };
+
+      // 璁剧疆琛ㄥ崟鏁版嵁
+      if (data.expertconclusion) {
+        form.value.expertconclusion = data.expertconclusion.toString();
+      }
+      if (data.expertopinion) {
+        form.value.expertopinion = data.expertopinion;
+      }
+
+      // 璁剧疆涓撳淇℃伅
+      if (data.expertname) {
+        expertInfo.value.name = data.expertname;
+      } else {
+        // 浠庣敤鎴蜂俊鎭幏鍙�
+        const userInfo = uni.getStorageSync("userInfo");
+        if (userInfo) {
+          expertInfo.value.name = userInfo.nickName || userInfo.userName || "";
+        }
+      }
+
+      // 璁剧疆绛惧悕
+      if (data.sigin) {
+        // 妫�鏌ョ鍚峌RL鏄惁鏄畬鏁磋矾寰�
+        if (data.sigin.startsWith("http")) {
+          signatureData.signatureUrl = data.sigin;
+        } else {
+          // 濡傛灉涓嶆槸瀹屾暣璺緞锛屽彲鑳介渶瑕佹嫾鎺ュ熀纭�URL
+          signatureData.signatureUrl = `/api${
+            data.sigin.startsWith("/") ? "" : "/"
+          }${data.sigin}`;
+        }
+        signatureData.signatureTime = data.conclusiontime || "";
+      }
+
+      // 瑙f瀽闄勪欢
+      if (data.filePatch) {
+        parseAnnexFiles(data.filePatch);
+      }
+
+      // 妫�鏌ユ槸鍚﹁秴鏃�
+      checkTimeoutStatus(data);
+
+      // 鏍规嵁鐘舵�佹帶鍒舵寜閽樉绀�
+      updateButtonStatus(data.receiveStatus);
+
+      // 鏇存柊涓婁紶鍙傛暟
+      uploadConfig.extraParams.caseNo = data.caseNo || "";
+      uploadConfig.extraParams.expertName = expertInfo.value.name;
+    } else {
+      uni.showToast({
+        title: res.msg || "鏈壘鍒板鏌ユ暟鎹�",
+        icon: "none",
+      });
+    }
+  } catch (error) {
+    if (error.message === "鏈櫥褰�") {
+      // 鉁� 浠�涔堥兘涓嶅仛锛屾嫤鎴櫒宸茬粡澶勭悊
+      return;
+    }
+    uni.showToast({ title: "鍔犺浇澶辫触", icon: "none" });
+  } finally {
+    uni.hideLoading();
+  }
+};
+
+// 妫�鏌ユ槸鍚﹁秴鏃�
+const checkTimeoutStatus = (data) => {
+  if (data.receiveStatus === "3") {
+    // 鐘舵�佸凡缁忔槸瓒呮椂
+    return;
+  }
+
+  // 濡傛灉鏈夋埅姝㈡椂闂达紝妫�鏌ユ槸鍚﹁秴杩囧綋鍓嶆椂闂�
+  if (data.endtime) {
+    const endTime = new Date(data.endtime);
+    const now = new Date();
+    if (now > endTime) {
+      // 鏍囪涓鸿秴鏃剁姸鎬�
+      caseInfo.value.receiveStatus = "3";
+      updateButtonStatus("3");
+    }
+  }
+};
+
+// 瑙f瀽闄勪欢鏂囦欢
+const parseAnnexFiles = (filePatch) => {
+  if (!filePatch) return;
+
+  try {
+    // 瑙f瀽JSON瀛楃涓�
+    let fileList = [];
+
+    // 妫�鏌ユ槸鍚︽槸JSON瀛楃涓叉牸寮�
+    if (filePatch.startsWith("[") && filePatch.endsWith("]")) {
+      // 澶勭悊杞箟瀛楃
+      const cleanJson = filePatch.replace(/\\"/g, '"');
+      fileList = JSON.parse(cleanJson);
+    } else if (filePatch.includes("fileName")) {
+      // 灏濊瘯鐩存帴瑙f瀽
+      fileList = JSON.parse(filePatch);
+    } else {
+      // 鏃ф牸寮忓鐞�
+      const oldFileList = filePatch.split(";").filter((item) => item.trim());
+      fileList = oldFileList.map((file) => ({
+        fileName: file.split("/").pop() || `闄勪欢`,
+        path: file,
+        fileUrl: file,
+      }));
+    }
+
+    // 杞崲涓洪渶瑕佺殑鏍煎紡
+    materials.value = fileList.map((file, index) => {
+      const fileName = file.fileName || `闄勪欢${index + 1}`;
+      const fileUrl = file.fileUrl || file.path || file.url || "";
+      const ext = fileName.split(".").pop().toLowerCase();
+      let icon = "file-text";
+      let color = "#909399";
+
+      if (["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
+        icon = "photo";
+        color = "#fa8c16";
+      } else if (["doc", "docx"].includes(ext)) {
+        icon = "file-text";
+        color = "#1890ff";
+      } else if (["xls", "xlsx", "csv"].includes(ext)) {
+        icon = "file-text";
+        color = "#52c41a";
+      } else if (["pdf"].includes(ext)) {
+        icon = "file-text";
+        color = "#f56c6c";
+      } else if (["txt", "text"].includes(ext)) {
+        icon = "file-text";
+        color = "#909399";
+      } else if (["zip", "rar", "7z"].includes(ext)) {
+        icon = "folder";
+        color = "#722ed1";
+      }
+
+      return {
+        id: file.infoid || index + 1,
+        name: fileName,
+        icon: icon,
+        color: color,
+        size: "--",
+        url: fileUrl,
+        type: ext,
+        createTime: file.createTime || "",
+      };
+    });
+  } catch (error) {
+    console.error("瑙f瀽闄勪欢澶辫触:", error, filePatch);
+
+    // 鍥為��鍒版棫鏍煎紡澶勭悊
+    const fileList = filePatch.split(";").filter((item) => item.trim());
+    materials.value = fileList.map((file, index) => {
+      const fileName = file.split("/").pop() || `闄勪欢${index + 1}`;
+      const ext = fileName.split(".").pop().toLowerCase();
+      let icon = "file-text";
+      let color = "#909399";
+
+      if (["jpg", "jpeg", "png", "gif", "bmp"].includes(ext)) {
+        icon = "photo";
+        color = "#fa8c16";
+      } else if (["doc", "docx"].includes(ext)) {
+        icon = "file-text";
+        color = "#1890ff";
+      } else if (["xls", "xlsx"].includes(ext)) {
+        icon = "file-text";
+        color = "#52c41a";
+      } else if (["pdf"].includes(ext)) {
+        icon = "file-text";
+        color = "#f56c6c";
+      }
+
+      return {
+        id: index + 1,
+        name: fileName,
+        icon: icon,
+        color: color,
+        size: "--",
+        url: file,
+      };
+    });
+  }
+};
+
+// 鏇存柊鎸夐挳鐘舵��
+const updateButtonStatus = (status) => {
+  const statusNum = status;
+  switch (statusNum) {
+    case "3": // 瓒呮椂
+    case "4": // 涓
+    case "5": // 宸插畬鎴�
+    case "6": // 宸查┏鍥�
+      showSaveBtn.value = false;
+      showSubmitBtn.value = false;
+      break;
+    default:
+      showSaveBtn.value = true;
+      showSubmitBtn.value = true;
+      submitBtnText.value = "鎻愪氦瀹℃煡";
+  }
+};
+
+// 鎵撳紑绛惧悕闈㈡澘
+const openSignaturePanel = () => {
+  if (isReadonly.value) {
+    uni.showToast({
+      title: "褰撳墠浠诲姟宸茶秴鏃讹紝涓嶅彲鎿嶄綔",
+      icon: "none",
+    });
+    return;
+  }
+
+  showSignaturePanel.value = true;
+  nextTick(() => {
+    initCanvas();
   });
+};
+
+// 鍏抽棴绛惧悕闈㈡澘
+const closeSignaturePanel = () => {
+  showSignaturePanel.value = false;
+  clearCanvas();
+};
+
+// 鍒濆鍖栫敾甯�
+const initCanvas = () => {
+  ctx = uni.createCanvasContext("signatureCanvas");
+  clearCanvas();
+};
+
+// 娓呯┖鐢诲竷
+const clearCanvas = () => {
+  if (!ctx) return;
+
+  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+  ctx.setStrokeStyle("#000000");
+  ctx.setLineWidth(3);
+  ctx.setLineCap("round");
+  ctx.setLineJoin("round");
+  ctx.draw();
+  strokeHistory = [];
+  tempSignatureData.value = "";
+};
+
+// 鎾ら攢涓婁竴姝�
+const undoLastStroke = () => {
+  if (strokeHistory.length === 0) return;
+
+  // 绉婚櫎鏈�鍚庝竴姝�
+  strokeHistory.pop();
+
+  // 閲嶆柊缁樺埗
+  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+  ctx.setStrokeStyle("#000000");
+  ctx.setLineWidth(3);
+  ctx.setLineCap("round");
+  ctx.setLineJoin("round");
+
+  // 缁樺埗鍘嗗彶绗旇抗
+  strokeHistory.forEach((stroke) => {
+    ctx.beginPath();
+    ctx.moveTo(stroke.startX, stroke.startY);
+    ctx.lineTo(stroke.endX, stroke.endY);
+    ctx.stroke();
+  });
+
+  ctx.draw();
+
+  // 鏇存柊棰勮
+  if (strokeHistory.length === 0) {
+    tempSignatureData.value = "";
+  } else {
+    getCanvasImage();
+  }
+};
+
+// 瑙︽懜寮�濮�
+const onTouchStart = (e) => {
+  if (isReadonly.value) return;
+
+  isDrawing = true;
+  const touch = e.touches[0];
+  lastX = touch.x;
+  lastY = touch.y;
+  ctx.beginPath();
+  ctx.moveTo(lastX, lastY);
+};
+
+// 瑙︽懜绉诲姩
+const onTouchMove = (e) => {
+  if (!isDrawing || isReadonly.value) return;
+
+  const touch = e.touches[0];
+  const currentX = touch.x;
+  const currentY = touch.y;
+
+  ctx.lineTo(currentX, currentY);
+  ctx.stroke();
+  ctx.draw(true);
+
+  // 淇濆瓨绗旇抗鍒板巻鍙茶褰�
+  strokeHistory.push({
+    startX: lastX,
+    startY: lastY,
+    endX: currentX,
+    endY: currentY,
+  });
+
+  lastX = currentX;
+  lastY = currentY;
+};
+
+// 瑙︽懜缁撴潫
+const onTouchEnd = () => {
+  if (!isDrawing) return;
+
+  isDrawing = false;
+  ctx.closePath();
+
+  // 鏇存柊棰勮
+  getCanvasImage();
+};
+
+// 鑾峰彇鐢诲竷鍥剧墖
+const getCanvasImage = () => {
+  uni.canvasToTempFilePath({
+    canvasId: "signatureCanvas",
+    success: (res) => {
+      tempSignatureData.value = res.tempFilePath;
+    },
+    fail: (err) => {
+      console.error("鑾峰彇鐢诲竷鍥剧墖澶辫触:", err);
+    },
+  });
+};
+
+// 纭绛惧悕
+const confirmSignature = async () => {
+  if (!tempSignatureData.value) {
+    uni.showToast({
+      title: "璇峰厛绛惧悕",
+      icon: "none",
+    });
+    return;
+  }
+
+  try {
+    uni.showLoading({ title: "淇濆瓨绛惧悕涓�..." });
+
+    // 涓婁紶绛惧悕鏂囦欢
+    const uploadResult = await uploadSignature(tempSignatureData.value);
+
+    if (uploadResult) {
+      // 鏇存柊绛惧悕鏁版嵁
+      signatureData.signatureUrl = uploadResult.url;
+      signatureData.signatureTime = new Date().toLocaleString("zh-CN");
+      signatureData.fileName =
+        uploadResult.originalFilename || `signature_${Date.now()}.png`;
+      signatureData.serverData = uploadResult;
+
+      // 淇濆瓨鍒版湰鍦�
+      saveSignatureToLocal(uploadResult);
+
+      uni.hideLoading();
+      uni.showToast({
+        title: "绛惧悕淇濆瓨鎴愬姛",
+        icon: "success",
+      });
+
+      showSignaturePanel.value = false;
+      clearCanvas();
+    } else {
+      uni.hideLoading();
+      uni.showToast({
+        title: "绛惧悕涓婁紶澶辫触",
+        icon: "none",
+      });
+    }
+  } catch (error) {
+    console.error("绛惧悕涓婁紶澶辫触:", error);
+    uni.hideLoading();
+    uni.showToast({
+      title: "绛惧悕涓婁紶澶辫触",
+      icon: "none",
+    });
+  }
+};
+
+// 涓婁紶绛惧悕鏂囦欢鏂规硶
+const uploadSignature = (filePath) => {
+  return new Promise((resolve, reject) => {
+    const token = uni.getStorageSync("token");
+
+    // 鑾峰彇鐢ㄦ埛淇℃伅
+    const userInfo = uni.getStorageSync("userInfo");
+    const expertName =
+      userInfo?.nickName || userInfo?.userName || expertInfo.value.name;
+
+    uni.uploadFile({
+      url: "/api/common/upload",
+      filePath: filePath,
+      name: "file",
+      header: {
+        Authorization: `Bearer ${token}`,
+      },
+      formData: {
+        // 娣诲姞棰濆鍙傛暟锛屽弬鑰冮檮浠剁粍浠�
+        bizType: "expert_review_signature",
+        caseNo: caseInfo.value.caseNo || "",
+        expertName: expertName,
+        uploadType: "signature",
+      },
+      success: (res) => {
+        if (res.statusCode === 200) {
+          const data = JSON.parse(res.data);
+          console.log("绛惧悕涓婁紶鎴愬姛:", data);
+
+          if (data.code === 200) {
+            resolve({
+              url: data.url,
+              fileName: data.fileName,
+              newFileName: data.newFileName,
+              originalFilename: data.originalFilename,
+              filePath: data.filePath || data.fileName,
+              size: data.size,
+            });
+          } else {
+            reject(new Error(data.msg || "涓婁紶澶辫触"));
+          }
+        } else {
+          reject(new Error(`涓婁紶澶辫触锛岀姸鎬佺爜: ${res.statusCode}`));
+        }
+      },
+      fail: (err) => {
+        reject(err);
+      },
+    });
+  });
+};
+
+// 淇濆瓨鍒版湰鍦�
+const saveSignatureToLocal = (uploadData) => {
+  try {
+    const signatureInfo = {
+      signatureUrl: signatureData.signatureUrl,
+      signatureTime: signatureData.signatureTime,
+      fileName: signatureData.fileName,
+      uploadData: uploadData, // 淇濆瓨涓婁紶杩斿洖鐨勬暟鎹�
+      caseNo: caseInfo.value.caseNo,
+      timestamp: Date.now(),
+    };
+
+    uni.setStorageSync("expert_review_signature", signatureInfo);
+  } catch (error) {
+    console.error("淇濆瓨绛惧悕鍒版湰鍦板け璐�:", error);
+  }
+};
+
+// 鍒犻櫎绛惧悕
+const removeSignature = () => {
+  if (isReadonly.value) {
+    uni.showToast({
+      title: "褰撳墠浠诲姟宸茶秴鏃讹紝涓嶅彲鎿嶄綔",
+      icon: "none",
+    });
+    return;
+  }
+
+  uni.showModal({
+    title: "鎻愮ず",
+    content: "纭畾瑕佸垹闄ょ鍚嶅悧锛�",
+    success: (res) => {
+      if (res.confirm) {
+        signatureData.signatureUrl = "";
+        signatureData.signatureTime = "";
+        signatureData.fileName = "";
+        signatureData.serverData = null;
+        uni.removeStorageSync("expert_review_signature");
+      }
+    },
+  });
+};
+
+// 棰勮绛惧悕
+// 棰勮绛惧悕
+const previewSignature = () => {
+  if (signatureData.signatureUrl) {
+    // 妫�鏌RL鏄惁闇�瑕佹嫾鎺ュ熀纭�璺緞
+    let previewUrl = signatureData.signatureUrl;
+    if (!previewUrl.startsWith("http")) {
+      // 鍙兘闇�瑕佹嫾鎺ユ湇鍔″櫒鍩虹URL
+      previewUrl = `${userStore?.baseUrlHt || ""}${previewUrl}`;
+    }
+
+    uni.previewImage({
+      urls: [previewUrl],
+    });
+  }
+};
+
+// 棰勮鏉愭枡
+// 棰勮鏉愭枡
+const previewMaterial = (material) => {
+  if (material.url) {
+    uni.showLoading({ title: "鍔犺浇涓�..." });
+
+    const fileExt =
+      material.type || material.url.split(".").pop().toLowerCase();
+
+    // 鍒ゆ柇鏂囦欢绫诲瀷
+    if (["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(fileExt)) {
+      // 鍥剧墖鐩存帴棰勮
+      uni.previewImage({
+        urls: [material.url],
+        current: 0,
+        success: () => {
+          console.log("鍥剧墖棰勮鎴愬姛");
+        },
+        fail: (err) => {
+          console.error("鍥剧墖棰勮澶辫触:", err);
+          uni.showToast({
+            title: "鍥剧墖鍔犺浇澶辫触",
+            icon: "none",
+          });
+        },
+        complete: () => {
+          uni.hideLoading();
+        },
+      });
+    } else if (
+      ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"].includes(
+        fileExt,
+      )
+    ) {
+      // 鏂囨。鏂囦欢涓嬭浇鍚庢墦寮�
+      uni.downloadFile({
+        url: material.url,
+        success: (res) => {
+          if (res.statusCode === 200) {
+            const filePath = res.tempFilePath;
+            uni.openDocument({
+              filePath: filePath,
+              showMenu: true,
+              fileType: fileExt === "pdf" ? "pdf" : "",
+              success: () => {
+                console.log("鎵撳紑鏂囨。鎴愬姛");
+              },
+              fail: (err) => {
+                console.error("鎵撳紑鏂囨。澶辫触:", err);
+                uni.showToast({
+                  title: "鏃犳硶鎵撳紑璇ユ枃浠�",
+                  icon: "none",
+                });
+              },
+            });
+          }
+        },
+        fail: (err) => {
+          console.error("涓嬭浇鏂囦欢澶辫触:", err);
+          uni.showToast({
+            title: "鏂囦欢涓嬭浇澶辫触",
+            icon: "none",
+          });
+        },
+        complete: () => {
+          uni.hideLoading();
+        },
+      });
+    } else {
+      // 鍏朵粬鏂囦欢绫诲瀷
+      uni.showToast({
+        title: `鏆備笉鏀寔棰勮${fileExt}鏍煎紡鏂囦欢`,
+        icon: "none",
+      });
+      uni.hideLoading();
+    }
+  } else {
+    uni.showToast({
+      title: `棰勮: ${material.name}`,
+      icon: "none",
+    });
+  }
 };
 
 const onConclusionChange = (value) => {
   console.log("閫変腑缁撹:", value);
 };
 
-const saveDraft = () => {
-  uni.showToast({
-    title: "鑽夌淇濆瓨鎴愬姛",
-    icon: "success"
-  });
-  reviewStatus.value = "drafted";
+// 淇濆瓨鑽夌
+const saveDraft = async () => {
+  if (isReadonly.value) {
+    uni.showToast({
+      title: "褰撳墠浠诲姟宸茶秴鏃讹紝涓嶅彲鎿嶄綔",
+      icon: "none",
+    });
+    return;
+  }
+
+  if (!validateForm(true)) return;
+
+  try {
+    uni.showLoading({ title: "淇濆瓨涓�..." });
+
+    const submitData = {
+      fcid: fcid.value,
+      expertconclusion: form.value.expertconclusion,
+      expertopinion: form.value.expertopinion,
+      sigin: signatureData.signatureUrl, // 浣跨敤涓婁紶鍚庣殑瀹屾暣璺緞
+      receiveStatus: "2", // 宸叉帴鏀剁姸鎬�
+      conclusiontime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+    };
+
+    const res = await uni.$uapi.post(
+      "/project/ethicalreviewopinions/edit",
+      submitData,
+    );
+
+    if (res.code === 200) {
+      uni.showToast({
+        title: "淇濆瓨鎴愬姛",
+        icon: "success",
+      });
+      // 鏇存柊鏈湴鐘舵��
+      caseInfo.value.receiveStatus = "2";
+      updateButtonStatus("2");
+    } else {
+      uni.showToast({
+        title: res.msg || "淇濆瓨澶辫触",
+        icon: "none",
+      });
+    }
+  } catch (error) {
+    console.error("淇濆瓨鑽夌澶辫触:", error);
+    uni.showToast({
+      title: "淇濆瓨澶辫触",
+      icon: "none",
+    });
+  } finally {
+    uni.hideLoading();
+  }
 };
 
+// 鎻愪氦瀹℃煡
 const submitReview = () => {
+  console.log(1);
+
+  if (isReadonly.value) {
+    uni.showToast({
+      title: "褰撳墠浠诲姟宸茶秴鏃讹紝涓嶅彲鎿嶄綔",
+      icon: "none",
+    });
+    return;
+  }
+  console.log(2);
+
+  if (!validateForm()) return;
+
+  modalTitle.value = "纭鎻愪氦";
+  modalContent.value = "纭畾瑕佹彁浜ゅ鏌ユ剰瑙佸悧锛熸彁浜ゅ悗灏嗘棤娉曚慨鏀广��";
   showSubmitModal.value = true;
 };
 
-const confirmSubmit = () => {
-  uni.showLoading({ title: "鎻愪氦涓�..." });
-  
-  setTimeout(() => {
-    uni.hideLoading();
+// 纭鎻愪氦
+const confirmSubmit = async () => {
+  try {
+    uni.showLoading({ title: "鎻愪氦涓�..." });
+    console.log(caseInfo.value, "form.value");
+
+    const submitData = {
+      id: fcid.value,
+      expertconclusion: form.value.expertconclusion,
+      expertopinion: form.value.expertopinion,
+      sigin: signatureData.signatureUrl,
+      expertType: caseInfo.value.expertType,
+      // 鏍规嵁缁撹璁剧疆鐘舵��
+      receiveStatus: form.value.expertconclusion == "1" ? "5" : "6", // 5-瀹屾垚, 6-椹冲洖
+      conclusiontime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+    };
+
+    const res = await uni.$uapi.post(
+      "/project/ethicalreviewopinions/edit",
+      submitData,
+    );
+
+    if (res.code === 200) {
+      uni.showToast({
+        title: "鎻愪氦鎴愬姛",
+        icon: "success",
+        duration: 2000,
+      });
+
+      // 鏇存柊鏈湴鐘舵��
+      caseInfo.value.receiveStatus = submitData.receiveStatus;
+      updateButtonStatus(submitData.receiveStatus);
+
+      showSubmitModal.value = false;
+
+      setTimeout(() => {
+        uni.navigateBack();
+      }, 1500);
+    } else {
+      uni.showToast({
+        title: res.msg || "鎻愪氦澶辫触",
+        icon: "none",
+      });
+    }
+  } catch (error) {
+    console.error("鎻愪氦澶辫触:", error);
     uni.showToast({
-      title: "瀹℃煡鎰忚鎻愪氦鎴愬姛",
-      icon: "success"
+      title: "鎻愪氦澶辫触",
+      icon: "none",
     });
-    reviewStatus.value = "submitted";
-    showSubmitModal.value = false;
-    
-    setTimeout(() => {
-      uni.navigateBack();
-    }, 1500);
-  }, 2000);
+  } finally {
+    uni.hideLoading();
+  }
+};
+
+// 琛ㄥ崟楠岃瘉
+const validateForm = (isDraft = false) => {
+  if (!form.value.expertconclusion && !isDraft) {
+    uni.showToast({
+      title: "璇烽�夋嫨瀹℃煡缁撹",
+      icon: "none",
+    });
+    return false;
+  }
+  console.log(3);
+
+  if (!form.value.expertopinion.trim() && !isDraft) {
+    uni.showToast({
+      title: "璇疯緭鍏ュ鏌ユ剰瑙�",
+      icon: "none",
+    });
+    return false;
+  }
+  console.log(signatureData, "signatureData");
+
+  if (!signatureData.signatureUrl && !isDraft) {
+    uni.showToast({
+      title: "璇疯繘琛屾墜鍐欑鍚�",
+      icon: "none",
+    });
+    return false;
+  }
+
+  return true;
 };
 </script>
 
@@ -325,13 +1269,17 @@
           background: #fff2e8;
           color: #fa8c16;
         }
-        &.drafted {
+        &.submitted {
           background: #e6f7ff;
           color: #1890ff;
         }
-        &.submitted {
+        &.success {
           background: #f6ffed;
           color: #52c41a;
+        }
+        &.error {
+          background: #fff1f0;
+          color: #ff4d4f;
         }
       }
     }
@@ -363,7 +1311,46 @@
       }
     }
   }
+  .material-left {
+    display: flex;
+    align-items: center;
+    flex: 1;
 
+    .file-info {
+      margin-left: 16rpx;
+      display: flex;
+      flex-direction: column;
+    }
+
+    .file-name {
+      font-size: 26rpx;
+      color: #303133;
+      max-width: 400rpx;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .file-meta {
+      display: flex;
+      align-items: center;
+      gap: 8rpx;
+      margin-top: 4rpx;
+
+      .file-type {
+        font-size: 20rpx;
+        color: #909399;
+        background: #f0f2f5;
+        padding: 2rpx 8rpx;
+        border-radius: 4rpx;
+      }
+
+      .file-time {
+        font-size: 20rpx;
+        color: #909399;
+      }
+    }
+  }
   .materials-section {
     padding: 24rpx;
 
@@ -434,6 +1421,30 @@
   .review-form {
     padding: 24rpx;
 
+    .section-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20rpx;
+
+      .section-title {
+        font-size: 28rpx;
+        font-weight: 600;
+        color: #303133;
+      }
+
+      .timeout-badge {
+        display: flex;
+        align-items: center;
+        gap: 4rpx;
+        padding: 4rpx 12rpx;
+        background: #fff2e8;
+        border-radius: 12rpx;
+        font-size: 22rpx;
+        color: #fa8c16;
+      }
+    }
+
     .form-group {
       margin-bottom: 32rpx;
 
@@ -443,36 +1454,6 @@
         font-weight: 600;
         color: #303133;
         margin-bottom: 20rpx;
-      }
-    }
-
-    .risk-assessment {
-      .risk-item {
-        .risk-label {
-          display: block;
-          font-size: 26rpx;
-          color: #606266;
-          margin-bottom: 16rpx;
-        }
-
-        .risk-slider-compact {
-          .risk-levels {
-            display: flex;
-            justify-content: space-between;
-            margin-bottom: 12rpx;
-
-            .level-label {
-              font-size: 22rpx;
-              color: #c0c4cc;
-              transition: color 0.3s;
-
-              &.active {
-                color: #f56c6c;
-                font-weight: 500;
-              }
-            }
-          }
-        }
       }
     }
 
@@ -501,7 +1482,7 @@
 
   .action-bar-compact {
     position: fixed;
-    bottom: 96rpx;
+    bottom: 0rpx;
     left: 0;
     right: 0;
     display: flex;
@@ -529,7 +1510,7 @@
       }
 
       &.submit-btn {
-        background: linear-gradient(135deg, #0f95b0, #89C4C1) !important;
+        background: linear-gradient(135deg, #0f95b0, #89c4c1) !important;
         color: #fff;
 
         &:disabled {
@@ -540,6 +1521,271 @@
 
       &:active:not(:disabled) {
         transform: scale(0.98);
+      }
+    }
+  }
+
+  .readonly-tip {
+    display: flex;
+    align-items: center;
+    gap: 8rpx;
+    padding: 20rpx 24rpx;
+    font-size: 24rpx;
+    color: #fa8c16;
+  }
+}
+
+.signature-section {
+  margin-top: 20rpx;
+  padding: 24rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20rpx;
+
+    .section-title {
+      font-size: 28rpx;
+      font-weight: 600;
+      color: #333;
+    }
+
+    .timeout-badge {
+      display: flex;
+      align-items: center;
+      gap: 4rpx;
+      padding: 4rpx 12rpx;
+      background: #fff2e8;
+      border-radius: 12rpx;
+      font-size: 22rpx;
+      color: #fa8c16;
+    }
+  }
+}
+
+.signature-content {
+  .signed-preview {
+    display: flex;
+    align-items: center;
+    padding: 20rpx;
+    background: #f8f9fa;
+    border-radius: 12rpx;
+    gap: 20rpx;
+
+    .signature-image {
+      width: 200rpx;
+      height: 100rpx;
+      border: 1rpx solid #dcdfe6;
+      border-radius: 8rpx;
+      background: #fff;
+    }
+
+    .signature-info {
+      flex: 1;
+
+      .signature-name {
+        display: block;
+        font-size: 26rpx;
+        color: #333;
+        margin-bottom: 8rpx;
+        font-weight: 500;
+      }
+
+      .signature-time {
+        display: block;
+        font-size: 24rpx;
+        color: #666;
+        margin-bottom: 8rpx;
+      }
+
+      .signature-actions {
+        margin-top: 12rpx;
+
+        .re-sign-btn {
+          padding: 8rpx 16rpx;
+          border: 1rpx solid #dcdfe6;
+          background: #fff;
+          border-radius: 8rpx;
+          font-size: 24rpx;
+          color: #f56c6c;
+          display: flex;
+          align-items: center;
+          gap: 6rpx;
+
+          &:active {
+            background: #f5f7fa;
+          }
+        }
+      }
+    }
+  }
+
+  .signature-upload {
+    .signature-upload-area {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      height: 200rpx;
+      border: 2rpx dashed #c0c4cc;
+      border-radius: 12rpx;
+      background: #fafafa;
+      transition: all 0.3s;
+
+      &:active {
+        background: #f0f2f5;
+        border-color: #007aff;
+      }
+
+      .upload-hint {
+        font-size: 28rpx;
+        color: #606266;
+        margin-top: 16rpx;
+        margin-bottom: 8rpx;
+      }
+
+      .upload-tip {
+        font-size: 24rpx;
+        color: #909399;
+      }
+    }
+  }
+
+  .signature-disabled {
+    .signature-disabled-area {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      height: 200rpx;
+      border: 2rpx solid #e4e7ed;
+      border-radius: 12rpx;
+      background: #f8f9fa;
+
+      .disabled-hint {
+        font-size: 28rpx;
+        color: #909399;
+        margin-top: 16rpx;
+        margin-bottom: 8rpx;
+      }
+
+      .disabled-tip {
+        font-size: 24rpx;
+        color: #c0c4cc;
+      }
+    }
+  }
+}
+
+// 绛惧悕妯℃�佹鏍峰紡
+.signature-modal {
+  padding: 30rpx;
+  background: #fff;
+  border-radius: 20rpx 20rpx 0 0;
+  max-height: 80vh;
+  overflow: hidden;
+
+  .modal-header {
+    text-align: center;
+    margin-bottom: 30rpx;
+
+    .modal-title {
+      font-size: 32rpx;
+      font-weight: 600;
+      color: #303133;
+      display: block;
+      margin-bottom: 8rpx;
+    }
+
+    .modal-subtitle {
+      font-size: 24rpx;
+      color: #909399;
+    }
+  }
+
+  .signature-canvas-container {
+    .signature-canvas {
+      display: block;
+      margin: 0 auto;
+      border: 2rpx solid #e4e7ed;
+      border-radius: 8rpx;
+      background: #fff;
+      touch-action: none;
+    }
+
+    .canvas-actions {
+      display: flex;
+      gap: 20rpx;
+      margin: 20rpx 0;
+      padding: 0 20rpx;
+
+      .action-btn {
+        flex: 1;
+        height: 60rpx;
+        border-radius: 30rpx;
+        border: 1rpx solid #dcdfe6;
+        background: #fff;
+        font-size: 24rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 8rpx;
+
+        &.clear-btn {
+          color: #f56c6c;
+          border-color: #f56c6c;
+        }
+
+        &.redo-btn {
+          color: #1890ff;
+          border-color: #1890ff;
+
+          &:disabled {
+            color: #c0c4cc;
+            border-color: #e4e7ed;
+          }
+        }
+
+        &.confirm-btn {
+          background: linear-gradient(135deg, #0f95b0, #89c4c1);
+          color: #fff;
+          border: none;
+
+          &:disabled {
+            background: #c0c4cc;
+            opacity: 0.6;
+          }
+        }
+
+        &:active:not(:disabled) {
+          transform: scale(0.98);
+        }
+      }
+    }
+
+    .signature-preview {
+      margin-top: 20rpx;
+      padding: 20rpx;
+      background: #f8f9fa;
+      border-radius: 8rpx;
+
+      .preview-title {
+        display: block;
+        font-size: 24rpx;
+        color: #606266;
+        margin-bottom: 12rpx;
+      }
+
+      .preview-image {
+        width: 200rpx;
+        height: 80rpx;
+        border: 1rpx solid #dcdfe6;
+        border-radius: 4rpx;
+        background: #fff;
       }
     }
   }
@@ -558,12 +1804,33 @@
     .action-bar-compact {
       padding: 16rpx;
       gap: 16rpx;
-      
+
       .action-btn {
         height: 72rpx;
         font-size: 26rpx;
       }
     }
   }
+
+  .signature-modal {
+    padding: 20rpx;
+
+    .signature-canvas-container {
+      .signature-canvas {
+        width: 100% !important;
+        height: 250rpx !important;
+      }
+
+      .canvas-actions {
+        // flex-direction: column;
+        gap: 12rpx;
+        padding: 0;
+
+        .action-btn {
+          height: 72rpx;
+        }
+      }
+    }
+  }
 }
-</style>
\ No newline at end of file
+</style>

--
Gitblit v1.9.3