From 6f5bc9367a1bfaa259038268c150b3b91b7c86e4 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期六, 20 九月 2025 10:05:12 +0800
Subject: [PATCH] 设备运行管理

---
 src/views/equipmentManagement/operationManagement/index.vue |  824 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 824 insertions(+), 0 deletions(-)

diff --git a/src/views/equipmentManagement/operationManagement/index.vue b/src/views/equipmentManagement/operationManagement/index.vue
new file mode 100644
index 0000000..40cc3cb
--- /dev/null
+++ b/src/views/equipmentManagement/operationManagement/index.vue
@@ -0,0 +1,824 @@
+<template>
+  <div class="app-container">
+
+    <!-- 缁熻姒傝鍗$墖 -->
+    <div class="stats-overview">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-card class="stats-card">
+            <div class="stats-content">
+              <div class="stats-icon running">
+                <el-icon><VideoPlay /></el-icon>
+              </div>
+              <div class="stats-info">
+                <div class="stats-value">{{ overviewData.runningDevices }}</div>
+                <div class="stats-label">杩愯璁惧</div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+        <el-col :span="6">
+          <el-card class="stats-card">
+            <div class="stats-content">
+              <div class="stats-icon stopped">
+                <el-icon><VideoPause /></el-icon>
+              </div>
+              <div class="stats-info">
+                <div class="stats-value">{{ overviewData.stoppedDevices }}</div>
+                <div class="stats-label">鍋滄満璁惧</div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+        <el-col :span="6">
+          <el-card class="stats-card">
+            <div class="stats-content">
+              <div class="stats-icon alarm">
+                <el-icon><Warning /></el-icon>
+              </div>
+              <div class="stats-info">
+                <div class="stats-value">{{ overviewData.alarmCount }}</div>
+                <div class="stats-label">鎶ヨ鏁伴噺</div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+        <el-col :span="6">
+          <el-card class="stats-card">
+            <div class="stats-content">
+              <div class="stats-icon maintenance">
+                <el-icon><Tools /></el-icon>
+              </div>
+              <div class="stats-info">
+                <div class="stats-value">{{ overviewData.maintenanceCount }}</div>
+                <div class="stats-label">缁存姢涓�</div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+
+    <!-- 涓昏鍐呭鍖哄煙 -->
+    <el-row :gutter="20">
+      <!-- 宸︿晶锛氳澶囧惎鍋滆褰� -->
+      <el-col :span="12">
+        <el-card class="main-card">
+          <template #header>
+            <div class="card-header">
+              <span>璁惧鍚仠璁板綍</span>
+              <el-button type="primary" size="small" @click="refreshDeviceRecords">
+                <el-icon><Refresh /></el-icon>
+                鍒锋柊
+              </el-button>
+            </div>
+          </template>
+          
+          <!-- 璁惧鐘舵�佺瓫閫� -->
+          <div class="filter-section">
+            <el-radio-group v-model="deviceFilter" @change="filterDeviceRecords">
+              <el-radio-button label="all">鍏ㄩ儴</el-radio-button>
+              <el-radio-button label="start">鍚姩</el-radio-button>
+              <el-radio-button label="stop">鍋滄満</el-radio-button>
+            </el-radio-group>
+          </div>
+
+          <!-- 璁惧璁板綍鍒楄〃 -->
+          <div class="device-records">
+            <div 
+              v-for="record in filteredDeviceRecords" 
+              :key="record.id"
+              class="device-record"
+              :class="record.type"
+            >
+              <div class="record-icon">
+                <el-icon v-if="record.type === 'start'"><VideoPlay /></el-icon>
+                <el-icon v-else><VideoPause /></el-icon>
+              </div>
+              <div class="record-content">
+                <div class="device-name">{{ record.deviceName }}</div>
+                <div class="record-time">{{ record.time }}</div>
+                <div class="record-status" :class="record.type">
+                  {{ record.type === 'start' ? '璁惧鍚姩' : '璁惧鍋滄満' }}
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+
+      <!-- 鍙充晶锛氳缃紑鍋滃伐淇℃伅 -->
+      <el-col :span="12">
+        <el-card class="main-card">
+          <template #header>
+            <div class="card-header">
+              <span>瑁呯疆寮�鍋滃伐淇℃伅</span>
+              <el-button type="success" size="small" @click="refreshUnitInfo">
+                <el-icon><Refresh /></el-icon>
+                鍒锋柊
+              </el-button>
+            </div>
+          </template>
+
+          <!-- 瑁呯疆鐘舵�佺瓫閫� -->
+          <div class="filter-section">
+            <el-radio-group v-model="unitFilter" @change="filterUnitInfo">
+              <el-radio-button label="all">鍏ㄩ儴</el-radio-button>
+              <el-radio-button label="startup">寮�宸�</el-radio-button>
+              <el-radio-button label="shutdown">鍋滃伐</el-radio-button>
+              <el-radio-button label="unplanned">闈炶鍒掑仠宸�</el-radio-button>
+            </el-radio-group>
+          </div>
+
+          <!-- 瑁呯疆淇℃伅鍒楄〃 -->
+          <div class="unit-info">
+            <div 
+              v-for="unit in filteredUnitInfo" 
+              :key="unit.id"
+              class="unit-item"
+              :class="unit.status"
+            >
+              <div class="unit-header">
+                <div class="unit-name">{{ unit.unitName }}</div>
+                <div class="unit-status" :class="unit.status">
+                  {{ getUnitStatusText(unit.status) }}
+                </div>
+              </div>
+              <div class="unit-details">
+                <div class="detail-item">
+                  <span class="label">寮�濮嬫椂闂达細</span>
+                  <span class="value">{{ unit.startTime }}</span>
+                </div>
+                <div class="detail-item" v-if="unit.endTime">
+                  <span class="label">缁撴潫鏃堕棿锛�</span>
+                  <span class="value">{{ unit.endTime }}</span>
+                </div>
+                <div class="detail-item" v-if="unit.reason">
+                  <span class="label">鍘熷洜锛�</span>
+                  <span class="value">{{ unit.reason }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 鎶ヨ淇℃伅闆嗕腑灞曠ず -->
+    <el-card class="alarm-card">
+      <template #header>
+        <div class="card-header">
+          <span>鎶ヨ淇℃伅闆嗕腑灞曠ず</span>
+          <div class="alarm-actions">
+            <el-button type="warning" size="small" @click="refreshAlarms">
+              <el-icon><Refresh /></el-icon>
+              鍒锋柊
+            </el-button>
+            <el-button type="danger" size="small" @click="clearAlarms">
+              <el-icon><Delete /></el-icon>
+              娓呴櫎宸插鐞�
+            </el-button>
+          </div>
+        </div>
+      </template>
+
+      <!-- 鎶ヨ绾у埆绛涢�� -->
+      <div class="filter-section">
+        <el-radio-group v-model="alarmFilter" @change="filterAlarms">
+          <el-radio-button label="all">鍏ㄩ儴</el-radio-button>
+          <el-radio-button label="critical">涓ラ噸</el-radio-button>
+          <el-radio-button label="warning">璀﹀憡</el-radio-button>
+          <el-radio-button label="info">淇℃伅</el-radio-button>
+        </el-radio-group>
+      </div>
+
+      <!-- 鎶ヨ淇℃伅琛ㄦ牸 -->
+      <el-table
+        :data="filteredAlarms"
+        style="width: 100%"
+        :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+        max-height="400"
+      >
+        <el-table-column
+          align="center"
+          label="搴忓彿"
+          type="index"
+          width="60"
+        />
+        <el-table-column
+          label="鎶ヨ鏃堕棿"
+          prop="alarmTime"
+          width="150"
+          align="center"
+        />
+        <el-table-column
+          label="璁惧鍚嶇О"
+          prop="deviceName"
+          width="150"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="鎶ヨ绾у埆"
+          prop="level"
+          width="100"
+          align="center"
+        >
+          <template #default="scope">
+            <el-tag 
+              :type="getAlarmTagType(scope.row.level)"
+              size="small"
+            >
+              {{ scope.row.level }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="鎶ヨ鍐呭"
+          prop="content"
+          min-width="200"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="澶勭悊鐘舵��"
+          prop="status"
+          width="100"
+          align="center"
+        >
+          <template #default="scope">
+            <el-tag 
+              :type="scope.row.status === '宸插鐞�' ? 'success' : 'danger'"
+              size="small"
+            >
+              {{ scope.row.status }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="澶勭悊浜�"
+          prop="handler"
+          width="100"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="鎿嶄綔"
+          width="120"
+          align="center"
+        >
+          <template #default="scope">
+            <el-button
+              v-if="scope.row.status === '鏈鐞�'"
+              type="primary"
+              size="small"
+              @click="handleAlarm(scope.row)"
+            >
+              澶勭悊
+            </el-button>
+            <el-button
+              v-else
+              type="info"
+              size="small"
+              disabled
+            >
+              宸插鐞�
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+  VideoPlay,
+  VideoPause,
+  Warning,
+  Tools,
+  Refresh,
+  Delete
+} from '@element-plus/icons-vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const deviceFilter = ref('all')
+const unitFilter = ref('all')
+const alarmFilter = ref('all')
+
+// 姒傝鏁版嵁
+const overviewData = reactive({
+  runningDevices: 15,
+  stoppedDevices: 3,
+  alarmCount: 8,
+  maintenanceCount: 2
+})
+
+// 璁惧鍚仠璁板綍鏁版嵁
+const deviceRecords = ref([
+  {
+    id: 1,
+    deviceName: '鍘嬬缉鏈篈-001',
+    type: 'start',
+    time: '2024-01-15 08:30:25'
+  },
+  {
+    id: 2,
+    deviceName: '娉礏-002',
+    type: 'stop',
+    time: '2024-01-15 08:25:10'
+  },
+  {
+    id: 3,
+    deviceName: '椋庢満C-003',
+    type: 'start',
+    time: '2024-01-15 08:20:15'
+  },
+  {
+    id: 4,
+    deviceName: '鎼呮媽鍣―-004',
+    type: 'start',
+    time: '2024-01-15 08:15:30'
+  },
+  {
+    id: 5,
+    deviceName: '鍔犵儹鍣‥-005',
+    type: 'stop',
+    time: '2024-01-15 08:10:45'
+  },
+  {
+    id: 6,
+    deviceName: '鍐峰嵈鍣‵-006',
+    type: 'start',
+    time: '2024-01-15 08:05:20'
+  }
+])
+
+// 瑁呯疆寮�鍋滃伐淇℃伅鏁版嵁
+const unitInfo = ref([
+  {
+    id: 1,
+    unitName: '鍙嶅簲瑁呯疆A',
+    status: 'startup',
+    startTime: '2024-01-15 08:00:00',
+    endTime: null,
+    reason: null
+  },
+  {
+    id: 2,
+    unitName: '鍒嗙瑁呯疆B',
+    status: 'shutdown',
+    startTime: '2024-01-15 07:30:00',
+    endTime: '2024-01-15 08:00:00',
+    reason: '璁″垝缁存姢'
+  },
+  {
+    id: 3,
+    unitName: '绮惧埗瑁呯疆C',
+    status: 'unplanned',
+    startTime: '2024-01-15 07:45:00',
+    endTime: null,
+    reason: '璁惧鏁呴殰'
+  },
+  {
+    id: 4,
+    unitName: '鍖呰瑁呯疆D',
+    status: 'startup',
+    startTime: '2024-01-15 08:15:00',
+    endTime: null,
+    reason: null
+  }
+])
+
+// 鎶ヨ淇℃伅鏁版嵁
+const alarms = ref([
+  {
+    id: 1,
+    alarmTime: '2024-01-15 08:30:00',
+    deviceName: '鍘嬬缉鏈篈-001',
+    level: '涓ラ噸',
+    content: '娓╁害杩囬珮鎶ヨ',
+    status: '鏈鐞�',
+    handler: ''
+  },
+  {
+    id: 2,
+    alarmTime: '2024-01-15 08:25:00',
+    deviceName: '娉礏-002',
+    level: '璀﹀憡',
+    content: '鍘嬪姏寮傚父',
+    status: '宸插鐞�',
+    handler: '寮犱笁'
+  },
+  {
+    id: 3,
+    alarmTime: '2024-01-15 08:20:00',
+    deviceName: '椋庢満C-003',
+    level: '淇℃伅',
+    content: '杩愯鏃堕棿杈惧埌缁存姢鍛ㄦ湡',
+    status: '鏈鐞�',
+    handler: ''
+  },
+  {
+    id: 4,
+    alarmTime: '2024-01-15 08:15:00',
+    deviceName: '鎼呮媽鍣―-004',
+    level: '涓ラ噸',
+    content: '鎸姩寮傚父',
+    status: '鏈鐞�',
+    handler: ''
+  },
+  {
+    id: 5,
+    alarmTime: '2024-01-15 08:10:00',
+    deviceName: '鍔犵儹鍣‥-005',
+    level: '璀﹀憡',
+    content: '鍔犵儹鏁堢巼涓嬮檷',
+    status: '宸插鐞�',
+    handler: '鏉庡洓'
+  }
+])
+
+// 璁$畻灞炴�� - 杩囨护鍚庣殑璁惧璁板綍
+const filteredDeviceRecords = computed(() => {
+  if (deviceFilter.value === 'all') {
+    return deviceRecords.value
+  }
+  return deviceRecords.value.filter(record => record.type === deviceFilter.value)
+})
+
+// 璁$畻灞炴�� - 杩囨护鍚庣殑瑁呯疆淇℃伅
+const filteredUnitInfo = computed(() => {
+  if (unitFilter.value === 'all') {
+    return unitInfo.value
+  }
+  return unitInfo.value.filter(unit => unit.status === unitFilter.value)
+})
+
+// 璁$畻灞炴�� - 杩囨护鍚庣殑鎶ヨ淇℃伅
+const filteredAlarms = computed(() => {
+  if (alarmFilter.value === 'all') {
+    return alarms.value
+  }
+  return alarms.value.filter(alarm => alarm.level === alarmFilter.value)
+})
+
+// 鏂规硶
+const refreshDeviceRecords = () => {
+  ElMessage.success('璁惧璁板綍宸插埛鏂�')
+  // 杩欓噷鍙互璋冪敤API鑾峰彇鏈�鏂版暟鎹�
+}
+
+const refreshUnitInfo = () => {
+  ElMessage.success('瑁呯疆淇℃伅宸插埛鏂�')
+  // 杩欓噷鍙互璋冪敤API鑾峰彇鏈�鏂版暟鎹�
+}
+
+const refreshAlarms = () => {
+  ElMessage.success('鎶ヨ淇℃伅宸插埛鏂�')
+  // 杩欓噷鍙互璋冪敤API鑾峰彇鏈�鏂版暟鎹�
+}
+
+const clearAlarms = async () => {
+  try {
+    await ElMessageBox.confirm('纭畾瑕佹竻闄ゆ墍鏈夊凡澶勭悊鐨勬姤璀︿俊鎭悧锛�', '纭娓呴櫎', {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
+    })
+    
+    alarms.value = alarms.value.filter(alarm => alarm.status === '鏈鐞�')
+    ElMessage.success('宸叉竻闄ゆ墍鏈夊凡澶勭悊鐨勬姤璀︿俊鎭�')
+  } catch {
+    // 鐢ㄦ埛鍙栨秷鎿嶄綔
+  }
+}
+
+const filterDeviceRecords = () => {
+  // 杩囨护閫昏緫宸插湪璁$畻灞炴�т腑澶勭悊
+}
+
+const filterUnitInfo = () => {
+  // 杩囨护閫昏緫宸插湪璁$畻灞炴�т腑澶勭悊
+}
+
+const filterAlarms = () => {
+  // 杩囨护閫昏緫宸插湪璁$畻灞炴�т腑澶勭悊
+}
+
+const getUnitStatusText = (status) => {
+  const statusMap = {
+    startup: '寮�宸ヤ腑',
+    shutdown: '宸插仠宸�',
+    unplanned: '闈炶鍒掑仠宸�'
+  }
+  return statusMap[status] || status
+}
+
+const getAlarmTagType = (level) => {
+  const typeMap = {
+    '涓ラ噸': 'danger',
+    '璀﹀憡': 'warning',
+    '淇℃伅': 'info'
+  }
+  return typeMap[level] || 'info'
+}
+
+const handleAlarm = (alarm) => {
+  ElMessageBox.prompt('璇疯緭鍏ュ鐞嗚鏄�', '澶勭悊鎶ヨ', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    inputPlaceholder: '璇疯緭鍏ュ鐞嗚鏄�'
+  }).then(({ value }) => {
+    alarm.status = '宸插鐞�'
+    alarm.handler = '褰撳墠鐢ㄦ埛' // 杩欓噷搴旇鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛
+    ElMessage.success('鎶ヨ澶勭悊瀹屾垚')
+  }).catch(() => {
+    // 鐢ㄦ埛鍙栨秷鎿嶄綔
+  })
+}
+
+// 缁勪欢鎸傝浇鏃跺垵濮嬪寲鏁版嵁
+onMounted(() => {
+  // 杩欓噷鍙互璋冪敤API鑾峰彇鍒濆鏁版嵁
+  console.log('杩愯绠$悊椤甸潰宸插姞杞�')
+})
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: 100vh;
+}
+
+
+.stats-overview {
+  margin-bottom: 20px;
+}
+
+.stats-card {
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.stats-content {
+  display: flex;
+  align-items: center;
+  padding: 10px 0;
+}
+
+.stats-icon {
+  width: 50px;
+  height: 50px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 15px;
+  font-size: 24px;
+  color: #fff;
+}
+
+.stats-icon.running {
+  background: linear-gradient(135deg, #67C23A, #85CE61);
+}
+
+.stats-icon.stopped {
+  background: linear-gradient(135deg, #F56C6C, #F78989);
+}
+
+.stats-icon.alarm {
+  background: linear-gradient(135deg, #E6A23C, #EEBE77);
+}
+
+.stats-icon.maintenance {
+  background: linear-gradient(135deg, #409EFF, #66B1FF);
+}
+
+.stats-info {
+  flex: 1;
+}
+
+.stats-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #333;
+  line-height: 1;
+  margin-bottom: 5px;
+}
+
+.stats-label {
+  font-size: 14px;
+  color: #666;
+}
+
+.main-card {
+  margin-bottom: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.filter-section {
+  margin-bottom: 15px;
+}
+
+.device-records {
+  max-height: 400px;
+  overflow-y: auto;
+}
+
+.device-record {
+  display: flex;
+  align-items: center;
+  padding: 12px;
+  margin-bottom: 8px;
+  border-radius: 6px;
+  background: #f8f9fa;
+  border-left: 4px solid #ddd;
+}
+
+.device-record.start {
+  border-left-color: #67C23A;
+  background: #f0f9ff;
+}
+
+.device-record.stop {
+  border-left-color: #F56C6C;
+  background: #fef0f0;
+}
+
+.record-icon {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 12px;
+  font-size: 16px;
+  color: #fff;
+}
+
+.device-record.start .record-icon {
+  background: #67C23A;
+}
+
+.device-record.stop .record-icon {
+  background: #F56C6C;
+}
+
+.record-content {
+  flex: 1;
+}
+
+.device-name {
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 4px;
+}
+
+.record-time {
+  font-size: 12px;
+  color: #666;
+  margin-bottom: 4px;
+}
+
+.record-status {
+  font-size: 12px;
+  padding: 2px 8px;
+  border-radius: 12px;
+  display: inline-block;
+}
+
+.record-status.start {
+  background: #e1f3d8;
+  color: #67C23A;
+}
+
+.record-status.stop {
+  background: #fde2e2;
+  color: #F56C6C;
+}
+
+.unit-info {
+  max-height: 400px;
+  overflow-y: auto;
+}
+
+.unit-item {
+  padding: 15px;
+  margin-bottom: 12px;
+  border-radius: 6px;
+  background: #f8f9fa;
+  border-left: 4px solid #ddd;
+}
+
+.unit-item.startup {
+  border-left-color: #67C23A;
+  background: #f0f9ff;
+}
+
+.unit-item.shutdown {
+  border-left-color: #409EFF;
+  background: #f0f9ff;
+}
+
+.unit-item.unplanned {
+  border-left-color: #E6A23C;
+  background: #fdf6ec;
+}
+
+.unit-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.unit-name {
+  font-weight: 500;
+  color: #333;
+  font-size: 14px;
+}
+
+.unit-status {
+  font-size: 12px;
+  padding: 4px 8px;
+  border-radius: 12px;
+  display: inline-block;
+}
+
+.unit-status.startup {
+  background: #e1f3d8;
+  color: #67C23A;
+}
+
+.unit-status.shutdown {
+  background: #e1f3ff;
+  color: #409EFF;
+}
+
+.unit-status.unplanned {
+  background: #fdf6ec;
+  color: #E6A23C;
+}
+
+.unit-details {
+  font-size: 12px;
+  color: #666;
+}
+
+.detail-item {
+  margin-bottom: 4px;
+}
+
+.detail-item .label {
+  font-weight: 500;
+  margin-right: 4px;
+}
+
+.alarm-card {
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.alarm-actions {
+  display: flex;
+  gap: 8px;
+}
+
+:deep(.el-card__header) {
+  background: #f8f9fa;
+  border-bottom: 1px solid #e9ecef;
+  font-weight: 500;
+}
+
+:deep(.el-table .el-table__header-wrapper th) {
+  background-color: #F0F1F5 !important;
+  color: #333333;
+  font-weight: 600;
+}
+
+:deep(.el-table .el-table__body-wrapper td) {
+  padding: 8px 0;
+}
+
+:deep(.el-radio-button__inner) {
+  border-radius: 4px;
+}
+
+:deep(.el-radio-button:first-child .el-radio-button__inner) {
+  border-left: 1px solid #dcdfe6;
+  border-radius: 4px;
+}
+
+:deep(.el-radio-button:last-child .el-radio-button__inner) {
+  border-radius: 4px;
+}
+</style>

--
Gitblit v1.9.3