jc-wangyt 4 týždňov pred
rodič
commit
3c242bc449

+ 7 - 6
src/api/login.js

@@ -1,15 +1,15 @@
 import request from '@/utils/request'
 
 // 登录方法
-export function login(username, password, code, uuid) {
+export function login(username, password, captcha, uuid) {
   const data = {
     username,
     password,
-    code,
+    captcha,
     uuid
   }
   return request({
-    url: '/login',
+    url: '/auth/login',
     headers: {
       isToken: false,
       repeatSubmit: false
@@ -50,11 +50,12 @@ export function logout() {
 // 获取验证码
 export function getCodeImg() {
   return request({
-    url: '/captchaImage',
+    url: '/auth/getCaptchaImag',
     headers: {
       isToken: false
     },
-    method: 'get',
-    timeout: 20000
+    method: 'POST',
+    timeout: 20000,
+    responseType: 'blob'
   })
 }

+ 13 - 0
src/api/newApi/alarm/index.js

@@ -0,0 +1,13 @@
+import request from '@/utils/request'
+
+/**
+ * 获取告警统计数据
+ * @param {*} month 格式为yyyy-MM
+ * @returns 
+ */
+export function getAlarmStats(month) {
+  return request({
+    url: '/alarm/report/stats?month=' + month,
+    method: 'get'
+  })
+}

+ 9 - 0
src/api/newApi/driving/index.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+// 获取实时告警数据
+export function getRealTimeAlarm(data) {
+    return request({
+        url: '/alarm/realtime',
+        method: 'get',
+        params: data
+    })
+}

+ 35 - 0
src/api/newApi/inspection/index.js

@@ -0,0 +1,35 @@
+import request from '@/utils/request'
+// 获取任务列表
+export function getTaskList(data) {
+  return request({
+    url: '/inspection/tasks',
+    method: 'get',
+    params: data
+  })
+}
+
+// 新增任务
+export function addTask(data) {
+  return request({
+    url: '/inspection/task',
+    method: 'post',
+    data
+  })
+}
+
+// 获取任务删除
+export function deleteTask(id) {
+  return request({
+    url: `/inspection/task/${id}`,
+    method: 'delete'
+  })
+}
+
+// /stage-api/inspection/task/{id} // 更新任务
+export function updateTask(data) {
+  return request({
+    url: `/inspection/task/${data.id}`,
+    method: 'put',
+    data
+  })
+}

+ 216 - 0
src/router/index copy.js

@@ -0,0 +1,216 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+
+/* Layout */
+import Layout from '@/layout'
+
+/**
+ * Note: 路由配置项
+ *
+ * hidden: true                     // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
+ * alwaysShow: true                 // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
+ *                                  // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
+ *                                  // 若你想不管路由下面的 children 声明的个数都显示你的根路由
+ *                                  // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
+ * redirect: noRedirect             // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
+ * name:'router-name'               // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
+ * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
+ * roles: ['admin', 'common']       // 访问路由的角色权限
+ * permissions: ['a:a:a', 'b:b:b']  // 访问路由的菜单权限
+ * meta : {
+    noCache: true                   // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
+    title: 'title'                  // 设置该路由在侧边栏和面包屑中展示的名字
+    icon: 'svg-name'                // 设置该路由的图标,对应路径src/assets/icons/svg
+    breadcrumb: false               // 如果设置为false,则不会在breadcrumb面包屑中显示
+    activeMenu: '/system/user'      // 当路由设置了该属性,则会高亮相对应的侧边栏。
+  }
+ */
+
+// 公共路由
+export const constantRoutes = [
+  {
+    path: '/redirect',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: '/redirect/:path(.*)',
+        component: () => import('@/views/redirect')
+      }
+    ]
+  },
+  {
+    path: '/login',
+    component: () => import('@/views/login'),
+    hidden: true
+  },
+  {
+    path: '/register',
+    component: () => import('@/views/register'),
+    hidden: true
+  },
+  {
+    path: '/404',
+    component: () => import('@/views/error/404'),
+    hidden: true
+  },
+  {
+    path: '/401',
+    component: () => import('@/views/error/401'),
+    hidden: true
+  },
+  {
+    path: '',
+    redirect: '/login',
+    hidden: true
+  },
+  {
+    path: '/home',
+    component: Layout,
+    meta: { title: '算网全流程平台', icon: 'dashboard', affix: true },
+    children: [
+      // {
+      //   path: 'index',
+      //   component: () => import('@/views/index'),
+      //   name: 'Index',
+      //   meta: { title: '首页', icon: 'dashboard', affix: true }
+      // },
+      {
+        path: 'bizoppmanage',
+        component: () => import('@/views/bizoppmanage/index'),
+        name: 'BizOppManage',
+        meta: { title: '商机运营', icon: 'dashboard', affix: true }
+      },
+      {
+        path: 'bizoppdetails',
+        component: () => import('@/views/bizoppmanage/details'),
+        name: 'BizOppDetails',
+        meta: { title: '商机详情', noCache: true, icon: 'dashboard', breadcrumb: false },
+        hidden: true
+      },
+      {
+        path: 'openMarketBid',
+        component: () => import('@/views/openMarketBid/index'),
+        name: 'OpenMarketBid',
+        meta: { title: '公开市场', icon: 'dashboard', affix: true }
+      },
+      {
+        path: 'openMarketBidDetails',
+        component: () => import('@/views/openMarketBid/details'),
+        name: 'OpenMarketBidDetails',
+        meta: { title: '公开市场详情', noCache: true, icon: 'dashboard', breadcrumb: false },
+        hidden: true
+      },
+
+    ]
+  },
+
+  {
+    path: '/user',
+    component: Layout,
+    hidden: true,
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'profile',
+        component: () => import('@/views/system/user/profile/index'),
+        name: 'Profile',
+        meta: { title: '个人中心', icon: 'user' }
+      }
+    ]
+  }
+]
+
+// 动态路由,基于用户权限动态去加载
+export const dynamicRoutes = [
+  {
+    path: '/system/user-auth',
+    component: Layout,
+    hidden: true,
+    permissions: ['system:user:edit'],
+    children: [
+      {
+        path: 'role/:userId(\\d+)',
+        component: () => import('@/views/system/user/authRole'),
+        name: 'AuthRole',
+        meta: { title: '分配角色', activeMenu: '/system/user' }
+      }
+    ]
+  },
+  {
+    path: '/system/role-auth',
+    component: Layout,
+    hidden: true,
+    permissions: ['system:role:edit'],
+    children: [
+      {
+        path: 'user/:roleId(\\d+)',
+        component: () => import('@/views/system/role/authUser'),
+        name: 'AuthUser',
+        meta: { title: '分配用户', activeMenu: '/system/role' }
+      }
+    ]
+  },
+  {
+    path: '/system/dict-data',
+    component: Layout,
+    hidden: true,
+    permissions: ['system:dict:list'],
+    children: [
+      {
+        path: 'index/:dictId(\\d+)',
+        component: () => import('@/views/system/dict/data'),
+        name: 'Data',
+        meta: { title: '字典数据', activeMenu: '/system/dict' }
+      }
+    ]
+  },
+  {
+    path: '/monitor/job-log',
+    component: Layout,
+    hidden: true,
+    permissions: ['monitor:job:list'],
+    children: [
+      {
+        path: 'index/:jobId(\\d+)',
+        component: () => import('@/views/monitor/job/log'),
+        name: 'JobLog',
+        meta: { title: '调度日志', activeMenu: '/monitor/job' }
+      }
+    ]
+  },
+  {
+    path: '/tool/gen-edit',
+    component: Layout,
+    hidden: true,
+    permissions: ['tool:gen:edit'],
+    children: [
+      {
+        path: 'index/:tableId(\\d+)',
+        component: () => import('@/views/tool/gen/editTable'),
+        name: 'GenEdit',
+        meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
+      }
+    ]
+  }
+]
+
+// 防止连续点击多次路由报错
+let routerPush = Router.prototype.push
+let routerReplace = Router.prototype.replace
+// push
+Router.prototype.push = function push(location) {
+  return routerPush.call(this, location).catch(err => err)
+}
+// replace
+Router.prototype.replace = function push(location) {
+  return routerReplace.call(this, location).catch(err => err)
+}
+
+export default new Router({
+  mode: 'history', // 去掉url中的#
+  scrollBehavior: () => ({ y: 0 }),
+  routes: constantRoutes
+})

+ 82 - 33
src/router/index.js

@@ -61,53 +61,102 @@ export const constantRoutes = [
     component: () => import('@/views/error/401'),
     hidden: true
   },
+  // {
+  //   path: '',
+  //   component: Layout,
+  //   redirect: 'bizoppmanage',
+  //   meta: { title: '算网全流程平台', icon: 'dashboard', affix: true },
+  //   children: [
+  //     {
+  //       path: 'bizoppmanage',
+  //       component: () => import('@/views/bizoppmanage/index'),
+  //       name: 'BizOppManage',
+  //       meta: { title: '商机运营', icon: 'dashboard', affix: true },
+  //     },
+  //     {
+  //       path: 'bizoppdetails',
+  //       component: () => import('@/views/bizoppmanage/details'),
+  //       name: 'BizOppDetails',
+  //       meta: { title: '商机详情', noCache: true, icon: 'dashboard', breadcrumb: false },
+  //     },
+  //     {
+  //       path: 'openMarketBid',
+  //       component: () => import('@/views/openMarketBid/index'),
+  //       name: 'OpenMarketBid',
+  //       meta: { title: '公开市场', icon: 'dashboard', affix: true },
+  //     },
+  //     {
+  //       path: 'openMarketBidDetails',
+  //       component: () => import('@/views/openMarketBid/details'),
+  //       name: 'OpenMarketBidDetails',
+  //       meta: { title: '公开市场详情', noCache: true, icon: 'dashboard', breadcrumb: false },
+  //     },
+
+  //   ]
+  // },
+  
   {
     path: '',
     component: Layout,
-    redirect: 'bizoppmanage',
-    meta: { title: '算网全流程平台', icon: 'dashboard', affix: true },
+    redirect: '/alarm/index',
+  },
+  {
+    path: '/alarm',
+    component: Layout,
+    redirect: 'index',
     children: [
-      // {
-      //   path: 'index',
-      //   component: () => import('@/views/index'),
-      //   name: 'Index',
-      //   meta: { title: '首页', icon: 'dashboard', affix: true }
-      // },
-      {
-        path: 'bizoppmanage',
-        component: () => import('@/views/bizoppmanage/index'),
-        name: 'BizOppManage',
-        meta: { title: '商机运营', icon: 'dashboard', affix: true }
-      },
-      {
-        path: 'bizoppdetails',
-        component: () => import('@/views/bizoppmanage/details'),
-        name: 'BizOppDetails',
-        meta: { title: '商机详情', noCache: true, icon: 'dashboard', breadcrumb: false },
-        hidden: true
-      },
       {
-        path: 'openMarketBid',
-        component: () => import('@/views/openMarketBid/index'),
-        name: 'OpenMarketBid',
-        meta: { title: '公开市场', icon: 'dashboard', affix: true }
+        path: 'index',
+        component: () => import('@/views/alarm/index'),
+        name: 'Alarm',
+        meta: { title: '告警报表统计', icon: 'dashboard', affix: true },
       },
+    ]
+  },
+  // 机房巡检管理
+  // {
+  //   path: '/inspection',
+  //   component: Layout,
+  //   redirect: 'index',
+  //   meta: { title: '巡检管理', icon: 'dashboard', affix: true },
+  //   children: [
+  //     {
+  //       path: 'index',
+  //       component: () => import('@/views/inspection/index'),
+  //       name: 'Inspection',
+  //       meta: { title: '机房巡检管理',  affix: true },
+  //     },
+  //     {
+  //       path: 'task-management',
+  //       component: () => import('@/views/inspection/task-management'),
+  //       name: 'TaskManagement',
+  //       meta: { title: '巡检任务管理', noCache: true },
+  //     },
+  //     {
+  //       path: 'input',
+  //       component: () => import('@/views/inspection/input'),
+  //       name: 'InspectionInput',
+  //       meta: { title: '巡检录入', noCache: true },
+  //     },
+  //   ]
+  // },
+  // 告警数据驾驶舱
+  {
+    path: '/driving',
+    component: Layout,
+    children: [
       {
-        path: 'openMarketBidDetails',
-        component: () => import('@/views/openMarketBid/details'),
-        name: 'OpenMarketBidDetails',
-        meta: { title: '公开市场详情', noCache: true, icon: 'dashboard', breadcrumb: false },
-        hidden: true
+        path: 'index',
+        component: () => import('@/views/driving/index'),
+        name: 'Driving',
+        meta: { title: '告警数据驾驶舱', icon: 'dashboard', affix: true },
       },
-
     ]
   },
-
   {
     path: '/user',
     component: Layout,
     hidden: true,
-    redirect: 'noredirect',
     children: [
       {
         path: 'profile',

+ 4 - 2
src/store/modules/user.js

@@ -49,8 +49,10 @@ const user = {
       const uuid = userInfo.uuid
       return new Promise((resolve, reject) => {
         login(username, password, code, uuid).then(res => {
-          setToken(res.token)
-          commit('SET_TOKEN', res.token)
+          // setToken(res.token)
+          // commit('SET_TOKEN', res.token)
+          setToken("369c83da-e421-476c-9d9f-f7c14038a25a")
+          commit('SET_TOKEN',  "369c83da-e421-476c-9d9f-f7c14038a25a")
           resolve()
         }).catch(error => {
           reject(error)

+ 2 - 2
src/utils/auth.js

@@ -4,8 +4,8 @@ const TokenKey = 'Admin-Token'
 
 export function getToken() {
   // 开发环境临时使用假token,跳过登录
-  return '123456'
-  // return Cookies.get(TokenKey)
+  // return '123456'
+  return Cookies.get(TokenKey)
 }
 
 export function setToken(token) {

+ 20 - 3
src/utils/index.js

@@ -218,7 +218,7 @@ export function getTime(type) {
 export function debounce(func, wait, immediate) {
   let timeout, args, context, timestamp, result
 
-  const later = function() {
+  const later = function () {
     // 据上一次触发时间间隔
     const last = +new Date() - timestamp
 
@@ -235,7 +235,7 @@ export function debounce(func, wait, immediate) {
     }
   }
 
-  return function(...args) {
+  return function (...args) {
     context = this
     timestamp = +new Date()
     const callNow = immediate && !timeout
@@ -373,7 +373,24 @@ export const beautifierConf = {
     indent_empty_lines: true
   }
 }
-
+// blob转换base64格式
+export function blobToBase64(blob) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onload = () => {
+      // 移除事件监听器
+      reader.onload = null;
+      reader.onerror = null;
+      resolve(reader.result);
+    };
+    reader.onerror = (error) => {
+      reader.onload = null;
+      reader.onerror = null;
+      reject(new Error(`Blob转换失败: ${error.target.error}`));
+    };
+    reader.readAsDataURL(blob);
+  });
+}
 // 首字母大小
 export function titleCase(str) {
   return str.replace(/( |^)[a-z]/g, L => L.toUpperCase())

+ 212 - 0
src/views/alarm/index.vue

@@ -0,0 +1,212 @@
+<template>
+  <div class="alarm-report-container">
+    <el-card class="operate-card">
+      <!-- 顶部操作区 -->
+      <div class="operate-area">
+        <el-button-group>
+          <el-button v-for="month in months" :key="month.value"
+            :type="currentMonth === month.value ? 'primary' : 'default'" @click="handleMonthChange(month.value)">
+            {{ month.label }}
+          </el-button>
+        </el-button-group>
+        <el-button type="default" @click="handleRefresh">
+          <i class="el-icon-refresh"></i> 刷新
+        </el-button>
+        <el-button type="default" @click="handleExport">
+          <i class="el-icon-download"></i> 导出
+        </el-button>
+      </div>
+    </el-card>
+    <!-- 中部系统总览区 -->
+    <el-card class="overall-card">
+      <div slot="header" class="card-header">
+        <span>系统概览</span>
+      </div>
+      <div class="overall-content">
+        <div v-for="(item, index) in overviewData" :key="index" class="overall-item">
+          <el-statistic :value="item.value" :precision="0" :value-style="{ color: '#409EFF' }">
+            <template slot="prefix">
+              <el-icon class="el-icon-info"></el-icon>
+            </template>
+            <template slot="suffix">
+              {{ item.unit }}
+            </template>
+          </el-statistic>
+          <div class="overall-label">{{ item.label }}</div>
+        </div>
+      </div>
+    </el-card>
+
+    <!-- 底部各系统告警统计展示区 -->
+    <el-card class="system-stats-card">
+      <div slot="header" class="card-header">
+        <span>各系统告警统计</span>
+      </div>
+      <el-table :data="systemStatsData" style="width: 100%" v-loading="loadingsystemStats">
+        <el-table-column prop="systemName" label="系统名称" width="120" />
+        <el-table-column prop="totalAlarms" label="告警总数" align="center" />
+        <el-table-column prop="processedAlarms" label="已处理告警总数" align="center" />
+        <el-table-column prop="unprocessedAlarms" label="未处理告警总数" align="center" />
+        <el-table-column prop="contentAlarms" label="内容类告警总数" align="center" />
+        <el-table-column prop="thresholdAlarms" label="阈值类告警总数" align="center" />
+        <el-table-column prop="existenceAlarms" label="存在性告警总数" align="center" />
+        <el-table-column prop="indicator" label="各指标的告警总数" align="center" />
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { getAlarmStats } from '@/api/newApi/alarm'
+
+export default {
+  data() {
+    return {
+      loadingsystemStats: false,
+      currentMonth: '2026-01',
+      months: [],
+      alarmData: {//告警统计数据
+        overall: {},
+        indicatorStats: []
+      }
+    }
+  },
+  computed: {
+    // 根据当前选择的月份获取系统总览数据
+    overviewData() {
+      const overall = this.alarmData.overall || {};
+      return [
+        { label: '告警总数', value: overall.total, unit: '件' },
+        { label: '已处理告警总数', value: overall.processed, unit: '件' },
+        { label: '未处理告警总数', value: overall.unprocessed, unit: '件' },
+        { label: '内容类告警总数', value: overall.contentCount, unit: '件' },
+        { label: '阈值类告警总数', value: overall.thresholdCount, unit: '件' },
+        { label: '存在性告警总数', value: overall.existenceCount, unit: '件' }
+      ];
+    },
+    // 根据当前选择的月份获取各系统告警统计数据
+    systemStatsData() {
+      return (this.alarmData.indicatorStats || []).map(item => ({
+        systemName: item.system_name || '',
+        totalAlarms: item.total || 0,
+        processedAlarms: item.processed || 0,
+        unprocessedAlarms: item.unprocessed || 0,
+        contentAlarms: item.contentCount || 0,
+        thresholdAlarms: item.thresholdCount || 0,
+        existenceAlarms: item.existenceCount || 0,
+        indicator: item.total || 0,
+        dataNoDeliveredCount: item.dataNoDeliveredCount || 0,
+        internalConnectCount: item.internalConnectCount || 0,
+        interfaceFrequencyCount: item.interfaceFrequencyCount || 0,
+        databaseConnectCount: item.databaseConnectCount || 0,
+        pageTamperProofCount: item.pageTamperProofCount || 0,
+        networkBoundaryConnectCount: item.networkBoundaryConnectCount || 0,
+        scheduledTaskCount: item.scheduledTaskCount || 0,
+        ossUseCount: item.indicator_ossUseCount || 0,
+        abnormalAccessCount: item.abnormalAccessCount || 0
+      }));
+    }
+  },
+  created() {
+    this.getAlarmEven();
+  },
+  methods: {
+    /**
+     * 获取告警统计数据
+     */
+    getAlarmEven() {
+      this.loadingsystemStats = true;
+      getAlarmStats(this.currentMonth).then(res => {
+        if (res.code === 200) {
+          this.alarmData = {
+            overall: res.data.overall || {},
+            indicatorStats: res.data.indicatorStats || []
+          };
+          console.log(this.alarmData, ' this.alarmData')
+          // 更新月份列表
+          if (res.data.months && Array.isArray(res.data.months)) {
+            this.months = res.data.months.map(month => {
+              const [year, monthNum] = month.split('-');
+              return {
+                label: `${monthNum}月`,
+                value: month
+              };
+            });
+          }
+          this.loadingsystemStats = false;
+        }
+      })
+    },
+    // 处理月份切换
+    handleMonthChange(month) {
+      this.currentMonth = month;
+      // 切换月份后重新获取数据
+      this.getAlarmEven();
+    },
+    // 处理刷新
+    handleRefresh() {
+      // 刷新时重新获取数据
+      this.getAlarmEven();
+      this.$message.success('数据已刷新');
+    },
+    // 处理导出
+    handleExport() {
+      this.$message.success('导出功能已触发');
+    }
+  }
+}
+</script>
+
+<style scoped>
+.alarm-report-container {
+  padding: 20px;
+}
+
+.operate-card {
+  margin-bottom: 20px;
+}
+
+.operate-area {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.overall-card {
+  margin-bottom: 20px;
+}
+
+.card-header {
+  font-weight: bold;
+  font-size: 16px;
+}
+
+.overall-content {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 20px;
+  padding: 20px 0;
+}
+
+.overall-item {
+  text-align: center;
+  padding: 20px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  background-color: #f9fafc;
+}
+
+.overall-label {
+  margin-top: 10px;
+  font-size: 14px;
+  color: #666666;
+}
+
+.system-stats-card {
+  margin-bottom: 20px;
+}
+
+.el-table {
+  margin-top: 20px;
+}
+</style>

+ 959 - 0
src/views/driving/index.vue

@@ -0,0 +1,959 @@
+<template>
+  <div class="driving-container">
+    <!-- 页面头部 -->
+    <div class="page-header">
+      <h2>告警数据驾驶舱</h2>
+      <div class="header-right">
+        <el-date-picker v-model="currentDate" type="datetime" placeholder="选择日期时间" format="yyyy-MM-dd HH:mm:ss"
+          value-format="yyyy-MM-dd HH:mm:ss" style="width: 200px; margin-right: 10px">
+        </el-date-picker>
+        <el-button type="primary" icon="el-icon-refresh" @click="refreshData">刷新</el-button>
+        <el-checkbox v-model="autoRefresh" style="margin-left: 10px">自动刷新</el-checkbox>
+      </div>
+    </div>
+
+    <!-- 第一行:指标分类数据获取情况 + 实时告警列表 + 七日告警数据分布 -->
+    <div class="row row-1">
+      <!-- 指标分类数据获取情况 -->
+      <div class="col col-1">
+        <div class="card">
+          <div class="card-header">
+            <h3>指标分类数据获取情况</h3>
+          </div>
+          <div class="card-body">
+            <el-tabs v-model="activeCategoryTab" @tab-click="handleTabClick">
+              <el-tab-pane label="内容类" name="content">
+                <div class="chart-wrapper">
+                  <div ref="contentChart"></div>
+                </div>
+              </el-tab-pane>
+              <el-tab-pane label="阈值类" name="threshold">
+                <div class="chart-wrapper">
+                  <div ref="thresholdChart"></div>
+                </div>
+              </el-tab-pane>
+              <el-tab-pane label="存在性" name="existence">
+                <div class="chart-wrapper">
+                  <div ref="existenceChart"></div>
+                </div>
+              </el-tab-pane>
+            </el-tabs>
+          </div>
+        </div>
+      </div>
+
+      <!-- 实时告警列表 -->
+      <div class="col col-2">
+        <div class="card">
+          <div class="card-header">
+            <h3>实时告警列表</h3>
+          </div>
+          <div class="card-body">
+            <div class="alarm-list" ref="alarmList">
+              <div class="alarm-items-container">
+                <div v-for="alarm in realTimeAlarms" :key="alarm.id" class="alarm-item">
+                  <div class="alarm-indicator">{{ alarm.indicator }}</div>
+                  <div class="alarm-time">{{ alarm.occurTime }}/{{ alarm.alarmType }}</div>
+                  <div class="alarm-status">
+                    <el-tag type="danger"> 未处理 </el-tag>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 七日告警数据分布 -->
+      <div class="col col-3">
+        <div class="card">
+          <div class="card-header">
+            <h3>七日告警数据分布</h3>
+          </div>
+          <div class="card-body">
+            <div class="total-alarms">
+              <div class="total-label">七日内总告警数</div>
+              <div class="total-count">{{ sevenDayTotalAlarms }}</div>
+            </div>
+            <div class="chart-wrapper">
+              <div ref="distributionChart"></div>
+            </div>
+            <div class="legend">
+              <div v-for="item in distributionLegend" :key="item.name" class="legend-item">
+                <span class="legend-color" :style="{ backgroundColor: item.color }"></span>
+                <span class="legend-text">{{ item.name }}: {{ item.value }}%</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 第二行:七日告警趋势分析 + 各系统七日告警 -->
+    <div class="row row-2">
+      <!-- 七日告警趋势分析 -->
+      <div class="col col-4">
+        <div class="card">
+          <div class="card-header">
+            <h3>七日告警趋势分析</h3>
+          </div>
+          <div class="card-body">
+            <div class="chart-wrapper">
+              <div ref="trendChart"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 各系统七日告警 -->
+      <div class="col col-5">
+        <div class="card">
+          <div class="card-header">
+            <h3>各系统七日告警</h3>
+          </div>
+          <div class="card-body">
+            <div class="chart-wrapper">
+              <div ref="systemChart"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+import { getRealTimeAlarm } from '@/api/newApi/driving'
+
+export default {
+  data() {
+    return {
+      currentDate: new Date().toISOString().slice(0, 19).replace('T', ' '),
+      autoRefresh: false,
+      refreshInterval: null,
+      activeCategoryTab: 'content',
+
+     
+      realTimeAlarms: [],//实时告警列表
+
+
+      sevenDayTotalAlarms: 365,
+
+      distributionLegend: [
+        { name: 'CPU使用率', value: 30, color: '#5470c6' },
+        { name: '内存使用率', value: 25, color: '#91cc75' },
+        { name: '磁盘空间', value: 20, color: '#fac858' },
+        { name: '网络延迟', value: 15, color: '#ee6666' },
+        { name: '服务可用性', value: 10, color: '#73c0de' }
+      ],
+
+      // 图表实例
+      charts: {}
+    }
+  },
+  mounted() {
+
+    this.initCharts()
+    // 只有在autoRefresh为true时才启动自动刷新
+    if (this.autoRefresh) {
+      this.startAutoRefresh()
+    }
+  },
+  beforeDestroy() {
+    this.stopAutoRefresh()
+    this.destroyCharts()
+  },
+  watch: {
+    autoRefresh(val) {
+      if (val) {
+        this.startAutoRefresh()
+      } else {
+        this.stopAutoRefresh()
+      }
+    }
+  },
+  methods: {
+    getRealEvent() {
+      // 初始化实时告警列表
+      getRealTimeAlarm().then(res => {
+        if (res.code === 200) {
+          this.realTimeAlarms = res.data || []
+        }
+      })
+    },
+    initCharts() {
+      this.initCategoryCharts()
+      this.initDistributionChart()
+      this.initTrendChart()
+      this.initSystemChart()
+      this.getRealEvent()
+    },
+
+    initCategoryCharts() {
+      // 初始化当前激活的选项卡对应的图表
+      this.initCategoryChart(this.activeCategoryTab)
+    },
+
+    initDistributionChart() {
+      const chartRef = this.$refs.distributionChart
+      console.log('初始化分布图表:', chartRef)
+      if (!chartRef) {
+        console.error('未找到分布图表引用')
+        return
+      }
+
+      // 确保容器有正确的尺寸
+      chartRef.style.width = '100%'
+      chartRef.style.height = '100%'
+      chartRef.style.minHeight = '200px'
+
+      try {
+        const distributionChart = echarts.init(chartRef)
+        console.log('分布图表初始化成功')
+        distributionChart.setOption({
+          tooltip: {
+            trigger: 'item',
+            formatter: '{a} <br/>{b}: {c} ({d}%)'
+          },
+          series: [
+            {
+              name: '告警分布',
+              type: 'pie',
+              radius: '60%',
+              center: ['50%', '50%'],
+              data: [
+                { value: 30, name: 'CPU使用率', itemStyle: { color: '#5470c6' } },
+                { value: 25, name: '内存使用率', itemStyle: { color: '#91cc75' } },
+                { value: 20, name: '磁盘空间', itemStyle: { color: '#fac858' } },
+                { value: 15, name: '网络延迟', itemStyle: { color: '#ee6666' } },
+                { value: 10, name: '服务可用性', itemStyle: { color: '#73c0de' } }
+              ],
+              emphasis: {
+                itemStyle: {
+                  shadowBlur: 10,
+                  shadowOffsetX: 0,
+                  shadowColor: 'rgba(0, 0, 0, 0.5)'
+                }
+              }
+            }
+          ]
+        })
+        this.charts.distributionChart = distributionChart
+        console.log('分布图表配置设置成功')
+      } catch (error) {
+        console.error('初始化分布图表时出错:', error)
+      }
+    },
+
+    initTrendChart() {
+      const chartRef = this.$refs.trendChart
+      console.log('初始化趋势图表:', chartRef)
+      if (!chartRef) {
+        console.error('未找到趋势图表引用')
+        return
+      }
+
+      // 确保容器有正确的尺寸
+      chartRef.style.width = '100%'
+      chartRef.style.height = '100%'
+      chartRef.style.minHeight = '200px'
+
+      try {
+        const trendChart = echarts.init(chartRef)
+        console.log('趋势图表初始化成功')
+        trendChart.setOption({
+          tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+              type: 'cross',
+              label: {
+                backgroundColor: '#6a7985'
+              }
+            }
+          },
+          legend: {
+            data: ['内容类', '阈值类', '存在性']
+          },
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '3%',
+            containLabel: true
+          },
+          xAxis: [
+            {
+              type: 'category',
+              boundaryGap: false,
+              data: ['11-07', '11-08', '11-09', '11-10', '11-11', '11-12', '11-13']
+            }
+          ],
+          yAxis: [
+            {
+              type: 'value'
+            }
+          ],
+          series: [
+            {
+              name: '内容类',
+              type: 'line',
+              stack: 'Total',
+              areaStyle: {},
+              emphasis: {
+                focus: 'series'
+              },
+              data: [12, 19, 3, 5, 2, 3, 15],
+              lineStyle: {
+                color: '#ee6666'
+              },
+              itemStyle: {
+                color: '#ee6666'
+              }
+            },
+            {
+              name: '阈值类',
+              type: 'line',
+              stack: 'Total',
+              areaStyle: {},
+              emphasis: {
+                focus: 'series'
+              },
+              data: [11, 22, 33, 44, 55, 43, 32],
+              lineStyle: {
+                color: '#5470c6'
+              },
+              itemStyle: {
+                color: '#5470c6'
+              }
+            },
+            {
+              name: '存在性',
+              type: 'line',
+              stack: 'Total',
+              areaStyle: {},
+              emphasis: {
+                focus: 'series'
+              },
+              data: [12, 21, 15, 18, 22, 25, 20],
+              lineStyle: {
+                color: '#91cc75'
+              },
+              itemStyle: {
+                color: '#91cc75'
+              }
+            }
+          ]
+        })
+        this.charts.trendChart = trendChart
+        console.log('趋势图表配置设置成功')
+      } catch (error) {
+        console.error('初始化趋势图表时出错:', error)
+      }
+    },
+
+    initSystemChart() {
+      const chartRef = this.$refs.systemChart
+      console.log('初始化系统图表:', chartRef)
+      if (!chartRef) {
+        console.error('未找到系统图表引用')
+        return
+      }
+
+      // 确保容器有正确的尺寸
+      chartRef.style.width = '100%'
+      chartRef.style.height = '100%'
+      chartRef.style.minHeight = '200px'
+
+      try {
+        const systemChart = echarts.init(chartRef)
+        console.log('系统图表初始化成功')
+        systemChart.setOption({
+          tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+              type: 'shadow'
+            }
+          },
+          legend: {
+            data: ['内容类', '阈值类', '存在性']
+          },
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '3%',
+            containLabel: true
+          },
+          xAxis: [
+            {
+              type: 'category',
+              data: ['系统1', '系统2', '系统3', '系统4']
+            }
+          ],
+          yAxis: [
+            {
+              type: 'value'
+            }
+          ],
+          series: [
+            {
+              name: '内容类',
+              type: 'bar',
+              stack: 'total',
+              emphasis: {
+                focus: 'series'
+              },
+              data: [32, 28, 35, 25],
+              itemStyle: {
+                color: '#ee6666'
+              }
+            },
+            {
+              name: '阈值类',
+              type: 'bar',
+              stack: 'total',
+              emphasis: {
+                focus: 'series'
+              },
+              data: [45, 40, 38, 42],
+              itemStyle: {
+                color: '#5470c6'
+              }
+            },
+            {
+              name: '存在性',
+              type: 'bar',
+              stack: 'total',
+              emphasis: {
+                focus: 'series'
+              },
+              data: [18, 22, 15, 20],
+              itemStyle: {
+                color: '#91cc75'
+              }
+            }
+          ]
+        })
+        this.charts.systemChart = systemChart
+        console.log('系统图表配置设置成功')
+      } catch (error) {
+        console.error('初始化系统图表时出错:', error)
+      }
+    },
+
+    handleTabClick(tab) {
+      // 当切换选项卡时,确保图表能够正确显示
+      setTimeout(() => {
+        // 无论之前是否存在图表实例,都重新初始化图表
+        if (tab.name === 'content' && this.$refs.contentChart) {
+          // 销毁旧图表实例
+          if (this.charts.contentChart) {
+            this.charts.contentChart.dispose()
+            this.charts.contentChart = null
+          }
+          // 创建新图表实例
+          this.initCategoryChart('content')
+        } else if (tab.name === 'threshold' && this.$refs.thresholdChart) {
+          // 销毁旧图表实例
+          if (this.charts.thresholdChart) {
+            this.charts.thresholdChart.dispose()
+            this.charts.thresholdChart = null
+          }
+          // 创建新图表实例
+          this.initCategoryChart('threshold')
+        } else if (tab.name === 'existence' && this.$refs.existenceChart) {
+          // 销毁旧图表实例
+          if (this.charts.existenceChart) {
+            this.charts.existenceChart.dispose()
+            this.charts.existenceChart = null
+          }
+          // 创建新图表实例
+          this.initCategoryChart('existence')
+        }
+      }, 100)
+    },
+
+    initCategoryChart(type) {
+      // 初始化单个分类图表
+      const chartRef = this.$refs[`${type}Chart`]
+      console.log('初始化图表类型:', type, '图表引用:', chartRef)
+      if (!chartRef) {
+        console.error('未找到图表引用,类型:', type)
+        return
+      }
+
+      // 确保容器有正确的尺寸
+      chartRef.style.width = '100%'
+      chartRef.style.height = '100%'
+      chartRef.style.minHeight = '200px'
+
+      try {
+        const chart = echarts.init(chartRef)
+        console.log('图表初始化成功:', type)
+        let option = {}
+
+        if (type === 'content') {
+          option = {
+            tooltip: {
+              trigger: 'item',
+              formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+              orient: 'vertical',
+              left: 10,
+              data: ['正常', '无数据', '未配置']
+            },
+            series: [
+              {
+                name: '内容类',
+                type: 'pie',
+                radius: ['40%', '70%'],
+                avoidLabelOverlap: false,
+                itemStyle: {
+                  borderRadius: 10,
+                  borderColor: '#fff',
+                  borderWidth: 2
+                },
+                label: {
+                  show: false,
+                  position: 'center'
+                },
+                emphasis: {
+                  label: {
+                    show: true,
+                    fontSize: '18',
+                    fontWeight: 'bold'
+                  }
+                },
+                labelLine: {
+                  show: false
+                },
+                data: [
+                  { value: 15, name: '正常', itemStyle: { color: '#91cc75' } },
+                  { value: 3, name: '无数据', itemStyle: { color: '#ee6666' } },
+                  { value: 2, name: '未配置', itemStyle: { color: '#d9d9d9' } }
+                ]
+              }
+            ]
+          }
+        } else if (type === 'threshold') {
+          option = {
+            tooltip: {
+              trigger: 'item',
+              formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+              orient: 'vertical',
+              left: 10,
+              data: ['正常', '无数据', '未配置']
+            },
+            series: [
+              {
+                name: '阈值类',
+                type: 'pie',
+                radius: ['40%', '70%'],
+                avoidLabelOverlap: false,
+                itemStyle: {
+                  borderRadius: 10,
+                  borderColor: '#fff',
+                  borderWidth: 2
+                },
+                label: {
+                  show: false,
+                  position: 'center'
+                },
+                emphasis: {
+                  label: {
+                    show: true,
+                    fontSize: '18',
+                    fontWeight: 'bold'
+                  }
+                },
+                labelLine: {
+                  show: false
+                },
+                data: [
+                  { value: 12, name: '正常', itemStyle: { color: '#91cc75' } },
+                  { value: 5, name: '无数据', itemStyle: { color: '#ee6666' } },
+                  { value: 3, name: '未配置', itemStyle: { color: '#d9d9d9' } }
+                ]
+              }
+            ]
+          }
+        } else if (type === 'existence') {
+          option = {
+            tooltip: {
+              trigger: 'item',
+              formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+              orient: 'vertical',
+              left: 10,
+              data: ['正常', '无数据', '未配置']
+            },
+            series: [
+              {
+                name: '存在性',
+                type: 'pie',
+                radius: ['40%', '70%'],
+                avoidLabelOverlap: false,
+                itemStyle: {
+                  borderRadius: 10,
+                  borderColor: '#fff',
+                  borderWidth: 2
+                },
+                label: {
+                  show: false,
+                  position: 'center'
+                },
+                emphasis: {
+                  label: {
+                    show: true,
+                    fontSize: '18',
+                    fontWeight: 'bold'
+                  }
+                },
+                labelLine: {
+                  show: false
+                },
+                data: [
+                  { value: 18, name: '正常', itemStyle: { color: '#91cc75' } },
+                  { value: 2, name: '无数据', itemStyle: { color: '#ee6666' } },
+                  { value: 0, name: '未配置', itemStyle: { color: '#d9d9d9' } }
+                ]
+              }
+            ]
+          }
+        }
+
+        if (option && chart) {
+          chart.setOption(option)
+          this.charts[`${type}Chart`] = chart
+          console.log('图表配置设置成功:', type)
+        }
+      } catch (error) {
+        console.error('初始化图表时出错:', type, error)
+      }
+    },
+
+    refreshData() {
+      // 模拟数据刷新
+      console.log('刷新数据...')
+      this.currentDate = new Date().toISOString().slice(0, 19).replace('T', ' ')
+
+      // 随机更新实时告警列表
+      this.getRealEvent()
+
+      // 重新初始化图表以确保正确显示
+      setTimeout(() => {
+        this.destroyCharts()
+        this.initCharts()
+      }, 100)
+    },
+
+    startAutoRefresh() {
+      this.stopAutoRefresh()
+      this.refreshInterval = setInterval(() => {
+        this.refreshData()
+      }, 5000)
+    },
+
+    stopAutoRefresh() {
+      if (this.refreshInterval) {
+        clearInterval(this.refreshInterval)
+        this.refreshInterval = null
+      }
+    },
+
+    resizeCharts() {
+      Object.values(this.charts).forEach(chart => {
+        chart.resize()
+      })
+    },
+
+    destroyCharts() {
+      Object.values(this.charts).forEach(chart => {
+        chart.dispose()
+      })
+    },
+
+    formatTime(date) {
+      const year = date.getFullYear()
+      const month = String(date.getMonth() + 1).padStart(2, '0')
+      const day = String(date.getDate()).padStart(2, '0')
+      const hours = String(date.getHours()).padStart(2, '0')
+      const minutes = String(date.getMinutes()).padStart(2, '0')
+      return `${year}-${month}-${day} ${hours}:${minutes}`
+    }
+  }
+}
+</script>
+
+<style scoped>
+.driving-container {
+  padding: 20px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+  height: 100vh;
+  box-sizing: border-box;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 15px 20px;
+  background-color: #ffffff;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.page-header h2 {
+  margin: 0;
+  color: #303133;
+}
+
+.header-right {
+  display: flex;
+  align-items: center;
+}
+
+.row {
+  display: flex;
+  gap: 20px;
+  margin-bottom: 20px;
+  flex: 1;
+  min-height: 0;
+}
+
+.row-1 {
+  flex: 1;
+  min-height: 300px;
+}
+
+.row-2 {
+  flex: 1;
+  min-height: 350px;
+}
+
+.col {
+  background-color: #ffffff;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+}
+
+.col-1,
+.col-2,
+.col-3,
+.col-4,
+.col-5 {
+  flex: 1;
+}
+
+.card {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+}
+
+.card-header {
+  padding: 15px 20px;
+  border-bottom: 1px solid #ebeef5;
+  background-color: #fafafa;
+  flex-shrink: 0;
+}
+
+.card-header h3 {
+  margin: 0;
+  font-size: 16px;
+  color: #303133;
+}
+
+.card-body {
+  flex: 1;
+  padding: 20px;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.el-tabs {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.el-tabs__header {
+  flex-shrink: 0;
+}
+
+.el-tabs__content {
+  flex: 1;
+  overflow: hidden;
+  display: flex;
+}
+
+.el-tab-pane {
+  flex: 1;
+  display: flex;
+}
+
+.chart-wrapper {
+  flex: 1;
+  min-height: 200px;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.chart-wrapper>div {
+  width: 100%;
+  height: 100%;
+  min-height: 200px;
+}
+
+.alarm-list {
+  flex: 1;
+  overflow: hidden;
+  position: relative;
+}
+
+.alarm-items-container {
+  height: 100%;
+  animation: scroll 20s linear infinite;
+}
+
+.alarm-item {
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.alarm-indicator {
+  flex: 1;
+  font-weight: 500;
+}
+
+.alarm-time {
+  width: 180px;
+  font-size: 12px;
+  color: #909399;
+}
+
+.alarm-status {
+  width: 80px;
+  text-align: right;
+}
+
+.total-alarms {
+  text-align: center;
+  margin-bottom: 20px;
+}
+
+.total-label {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 5px;
+}
+
+.total-count {
+  font-size: 36px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.legend {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  margin-top: 10px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+}
+
+.legend-color {
+  width: 12px;
+  height: 12px;
+  border-radius: 2px;
+  margin-right: 5px;
+}
+
+@keyframes scroll {
+  0% {
+    transform: translateY(0);
+  }
+
+  100% {
+    transform: translateY(-100%);
+  }
+}
+
+/* 响应式设计 */
+@media screen and (max-width: 1200px) {
+  .driving-container {
+    height: auto;
+    min-height: 100vh;
+    overflow: auto;
+  }
+
+  .row {
+    flex-direction: column;
+    height: auto;
+    min-height: 400px;
+  }
+
+  .row-1,
+  .row-2 {
+    min-height: 400px;
+  }
+
+  .col {
+    min-height: 400px;
+    margin-bottom: 20px;
+  }
+
+  .chart-wrapper {
+    min-height: 300px;
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 10px;
+  }
+
+  .header-right {
+    width: 100%;
+    justify-content: space-between;
+  }
+
+  .driving-container {
+    padding: 10px;
+  }
+
+  .card-body {
+    padding: 15px;
+  }
+
+  .chart-wrapper {
+    min-height: 250px;
+  }
+}
+</style>

+ 117 - 0
src/views/inspection/index.vue

@@ -0,0 +1,117 @@
+<template>
+  <div class="inspection-container">
+    <h2>机房巡检管理</h2>
+    
+    <!-- 顶部操作区 -->
+    <div class="top-operation">
+      <el-button type="primary" @click="startInspection">开始巡检</el-button>
+      <el-button type="success" @click="goToTaskManagement">巡检任务管理</el-button>
+    </div>
+    
+    <!-- 中间巡检记录列表 -->
+    <div class="inspection-records">
+      <el-table :data="inspectionRecords" style="width: 100%">
+        <el-table-column prop="taskName" label="任务名" width="180"></el-table-column>
+        <el-table-column prop="taskDescription" label="任务说明" ></el-table-column>
+        <el-table-column prop="inspectionResult" label="巡检结果" width="100">
+          <template slot-scope="scope">
+            <el-tag :type="scope.row.inspectionResult === '正常' ? 'success' : 'danger'">
+              {{ scope.row.inspectionResult }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="resultDescription" label="结果说明" width="200"></el-table-column>
+        <el-table-column prop="inspector" label="巡检人员" width="120"></el-table-column>
+        <el-table-column prop="inspectionTime" label="巡检时间" width="180"></el-table-column>
+        <el-table-column prop="workPhoto" label="工作照" width="120">
+          <template slot-scope="scope">
+            <el-image
+              :src="scope.row.workPhoto"
+              fit="cover"
+              style="width: 80px; height: 60px; cursor: pointer"
+              @click="previewImage(scope.row.workPhoto)"
+            >
+              <div slot="error" class="image-slot">
+                <i class="el-icon-picture-outline"></i>
+              </div>
+            </el-image>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    
+    <!-- 图片预览对话框 -->
+    <el-dialog
+      :visible.sync="dialogVisible"
+      title="工作照预览"
+      width="800px"
+    >
+      <img :src="dialogImageUrl" style="width: 100%" />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getTaskList } from '@/api/newApi/inspection'
+export default {
+  data() {
+    return {
+      inspectionRecords: [],
+      dialogVisible: false,
+      dialogImageUrl: ''
+    }
+  },
+  created() {
+    this.getInspectionRecords()
+  },
+  methods: {
+    // 获取任务列表
+    getInspectionRecords() {
+      getTaskList().then(res => {
+        if (res.code === 200) {
+          this.inspectionRecords = res.data
+        }
+      })
+    },
+    startInspection() {
+      this.$router.push('/inspection/input')
+    },
+    goToTaskManagement() {
+      this.$router.push('/inspection/task-management')
+    },
+    previewImage(url) {
+      this.dialogImageUrl = url
+      this.dialogVisible = true
+    }
+  }
+}
+</script>
+
+<style scoped>
+.inspection-container {
+  padding: 20px;
+}
+
+.top-operation {
+  margin-bottom: 20px;
+}
+
+.top-operation .el-button {
+  margin-right: 10px;
+}
+
+.inspection-records {
+  margin-top: 20px;
+}
+
+.image-slot {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 80px;
+  height: 60px;
+  background-color: #f5f7fa;
+  color: #909399;
+  font-size: 20px;
+}
+</style>

+ 369 - 0
src/views/inspection/input.vue

@@ -0,0 +1,369 @@
+<template>
+  <div class="inspection-input-container">
+    <h2>巡检录入</h2>
+    
+    <!-- 巡检任务信息 -->
+    <div class="inspection-info">
+      <div class="info-header">
+        <h3>{{ taskName }}</h3>
+      </div>
+      <div class="info-details">
+        <div class="info-item">
+          <span class="label">巡检人:</span>
+          <span>{{ inspector }}</span>
+        </div>
+        <div class="info-item">
+          <span class="label">工号:</span>
+          <span>{{ inspectorId }}</span>
+        </div>
+        <div class="info-item">
+          <span class="label">开始时间:</span>
+          <span>{{ inspectionStartTime }}</span>
+        </div>
+        <div class="info-item">
+          <span class="label">当前时间:</span>
+          <span>{{ currentTime }}</span>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 指标分类及巡检点 -->
+    <div class="indicator-categories">
+      <div v-for="(category, categoryIndex) in categories" :key="categoryIndex" class="category-section">
+        <h4>{{ category.name }}</h4>
+        <div v-for="indicator in category.indicators" :key="indicator.name" class="indicator-item">
+          <div class="indicator-name">{{ indicator.name }}</div>
+          <div class="indicator-result">
+            <el-radio-group v-model="indicator.result">
+              <el-radio label="正常">正常</el-radio>
+              <el-radio label="异常">异常</el-radio>
+            </el-radio-group>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 结果说明 -->
+    <div class="result-description">
+      <h4>结果说明</h4>
+      <el-input
+        v-model="resultDescription"
+        type="textarea"
+        placeholder="如有异常项,请填写详细说明"
+        rows="4"
+        :required="hasAbnormalResults"
+      ></el-input>
+      <div v-if="hasAbnormalResults && !resultDescription" class="error-tip">
+        存在异常项,结果说明为必填项
+      </div>
+    </div>
+    
+    <!-- 上传工作照 -->
+    <div class="work-photo-upload">
+      <h4>上传工作照</h4>
+      <div class="upload-section">
+        <el-upload
+          class="upload-demo"
+          action="#"
+          :auto-upload="false"
+          :on-change="handlePhotoUpload"
+          :show-file-list="false"
+        >
+          <el-image
+            v-if="workPhoto"
+            :src="workPhoto"
+            fit="cover"
+            style="width: 200px; height: 150px; cursor: pointer"
+          ></el-image>
+          <el-button v-else size="large" type="primary">点击上传工作照</el-button>
+        </el-upload>
+        
+        <div class="photo-validation" v-if="workPhoto">
+          <div class="validation-item" :class="{ 'valid': isPhotoTimeValid }">
+            <i class="el-icon-check" v-if="isPhotoTimeValid"></i>
+            <i class="el-icon-close" v-else></i>
+            工作照拍摄时间符合要求
+          </div>
+          <div class="validation-item" :class="{ 'valid': isPhotoFeatureValid }">
+            <i class="el-icon-check" v-if="isPhotoFeatureValid"></i>
+            <i class="el-icon-close" v-else></i>
+            工作照拍摄环境特征符合要求
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 提交按钮 -->
+    <div class="submit-section">
+      <el-button type="primary" size="large" @click="submitInspection" style="width: 200px;">提交</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      taskName: '机房巡检A区',
+      inspector: '小A',
+      inspectorId: 'jc_xiaoA',
+      inspectionStartTime: '2026年1月13日 星期二 16:30',
+      currentTime: '',
+      categories: [
+        {
+          name: '环境指标监控',
+          indicators: [
+            { name: '温度检查', result: '' },
+            { name: '湿度检查', result: '' }
+          ]
+        },
+        {
+          name: '电力系统',
+          indicators: [
+            { name: '电压检查', result: '' },
+            { name: '电流检查', result: '' }
+          ]
+        },
+        {
+          name: '洁净度',
+          indicators: [
+            { name: '机房清洁度', result: '' }
+          ]
+        }
+      ],
+      resultDescription: '',
+      workPhoto: '',
+      isPhotoTimeValid: false,
+      isPhotoFeatureValid: false
+    }
+  },
+  computed: {
+    hasAbnormalResults() {
+      return this.categories.some(category => 
+        category.indicators.some(indicator => indicator.result === '异常')
+      )
+    }
+  },
+  mounted() {
+    this.updateCurrentTime()
+    setInterval(this.updateCurrentTime, 1000)
+  },
+  methods: {
+    updateCurrentTime() {
+      const now = new Date()
+      const options = {
+        year: 'numeric',
+        month: 'long',
+        day: 'numeric',
+        weekday: 'long',
+        hour: '2-digit',
+        minute: '2-digit',
+        second: '2-digit'
+      }
+      this.currentTime = now.toLocaleString('zh-CN', options)
+    },
+    handlePhotoUpload(file) {
+      this.workPhoto = URL.createObjectURL(file.raw);
+      
+      //一定是对的模拟
+      this.isPhotoTimeValid = true
+      this.isPhotoFeatureValid = true
+    },
+    submitInspection() {
+      // 验证所有巡检点都已填写结果
+      console.log('Categories data:', this.categories)
+      
+      // 详细检查每个指标的结果
+      let hasEmptyResult = false
+      this.categories.forEach((category, categoryIndex) => {
+        category.indicators.forEach((indicator, indicatorIndex) => {
+          console.log(`Category ${categoryIndex}, Indicator ${indicatorIndex}:`, indicator.name, 'Result:', indicator.result)
+          if (!indicator.result) {
+            hasEmptyResult = true
+            console.log( indicator.name)
+          }
+        })
+      })
+      
+      console.log(hasEmptyResult)
+      
+      if (hasEmptyResult) {
+        this.$message({ type: 'error', message: '请完成所有巡检点的结果填写' })
+        return
+      }
+      
+      // 验证异常结果必须填写说明
+      if (this.hasAbnormalResults && !this.resultDescription) {
+        this.$message({ type: 'error', message: '存在异常项,结果说明为必填项' })
+        return
+      }
+      
+      // 验证工作照上传
+      if (!this.workPhoto) {
+        this.$message({ type: 'error', message: '请上传工作照' })
+        return
+      }
+      
+      // 验证工作照时间和特征
+      if (!this.isPhotoTimeValid) {
+        this.$message({ type: 'error', message: '工作照拍摄时间不符合要求' })
+        return
+      }
+      
+      if (!this.isPhotoFeatureValid) {
+        this.$message({ type: 'error', message: '工作照拍摄环境特征不符合要求' })
+        return
+      }
+      
+      // 模拟提交操作
+      this.$confirm('确定要提交巡检结果吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'info'
+      }).then(() => {
+        // 模拟生成巡检记录
+        this.$message({ type: 'success', message: '巡检提交成功,系统已生成巡检记录' })
+        
+        // 跳转回巡检管理页面
+        setTimeout(() => {
+          this.$router.push('/inspection/task-management')
+        }, 1500)
+      }).catch(() => {
+        this.$message({ type: 'info', message: '取消提交' })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.inspection-input-container {
+  padding: 20px;
+}
+
+.inspection-info {
+  background-color: #f5f7fa;
+  padding: 20px;
+  border-radius: 4px;
+  margin-bottom: 30px;
+}
+
+.info-header {
+  margin-bottom: 15px;
+}
+
+.info-header h3 {
+  margin: 0;
+  color: #303133;
+}
+
+.info-details {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+}
+
+.label {
+  font-weight: bold;
+  margin-right: 5px;
+  color: #606266;
+}
+
+.indicator-categories {
+  margin-bottom: 30px;
+}
+
+.category-section {
+  margin-bottom: 30px;
+}
+
+.category-section h4 {
+  margin-bottom: 15px;
+  color: #303133;
+}
+
+.indicator-item {
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  margin-bottom: 10px;
+  background-color: #ffffff;
+}
+
+.indicator-name {
+  width: 180px;
+  font-weight: 500;
+}
+
+.indicator-result {
+  flex: 1;
+}
+
+.inspection-result {
+  display: flex;
+  gap: 20px;
+}
+
+.result-description {
+  margin-bottom: 30px;
+}
+
+.result-description h4 {
+  margin-bottom: 10px;
+  color: #303133;
+}
+
+.error-tip {
+  color: #f56c6c;
+  font-size: 12px;
+  margin-top: 5px;
+}
+
+.work-photo-upload {
+  margin-bottom: 30px;
+}
+
+.work-photo-upload h4 {
+  margin-bottom: 15px;
+  color: #303133;
+}
+
+.upload-section {
+  display: flex;
+  align-items: flex-start;
+  gap: 30px;
+}
+
+.photo-validation {
+  margin-top: 20px;
+}
+
+.validation-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  color: #f56c6c;
+}
+
+.validation-item.valid {
+  color: #67c23a;
+}
+
+.validation-item i {
+  margin-right: 8px;
+  font-size: 16px;
+}
+
+.submit-section {
+  display: flex;
+  justify-content: center;
+  margin-top: 40px;
+}
+</style>

+ 448 - 0
src/views/inspection/task-management.vue

@@ -0,0 +1,448 @@
+<template>
+  <div class="task-management-container">
+    <h2>巡检任务管理</h2>
+
+    <!-- 顶部操作区 -->
+    <div class="top-operation">
+      <el-button type="danger" @click="deleteTasks" :disabled="selectedTasks.length === 0">删除</el-button>
+      <el-button type="primary" @click="openTaskDrawer">新建巡检任务</el-button>
+      <el-button @click="goBack">返回上一级</el-button>
+    </div>
+
+    <!-- 中间任务列表 -->
+    <div class="task-list">
+      <el-table :data="tasks" style="width: 100%" v-loading=loading @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column prop="taskName" label="任务名" width="180"></el-table-column>
+        <el-table-column prop="taskDescription" label="任务说明"></el-table-column>
+        <el-table-column prop="baselineImage" label="巡检基准图" width="120">
+          <template slot-scope="scope">
+            <el-image :src="scope.row.baselineImage" fit="cover" style="width: 80px; height: 60px; cursor: pointer"
+              @click="previewImage(scope.row.baselineImage)">
+              <div slot="error" class="image-slot">
+                <i class="el-icon-picture-outline"></i>
+              </div>
+            </el-image>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="180">
+          <template slot-scope="scope">
+            <el-button size="small" @click="viewTask(scope.row)">查看</el-button>
+            <el-button size="small" type="primary" @click="editTask(scope.row)">编辑</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 任务配置抽屉 -->
+    <el-drawer title="巡检任务配置" :visible.sync="drawerVisible" size="80%" direction="rtl">
+      <div class="task-config">
+        <el-button v-if="!isViewMode" type="info" @click="resetForm"
+          style="position: absolute; top: 20px; right: 20px">重置</el-button>
+
+        <!-- 任务基本信息配置区域 -->
+        <el-form :model="taskForm" label-width="120px">
+          <el-form-item label="任务名称" required>
+            <el-input v-model="taskForm.taskName" placeholder="例如:2026黑龙江机房A区巡检任务" :disabled="isViewMode"></el-input>
+          </el-form-item>
+          <el-form-item label="任务完成基准图">
+            <template v-if="isViewMode">
+              <template v-if="taskForm.baselineImage">
+                <el-image :src="taskForm.baselineImage" fit="cover" style="width: 120px; height: 80px; cursor: pointer"
+                  @click="previewImage(taskForm.baselineImage)"></el-image>
+              </template>
+              <template v-else>
+                <div class="image-slot">
+                  <i class="el-icon-picture-outline"></i>
+                </div>
+              </template>
+            </template>
+            <el-upload v-else class="avatar-uploader" :action="uploadUrl" :show-file-list="false"
+              :on-success="handleAvatarSuccess">
+              <img style="width: 55px; height: 55px; " v-if="taskForm.baselineImage" :src="taskForm.baselineImage"
+                class="avatar">
+              <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+            </el-upload>
+            <!-- <div class="upload-tip">上传附件3(巡检对照基准图),并与当前任务进行关联</div> -->
+          </el-form-item>
+          <el-form-item label="任务描述">
+            <el-input v-model="taskForm.taskDescription" type="textarea" placeholder="简要描述任务的说明和特点" rows="3"
+              :disabled="isViewMode"></el-input>
+          </el-form-item>
+        </el-form>
+
+        <!-- 巡检项配置区域 -->
+        <div class="indicator-config">
+          <h3 style="margin-top: 30px; margin-bottom: 20px;">
+            <span style="color: red">*</span>指标分类配置
+            <el-button v-if="!isViewMode" type="primary" size="small" @click="openAddCategoryDialog"
+              style="margin-left: 20px">添加分类</el-button>
+          </h3>
+
+          <div v-for="(category, categoryIndex) in taskForm.categories" :key="categoryIndex" class="category-item">
+            <div class="category-header">
+              <p >{{ category.categoryName }}</p>
+              <el-button size="small" @click="openAddItemDialog(categoryIndex)" :disabled="isViewMode">+</el-button>
+              <el-button size="small" type="danger" @click="removeCategory(categoryIndex)" :disabled="isViewMode">删除</el-button>
+            </div>
+
+            <div v-if="category.expanded" class="category-content">
+              <div v-for="(item, itemIndex) in category.items" :key="itemIndex" class="item-row">
+                <p style="margin-left: 20px;">{{ item.itemName }}</p>
+                <el-button size="mini" type="danger" @click="removeItem(categoryIndex, itemIndex)" :disabled="isViewMode" style="margin-left: 10px">删除</el-button>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 底部按钮区 -->
+        <div class="bottom-buttons" style="margin-top: 40px; text-align: right;">
+          <el-button @click="drawerVisible = false">取消</el-button>
+          <el-button v-if="!isViewMode" type="primary" @click="saveTask">保存配置</el-button>
+        </div>
+      </div>
+    </el-drawer>
+
+    <!-- 图片预览对话框 -->
+    <el-dialog :visible.sync="dialogVisible" title="图片预览" width="600px">
+      <img :src="dialogImageUrl" style="width: 100%" />
+    </el-dialog>
+
+    <!-- 添加分类对话框 -->
+    <el-dialog :visible.sync="categoryDialogVisible" title="添加分类" width="400px">
+      <el-form :model="categoryForm" label-width="100px">
+        <el-form-item label="指标分类" required>
+          <el-input v-model="categoryForm.categoryName" placeholder="请输入分类名称"></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="categoryDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="saveCategory">保存</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 添加巡检项对话框 -->
+    <el-dialog :visible.sync="itemDialogVisible" title="添加巡检项" width="400px">
+      <el-form :model="itemForm" label-width="100px">
+        <el-form-item label="巡检项名称" required>
+          <el-input v-model="itemForm.itemName" placeholder="请输入巡检项名称"></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="itemDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="saveItem">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getTaskList, addTask, deleteTask, updateTask } from '@/api/newApi/inspection'
+export default {
+  data() {
+    return {
+      loading: false,
+      uploadUrl: process.env.VUE_APP_BASE_API + '/inspection/saveBenchmarkPhoto',
+      tasks: [],
+      selectedTasks: [],
+      drawerVisible: false,
+      dialogVisible: false,
+      dialogImageUrl: '',
+      isViewMode: false,
+      taskForm: {
+        taskName: '',
+        taskDescription: '',
+        status: 1,
+        categories: []
+      },
+      categoryDialogVisible: false,
+      itemDialogVisible: false,
+      currentCategoryIndex: -1,
+      categoryForm: {
+        categoryName: ''
+      },
+      itemForm: {
+        itemName: ''
+      }
+    }
+  },
+  created() {
+    this.getTasks()
+  },
+  methods: {
+    // 获取任务列表
+    getTasks() {
+      this.loading = true
+      getTaskList().then(res => {
+        if (res.success) {
+          this.loading = false
+          this.tasks = res.data
+        }
+      })
+    },
+    handleSelectionChange(val) {
+      this.selectedTasks = val
+    },
+    deleteTasks() {
+      if (this.selectedTasks.length === 0) return
+
+      this.$confirm('确定要删除选中的任务吗?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        // 模拟删除操作
+        this.selectedTasks.forEach(task => {
+          console.log(task, 'task')
+          deleteTask(task.id)
+        })
+        this.loading = true
+        this.$message({ type: 'success', message: '删除成功' })
+        setTimeout(() => {
+          this.getTasks()
+          this.loading = false
+        }, 2000);
+      }).catch(() => {
+        this.$message({ type: 'info', message: '取消删除' })
+      })
+    },
+    openTaskDrawer() {
+      this.resetForm()
+      this.isViewMode = false
+      this.drawerVisible = true
+    },
+    editTask(task) {
+      this.isViewMode = false
+      // 创建深拷贝,避免修改原数据
+      this.taskForm = JSON.parse(JSON.stringify(task))
+      // 确保categories属性存在
+      if (!this.taskForm.categories) {
+        this.taskForm.categories = []
+      }
+      // 确保每个分类都有expanded属性
+      this.taskForm.categories.forEach(category => {
+        if (category.expanded === undefined) {
+          category.expanded = true
+        }
+      })
+      // 确保status属性存在
+      if (this.taskForm.status === undefined) {
+        this.taskForm.status = 1
+      }
+      this.drawerVisible = true
+    },
+    viewTask(task) {
+      console.log(task, 'task')
+      this.isViewMode = true
+      // 创建深拷贝,避免修改原数据
+      this.taskForm = JSON.parse(JSON.stringify(task))
+      // 确保categories属性存在
+      if (!this.taskForm.categories) {
+        this.taskForm.categories = []
+      }
+      // 确保每个分类都有expanded属性
+      this.taskForm.categories.forEach(category => {
+        if (category.expanded === undefined) {
+          category.expanded = true
+        }
+      })
+      // 确保status属性存在
+      if (this.taskForm.status === undefined) {
+        this.taskForm.status = 1
+      }
+      this.drawerVisible = true
+    },
+    goBack() {
+      this.$router.push('/inspection/index')
+    },
+    resetForm() {
+      this.taskForm = {
+        taskName: '',
+        taskDescription: '',
+        status: 1,
+        categories: []
+      }
+    },
+    openAddCategoryDialog() {
+      if (this.isViewMode) return
+      this.categoryForm = {
+        categoryName: ''
+      }
+      this.categoryDialogVisible = true
+    },
+    saveCategory() {
+      if (this.isViewMode) return
+      if (!this.categoryForm.categoryName) {
+        this.$message({ type: 'error', message: '请输入分类名称' })
+        return
+      }
+      
+      this.taskForm.categories.push({
+        categoryName: this.categoryForm.categoryName,
+        expanded: true,
+        items: []
+      })
+      
+      this.categoryDialogVisible = false
+    },
+    openAddItemDialog(categoryIndex) {
+      if (this.isViewMode) return
+      this.currentCategoryIndex = categoryIndex
+      this.itemForm = {
+        itemName: ''
+      }
+      this.itemDialogVisible = true
+    },
+    saveItem() {
+      if (this.isViewMode) return
+      if (!this.itemForm.itemName) {
+        this.$message({ type: 'error', message: '请输入巡检项名称' })
+        return
+      }
+      
+      this.taskForm.categories[this.currentCategoryIndex].items.push({
+        itemName: this.itemForm.itemName,
+        checked: true
+      })
+      
+      this.itemDialogVisible = false
+    },
+    removeCategory(categoryIndex) {
+      if (this.isViewMode) return
+      this.taskForm.categories.splice(categoryIndex, 1)
+    },
+    removeItem(categoryIndex, itemIndex) {
+      if (this.isViewMode) return
+      this.taskForm.categories[categoryIndex].items.splice(itemIndex, 1)
+    },
+    handleAvatarSuccess(res, file) {
+      console.log(res)
+      this.taskForm.baselineImage = "http://10.130.22.73:1090/stage-api" + res.data
+      this.$forceUpdate()
+      console.log(this.taskForm.baselineImage)
+    },
+    saveTask() {
+      // 验证表单
+      if (!this.taskForm.taskName) {
+        this.$message({ type: 'error', message: '请填写任务名称' })
+        return
+      }
+      
+      // 确保每个分类都有名称,且每个分类下至少有一个巡检项
+      for (let i = 0; i < this.taskForm.categories.length; i++) {
+        const category = this.taskForm.categories[i]
+        if (!category.categoryName) {
+          this.$message({ type: 'error', message: `请填写第${i + 1}个分类的名称` })
+          return
+        }
+        if (category.items.length === 0) {
+          this.$message({ type: 'error', message: `请为第${i + 1}个分类添加至少一个巡检项` })
+          return
+        }
+        // 确保每个巡检项都有名称
+        for (let j = 0; j < category.items.length; j++) {
+          const item = category.items[j]
+          if (!item.itemName) {
+            this.$message({ type: 'error', message: `请填写第${i + 1}个分类下第${j + 1}个巡检项的名称` })
+            return
+          }
+        }
+      }
+      
+      // 调用API保存任务
+      const savePromise = this.taskForm.id ? updateTask(this.taskForm) : addTask(this.taskForm)
+      savePromise.then(res => {
+        if (res.success || res.code === 200) {
+          this.$message({ type: 'success', message: this.taskForm.id ? '更新成功' : '保存成功' })
+          this.getTasks()
+          this.drawerVisible = false
+        } else {
+          this.$message({ type: 'error', message: (this.taskForm.id ? '更新' : '保存') + '失败:' + (res.message || '未知错误') })
+        }
+      }).catch(err => {
+        this.$message({ type: 'error', message: (this.taskForm.id ? '更新' : '保存') + '失败:网络错误' })
+      })
+    },
+    previewImage(url) {
+      this.dialogImageUrl = url
+      this.dialogVisible = true
+    }
+  }
+}
+</script>
+
+<style scoped>
+.task-management-container {
+  padding: 20px;
+}
+
+.top-operation {
+  margin-bottom: 20px;
+}
+
+.top-operation .el-button {
+  margin-right: 10px;
+}
+
+.task-list {
+  margin-top: 20px;
+}
+
+.image-slot {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 80px;
+  height: 60px;
+  background-color: #f5f7fa;
+  color: #909399;
+  font-size: 20px;
+}
+
+.task-config {
+  padding: 20px;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 5px;
+}
+
+.category-item {
+  margin-bottom: 20px;
+  padding: 15px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+}
+
+.category-header {
+  margin-bottom: 10px;
+  display: flex;
+  align-items: center;
+}
+
+.category-header .el-checkbox {
+  flex: 1;
+  margin-right: 10px;
+}
+
+.category-content {
+  margin-top: 10px;
+  margin-left: 10px;
+}
+
+.item-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.item-row .el-checkbox {
+  flex: 1;
+}
+
+.item-row .el-button {
+  margin-left: 10px;
+}
+</style>

+ 27 - 36
src/views/login.vue

@@ -1,51 +1,31 @@
 <template>
   <div class="login">
     <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
-      <h3 class="title">{{title}}</h3>
+      <h3 class="title">{{ title }}</h3>
       <el-form-item prop="username">
-        <el-input
-          v-model="loginForm.username"
-          type="text"
-          auto-complete="off"
-          placeholder="账号"
-        >
+        <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
           <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
         </el-input>
       </el-form-item>
       <el-form-item prop="password">
-        <el-input
-          v-model="loginForm.password"
-          type="password"
-          auto-complete="off"
-          placeholder="密码"
-          @keyup.enter.native="handleLogin"
-        >
+        <el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码"
+          @keyup.enter.native="handleLogin">
           <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
         </el-input>
       </el-form-item>
       <el-form-item prop="code" v-if="captchaEnabled">
-        <el-input
-          v-model="loginForm.code"
-          auto-complete="off"
-          placeholder="验证码"
-          style="width: 63%"
-          @keyup.enter.native="handleLogin"
-        >
+        <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%"
+          @keyup.enter.native="handleLogin">
           <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
         </el-input>
         <div class="login-code">
-          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
+          <img :src="codeUrl" @click="getCode" class="login-code-img" />
         </div>
       </el-form-item>
       <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
       <el-form-item style="width:100%;">
-        <el-button
-          :loading="loading"
-          size="medium"
-          type="primary"
-          style="width:100%;"
-          @click.native.prevent="handleLogin"
-        >
+        <el-button :loading="loading" size="medium" type="primary" style="width:100%;"
+          @click.native.prevent="handleLogin">
           <span v-if="!loading">登 录</span>
           <span v-else>登 录 中...</span>
         </el-button>
@@ -65,6 +45,7 @@
 import { getCodeImg } from "@/api/login"
 import Cookies from "js-cookie"
 import { encrypt, decrypt } from '@/utils/jsencrypt'
+import { blobToBase64 } from '@/utils/index'
 import defaultSettings from '@/settings'
 
 export default {
@@ -76,7 +57,7 @@ export default {
       codeUrl: "",
       loginForm: {
         username: "admin",
-        password: "admin123",
+        password: "Welcome1#",
         rememberMe: false,
         code: "",
         uuid: ""
@@ -100,7 +81,7 @@ export default {
   },
   watch: {
     $route: {
-      handler: function(route) {
+      handler: function (route) {
         this.redirect = route.query && route.query.redirect
       },
       immediate: true
@@ -111,13 +92,14 @@ export default {
     this.getCookie()
   },
   methods: {
+    // blob转换base64格式
+
     getCode() {
       getCodeImg().then(res => {
-        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
-        if (this.captchaEnabled) {
-          this.codeUrl = "data:image/gif;base64," + res.img
+        blobToBase64(res).then(base64 => {
+          this.codeUrl = base64
           this.loginForm.uuid = res.uuid
-        }
+        })
       })
     },
     getCookie() {
@@ -144,7 +126,7 @@ export default {
             Cookies.remove('rememberMe')
           }
           this.$store.dispatch("Login", this.loginForm).then(() => {
-            this.$router.push({ path: this.redirect || "/" }).catch(()=>{})
+            this.$router.push({ path: this.redirect || "/" }).catch(() => { })
           }).catch(() => {
             this.loading = false
             if (this.captchaEnabled) {
@@ -167,6 +149,7 @@ export default {
   background-image: url("../assets/images/login-background.jpg");
   background-size: cover;
 }
+
 .title {
   margin: 0px auto 30px auto;
   text-align: center;
@@ -179,32 +162,39 @@ export default {
   width: 400px;
   padding: 25px 25px 5px 25px;
   z-index: 1;
+
   .el-input {
     height: 38px;
+
     input {
       height: 38px;
     }
   }
+
   .input-icon {
     height: 39px;
     width: 14px;
     margin-left: 2px;
   }
 }
+
 .login-tip {
   font-size: 13px;
   text-align: center;
   color: #bfbfbf;
 }
+
 .login-code {
   width: 33%;
   height: 38px;
   float: right;
+
   img {
     cursor: pointer;
     vertical-align: middle;
   }
 }
+
 .el-login-footer {
   height: 40px;
   line-height: 40px;
@@ -217,6 +207,7 @@ export default {
   font-size: 12px;
   letter-spacing: 1px;
 }
+
 .login-code-img {
   height: 38px;
 }