From f806aff5702fc6be9c9348d51964366cbf434bf7 Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期四, 11 六月 2026 09:43:39 +0800
Subject: [PATCH] 维护

---
 src/api/businessApi/ethicalReview.js                   |    6 
 管理端 (3).zip                                            |    0 
 src/views/business/decide/DecideInfo.vue               |   91 +++++++++++----
 src/views/business/ethicalReview/ethicalReviewInfo.vue |  127 ++++++++++++++-------
 管理端 (2).zip                                            |    0 
 src/components/FilePreviewDialog/index.vue             |  114 ++++++++++++++++--
 6 files changed, 253 insertions(+), 85 deletions(-)

diff --git a/src/api/businessApi/ethicalReview.js b/src/api/businessApi/ethicalReview.js
index ba15f35..7a28f1b 100644
--- a/src/api/businessApi/ethicalReview.js
+++ b/src/api/businessApi/ethicalReview.js
@@ -57,9 +57,9 @@
 // 涓撳娑堟伅鎺ㄩ��
 export function sendNotification(data) {
   return request({
-    url: "/system/dingtalk/sendNotification",
-    method: "post",
-    data: data
+    url: "/GiLink/sendExpert",
+    method: "get",
+    params: data
   });
 }
 // 鐭俊
diff --git a/src/components/FilePreviewDialog/index.vue b/src/components/FilePreviewDialog/index.vue
index ff472c1..4bfff0a 100644
--- a/src/components/FilePreviewDialog/index.vue
+++ b/src/components/FilePreviewDialog/index.vue
@@ -11,7 +11,10 @@
   >
     <!-- 鍔犺浇鐘舵�� -->
     <div v-if="loading" class="preview-loading">
-      <i class="el-icon-loading" style="font-size: 40px; margin-bottom: 16px;"></i>
+      <i
+        class="el-icon-loading"
+        style="font-size: 40px; margin-bottom: 16px;"
+      ></i>
       <span>鏂囦欢鍔犺浇涓�...</span>
     </div>
 
@@ -19,11 +22,19 @@
     <div v-else-if="fileType === 'image'" class="preview-container">
       <div class="image-toolbar">
         <el-button-group>
-          <el-button size="mini" @click="zoomImageIn" :disabled="imageScale >= 300">
+          <el-button
+            size="mini"
+            @click="zoomImageIn"
+            :disabled="imageScale >= 300"
+          >
             <i class="el-icon-zoom-in"></i> 鏀惧ぇ
           </el-button>
           <el-button size="mini" disabled>{{ imageScale }}%</el-button>
-          <el-button size="mini" @click="zoomImageOut" :disabled="imageScale <= 50">
+          <el-button
+            size="mini"
+            @click="zoomImageOut"
+            :disabled="imageScale <= 50"
+          >
             <i class="el-icon-zoom-out"></i> 缂╁皬
           </el-button>
           <el-button size="mini" @click="resetImageZoom">
@@ -83,11 +94,7 @@
           </el-button>
         </el-button-group>
 
-        <el-button
-          size="mini"
-          type="success"
-          @click="handleDownload"
-        >
+        <el-button size="mini" type="success" @click="handleDownload">
           <i class="el-icon-download"></i> 涓嬭浇
         </el-button>
       </div>
@@ -111,7 +118,10 @@
     </div>
 
     <!-- Office鏂囨。棰勮 -->
-    <div v-else-if="fileType === 'office'" class="preview-container office-preview">
+    <div
+      v-else-if="fileType === 'office'"
+      class="preview-container office-preview"
+    >
       <div class="office-toolbar">
         <el-alert
           title="Office鏂囨。棰勮鎻愮ず"
@@ -127,7 +137,11 @@
 
       <div class="office-content">
         <iframe
-          :src="`https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(fileUrl)}`"
+          :src="
+            `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(
+              fileUrl
+            )}`
+          "
           width="100%"
           height="600px"
           frameborder="0"
@@ -177,6 +191,11 @@
       type: Object,
       default: () => ({})
     }
