From 5d78583b5180ddaca6c0cb254ad8de58accca86e Mon Sep 17 00:00:00 2001
From: licp <lichunping@guanfang.com.cn>
Date: 星期五, 20 十二月 2024 13:09:38 +0800
Subject: [PATCH] 完成投诉迁移

---
 src/components/view/a7-complaint.vue |  628 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/assets/api/controller.js         |   11 +
 2 files changed, 639 insertions(+), 0 deletions(-)

diff --git a/src/assets/api/controller.js b/src/assets/api/controller.js
index 81f7b05..960a4c5 100644
--- a/src/assets/api/controller.js
+++ b/src/assets/api/controller.js
@@ -62,6 +62,7 @@
     qualityMonitor,
     measuresAddressRisksOpportunities,
     processReport,
+    processComplain,
   }
 }
 
@@ -814,4 +815,14 @@
   getProcessReport:'/processReport/getProcessReport',//鏌ョ湅
   doProcessReport:'/processReport/doProcessReport',//淇敼
   exportProcessReport:'/processReport/exportProcessReport',//瀵煎嚭
+}
+
+// 鎶曡瘔
+const processComplain = {
+  pageProcessComplain:'/processComplain/pageProcessComplain',//鎶曡瘔澶勭悊鍒嗛〉
+  addProcessComplain:'/processComplain/addProcessComplain',//鎶曡瘔澶勭悊鏂板
+  delProcessComplain:'/processComplain/delProcessComplain',//鎶曡瘔澶勭悊鍒犻櫎
+  getProcessComplain:'/processComplain/getProcessComplain',//鎶曡瘔澶勭悊璇︽儏
+  doProcessComplain:'/processComplain/doProcessComplain',//鎶曡瘔澶勭悊淇敼
+  exportProcessComplain :'/processComplain/exportProcessComplain ',//鎶曡瘔澶勭悊瀵煎嚭
 }
