From be125538c6e9c17a923c9dbe1e4cca9962b0ed39 Mon Sep 17 00:00:00 2001
From: yaowanxin <3588231647@qq.com>
Date: 星期一, 08 九月 2025 10:17:53 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev' into ywx

---
 src/views/collaborativeApproval/notificationManagement/index.vue |  343 +++++++++++++++++++++++++++++---------------------------
 1 files changed, 179 insertions(+), 164 deletions(-)

diff --git a/src/views/collaborativeApproval/notificationManagement/index.vue b/src/views/collaborativeApproval/notificationManagement/index.vue
index 2db6780..8e1b792 100644
--- a/src/views/collaborativeApproval/notificationManagement/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/index.vue
@@ -87,6 +87,8 @@
                 v-model="form.expireDate"
                 type="date"
                 placeholder="璇烽�夋嫨鏈夋晥鏈�"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
                 style="width: 100%"
               />
             </el-form-item>
@@ -152,6 +154,8 @@
               <el-date-picker
                 v-model="meetingForm.startTime"
                 type="datetime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                format="YYYY-MM-DD HH:mm:ss"
                 placeholder="璇烽�夋嫨寮�濮嬫椂闂�"
                 style="width: 100%"
               />
@@ -319,6 +323,8 @@
 import PIMTable from "@/components/PIMTable/PIMTable.vue";
 import { userListNoPageByTenantId } from "@/api/system/user.js";
 import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js";
+import { listNotification, addNotification, updateNotification, delNotification,addOnlineMeeting,addFileSharing } from "@/api/collaborativeApproval/notificationManagement.js";
+import { id } from "element-plus/es/locales.mjs";
 
 // 琛ㄥ崟楠岃瘉瑙勫垯
 const rules = {
@@ -364,7 +370,7 @@
   tableLoading: false,
   page: {
     current: 1,
-    size: 100,
+    size: 20,
     total: 0,
   },
   tableData: [],
@@ -373,7 +379,7 @@
   form: {
     title: "",
     type: "",
-    priority: "medium",
+    priority: "",
     content: "",
     departments: [],
     expireDate: "",
@@ -403,11 +409,11 @@
   fileList: []
 });
 
-const { 
-  searchForm, 
-  tableLoading, 
-  page, 
-  tableData, 
+const {
+  searchForm,
+  tableLoading,
+  page,
+  tableData,
   selectedIds,
   form,
   dialogVisible,
@@ -556,47 +562,6 @@
     ]
   }
 ]);