+    // 娣诲姞baseUrlHt浣滀负props浼犲叆
+    // baseUrlHt: {
+    //   type: String,
+    //   default: process.env.VUE_APP_BASE_API
+    // }
   },
   data() {
     return {
@@ -185,6 +204,7 @@
       // 鍔犺浇鐘舵��
       loading: false,
       pdfLoading: false,
+      baseUrlHt: "",
       // 鏂囦欢淇℃伅
       fileUrl: "",
       fileName: "",
@@ -214,19 +234,75 @@
       }
     },
     previewVisible(newVal) {
-      this.$emit('update:visible', newVal);
+      this.$emit("update:visible", newVal);
       if (!newVal) {
         this.handleClose();
       }
     }
   },
+  created() {
+    this.calculateBaseUrl();
+  },
   methods: {
+    /** URL澶勭悊鍑芥暟 - 灏嗗畬鏁碪RL鏇挎崲涓篵aseUrlHt */
+    processFileUrl(url) {
+      if (!url) return "";
+
+      // 濡傛灉宸茬粡鏄畬鏁寸殑http鎴杊ttps閾炬帴
+      if (url.startsWith("http://") || url.startsWith("https://")) {
+        // 鎵惧埌绗笁涓枩鏉犲悗鐨勪綅缃紝鎻愬彇璺緞閮ㄥ垎
+        const thirdSlashIndex = url.indexOf("/", 8); // 浠巋ttp://鎴杊ttps://涔嬪悗寮�濮嬫壘
+        if (thirdSlashIndex !== -1) {
+          return `${this.baseUrlHt}${url.substring(thirdSlashIndex)}`;
+        }
+        return this.baseUrlHt; // 濡傛灉娌℃湁璺緞閮ㄥ垎锛屽彧杩斿洖baseUrlHt
+      }
+
+      // 鐩稿璺緞澶勭悊
+      if (url.startsWith("/")) {
+        return `${this.baseUrlHt}${url}`;
+      }
+
+      return `${this.baseUrlHt}/${url}`;
+    },
+    /** 璁$畻 baseUrlHt */
+    calculateBaseUrl() {
+      // 鑾峰彇褰撳墠娴忚鍣ㄥ湴鍧�
+      const currentUrl = window.location.href;
+      console.log("褰撳墠娴忚鍣ㄥ湴鍧�:", currentUrl);
+
+      try {
+        // 浣跨敤 URL 瀵硅薄瑙f瀽
+        const urlObj = new URL(currentUrl);
+        // 鑾峰彇涓绘満鍚嶏紙IP 鎴栧煙鍚嶏級
+        const hostname = urlObj.hostname;
+        // 鎷兼帴绔彛 :9095
+        this.baseUrlHt = `http://${hostname}:9095`;
+        console.log("璁$畻寰楀埌鐨� baseUrlHt:", this.baseUrlHt);
+      } catch (error) {
+        console.error("瑙f瀽URL澶辫触:", error);
+        // 澶囩敤鏂规锛氫娇鐢ㄦ鍒欐彁鍙�
+        const match = currentUrl.match(/https?:\/\/([^:\/]+)/);
+        if (match) {
+          this.baseUrlHt = `http://${match[1]}:9095`;
+        } else {
+          // 鏈�鍚庣殑澶囩敤鏂规
+          this.baseUrlHt =
+            process.env.VUE_APP_BASE_API || "http://localhost:9095";
+        }
+      }
+    },
+
     /** 鍒濆鍖栭瑙� */
     initPreview() {
       if (!this.file) return;
 
       this.fileName = this.file.fileName || this.file.name || "鏈煡鏂囦欢";
-      this.fileUrl = this.file.fileUrl || this.file.path || this.file.url;
+      const originalUrl = this.file.fileUrl || this.file.path || this.file.url;
+
+      // 澶勭悊URL锛屾浛鎹㈠煙鍚嶉儴鍒�
+      this.fileUrl = this.processFileUrl(originalUrl);
+
       this.fileType = this.getFileType(this.fileName);
 
       this.loading = true;
@@ -250,7 +326,10 @@
     getFileType(fileName) {
       if (!fileName) return "other";
 
-      const extension = fileName.split('.').pop().toLowerCase();
+      const extension = fileName
+        .split(".")
+        .pop()
+        .toLowerCase();
       const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
       const pdfTypes = ["pdf"];
       const officeTypes = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"];
@@ -263,7 +342,12 @@
 
     /** 鑾峰彇鏂囦欢鎵╁睍鍚� */
     getFileExtension(filename) {
-      return filename.split('.').pop().toLowerCase() || "鏈煡";
+      return (
+        filename
+          .split(".")
+          .pop()
+          .toLowerCase() || "鏈煡"
+      );
     },
 
     /** PDF鍔犺浇瀹屾垚 */
@@ -337,7 +421,7 @@
       this.imageScale = 100;
     },
 
-    /** 涓嬭浇鏂囦欢 */
+    /** 涓嬭浇鏂囦欢 - 涔熼渶瑕佸鐞哢RL */
     handleDownload() {
       if (!this.fileUrl) {
         this.$message.warning("鏂囦欢璺緞涓嶅瓨鍦紝鏃犳硶涓嬭浇");
diff --git a/src/views/business/decide/DecideInfo.vue b/src/views/business/decide/DecideInfo.vue
index 894015e..cdc5db6 100644
--- a/src/views/business/decide/DecideInfo.vue
+++ b/src/views/business/decide/DecideInfo.vue
@@ -502,6 +502,13 @@
         </el-button>
       </div>
     </el-dialog>
+    <!-- 闄勪欢棰勮 -->
+    <FilePreviewDialog
+      :visible="previewVisible"
+      :file="currentPreviewFile"
+      @close="previewVisible = false"
+      @download="handleDownload"
+    />
   </div>
 </template>
 
@@ -513,10 +520,11 @@
 } from "@/api/businessApi";
 import { getToken } from "@/utils/auth";
 import CaseBasicInfo from "@/components/CaseBasicInfo";
+import FilePreviewDialog from "@/components/FilePreviewDialog";
 
 export default {
   name: "DeathJudgmentDetail",
-  components: { CaseBasicInfo },
+  components: { CaseBasicInfo, FilePreviewDialog },
 
   data() {
     return {
@@ -527,7 +535,9 @@
 
       // 鍒ゅ畾绫诲瀷鏍囩
       activeJudgmentType: "brain", // 榛樿鏄剧ず鑴戞浜�
-
+      // 棰勮鐩稿叧
+      previewVisible: false,
+      currentPreviewFile: null,
       // 琛ㄥ崟鏁版嵁
       form: {
         id: undefined,
@@ -1000,30 +1010,63 @@
     },
 
     // 棰勮闄勪欢
-    handlePreview(attachment) {
-      if (attachment.fileName.endsWith(".pdf")) {
-        window.open(attachment.fileUrl, "_blank");
-      } else if (attachment.fileName.match(/\.(jpg|jpeg|png)$/i)) {
-        this.$alert(
-          `<img src="${attachment.fileUrl}" style="max-width: 100%;" alt="${attachment.fileName}">`,
-          "鍥剧墖棰勮",
-          {
-            dangerouslyUseHTMLString: true,
-            customClass: "image-preview-dialog"
-          }
-        );
-      } else {
-        this.$message.info("璇ユ枃浠剁被鍨嬫殏涓嶆敮鎸佸湪绾块瑙堬紝璇蜂笅杞藉悗鏌ョ湅");
-      }
-    },
+    handlePreview(file) {
+      console.log(file, "file");
 
+      this.currentPreviewFile = {
+        fileName: file.fileName,
+        fileUrl: file.path || file.fileUrl,
+        fileType: this.getFileType(file.fileName)
+      };
+      this.previewVisible = true;
+      // if (attachment.fileName.endsWith(".pdf")) {
+      //   window.open(attachment.fileUrl, "_blank");
+      // } else if (attachment.fileName.match(/\.(jpg|jpeg|png)$/i)) {
+      //   this.$alert(
+      //     `<img src="${attachment.fileUrl}" style="max-width: 100%;" alt="${attachment.fileName}">`,
+      //     "鍥剧墖棰勮",
+      //     {
+      //       dangerouslyUseHTMLString: true,
+      //       customClass: "image-preview-dialog"
+      //     }
+      //   );
+      // } else {
+      //   this.$message.info("璇ユ枃浠剁被鍨嬫殏涓嶆敮鎸佸湪绾块瑙堬紝璇蜂笅杞藉悗鏌ョ湅");
+      // }
+    },
+    getFileType(fileName) {
+      if (!fileName) return "other";
+
+      const extension = fileName
+        .split(".")
+        .pop()
+        .toLowerCase();
+      const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
+      const pdfTypes = ["pdf"];
+      const officeTypes = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"];
+
+      if (imageTypes.includes(extension)) return "image";
+      if (pdfTypes.includes(extension)) return "pdf";
+      if (officeTypes.includes(extension)) return "office";
+      return "other";
+    },
     // 涓嬭浇闄勪欢
-    handleDownload(attachment) {
-      const link = document.createElement("a");
-      link.href = attachment.fileUrl;
-      link.download = attachment.fileName;
-      link.click();
-      this.$message.success(`寮�濮嬩笅杞�: ${attachment.fileName}`);
+    handleDownload(file) {
+      const fileUrl = file.path || file.fileUrl;
+      const fileName = file.fileName;
+
+      if (fileUrl) {
+        const link = document.createElement("a");
+        link.href = fileUrl;
+        link.download = fileName;
+        link.style.display = "none";
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+        this.$message.success("寮�濮嬩笅杞芥枃浠�");
+      } else {
+        this.$message.warning("鏂囦欢璺緞涓嶅瓨鍦紝鏃犳硶涓嬭浇");
+      }
     },
 
     // 缂栬緫淇℃伅
diff --git a/src/views/business/ethicalReview/ethicalReviewInfo.vue b/src/views/business/ethicalReview/ethicalReviewInfo.vue
index 8e56dfa..529af28 100644
--- a/src/views/business/ethicalReview/ethicalReviewInfo.vue
+++ b/src/views/business/ethicalReview/ethicalReviewInfo.vue
@@ -620,7 +620,7 @@
           </div>
         </el-form-item>
 
-        <el-form-item label="鍙戦�佹柟寮�" prop="sendType" required>
+        <!-- <el-form-item label="鍙戦�佹柟寮�" prop="sendType" required>
           <el-select
             v-model="sendForm.sendType"
             placeholder="璇烽�夋嫨鍙戦�佹柟寮�"
@@ -631,27 +631,27 @@
             <el-option label="鐭俊鍙戦��" value="2"></el-option>
             <el-option label="鍏朵粬鏂瑰紡" value="3"></el-option>
           </el-select>
-        </el-form-item>
+        </el-form-item> -->
 
-        <el-form-item label="鍙戦�佹爣棰�" prop="title" required>
+        <!-- <el-form-item label="鍙戦�佹爣棰�" prop="title" required>
           <el-input v-model="sendForm.title" placeholder="璇疯緭鍏ュ彂閫佹爣棰�" />
-        </el-form-item>
+        </el-form-item> -->
 
-        <el-form-item label="鍙戦�佸唴瀹�" prop="content" required>
+        <!-- <el-form-item label="鍙戦�佸唴瀹�" prop="content" required>
           <el-input
             type="textarea"
             :rows="4"
             v-model="sendForm.content"
             placeholder="璇疯緭鍏ュ彂閫佺粰涓撳鐨勫鏌ュ唴瀹硅鏄�"
           />
-        </el-form-item>
+        </el-form-item> -->
 
-        <el-form-item label="璺宠浆閾炬帴" prop="url">
+        <!-- <el-form-item label="璺宠浆閾炬帴" prop="url">
           <el-input
             v-model="sendForm.url"
             placeholder="璇疯緭鍏ヨ烦杞摼鎺ワ紙鍙�夛級"
           />
-        </el-form-item>
+        </el-form-item> -->
       </el-form>
       <div slot="footer">
         <el-button @click="sendDialogVisible = false">鍙栨秷</el-button>
@@ -962,7 +962,6 @@
         startTime: "",
         endTime: "",
         sendType: "0",
-        title: "浼︾悊瀹℃煡浠诲姟閫氱煡",
         content: "",
         url: ""
       },
@@ -1504,9 +1503,9 @@
         if (valid) {
           this.saveLoading = true;
           // 淇濆瓨娓呯┖id渚夸簬鍚庣鏁翠綋鍒犻櫎鏂板
-          this.form.ethicalreviewopinionsList.forEach(item => {
-            item.id = null;
-          });
+          // this.form.ethicalreviewopinionsList.forEach(item => {
+          //   item.id = null;
+          // });
           try {
             const submitData = {
               ...this.form,
@@ -1532,10 +1531,12 @@
               this.isEdit = false;
               if (!this.form.id && response.data && response.data.id) {
                 this.form.id = response.data.id;
-                this.$router.replace({
-                  query: { ...this.$route.query, id: this.form.id }
-                });
+                this.id = this.form.id;
+                // this.$router.replace({
+                //   query: { ...this.$route.query, id: this.form.id }
+                // });
               }
+              this.refreshPageData();
             } else {
               this.$message.error("淇濆瓨澶辫触锛�" + (response.msg || "鏈煡閿欒"));
             }
@@ -2060,6 +2061,9 @@
 
     // 鍙戦�佺粰鏅�氫笓瀹�
     handleSendToNormalExperts() {
+      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
+        return;
+      }
       this.currentSendExperts = this.availableNormalExperts;
       this.sendForm.expertType = "normal";
       this.sendForm.endTime = ""; // 閲嶇疆鎴鏃堕棿
@@ -2068,6 +2072,9 @@
 
     // 鍙戦�佺粰涓诲涓撳
     handleSendToChiefExpert() {
+      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
+        return;
+      }
       this.currentSendExperts = this.availableChiefExperts;
       this.sendForm.expertType = "chief";
       this.sendForm.endTime = ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
@@ -2076,12 +2083,40 @@
 
     // 鍙戦�佺粰鍗曚釜涓撳
     handleSendToExpert(expert) {
+      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
+        return;
+      }
       this.currentSendExperts = [expert];
       this.sendForm.expertType = expert.expertType == "1" ? "chief" : "normal";
       this.sendForm.endTime = expert.expertType == "1" ? "" : ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
       this.sendDialogVisible = true;
     },
 
+    validateAllIds(arr, idField = "id") {
+      if (!Array.isArray(arr) || arr.length === 0) {
+        this.$message.warning("璇峰厛閫夋嫨涓撳鍒楄〃");
+        return false;
+      }
+
+      const emptyIdItems = arr.filter(item => {
+        const id = item[idField];
+        return (
+          id === undefined ||
+          id === null ||
+          id === "" ||
+          id.toString().trim() === ""
+        );
+      });
+
+      if (emptyIdItems.length > 0) {
+        this.$message.warning(
+          "褰撳墠閫変腑涓撳鍒楄〃鏈夋湭淇濆瓨鏁版嵁锛岃淇濆瓨鍒锋柊鍚庡啀鍙戦��"
+        );
+        return false;
+      }
+
+      return true;
+    },
     // 鍙戦�佸璇濇鍏抽棴
     handleSendDialogClose() {
       this.sendForm = {
@@ -2090,7 +2125,6 @@
         startTime: "",
         endTime: "",
         sendType: "0",
-        title: "浼︾悊瀹℃煡浠诲姟閫氱煡",
         content: "",
         url: ""
       };
@@ -2110,20 +2144,20 @@
         return;
       }
 
-      if (!this.sendForm.sendType) {
-        this.$message.warning("璇烽�夋嫨鍙戦�佹柟寮�");
-        return;
-      }
+      // if (!this.sendForm.sendType) {
+      //   this.$message.warning("璇烽�夋嫨鍙戦�佹柟寮�");
+      //   return;
+      // }
 
-      if (!this.sendForm.title) {
-        this.$message.warning("璇疯緭鍏ュ彂閫佹爣棰�");
-        return;
-      }
+      // if (!this.sendForm.title) {
+      //   this.$message.warning("璇疯緭鍏ュ彂閫佹爣棰�");
+      //   return;
+      // }
 
-      if (!this.sendForm.content) {
-        this.$message.warning("璇疯緭鍏ュ彂閫佸唴瀹�");
-        return;
-      }
+      // if (!this.sendForm.content) {
+      //   this.$message.warning("璇疯緭鍏ュ彂閫佸唴瀹�");
+      //   return;
+      // }
 
       if (this.currentSendExperts.length == 0) {
         this.$message.warning("娌℃湁鎵惧埌鍙彂閫佺殑涓撳");
@@ -2245,7 +2279,6 @@
           startTime: "",
           endTime: "",
           sendType: "0",
-          title: "浼︾悊瀹℃煡浠诲姟閫氱煡",
           content: "",
           url: ""
         };
@@ -2262,27 +2295,35 @@
     },
     // 鍙戦�佸崟涓笓瀹剁殑鏂规硶
     async sendSingleExpert(expert, index) {
+      console.log(expert, "expert", index);
+
       try {
-        // 鏋勫缓鍙戦�佹暟鎹�
-        const sendData = {
-          number: expert.deptname || "", // 鐢ㄦ埛鎵嬫満鍙�
-          title: this.sendForm.title,
-          url: this.sendForm.url || "",
-          createTime: new Date()
-            .toISOString()
-            .replace("T", " ")
-            .substring(0, 19)
+        // // 鏋勫缓鍙戦�佹暟鎹�
+        // const sendData = {
+        //   number: expert.deptname || "", // 鐢ㄦ埛鎵嬫満鍙�
+        //   title: this.sendForm.title,
+        //   url: this.sendForm.url || "",
+        //   createTime: new Date()
+        //     .toISOString()
+        //     .replace("T", " ")
+        //     .substring(0, 19)
+        // };
+        const sendDatas = {
+          expertNo: expert.expertNo || "",
+          id: expert.id || "",
+          infoId: this.infoid || ""
         };
 
         console.log(`姝e湪鍙戦�佺 ${index + 1} 涓笓瀹�: ${expert.expertname}`);
 
         // 璋冪敤鍙戦�侀�氱煡鎺ュ彛
         // const response = await sendNotification(sendData);
-        const response = await sendcall({
-          tel: expert.donorno ? expert.donorno : 13634195431, // 杩欓噷搴旇鏄� expert.deptname 鎴� expert.phone
-          messageContent:
-            "闈掑矝澶у闄勫睘鍖婚櫌涓婃姤娼滃湪鎹愮尞妗堜緥锛岃鐧诲綍OPO绯荤粺鏌ョ湅璇︾粏淇℃伅锛屽強鏃惰繘琛屽鎺ャ�傜櫥褰曢摼鎺�:https://brdeddd.qduhosos.cn/dklejdj/deljf/index"
-        });
+        // const response = await sendcall({
+        //   tel: expert.donorno ? expert.donorno : 13634195431, // 杩欓噷搴旇鏄� expert.deptname 鎴� expert.phone
+        //   messageContent:
+        //     "闈掑矝澶у闄勫睘鍖婚櫌涓婃姤娼滃湪鎹愮尞妗堜緥锛岃鐧诲綍OPO绯荤粺鏌ョ湅璇︾粏淇℃伅锛屽強鏃惰繘琛屽鎺ャ�傜櫥褰曢摼鎺�:https://brdeddd.qduhosos.cn/dklejdj/deljf/index"
+        // });
+        const response = await sendNotification(sendDatas);
 
         if (response.code == 200) {
           return {
diff --git "a/\347\256\241\347\220\206\347\253\257 \0502\051.zip" "b/\347\256\241\347\220\206\347\253\257 \0502\051.zip"
new file mode 100644
index 0000000..0491ae9
--- /dev/null
+++ "b/\347\256\241\347\220\206\347\253\257 \0502\051.zip"
Binary files differ
diff --git "a/\347\256\241\347\220\206\347\253\257 \0503\051.zip" "b/\347\256\241\347\220\206\347\253\257 \0503\051.zip"
new file mode 100644
index 0000000..56e4c0d
--- /dev/null
+++ "b/\347\256\241\347\220\206\347\253\257 \0503\051.zip"
Binary files differ

--
Gitblit v1.9.3