\ No newline at end of file
diff --git a/src/components/view/a7-complaint.vue b/src/components/view/a7-complaint.vue
new file mode 100644
index 0000000..2016f42
--- /dev/null
+++ b/src/components/view/a7-complaint.vue
@@ -0,0 +1,628 @@
+<template>
+  <div class="Complaint">
+    <el-row class="title">
+      <el-col :span="12" style="padding-left: 20px;text-align: left;">鎶曡瘔</el-col>
+      <el-col :span="12" style="text-align: right;">
+        <el-button size="medium" type="primary" @click="handleDown" :loading="outLoading" v-if="outPower">瀵煎嚭</el-button>
+        <el-button size="medium" type="primary" @click="openAdd" v-if="addPower">鏂板</el-button>
+      </el-col>
+    </el-row>
+    <div class="search">
+      <div class="search_thing">
+        <div class="search_label">鏍峰搧缂栧彿锛�</div>
+        <div class="search_input"><el-input size="small" placeholder="璇疯緭鍏�" clearable v-model="componentData.entity.sampleCode"
+            @keyup.enter.native="refreshTable()"></el-input></div>
+      </div>
+      <div class="search_thing">
+        <div class="search_label">鎶曡瘔鍚嶇О锛�</div>
+        <div class="search_input">
+          <!-- <el-date-picker
+            v-model="componentData.entity.createTime"
+            type="date"
+            size="small"
+            placeholder="閫夋嫨鏃ユ湡"
+            format="yyyy-MM-dd HH:mm:ss"
+            value-format="yyyy-MM-dd HH:mm:ss" @change="refreshTable()">
+          </el-date-picker> -->
+          <el-input size="small" placeholder="璇疯緭鍏�" clearable v-model="componentData.entity.complainName"
+            @keyup.enter.native="refreshTable()"></el-input>
+        </div>
+      </div>
+      <div class="search_thing" style="padding-left: 30px;">
+        <el-button size="small" @click="refresh()">閲� 缃�</el-button>
+        <el-button size="small" type="primary" @click="refreshTable()">鏌� 璇�</el-button>
+      </div>
+    </div>
+    <div class="table">
+      <ValueTable ref="ValueTable" :url="$api.processComplain.pageProcessComplain"
+        :delUrl="$api.processComplain.delProcessComplain"
+        :componentData="componentData" :key="upIndex"/>
+    </div>
+    <el-dialog
+      title="鏂板"
+      :visible.sync="addDialogVisible"
+      width="400px">
+      <el-row>
+        <el-col :span="24" style="margin-bottom: 16px;">
+          <div class="search_thing">
+            <div class="search_label">鎶曡瘔鏂瑰悕绉帮細</div>
+            <div class="search_input"><el-input size="small" placeholder="璇疯緭鍏�" clearable v-model="addInfo.complainName"></el-input></div>
+          </div>
+        </el-col>
+        <el-col :span="24" style="margin-bottom: 16px;">
+          <div class="search_thing">
+            <div class="search_label">妫�楠屾姤鍛婄紪鍙凤細</div>
+            <div class="search_input">
+              <el-input size="small" placeholder="璇疯緭鍏�" clearable v-model="addInfo.code"></el-input>
+              <!-- <el-select v-model="addInfo.insReportId" filterable placeholder="璇烽�夋嫨" size="small" style="width: 100%;">
+                <el-option
+                  v-for="item in reportList"
+                  :key="item.id"
+                  :label="item.code"
+                  :value="item.id">
+                </el-option>
+              </el-select> -->
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="24" style="margin-bottom: 16px;">
+          <div class="search_thing">
+            <div class="search_label">鏍峰搧缂栧彿锛�</div>
+            <div class="search_input"><el-input size="small" placeholder="璇疯緭鍏�" clearable v-model="addInfo.sampleCode"></el-input></div>
+          </div>
+        </el-col>
+        <el-col :span="24">
+          <div class="search_thing">
+            <div class="search_label">鎶曡瘔鏂瑰紡锛�</div>
+            <div class="search_input"><el-input size="small" placeholder="璇疯緭鍏�" clearable v-model="addInfo.complainMethod"></el-input></div>
+          </div>
+        </el-col>
+      </el-row>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="addDialogVisible = false">鍙� 娑�</el-button>
+        <el-button type="primary" @click="handleAdd" :loading="addLoading">纭� 瀹�</el-button>
+      </span>
+    </el-dialog>
+    <el-dialog
+      :title="title"
+      :visible.sync="handleDialogVisible"
+      width="800px" :class="{downPdf:title=='瀵煎嚭'}" :modal="title!='瀵煎嚭'">
+      <div class="dialog-body">
+        <div id="dialogBody">
+          <h4 style="display: flex;align-items: center;flex-direction: column;justify-content: center;">
+          <span style="font-size: 20px;">瀹㈡埛鎶曡瘔鍙楃悊鍗�</span>
+          <span>Customer complaint receipts</span>
+        </h4>
+        <p style="display: flex;justify-content: space-between;margin-top: 16px;">
+          <span>{{ currentInfo0.complainNo }}</span>
+          <span>NO:</span>
+        </p>
+        <table border="1" class="tables" cellpadding="10">
+          <tr>
+            <td colspan="3">
+              <p>鎶曡瘔鏂瑰悕绉�</p>
+              <p class="en">Name of the complaining party</p>
+            </td>
+            <td colspan="3">{{ currentInfo0.complainName }}</td>
+          </tr>
+          <tr>
+            <td>
+              <p>妫�娴嬫姤鍛婄紪鍙�</p>
+              <p class="en">Test report number</p>
+            </td>
+            <td colspan="3">{{ currentInfo0.code }}</td>
+            <td >
+              <p>鏍峰搧缂栧彿</p>
+              <p class="en">Sample number</p>
+            </td>
+            <td>{{ currentInfo0.sampleCode }}</td>
+          </tr>
+          <tr>
+            <td>
+              <p>鎶曡瘔浜�</p>
+              <p class="en">Complainant</p>
+            </td>
+            <td>{{ currentInfo0.createUser }}</td>
+            <td>
+              <p>鐢佃瘽</p>
+              <p class="en">Phone</p>
+            </td>
+            <td>{{ currentInfo0.phone }}</td>
+            <td>
+              <p>E-Mail</p>
+              <p class="en">E-mail</p>
+            </td>
+            <td>{{ currentInfo0.email }}</td>
+          </tr>
+          <tr>
+            <td>
+              <p>鎶曡瘔鏂瑰紡</p>
+              <p class="en">Complaint method</p>
+            </td>
+            <td colspan="3">{{ currentInfo0.complainMethod }}</td>
+            <td >
+              <p>鎶曡瘔鏃ユ湡</p>
+              <p class="en">Date of complaint</p>
+            </td>
+            <td>{{ currentInfo0.createTime?currentInfo0.createTime.split(' ')[0]:'' }}</td>
+          </tr>
+          <tr>
+            <td>
+              <p>闂璁板綍</p>
+              <p class="en">Problem logging</p>
+            </td>
+            <td colspan="5">
+              <div class="user-content">
+                <el-input
+                  type="textarea"
+                  :rows="3"
+                  placeholder="璇疯緭鍏ュ唴瀹�"
+                  v-model="currentInfo0.problemRecords" v-if="title=='澶勭悊鎶曡瘔'">
+                </el-input>
+                <p v-else style="text-align: left;line-height: 26px;">{{ currentInfo0.problemRecords }}</p>
+              </div>
+              <div class="user-info" v-if="title!='澶勭悊鎶曡瘔'">
+                <div style="width: 200px;margin-right: 10px;">
+                  <p style="text-align: end;">璐ㄩ噺璐熻矗浜�:</p>
+                  <p class="en" style="text-align: end;">Quality Manager:</p>
+                </div>
+                <span>{{ currentInfo0.problemRecordsUserName }}</span>
+                <div style="width: 70px;">
+                  <p>鏃ユ湡:</p>
+                  <p class="en">Date:</p>
+                </div>
+                <span style="margin-right: 16px;">{{ currentInfo0.problemRecordsTime }}</span>
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <p>璐d换褰掑睘鍙婃姇璇夋槸鍚︽垚绔�</p>
+              <p class="en">Attribution of responsibility and whether the complaint is established</p>
+            </td>
+            <td colspan="5">
+              <div class="user-content">
+                <el-input
+                  type="textarea"
+                  :rows="3"
+                  placeholder="璇疯緭鍏ュ唴瀹�"
+                  v-model="currentInfo0.dutyOwnership" v-if="title=='澶勭悊鎶曡瘔'">
+                </el-input>
+                <p v-else style="text-align: left;line-height: 26px;">{{ currentInfo0.dutyOwnership }}</p>
+              </div>
+              <div class="user-info" v-if="title!='澶勭悊鎶曡瘔'">
+                <div style="width: 200px;margin-right: 10px;">
+                  <p style="text-align: end;">璐ㄩ噺璐熻矗浜�:</p>
+                  <p class="en" style="text-align: end;">Quality Manager:</p>
+                </div>
+                <span>{{ currentInfo0.dutyOwnershipUserName }}</span>
+                <div style="width: 70px;">
+                  <p>鏃ユ湡:</p>
+                  <p class="en">Date:</p>
+                </div>
+                <span style="margin-right: 16px;">{{ currentInfo0.dutyOwnershipTime }}</span>
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <p>鍘熷洜鍒嗘瀽</p>
+              <p class="en">Cause analysis</p>
+            </td>
+            <td colspan="5">
+              <div class="user-content">
+                <el-input
+                  type="textarea"
+                  :rows="3"
+                  placeholder="璇疯緭鍏ュ唴瀹�"
+                  v-model="currentInfo0.causeAnalysis" v-if="title=='澶勭悊鎶曡瘔'">
+                </el-input>
+                <p v-else style="text-align: left;line-height: 26px;">{{ currentInfo0.causeAnalysis }}</p>
+              </div>
+              <div class="user-info" v-if="title!='澶勭悊鎶曡瘔'">
+                <div style="width: 200px;margin-right: 10px;">
+                  <p style="text-align: end;">璐d换閮ㄩ棬璐熻矗浜�:</p>
+                  <p class="en" style="text-align: end;">Head of Responsible Department:</p>
+                </div>
+                <span>{{ currentInfo0.causeAnalysisUserName }}</span>
+                <div style="width: 70px;">
+                  <p>鏃ユ湡:</p>
+                  <p class="en">Date:</p>
+                </div>
+                <span style="margin-right: 16px;">{{ currentInfo0.causeAnalysisTime }}</span>
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <p>绾犳鎺柦</p>
+              <p class="en">Corrective actions</p>
+            </td>
+            <td colspan="5">
+              <div class="user-content">
+                <el-input
+                  type="textarea"
+                  :rows="3"
+                  placeholder="璇疯緭鍏ュ唴瀹�"
+                  v-model="currentInfo0.correctiveAction" v-if="title=='澶勭悊鎶曡瘔'">
+                </el-input>
+                <p v-else style="text-align: left;line-height: 26px;">{{ currentInfo0.correctiveAction }}</p>
+              </div>
+              <div class="user-info" v-if="title!='澶勭悊鎶曡瘔'">
+                <div style="width: 200px;margin-right: 10px;">
+                  <p style="text-align: end;">璐d换閮ㄩ棬璐熻矗浜�:</p>
+                  <p class="en" style="text-align: end;">Head of Responsible Department:</p>
+                </div>
+                <span>{{ currentInfo0.correctiveActionUserName }}</span>
+                <div style="width: 70px;">
+                  <p>鏃ユ湡:</p>
+                  <p class="en">Date:</p>
+                </div>
+                <span style="margin-right: 16px;">{{ currentInfo0.correctiveActionTime }}</span>
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <p>绾犳鎺柦纭</p>
+              <p class="en">Corrective actions confirmation</p>
+            </td>
+            <td colspan="5">
+              <div class="user-content">
+                <el-input
+                  type="textarea"
+                  :rows="3"
+                  placeholder="璇疯緭鍏ュ唴瀹�"
+                  v-model="currentInfo0.correctiveActionConfirmation" v-if="title=='澶勭悊鎶曡瘔'">
+                </el-input>
+                <p v-else style="text-align: left;line-height: 26px;">{{ currentInfo0.correctiveActionConfirmation }}</p>
+              </div>
+              <div class="user-info" v-if="title!='澶勭悊鎶曡瘔'">
+                <div style="width: 200px;margin-right: 10px;">
+                  <p style="text-align: end;">璐ㄩ噺璐熻矗浜�:</p>
+                  <p class="en" style="text-align: end;">Quality Manager:</p>
+                </div>
+                <span>{{ currentInfo0.correctiveActionConfirmationUserName }}</span>
+                <div style="width: 70px;">
+                  <p>鏃ユ湡:</p>
+                  <p class="en">Date:</p>
+                </div>
+                <span style="margin-right: 16px;">{{ currentInfo0.correctiveActionConfirmationTime }}</span>
+              </div>
+            </td>
+          </tr>
+        </table>
+        </div>
+      </div>
+      <span slot="footer" class="dialog-footer" v-if="title=='澶勭悊鎶曡瘔'">
+        <el-button @click="handleDialogVisible = false">鍙� 娑�</el-button>
+        <el-button type="primary" @click="submit" :loading="addLoading">鎻� 浜�</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import ValueTable from '../tool/value-table.vue'
+import {getYearAndMonthAndDays} from '../../util/date'
+import {exportHtmlToPDF} from '../../util/downHtmlToPDF'
+export default {
+  components: {
+    ValueTable
+  },
+  data() {
+    return {
+      addPower:false,
+      outLoading:false,
+      addDialogVisible:false,
+      addLoading:false,
+      handleDialogVisible:false,
+      title:'澶勭悊鎶曡瘔',
+      componentData: {
+          entity: {
+            sampleCode: null,
+            complainName: null,
+            orderBy: {
+              field: 'id',
+              order: 'asc'
+            }
+          },
+          isIndex: true,
+          showSelect: false,
+          select: false,
+          do: [{
+            id: 'handleLook',
+            font: '鏌ョ湅',
+            type: 'text',
+            method: 'handleLook',
+          }, {
+            id: 'delete',
+            font: '鍒犻櫎',
+            type: 'text',
+            method: 'doDiy'
+          }, {
+            id: 'handleWork',
+            font: '澶勭悊',
+            type: 'text',
+            method: 'handleWork'
+          }, {
+            id: 'handleOut',
+            font: '瀵煎嚭',
+            type: 'text',
+            method: 'handleOut'
+          }],
+          tagField: {},
+          selectField: {
+          },
+          requiredAdd: [],
+          requiredUp: [],
+          needSort: [],
+          inputType:聽''
+        },
+        entityCopy: {},
+        upIndex: 0,
+        addInfo:{},//鏂板淇℃伅
+        currentInfo:null,//鎺ュ彛璇锋眰鍥炴潵鐨勪俊鎭�
+        currentInfo0:{},//鐢ㄦ埛缂栬緫杩囧悗鐨勪俊鎭�
+        reportList:[],//鎶ュ憡鍒楄〃
+        outPower:false,
+    };
+  },
+  mounted() {
+    this.entityCopy = this.HaveJson(this.componentData.entity);
+    this.getPower()
+  },
+  methods: {
+    // 鏉冮檺鍒嗛厤
+    getPower() {
+      let power = JSON.parse(sessionStorage.getItem('power'))
+      let up = false
+      let del = false
+      let add = false
+      let out = false
+      for (var i = 0; i < power.length; i++) {
+        if (power[i].menuMethod == 'doProcessComplain') {
+          up = true
+        }
+        if (power[i].menuMethod == 'addProcessComplain') {
+          add = true
+        }
+        if (power[i].menuMethod == 'delProcessComplain') {
+          del = true
+        }
+        if (power[i].menuMethod == 'exportProcessComplain') {
+          out = true
+        }
+      }
+      if (!up) {
+        this.componentData.do.splice(2, 1)
+      }
+      if (!del) {
+        this.componentData.do.splice(1, 1)
+      }
+      this.outPower = out
+      this.addPower = add
+    },
+    getPageInsReport(){
+      this.$axios.post(this.$api.insReport.pageInsReport,
+      {
+        entity:{
+          orderBy:{
+            field: "id",
+            order: "desc"
+          }
+        },
+        page:{
+          current:-1,
+          size:-1
+        }
+      },{headers: { 'Content-Type': 'application/json' }}).then((res)=>{
+        this.reportList = res.data.body.records
+      })
+    },
+    openAdd() {
+      this.addInfo = {}
+      this.addDialogVisible = true
+    },
+    handleAdd(){
+      this.addLoading = true
+      this.$axios.post(this.$api.processComplain.addProcessComplain,
+      this.addInfo ,{headers: { 'Content-Type': 'application/json' }}
+    ).then((res)=>{
+        this.addLoading = false
+        if(res.code==201){
+          this.$message({
+            type: 'error',
+            message: '鏂板澶辫触'
+          })
+          return
+        }
+        this.$message({
+          type: 'success',
+          message: '鏂板鎴愬姛'
+        })
+        this.addDialogVisible = false
+        this.refresh()
+      })
+    },
+    // 澶勭悊鎶曡瘔
+    handleWork(row){
+      this.$axios.post(this.$api.processComplain.getProcessComplain,
+      {id:row.id}).then((res)=>{
+        this.currentInfo = res.data
+        this.currentInfo0 = this.HaveJson(res.data)
+        this.title = '澶勭悊鎶曡瘔'
+        this.handleDialogVisible = true
+      })
+    },
+    submit(){
+      this.handleParam('problemRecords')
+      this.handleParam('dutyOwnership')
+      this.handleParam('causeAnalysis')
+      this.handleParam('correctiveAction')
+      this.handleParam('correctiveActionConfirmation')
+      this.addLoading = true
+      for(let i in this.currentInfo0){
+        if(!this.currentInfo0[i]){
+          delete this.currentInfo0[i]
+        }
+      }
+      this.$axios.post(this.$api.processComplain.doProcessComplain,
+      this.currentInfo0 ,{headers: { 'Content-Type': 'application/json' }}
+    ).then((res)=>{
+        this.addLoading = false
+        if(res.code==201){
+          this.$message({
+            type: 'error',
+            message: '鎻愪氦澶辫触'
+          })
+          return
+        }
+        this.$message({
+          type: 'success',
+          message: '鎻愪氦鎴愬姛'
+        })
+        this.handleDialogVisible = false
+        this.refresh()
+      })
+    },
+    /**
+     * 澶勭悊鍙傛暟
+     *
+     * @param {string} type - 闇�瑕佸鐞嗙殑鍙傛暟绫诲瀷
+     */
+    handleParam(type){
+      if(this.currentInfo0[type]!=this.currentInfo[type]){
+        this.currentInfo0[type+'User'] = JSON.parse(localStorage.getItem("user")).userId
+        this.currentInfo0[type+'Time'] = getYearAndMonthAndDays()
+      }
+    },
+    // 鏌ョ湅鎶曡瘔
+    handleLook(row){
+      this.$axios.post(this.$api.processComplain.getProcessComplain,
+      {id:row.id}).then((res)=>{
+        this.currentInfo = res.data
+        this.currentInfo0 = this.HaveJson(res.data)
+        this.title = '鏌ョ湅鎶曡瘔'
+        this.handleDialogVisible = true
+      })
+    },
+    refreshTable() {
+      this.$refs['ValueTable'].selectList()
+    },
+    refresh() {
+      this.componentData.entity = this.HaveJson(this.entityCopy)
+      this.upIndex++
+      this.refreshTable()
+    },
+    handleDown(){
+      this.outLoading = true
+      this.$axios.post(this.$api.processComplain.exportProcessComplain,{entity:{sampleCode:this.componentData.entity.sampleCode,complainName:this.componentData.entity.complainName}},{responseType: "blob",headers: { 'Content-Type': 'application/json' }}).then(res => {
+        this.outLoading = false
+        if(res.code==201){
+          return
+        }
+        this.$message.success('瀵煎嚭鎴愬姛')
+        const blob = new Blob([res],{ type: 'application/octet-stream' });
+        const url = URL.createObjectURL(blob);
+        const link = document.createElement('a');
+        link.href = url;
+        link.download = '鎶曡瘔鎯呭喌姹囨�昏〃.xlsx';
+        link.click();
+      })
+    },
+    handleOut(row){
+      this.$axios.post(this.$api.processComplain.getProcessComplain,
+      {id:row.id}).then((res)=>{
+        if(res.code==201){
+          return
+        }
+        this.currentInfo = res.data
+        this.currentInfo0 = this.HaveJson(res.data)
+        this.title = '瀵煎嚭'
+        this.handleDialogVisible = true
+        setTimeout(() => {
+          this.$nextTick(() => {
+            const element = document.getElementById("dialogBody");
+            exportHtmlToPDF(element,'鎶曡瘔璇︽儏').then(res=>{
+              this.handleDialogVisible = false
+            })
+          })
+        }, 500);
+      })
+    }
+  },
+}
+</script>
+
+<style scoped>
+  .title {
+    height: 60px;
+    line-height: 60px;
+  }
+  .search {
+    background-color: #fff;
+    height: 80px;
+    display: flex;
+    align-items: center;
+  }
+
+  .search_thing {
+    width: 350px;
+    display: flex;
+    align-items: center;
+  }
+
+  .search_label {
+    width: 110px;
+    font-size: 14px;
+    text-align: right;
+  }
+
+  .search_input {
+    width: calc(100% - 110px);
+  }
+
+  .table {
+    margin-top: 10px;
+    background-color: #fff;
+    width: calc(100% - 40px);
+    height: calc(100% - 60px - 80px - 10px - 40px);
+    padding: 20px;
+  }
+  .dialog-body{
+    max-height: 75vh;
+    overflow-y: auto;
+  }
+  .tables {
+    table-layout: fixed;
+    width: 100%;
+    margin-top: 10px;
+  }
+
+  .tables td {
+    height: 40px;
+    width: 100px;
+    text-align: center;
+    font-size: 14px;
+    word-wrap: break-word;
+    white-space: normal;
+  }
+  .en{
+    font-size: 12px;
+    word-break: break-word; /* 鑷姩鏂 */
+    overflow-wrap: break-word; /* 闃叉婧㈠嚭 */
+    white-space: normal; /* 榛樿鎹㈣ */
+  }
+  .user-info{
+    display: flex;
+    align-items: center;
+    justify-content: end;
+  }
+  .user-content{
+    min-height: 60px;
+  }
+  .downPdf{
+    opacity: 0 !important;
+  }
+</style>

--
Gitblit v1.9.3