-
-// 妯℃嫙鏁版嵁
-let mockData = [
-  {
-    id: "1",
-    title: "2024骞存槬鑺傛斁鍋囬�氱煡",
-    type: "holiday",
-    priority: "high",
-    status: "published",
-    content: "鏍规嵁鍥藉瑙勫畾锛岀粨鍚堝叕鍙稿疄闄呮儏鍐碉紝鐜板皢2024骞存槬鑺傛斁鍋囧畨鎺掗�氱煡濡備笅...",
-    departments: ["鎶�鏈儴", "閿�鍞儴", "浜轰簨閮�", "璐㈠姟閮�", "杩愯惀閮�", "甯傚満閮�", "瀹㈡湇閮�"],
-    expireDate: "2024-02-15",
-    syncMethods: ["wechat", "dingtalk", "email"],
-    createTime: "2024-01-15 10:30:00"
-  },
-  {
-    id: "2",
-    title: "鎶�鏈儴鍛ㄤ緥浼氶�氱煡",
-    type: "meeting",
-    priority: "medium",
-    status: "published",
-    content: "鎶�鏈儴瀹氫簬姣忓懆浜斾笅鍗�2鐐瑰彫寮�鍛ㄤ緥浼氾紝璇峰悇浣嶅悓浜嬪噯鏃跺弬鍔�...",
-    departments: ["鎶�鏈儴"],
-    expireDate: "2024-01-20",
-    syncMethods: ["wechat", "dingtalk"],
-    createTime: "2024-01-14 15:20:00"
-  },
-  {
-    id: "3",
-    title: "鍛樺伐琛屼负瑙勮寖澶勭綒閫氱煡",
-    type: "penalty",
-    priority: "high",
-    status: "draft",
-    content: "涓虹淮鎶ゅ叕鍙告甯哥З搴忥紝瑙勮寖鍛樺伐琛屼负锛岀幇瀵硅繚鍙嶅叕鍙歌瀹氱殑琛屼负杩涜澶勭綒...",
-    departments: ["浜轰簨閮�", "鎶�鏈儴", "閿�鍞儴"],
-    expireDate: "2024-02-13",
-    syncMethods: ["wechat", "email"],
-    createTime: "2024-01-13 09:15:00"
-  }
-];
-
 // 閫氱煡鏍囬妯℃澘
 const titleTemplates = [
   "鍏充簬{year}骞磠holiday}鏀惧亣瀹夋帓鐨勯�氱煡",
@@ -631,7 +596,7 @@
     employeesLoading.value = true;
     // 浼樺厛浣跨敤绯荤粺鐢ㄦ埛鎺ュ彛锛堟寜绉熸埛鑾峰彇锛�
     const userResponse = await userListNoPageByTenantId();
-    
+
     if (userResponse.data) {
       employees.value = userResponse.data.map(user => ({
         label: user.nickName || user.userName || '鏈煡濮撳悕',
@@ -643,12 +608,12 @@
       })).filter(user => user.status === '0'); // 鍙樉绀烘甯哥姸鎬佺殑鐢ㄦ埛
     } else {
       // 濡傛灉绯荤粺鐢ㄦ埛鎺ュ彛澶辫触锛屼娇鐢ㄥ憳宸ュ彴璐︽帴鍙�
-      const response = await staffOnJobListPage({ 
-        pageNum: 1, 
-        pageSize: 1000, 
+      const response = await staffOnJobListPage({
+        pageNum: 1,
+        pageSize: 1000,
         staffState: 1 // 鍦ㄨ亴鐘舵��
       });
-      
+
       if (response.data && response.data.records) {
         employees.value = response.data.records.map(employee => ({
           label: employee.staffName || employee.name || '鏈煡濮撳悕',
@@ -664,9 +629,9 @@
     console.error('鑾峰彇鍛樺伐鍒楄〃澶辫触:', error);
     // 濡傛灉鎺ュ彛閮藉け璐ワ紝浣跨敤榛樿鏁版嵁
     employees.value = [
-      { label: "闄堝織寮�", value: "001", dept: "鎶�鏈儴", phone: "13800138001", email: "chenzhiqiang@company.com", status: "0" },
-      { label: "鍒橀泤濠�", value: "002", dept: "閿�鍞儴", phone: "13800138002", email: "liuyating@company.com", status: "0" },
-      { label: "鐜嬪缓鍥�", value: "003", dept: "浜轰簨閮�", phone: "13800138003", email: "wangjianguo@company.com", status: "0" }
+      { label: "寮犱笁", value: "001", dept: "鎶�鏈儴", phone: "13800138001", email: "zhangsan@company.com", status: "0" },
+      { label: "鏉庡洓", value: "002", dept: "閿�鍞儴", phone: "13800138002", email: "lisi@company.com", status: "0" },
+      { label: "鐜嬩簲", value: "003", dept: "浜轰簨閮�", phone: "13800138003", email: "wangwu@company.com", status: "0" }
     ];
   } finally {
     employeesLoading.value = false;
@@ -683,7 +648,7 @@
     }
     groups[dept].push(employee);
   });
-  
+
   // 鎸夐儴闂ㄥ悕绉版帓搴忥紝纭繚鏄剧ず椤哄簭涓�鑷�
   return Object.keys(groups)
     .sort()
@@ -697,7 +662,7 @@
 const filterEmployees = (query) => {
   if (query !== '') {
     const lowerQuery = query.toLowerCase();
-    return employees.value.filter(employee => 
+    return employees.value.filter(employee =>
       employee.label.toLowerCase().includes(lowerQuery) ||
       employee.dept.toLowerCase().includes(lowerQuery) ||
       (employee.phone && employee.phone.includes(query)) ||
@@ -712,18 +677,18 @@
 const refreshEmployees = async () => {
   ElMessage.info("姝e湪鍒锋柊鍛樺伐鍒楄〃...");
   await getEmployeesList();
-  
+
   // 缁熻鍚勯儴闂ㄤ汉鏁�
   const deptStats = {};
   employees.value.forEach(emp => {
     const dept = emp.dept || '鍏朵粬閮ㄩ棬';
     deptStats[dept] = (deptStats[dept] || 0) + 1;
   });
-  
+
   const deptInfo = Object.entries(deptStats)
     .map(([dept, count]) => `${dept}: ${count}浜篳)
     .join(', ');
-  
+
   ElMessage.success(`鍛樺伐鍒楄〃鍒锋柊瀹屾垚锛屽叡 ${employees.value.length} 浜� (${deptInfo})`);
 };
 
@@ -737,7 +702,7 @@
 const getEmployeeInfo = (employeeId) => {
   const employee = employees.value.find(emp => emp.value === employeeId);
   if (!employee) return null;
-  
+
   return {
     name: employee.label,
     dept: employee.dept,
@@ -776,7 +741,7 @@
   const now = new Date();
   const randomType = notificationTypes[Math.floor(Math.random() * notificationTypes.length)];
   const randomDept = departments[Math.floor(Math.random() * departments.length)];
-  
+
   // 鐢熸垚闅忔満鏍囬
   let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)];
   title = title
@@ -788,15 +753,15 @@
     .replace('{company}', ['鍏徃', '闆嗗洟', '鎬婚儴'][Math.floor(Math.random() * 4)])
     .replace('{project}', ['鏁板瓧鍖栬浆鍨�', '浜у搧鍗囩骇', '甯傚満鎷撳睍', '浜烘墠鍩瑰吇'][Math.floor(Math.random() * 4)])
     .replace('{policy}', ['鑰冨嫟', '钖叕', '绂忓埄', '鏅嬪崌'][Math.floor(Math.random() * 4)]);
-  
+
   // 闅忔満鐘舵��
   const statuses = ['draft', 'published'];
   const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
-  
+
   // 闅忔満浼樺厛绾�
   const priorities = ['low', 'medium', 'high'];
   const randomPriority = priorities[Math.floor(Math.random() * priorities.length)];
-  
+
   const newNotification = {
     id: newId,
     title: title,
@@ -809,15 +774,15 @@
     syncMethods: ["wechat", "dingtalk"],
     createTime: now.toLocaleString()
   };
-  
+
   // 娣诲姞鍒版暟鎹紑澶�
   mockData.unshift(newNotification);
-  
+
   // 淇濇寔鏁版嵁閲忓湪鍚堢悊鑼冨洿鍐咃紙鏈�澶氫繚鐣�20鏉★級
   if (mockData.length > 20) {
     mockData = mockData.slice(0, 20);
   }
-  
+
   console.log(`[${new Date().toLocaleString()}] 鑷姩鐢熸垚鏂伴�氱煡: ${title}`);
 };
 
@@ -844,24 +809,14 @@
 
 const getList = () => {
   tableLoading.value = true;
-  
-  setTimeout(() => {
-    let filteredData = [...mockData];
-    
-    if (searchForm.value.title) {
-      filteredData = filteredData.filter(item => 
-        item.title.toLowerCase().includes(searchForm.value.title.toLowerCase())
-      );
-    }
-    
-    if (searchForm.value.type) {
-      filteredData = filteredData.filter(item => item.type === searchForm.value.type);
-    }
-    
-    tableData.value = filteredData;
-    page.value.total = filteredData.length;
+  listNotification({...page.value, ...searchForm.value})
+  .then(res => {
     tableLoading.value = false;
-  }, 500);
+    tableData.value = res.data.records
+    page.value.total = res.data.total;
+  }).catch(err => {
+    tableLoading.value = false;
+  })
 };
 
 // 鍒嗛〉澶勭悊
@@ -883,23 +838,27 @@
     dialogTitle.value = "鏂板閫氱煡";
     // 閲嶇疆琛ㄥ崟
     Object.assign(form.value, {
+      id: "",
       title: "",
       type: "",
-      priority: "medium",
+      priority: "",
       content: "",
       departments: [],
       expireDate: "",
+      status: "draft",
       syncMethods: []
     });
   } else if (type === "edit" && row) {
     dialogTitle.value = "缂栬緫閫氱煡";
     Object.assign(form.value, {
+      id: row.id,
       title: row.title,
       type: row.type,
       priority: row.priority,
       content: row.content || "",
       departments: row.departments || [],
       expireDate: row.expireDate || "",
+      status: row.status,
       syncMethods: row.syncMethods || []
     });
   }
@@ -944,43 +903,30 @@
 const submitForm = async () => {
   try {
     await formRef.value.validate();
-    
+
     if (dialogType.value === "add") {
       // 鏂板閫氱煡
-      const newNotification = {
-        id: (mockData.length + 1).toString(),
-        title: form.value.title,
-        type: form.value.type,
-        priority: form.value.priority,
-        status: "draft",
-        content: form.value.content,
-        departments: form.value.departments,
-        expireDate: form.value.expireDate,
-        syncMethods: form.value.syncMethods,
-        createTime: new Date().toLocaleString()
-      };
-      
-      mockData.unshift(newNotification);
-      ElMessage.success("閫氱煡鍒涘缓鎴愬姛");
+      addNotification({...form.value}).then(res => {
+        if(res.code == 200){
+          ElMessage.success("娣诲姞鎴愬姛");
+          dialogVisible.value = false;
+          getList();
+        }
+      }).catch(err => {
+        ElMessage.error(err.msg);
+      })
     } else {
       // 缂栬緫閫氱煡
-      const index = mockData.findIndex(item => item.id === selectedIds.value[0]);
-      if (index !== -1) {
-        Object.assign(mockData[index], {
-          title: form.value.title,
-          type: form.value.type,
-          priority: form.value.priority,
-          content: form.value.content,
-          departments: form.value.departments,
-          expireDate: form.value.expireDate,
-          syncMethods: form.value.syncMethods
-        });
-        ElMessage.success("閫氱煡鏇存柊鎴愬姛");
-      }
+      updateNotification({...form.value}).then(res => {
+        if(res.code == 200){
+          ElMessage.success("鏇存柊鎴愬姛");
+          dialogVisible.value = false;
+          getList();
+        }
+      }).catch(err => {
+        ElMessage.error(err.msg);
+      })
     }
-    
-    dialogVisible.value = false;
-    getList();
   } catch (error) {
     console.error("琛ㄥ崟楠岃瘉澶辫触:", error);
   }
@@ -990,7 +936,7 @@
 const createMeeting = async () => {
   try {
     await meetingFormRef.value.validate();
-    
+
     // 妯℃嫙鍒涘缓浼氳
     const meetingInfo = {
       title: meetingForm.value.title,
@@ -998,22 +944,28 @@
       duration: meetingForm.value.duration,
       participants: meetingForm.value.participants,
       description: meetingForm.value.description,
-      platform: meetingForm.value.platform,
-      meetingId: `MTG${Date.now()}`
+      platform: meetingForm.value.platform
     };
-    
+    // 鏂板浼氳
+    addOnlineMeeting({...meetingInfo}).then(res => {
+      if(res.code == 200){
+        ElMessage.success("浼氳娣诲姞鎴愬姛");
+        meetingDialogVisible.value = false;
+        getList();
+      }
+    }).catch(err => {
+      ElMessage.error(err.msg);
+    })
     // 妯℃嫙鍙戦�佸埌浼佷笟寰俊/閽夐拤
-    const platformName = meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "鏈煡骞冲彴";
-    
-    ElMessage.success(`浼氳鍒涘缓鎴愬姛锛佷細璁甀D: ${meetingInfo.meetingId}锛屽皢閫氳繃${platformName}鍙戦�侀�氱煡`);
-    meetingDialogVisible.value = false;
-    
-         // 鑾峰彇鍙備細浜哄憳淇℃伅
+    // const platformName = meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "鏈煡骞冲彴";
+    // ElMessage.success(`浼氳鍒涘缓鎴愬姛锛佷細璁甀D: ${meetingInfo.meetingId}锛屽皢閫氳繃${platformName}鍙戦�侀�氱煡`);
+
+    // 鑾峰彇鍙備細浜哄憳淇℃伅
      const participantNames = meetingForm.value.participants.map(participantId => {
        const employee = employees.value.find(emp => emp.value === participantId);
        return employee ? employee.label : '鏈煡浜哄憳';
      }).join('銆�');
-     
+
      // 鑾峰彇鍙備細浜哄憳璇︾粏淇℃伅
      const participantDetails = meetingForm.value.participants.map(participantId => {
        const employee = employees.value.find(emp => emp.value === participantId);
@@ -1024,23 +976,29 @@
          email: employee.email
        } : null;
      }).filter(Boolean);
-    
+
     // 灏嗕細璁俊鎭坊鍔犲埌閫氱煡鍒楄〃
     const meetingNotification = {
-      id: (mockData.length + 1).toString(),
       title: `[浼氳閫氱煡] ${meetingInfo.title}`,
       type: "meeting",
       priority: "high",
       status: "published",
-             content: `浼氳鏃堕棿: ${meetingInfo.startTime}锛屾椂闀�: ${meetingInfo.duration}鍒嗛挓锛屽钩鍙�: ${meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "鏈煡骞冲彴"}锛屽弬浼氫汉鍛�: ${participantNames}锛屽叡${participantDetails.length}浜篳,
+      content: `浼氳鏃堕棿: ${meetingInfo.startTime}锛屾椂闀�: ${meetingInfo.duration}鍒嗛挓锛屽钩鍙�: ${meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "鏈煡骞冲彴"}锛屽弬浼氫汉鍛�: ${participantNames}锛屽叡${participantDetails.length}浜篳,
       departments: [],
       expireDate: "",
-      syncMethods: [meetingForm.value.platform],
-      createTime: new Date().toLocaleString()
+      syncMethods: [meetingForm.value.platform]
     };
-    
-    mockData.unshift(meetingNotification);
-    getList();
+    addNotification({...meetingNotification}).then(res => {
+        if(res.code == 200){
+          ElMessage.success("娣诲姞鎴愬姛");
+          // dialogVisible.value = false;
+          getList();
+        }
+      }).catch(err => {
+        ElMessage.error(err.msg);
+      })
+    // mockData.unshift(meetingNotification);
+    // getList();
   } catch (error) {
     console.error("浼氳琛ㄥ崟楠岃瘉澶辫触:", error);
   }
@@ -1053,16 +1011,16 @@
     ElMessage.error("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!");
     return false;
   }
-  
+
   const fileInfo = {
     name: file.name,
     size: file.size,
     type: file.type,
     uid: file.uid
   };
-  
+
   fileList.value.push(fileInfo);
-  fileShareForm.value.files.push(fileInfo);
+  fileShareForm.value.files.push(fileInfo.name);
   return false; // 闃绘鑷姩涓婁紶
 };
 
@@ -1082,27 +1040,34 @@
 const shareFiles = async () => {
   try {
     await fileShareFormRef.value.validate();
-    
+
     if (fileShareForm.value.files.length === 0) {
       ElMessage.warning("璇疯嚦灏戦�夋嫨涓�涓枃浠�");
       return;
     }
-    
+
     // 妯℃嫙鏂囦欢鍏变韩
     const shareInfo = {
       title: fileShareForm.value.title,
       description: fileShareForm.value.description,
       departments: fileShareForm.value.departments,
       files: fileShareForm.value.files,
-      shareId: `FILE${Date.now()}`
     };
-    
-    ElMessage.success(`鏂囦欢鍏变韩鎴愬姛锛佸叡浜獻D: ${shareInfo.shareId}锛屽凡閫氱煡鐩稿叧閮ㄩ棬`);
-    fileShareDialogVisible.value = false;
-    
+    addFileSharing({...shareInfo}).then(res => {
+      if(res.code == 200){
+        ElMessage.success("鏂囦欢鍏变韩鎴愬姛");
+        fileShareDialogVisible.value = false;
+        getList();
+      }
+    }).catch(err => {
+      ElMessage.error(err.msg);
+    })
+
+    // ElMessage.success(`鏂囦欢鍏变韩鎴愬姛锛佸叡浜獻D: ${shareInfo.shareId}锛屽凡閫氱煡鐩稿叧閮ㄩ棬`);
+
+
     // 灏嗘枃浠跺叡浜俊鎭坊鍔犲埌閫氱煡鍒楄〃
     const fileShareNotification = {
-      id: (mockData.length + 1).toString(),
       title: `[鏂囦欢鍏变韩] ${shareInfo.title}`,
       type: "temporary",
       priority: "medium",
@@ -1111,11 +1076,19 @@
       departments: shareInfo.departments,
       expireDate: "",
       syncMethods: ["wechat", "dingtalk"],
-      createTime: new Date().toLocaleString()
     };
-    
-    mockData.unshift(fileShareNotification);
-    getList();
+    addNotification({...fileShareNotification}).then(res => {
+      if(res.code == 200){
+        ElMessage.success("娣诲姞鎴愬姛");
+        // dialogVisible.value = false;
+        getList();
+      }
+    }).catch(err => {
+      ElMessage.error(err.msg);
+    })
+
+    // mockData.unshift(fileShareNotification);
+    // getList();
   } catch (error) {
     console.error("鏂囦欢鍏变韩琛ㄥ崟楠岃瘉澶辫触:", error);
   }
@@ -1123,33 +1096,75 @@
 
 // 鍙戝竷閫氱煡
 const publishNotification = (row) => {
-  row.status = "published";
-  ElMessage.success("閫氱煡鍙戝竷鎴愬姛");
-  getList();
+  Object.assign(form.value, {
+    id: row.id,
+    title: row.title,
+    type: row.type,
+    priority: row.priority,
+    content: row.content || "",
+    departments: row.departments || [],
+    expireDate: row.expireDate || "",
+    status: row.status,
+    syncMethods: row.syncMethods || []
+  });
+  form.value.status = "published";
+  updateNotification({...form.value}).then(res => {
+        if(res.code == 200){
+          ElMessage.success("閫氱煡鍙戝竷鎴愬姛");
+          getList();
+        }
+      }).catch(err => {
+        ElMessage.error(err.msg);
+      })
 };
 
 // 鎾ゅ洖閫氱煡
 const revokeNotification = (row) => {
-  row.status = "draft";
-  ElMessage.success("閫氱煡宸叉挙鍥�");
-  getList();
+    Object.assign(form.value, {
+    id: row.id,
+    title: row.title,
+    type: row.type,
+    priority: row.priority,
+    content: row.content || "",
+    departments: row.departments || [],
+    expireDate: row.expireDate || "",
+    status: row.status,
+    syncMethods: row.syncMethods || []
+  });
+  form.value.status = "draft";
+  updateNotification({...form.value}).then(res => {
+        if(res.code == 200){
+          ElMessage.success("閫氱煡宸叉挙鍥�");
+          getList();
+        }
+      }).catch(err => {
+        ElMessage.error(err.msg);
+      })
 };
 
 // 鍒犻櫎閫氱煡
 const handleDelete = () => {
-  if (selectedIds.value.length === 0) {
+  let ids = [];
+  if (selectedIds.value.length > 0) {
+    ids = selectedIds.value;
+  }else{
     ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑閫氱煡");
     return;
   }
-  
   ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎", {
     confirmButtonText: "纭",
     cancelButtonText: "鍙栨秷",
     type: "warning",
   }).then(() => {
-    ElMessage.success("鍒犻櫎鎴愬姛");
-    selectedIds.value = [];
-    getList();
+    delNotification(ids).then(res => {
+      if(res.code == 200){
+        ElMessage.success("鍒犻櫎鎴愬姛");
+        selectedIds.value = [];
+        getList();
+      }
+    }).catch(err => {
+      ElMessage.error(err.msg);
+    })
   }).catch(() => {
     // 鐢ㄦ埛鍙栨秷
   });

--
Gitblit v1.9.3