Browse Source

功能点完善

yangyuxuan 4 months ago
parent
commit
a373fe8769
4 changed files with 428 additions and 57 deletions
  1. 1 3
      src/utils/request.ts
  2. 348 0
      src/views/duty/eventHandling.vue
  3. 7 2
      src/views/duty/index.vue
  4. 72 52
      src/views/duty/shiftChange.vue

+ 1 - 3
src/utils/request.ts

@@ -124,9 +124,7 @@ service.interceptors.response.use(
                 isRelogin.show = true;
                 showConfirmDialog({
                     title: '系统提示',
-                    message: '登录状态已过期,您可以继续留在该页面,或者重新登录',
-                    confirmButtonText: '取消',
-                    cancelButtonText: ''
+                    message: '登录状态已过期,您可以继续留在该页面,或者重新登录'
                 }).then(() => {
                     isRelogin.show = false;
                     useUserStore().logout().then(() => {

+ 348 - 0
src/views/duty/eventHandling.vue

@@ -0,0 +1,348 @@
+<template>
+  <van-cell-group inset class="mobile-stats">
+    <van-cell title="今日事件统计" :border="false">
+      <template #extra>
+        <van-tag plain type="primary">{{ formatTime }}</van-tag>
+      </template>
+    </van-cell>
+
+    <van-grid :border="false" :column-num="2">
+      <van-grid-item>
+        <div class="stat-card total">
+          <div class="value">{{ stats.total }}</div>
+          <div class="label">事件总数</div>
+          <van-icon name="todo-list-o" size="16" />
+        </div>
+      </van-grid-item>
+
+      <van-grid-item>
+        <div class="stat-card success">
+          <div class="value">{{ stats.completed }}</div>
+          <div class="label">已完成</div>
+          <div style="display: flex; justify-content: center">
+            <van-tag
+              round
+              type="success"
+              style="
+                width: 80px;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+              "
+            >
+              {{ getPercentage(stats.completed) }}%
+            </van-tag>
+          </div>
+        </div>
+      </van-grid-item>
+
+      <van-grid-item>
+        <div class="stat-card danger">
+          <div class="value">{{ stats.pending }}</div>
+          <div class="label">未处理</div>
+          <div style="display: flex; justify-content: center">
+            <van-tag
+              round
+              type="danger"
+              style="
+                width: 80px;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+              "
+            >
+              <van-icon name="warning-o" /> {{ getPercentage(stats.pending) }}%
+            </van-tag>
+          </div>
+        </div>
+      </van-grid-item>
+
+      <van-grid-item>
+        <div class="stat-card warning">
+          <div class="value">{{ stats.processing }}</div>
+          <div class="label">处理中</div>
+          <div style="display: flex; justify-content: center">
+            <van-tag
+              round
+              type="warning"
+              style="
+                width: 80px;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+              "
+            >
+              <van-icon name="clock-o" /> {{ getPercentage(stats.processing) }}%
+            </van-tag>
+          </div>
+        </div>
+      </van-grid-item>
+    </van-grid>
+
+    <div class="refresh-area">
+      <van-button round size="small" :loading="loading" @click="refreshData">
+        <template #icon>
+          <van-icon name="replay" />
+        </template>
+        刷新数据
+      </van-button>
+    </div>
+  </van-cell-group>
+  <div class="change-container">
+    <div class="duty-card">
+      <el-table :data="dataList" border table-layout="auto">
+        <el-table-column label="事件编号" prop="data5" align="center" />
+        <el-table-column label="事件" prop="data1" align="center" />
+        <el-table-column label="发生时间" prop="data2" align="center" />
+        <el-table-column label="事件类型" prop="data2" align="center" />
+        <el-table-column label="紧急程度" prop="data6" align="center" />
+        <el-table-column label="状态" prop="data4" align="center" />
+        <el-table-column label="负责人" prop="data7" align="center" />
+        <el-table-column label="剩余响应时限" prop="data8" align="center" />
+        <el-table-column label="操作" align="center">
+          <template #default="scope">
+            <div
+              v-if="scope.row.data4 === '待处理'"
+              class="btn"
+              style="color: #1d92ff"
+              @click="showDetails(scope.row)"
+            >
+              处理并报送
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+  <div>
+    <van-popup v-model:show="showPicker">
+      <van-form @submit="onSubmit">
+        <div class="van-doc-block__title">事件处理</div>
+        <van-cell-group inset>
+          <van-field
+            v-model="form.name"
+            label="处理人"
+            placeholder="请输入处理人姓名"
+          />
+        </van-cell-group>
+        <div class="popup-footer">
+          <van-button class="cancel-btn" @click="handleCancel"
+            >取 消</van-button
+          >
+          <van-button type="primary" native-type="submit" class="confirm-btn"
+            >确 定</van-button
+          >
+        </div>
+      </van-form>
+    </van-popup>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from "vue";
+import { Toast } from "vant";
+import { ElTable, ElTableColumn } from "element-plus";
+
+const loading = ref(false);
+const showPicker = ref(false);
+const form = ref({
+  name: ""
+});
+const stats = ref({
+  total: 0,
+  pending: 0,
+  processing: 0,
+  completed: 0
+});
+
+const showDetails = () => {
+  showPicker.value = true;
+};
+const handleCancel = () => {
+  form.value.name = "";
+  showPicker.value = false;
+};
+// 获取当前时间
+const formatTime = computed(() => {
+  return new Date().toLocaleTimeString("zh-CN", {
+    hour: "2-digit",
+    minute: "2-digit"
+  });
+});
+
+// 计算百分比
+const getPercentage = value => {
+  if (stats.value.total === 0) return 0;
+  return Math.round((value / stats.value.total) * 100);
+};
+
+// 模拟数据获取
+const fetchData = () => {
+  loading.value = true;
+  // 模拟API请求延迟
+  setTimeout(() => {
+    stats.value = {
+      total: 42 + Math.floor(Math.random() * 5),
+      pending: Math.max(0, 8 + Math.floor(Math.random() * 3) - 1),
+      processing: 12 + Math.floor(Math.random() * 4) - 2,
+      completed: 22 + Math.floor(Math.random() * 3)
+    };
+    loading.value = false;
+    Toast.success("数据已更新");
+  }, 800);
+};
+
+// 手动刷新
+const refreshData = () => {
+  fetchData();
+};
+
+const dataList = ref([
+  {
+    data1: "网络安全攻击",
+    data2: "2025-4-14",
+    data3: "紧急",
+    data4: "待处理",
+    data5: "1020495",
+    data6: "网络攻击",
+    data7: "张三",
+    data8: "8小时"
+  },
+  {
+    data1: "电力系统故障",
+    data2: "2025-4-14",
+    data3: "高",
+    data4: "处理中",
+    data5: "1020495",
+    data6: "电力故障",
+    data7: "张三",
+    data8: "8小时"
+  }
+]);
+
+// 初始化加载
+onMounted(fetchData);
+</script>
+
+<style scoped lang="scss">
+.change-container {
+  .duty-card {
+    margin-left: 10px;
+    margin-right: 10px;
+    margin-top: 16px;
+    //width: 100%;
+    background-color: #fff;
+    border-radius: 4px;
+    &:first-child {
+      margin-top: 0;
+    }
+    .duty-header {
+      padding: 10px;
+      font-size: 16px;
+      font-weight: bold;
+      border-bottom: 1px solid #f0f1f1;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .add-text {
+        font-size: 14px;
+        color: #2c81ff;
+        cursor: pointer;
+      }
+    }
+    .duty-content {
+      padding: 10px;
+      .duty-item {
+        padding: 3px 0;
+        display: flex;
+        justify-content: space-between;
+        align-items: flex-start;
+        font-size: 14px;
+        .item-left {
+          display: flex;
+          align-items: center;
+          .icon {
+            display: inline-block;
+          }
+        }
+      }
+      .duty-item2 {
+        padding: 5px;
+        font-size: 14px;
+        background-color: #f2f2f2;
+        border-radius: 4px;
+        margin-top: 10px;
+        &:first-child {
+          margin-top: 0;
+        }
+        .text1 {
+          font-size: 12px;
+          color: rgba(0, 0, 0, 0.45);
+        }
+      }
+    }
+  }
+}
+.mobile-stats {
+  margin: 12px;
+  border-radius: 12px;
+  overflow: hidden;
+}
+
+/* 统计卡片通用样式 */
+.stat-card {
+  //padding: 12px;
+  width: 150px;
+  border-radius: 8px;
+  text-align: center;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+/* 各状态卡片颜色 */
+.stat-card.total {
+  background-color: #f0f9ff;
+}
+.stat-card.success {
+  background-color: #f6ffed;
+}
+.stat-card.danger {
+  background-color: #fff2f0;
+}
+.stat-card.warning {
+  background-color: #fffbe6;
+}
+
+.stat-card .value {
+  font-size: 24px;
+  font-weight: bold;
+  line-height: 1.2;
+  margin-bottom: 4px;
+}
+
+.stat-card .label {
+  font-size: 12px;
+  color: #666;
+  margin-bottom: 6px;
+}
+
+.stat-card :deep(.van-tag) {
+  margin-top: 4px;
+  font-size: 12px;
+}
+
+/* 刷新区域 */
+.refresh-area {
+  text-align: center;
+  padding: 8px 0;
+}
+
+///* 移动端适配 */
+//@media (max-width: 320px) {
+//  .stat-card .value {
+//    font-size: 20px;
+//  }
+//}
+</style>

+ 7 - 2
src/views/duty/index.vue

@@ -6,10 +6,11 @@
         <div :class="activeIndex === item.id ? 'tab-text text-active' : 'tab-text'">{{ item.name }}</div>
       </div>
     </div>
-    <div class="content">
+    <div class="content" :style="{ height: activeIndex === 'event' ? 'calc(100vh)' : 'calc(100vh - 230px)' }">
       <DutyCalendar v-if="activeIndex === 'calendar'" @changIndex="handleClickTab" />
       <ShiftChange v-else-if="activeIndex === 'shiftChange'" />
       <Rota v-else-if="activeIndex === 'rota'" />
+      <EventHandling v-else-if="activeIndex === 'event'" />
     </div>
   </div>
 </template>
@@ -19,12 +20,14 @@ import { ref } from "vue";
 import DutyCalendar from "@/views/duty/dutyCalendar.vue";
 import ShiftChange from "@/views/duty/shiftChange.vue";
 import Rota from "@/views/duty/rota.vue";
+import EventHandling from "@/views/duty/eventHandling.vue";
 
 let activeIndex = ref('calendar');
 let tabs = ref([
   { id: 'calendar', name: '值班日历', icon: 'tab1' },
   { id: 'shiftChange', name: '面对面换班', icon: 'tab2' },
   { id: 'rota', name: '值班表', icon: 'tab3' },
+  // { id: 'event', name: '事件处置', icon: 'tab3' } //事件处置
 ]);
 
 // 点击tab
@@ -70,7 +73,7 @@ const handleClickTab = (id) => {
   }
   .content {
     margin-top: 10px;
-    height: calc(100vh - 230px);
+    //height: calc(100vh - 230px);
     overflow-y: auto;
   }
   .tab1 {
@@ -98,3 +101,5 @@ const handleClickTab = (id) => {
   }
 }
 </style>
+<script setup lang="ts">
+</script>

+ 72 - 52
src/views/duty/shiftChange.vue

@@ -3,10 +3,16 @@
     <div class="duty-card">
       <div class="duty-header">
         <div>提醒事项</div>
-        <div v-if="status==0" class="add-text" @click="handleShowAdd('1')">新增事项</div>
+        <div v-if="status == 0" class="add-text" @click="handleShowAdd('1')">
+          新增事项
+        </div>
       </div>
       <div class="duty-content">
-        <div v-for="(item, index) in dutyData.remainds" :key="index" class="duty-item2">
+        <div
+          v-for="(item, index) in dutyData.remainds"
+          :key="index"
+          class="duty-item2"
+        >
           <div class="text1">{{ item.time }}</div>
           <div class="text2">{{ item.text }}</div>
         </div>
@@ -15,49 +21,64 @@
     <div class="duty-card">
       <div class="duty-header">
         <div>待办事项</div>
-        <div v-if="status==0" class="add-text" @click="handleShowAdd('2')">新增事项</div>
+        <div v-if="status == 0" class="add-text" @click="handleShowAdd('2')">
+          新增事项
+        </div>
       </div>
       <div class="duty-content">
-        <div v-for="(item, index) in dutyData.todos" :key="index" class="duty-item2">
+        <div
+          v-for="(item, index) in dutyData.todos"
+          :key="index"
+          class="duty-item2"
+        >
           <div class="text1">{{ item.time }}</div>
           <div class="text2">{{ item.text }}</div>
         </div>
       </div>
     </div>
     <div class="footer">
-      <div v-if="status==0" class="confirm-btn" @click="handleToShiftChange">确认交班</div>
-      <div v-if="status==1" class="confirm-btn handover_btn">已交班,交班时间:{{shift_time}}</div>
+      <div v-if="status == 0" class="confirm-btn" @click="handleToShiftChange">
+        确认交班
+      </div>
+      <div v-if="status == 1" class="confirm-btn handover_btn">
+        已交班,交班时间:{{ shift_time }}
+      </div>
     </div>
     <van-dialog
-        v-model:show="show"
-        class="custom-dialog"
-        :title="eventKey === '1' ? '新增提醒事项' : '新增待办事项'"
-        show-cancel-button
-        @cancel="handleCancel"
-        @confirm="handleConfirm"
+      v-model:show="show"
+      class="custom-dialog"
+      :title="eventKey === '1' ? '新增提醒事项' : '新增待办事项'"
+      show-cancel-button
+      @cancel="handleCancel"
+      @confirm="handleConfirm"
     >
-      <van-field v-model="content" :placeholder="eventKey === '1' ? '请输入提醒事项' : '请输入待办事项'"  type="textarea" rows="5" />
+      <van-field
+        v-model="content"
+        :placeholder="eventKey === '1' ? '请输入提醒事项' : '请输入待办事项'"
+        type="textarea"
+        rows="5"
+      />
     </van-dialog>
   </div>
 </template>
 
 <script lang="ts" setup name="shiftChange">
-import {onMounted, ref} from "vue";
-import {showConfirmDialog, showFailToast} from "vant";
-import {parseTime} from "@/utils/ruoyi";
+import { onMounted, ref } from "vue";
+import { showConfirmDialog, showFailToast } from "vant";
+import { parseTime } from "@/utils/ruoyi";
 import { dutyByDay, addDutyNotify, handover } from "@/api/duty/duty";
 
-const emits = defineEmits(['confirm']);
+const emits = defineEmits(["confirm"]);
 let show = ref(false);
-const current = ref('');
+const current = ref("");
 const status = ref(0);
-const shift_time = ref('');
-let eventKey = ref('');
-let content = ref('');
-const dutyData  = ref({
+const shift_time = ref("");
+let eventKey = ref("");
+let content = ref("");
+const dutyData = ref({
   shift_id: 0,
   shift_status: 0,
-  handover_time: '',
+  handover_time: "",
   remainds: [
     //{ text: '台风生成期间,请密切关注台风路径及等级变化情况1。', time: '2024-10-20 11:43:00' },
     //{ text: '台风生成期间,请密切关注台风路径及等级变化情况2。', time: '2024-10-20 11:43:00' },
@@ -69,42 +90,41 @@ const dutyData  = ref({
     //{ text: '台风即将登录,请通知相关单位人员下午2:00到现场开展台风联合值守工作3。', time: '2024-10-20 11:43:00' }
   ]
 });
-const handleShowAdd = (key) => {
+const handleShowAdd = key => {
   eventKey.value = key;
   show.value = true;
-}
+};
 const handleToShiftChange = () => {
   showConfirmDialog({
-    title: '提示',
-    message: '是否确认交班?',
+    title: "提示",
+    message: "是否确认交班?"
   }).then(() => {
     handover({
       shift_id: dutyData.value.shift_id
-    }).then((res)=>{
+    }).then(res => {
       getDuytData();
-    })
+    });
 
     // on confirm
-  })
-}
+  });
+};
 // 编辑
 const handleCancel = () => {
   show.value = false;
-  content.value = '';
-}
+  content.value = "";
+};
 // 新增
 const handleConfirm = () => {
-  if(content.value == '') {
+  if (content.value == "") {
     showFailToast("请输入事项内容");
-  }
-  else {
+  } else {
     addDutyNotify({
-      shift_id: dutyData.value.shift_id, 
-      notify_content: content.value, 
-      notify_type: eventKey.value}
-    ).then((res)=>{
+      shift_id: dutyData.value.shift_id,
+      notify_content: content.value,
+      notify_type: eventKey.value
+    }).then(res => {
       getDuytData();
-    })
+    });
   }
   // let method = eventKey.value === '1' ? 'method1' : 'method2';
   // method().then((res) => {
@@ -115,20 +135,20 @@ const handleConfirm = () => {
 };
 
 const getDuytData = () => {
-  dutyByDay({day: current.value}).then((res)=>{
+  dutyByDay({ day: current.value }).then(res => {
     dutyData.value = res.data;
     status.value = dutyData.value.shift_status;
     shift_time.value = dutyData.value.handover_time;
-    
+
     show.value = false;
-    content.value = '';
+    content.value = "";
   });
 };
 
-onMounted(()=>{
-  current.value = parseTime(new Date(), '{y}-{m}-{d}');
+onMounted(() => {
+  current.value = parseTime(new Date(), "{y}-{m}-{d}");
   getDuytData();
-})
+});
 </script>
 
 <style lang="scss" scoped>
@@ -151,7 +171,7 @@ onMounted(()=>{
       align-items: center;
       .add-text {
         font-size: 14px;
-        color: #2C81FF;
+        color: #2c81ff;
         cursor: pointer;
       }
     }
@@ -194,24 +214,24 @@ onMounted(()=>{
   left: 0;
   width: 100%;
   height: 55px;
-  background: #FFFFFF;
+  background: #ffffff;
   display: flex;
   justify-content: center;
   align-items: center;
   .confirm-btn {
     width: 110px;
     height: 40px;
-    background: #2C81FF;
+    background: #2c81ff;
     border-radius: 2px;
     font-size: 16px;
-    color: #FFFFFF;
+    color: #ffffff;
     display: flex;
     justify-content: center;
     align-items: center;
   }
   .handover_btn {
     background: #aaa;
-    width:330px;
+    width: 330px;
   }
 }
 </style>