2025/4/18团队开发进度报告

完成主页面的基本框架以及用户分角色登录:


lender.vue:


<template>

  <div>
    <!-- 头部开始 -->
    <div style="height: 60px; background-color: #8686e0; display: flex; align-items: center">
      <div style="width: 200px; display: flex; align-items: center; padding-left: 15px">
        <img width="40px" src="@/assets/logo.png">
        <span style=" font-size: 24px; margin-left: 5px ;color: white">这是系统</span>
      </div>

      <div style="flex: 1"></div>
      <div style="width: fit-content; display: flex; align-items: center; padding-right: 20px">
        <!-- 使用el-dropdown替换原来的头像和用户名显示 -->
        <el-dropdown @command="handleCommand" trigger="click">
          <div style="display: flex; align-items: center; cursor: pointer">
            <img src="../assets/user.png" alt="" style="width: 40px; height: 40px; border-radius: 50%;">
            <span style="color: white; margin-left: 5px">{{ userInfo.username || '用户名' }}</span>
          </div>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item command="logout">
                <div style="display: flex; align-items: center;">
                  <img src="@/assets/logout.png" class="dropdown-icon" alt="" />
                  退出登录
                </div>
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
    </div>
    <!-- 头部结束 -->


    <!-- 下面部分开始 -->
    <div style="display: flex; flex-direction: column">
      <!--   顶部导航   -->
      <div>
        <el-menu router :default-active="router.currentRoute.value.path" class="el-menu-demo" mode="horizontal">
          <!-- 动态生成菜单项,使用自定义图标 -->
          <el-menu-item v-for="item in menuItems" :key="item.index" :index="item.index">
            <img v-if="item.icon" :src="getIconPath(item.icon)" class="menu-icon" alt="" />
            {{ item.title }}
          </el-menu-item>
        </el-menu>
      </div>

      <!-- 主体区域开始 -->
      <div style="display: flex; justify-content: center; background-color: #f8f8ff; min-height: calc(100vh - 110px)">
        <div style="width: 80%; max-width: 1200px; padding: 20px; background-color: white; margin: 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1)">
          <RouterView />
        </div>
      </div>
      <!-- 主体区域结束 -->

    </div>
    <!-- 下面部分结束 -->

  </div>

</template>


<script setup>
import router from "@/router/index.js";
import { ref, onMounted, computed } from 'vue';

// 获取用户信息
const userInfo = ref({});
const userRole = computed(() => userInfo.value.role || '');

// 获取图标路径的函数
const getIconPath = (iconName) => {
  return `/src/assets/${iconName}.png`;
};

// 根据用户角色计算菜单项,图标名称改为对应的图片文件名
const menuItems = computed(() => {
  const baseMenus = [
    { index: '/lender/home', title: '首页', icon: 'home' }
  ];

  if (userRole.value === 'engineer') {
    return [...baseMenus, 
      { index: '/lender/work', title: '工作页', icon: 'work' }
    ];
  }

  if (userRole.value === 'admin' ) {
    return [...baseMenus,
      { index: '/lender/inspect', title: '巡检', icon: 'inspect' },
      { index: '/lender/maintenance', title: '保养', icon: 'maintenance' },
      { index: '/lender/test', title: '检测', icon: 'test' },
      { index: '/lender/device', title: '设备管理', icon: 'device' },
      { index: '/lender/approve', title: '审批', icon: 'approve' },
    ];
  }

  if ( userRole.value === 'super_admin') {
    return [...baseMenus,
      { index: '/lender/work', title: '工作页', icon: 'work' },
      { index: '/lender/inspect', title: '巡检', icon: 'inspect' },
      { index: '/lender/maintenance', title: '保养', icon: 'maintenance' },
      { index: '/lender/test', title: '检测', icon: 'test' },
      { index: '/lender/device', title: '设备管理', icon: 'device' },
      { index: '/lender/approve', title: '审批', icon: 'approve' },
    ];
  }

  return baseMenus;
});

// 处理下拉菜单命令
const handleCommand = (command) => {
  if (command === 'logout') {
    // 保存当前账号信息到localStorage
    const currentAccount = userInfo.value.id || userInfo.value.username || userInfo.value.phone || userInfo.value.email;
    localStorage.setItem('lastLoginAccount', currentAccount);
    
    // 清除用户信息
    localStorage.removeItem('userInfo');
    
    // 跳转到登录页
    router.push('/login');
  }
};

onMounted(() => {
  // 从localStorage获取用户信息
  const storedUserInfo = localStorage.getItem('userInfo');
  if (storedUserInfo) {
    userInfo.value = JSON.parse(storedUserInfo);
  }
});
</script>

<style scoped>
.el-menu .is-active{
  background-color: #e6ecf7;
}

/* 添加菜单图标样式 */
.menu-icon {
  width: 20px;
  height: 20px;
  margin-right: 5px;
  vertical-align: middle;
}

/* 添加下拉菜单图标样式 */
.dropdown-icon {
  width: 16px;
  height: 16px;
  margin-right: 8px;
  vertical-align: middle;
}
</style>

login.vue

<!-- vivy -->
<!-- 05/4/14 -->

<template>
  <div class="login-container">
    <div class="login-box">
      <el-card class="login-card">
        <div class="logo-container">
          <img src="@/assets/logo.png" alt="Logo" class="logo-image">
          <h2 class="login-title">系统登录</h2>
        </div>
        <el-form :model="loginForm" :rules="loginRules" ref="loginFormRef">
          <el-form-item prop="account">
            <el-input 
              v-model="loginForm.account" 
              placeholder="工号/用户名/手机号/邮箱"
              prefix-icon="User"
              class="custom-input"
            />
          </el-form-item>
          <el-form-item prop="password">
            <el-input 
              v-model="loginForm.password" 
              type="password" 
              placeholder="密码"
              prefix-icon="Lock"
              show-password
              class="custom-input"
            />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" :loading="loading" @click="handleLogin" class="login-button">
              登录
            </el-button>
          </el-form-item>
          <div class="register-link">
            还没有账号?<el-link type="primary" @click="goToRegister">立即注册</el-link>
          </div>
        </el-form>
      </el-card>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { login } from '@/api/user'

const router = useRouter()
const loading = ref(false)
const loginFormRef = ref(null)

const loginForm = reactive({
  account: '',
  password: ''
})

const loginRules = {
  account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
  password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}

const handleLogin = async () => {
  if (!loginFormRef.value) return
  
  await loginFormRef.value.validate(async (valid) => {
    if (valid) {
      loading.value = true
      try {
        const res = await login(loginForm)
        if (res.code === 200) {
          ElMessage.success('登录成功')
          // 存储用户信息到本地存储
          localStorage.setItem('userInfo', JSON.stringify(res.data))
          
          // 所有角色都跳转到同一个首页
          router.push('/lender/home')
        } else {
          ElMessage.error(res.msg || '登录失败')
        }
      } catch (error) {
        console.error('登录错误:', error)
        ElMessage.error('登录失败,请稍后重试')
      } finally {
        loading.value = false
      }
    }
  })
}

const goToRegister = () => {
  router.push('/register')
}

onMounted(() => {
  // 获取上次登录的账号信息
  const lastLoginAccount = localStorage.getItem('lastLoginAccount')
  if (lastLoginAccount) {
    loginForm.account = lastLoginAccount
  }
})
</script>

<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-image: linear-gradient(to right, #8686e0, #a5a5f3);
  background-size: cover;
  position: relative;
}

.login-container::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: url('@/assets/bg-pattern.png') repeat;
  opacity: 0.1;
}

.login-box {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 450px;
  padding: 0 20px;
}

.login-card {
  width: 100%;
  border-radius: 12px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
  padding: 10px;
  background-color: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(10px);
  transition: all 0.3s ease;
}

.login-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15);
}

.logo-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 30px;
}

.logo-image {
  width: 80px;
  height: 80px;
  margin-bottom: 15px;
}

.login-title {
  text-align: center;
  color: #8686e0;
  font-size: 24px;
  font-weight: 600;
  margin: 0;
}

.custom-input :deep(.el-input__wrapper) {
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  padding: 4px 12px;
  transition: all 0.3s;
}

.custom-input :deep(.el-input__wrapper:hover) {
  box-shadow: 0 2px 12px rgba(134, 134, 224, 0.2);
}

.custom-input :deep(.el-input__wrapper.is-focus) {
  box-shadow: 0 0 0 1px #8686e0 inset;
}

.login-button {
  width: 100%;
  height: 44px;
  border-radius: 8px;
  font-size: 16px;
  background-color: #8686e0;
  border-color: #8686e0;
  transition: all 0.3s;
}

.login-button:hover {
  background-color: #7575d0;
  border-color: #7575d0;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(134, 134, 224, 0.3);
}

.register-link {
  text-align: center;
  margin-top: 20px;
  font-size: 14px;
}

.register-link .el-link {
  color: #8686e0;
  font-weight: 500;
}
</style>

home.vue:

<!-- vivy -->
<!-- 05/4/25 统一首页 -->
<template>
  <div class="home-container">
    <el-row :gutter="20">
      <!-- 左侧个人信息卡片 -->
      <el-col :span="8">
        <el-card class="user-card">
          <div class="user-info">
            <img src="../assets/user.png" alt="" style="width: 120px; height: 120px; border-radius: 50%;">
            <h3>{{ userInfo.username || '用户' }}</h3>
            <p>工号: {{ userInfo.id || '-' }}</p>
            <p>电话: {{ userInfo.phone || '-' }}</p>
          </div>
          <el-divider></el-divider>
          <div class="stats">
            <div class="stat-item">
              <h4>待处理任务</h4>
              <p class="stat-value">{{ pendingTasks }}</p>
            </div>
            <div class="stat-item">
              <h4>已完成任务</h4>
              <p class="stat-value">{{ completedTasks }}</p>
            </div>
          </div>
        </el-card>
      </el-col>
      
      <!-- 右侧内容区域 -->
      <el-col :span="16">
        <!-- Engineer和Super_admin显示待处理任务 -->
        <el-card v-if="userRole === 'engineer' || userRole === 'super_admin'" class="task-card">
          <template #header>
            <div class="card-header">
              <span>待接取任务</span>
              <el-button 
                v-if="userRole === 'engineer'" 
                type="primary" 
                size="small" 
                @click="$router.push('/lender/work')"
              >
                查看全部任务
              </el-button>
            </div>
          </template>
          
          <div class="task-container">
            <el-empty v-if="recentTasks.length === 0" description="暂无待处理维修任务"></el-empty>
            
            <div v-else class="task-list">
              <div v-for="task in recentTasks" :key="task.taskId" class="task-item">
                <div class="task-info">
                  <h4>维修任务 #{{ task.faultId }}</h4>
                  <p>设备编号: {{ task.deviceId }}</p>
                  <p>故障描述: {{ task.faultDesc }}</p>
                </div>
                <div class="task-action">
                  <el-tag :type="getStatusType(task.status)">{{ task.status }}</el-tag>
                  <el-button 
                    size="small" 
                    type="primary" 
                    @click="handleTakeTask(task)"
                    style="margin-top: 10px;"
                  >
                    接取维修任务
                  </el-button>
                </div>
              </div>
            </div>
          </div>
        </el-card>
        
        <!-- Admin和Super_admin显示故障列表 -->
        <el-card v-if="userRole === 'admin' || userRole === 'super_admin'" 
                class="fault-card" 
                :style="userRole === 'super_admin' ? 'margin-top: 20px;' : ''">
          <template #header>
            <div class="card-header">
              <span>设备故障列表</span>
              <el-button type="primary" size="small" @click="showAddFaultDialog">添加故障报修</el-button>
            </div>
          </template>
          
          <div class="table-container">
            <el-table :data="faultList" border style="width: 100%" height="350">
              <el-table-column prop="faultId" label="故障编号" width="90" />
              <el-table-column prop="deviceId" label="设备编号" width="90" />
              <el-table-column prop="faultDesc" label="故障描述" min-width="120" show-overflow-tooltip />
              <el-table-column prop="reportType" label="报修类型" width="90" />
              <el-table-column prop="reportedName" label="报修人" width="80" />
              <el-table-column prop="reportedPhone" label="联系电话" width="110" />
              <el-table-column prop="status" label="状态" width="80">
                <template #default="scope">
                  <el-tag :type="getStatusType(scope.row.status)">
                    {{ scope.row.status }}
                  </el-tag>
                </template>
              </el-table-column>
            </el-table>
          </div>
        </el-card>
        
        <!-- 设备状态统计 -->
        <el-card class="device-card" style="margin-top: 20px;">
          <template #header>
            <div class="card-header">
              <span>设备状态概览</span>
            </div>
          </template>
          
          <!-- 替换原来的状态框为图表 -->
          <div class="chart-container">
            <v-chart class="chart" :option="chartOption" autoresize />
          </div>
        </el-card>
      </el-col>
    </el-row>
    
    <!-- 添加故障报修对话框 -->
    <el-dialog
      title="设备故障报修"
      v-model="dialogVisible"
      width="500px"
    >
      <el-form :model="faultForm" label-width="80px">
        <el-form-item label="设备编号">
          <el-select v-model="faultForm.deviceId" style="width: 100%">
            <el-option
              v-for="device in deviceList"
              :key="device.deviceId"
              :label="device.deviceId + ' - ' + device.deviceType"
              :value="device.deviceId"
              :disabled="device.status !== '正常'" 
            />
          </el-select>
        </el-form-item>
        <el-form-item label="故障描述">
          <el-input type="textarea" v-model="faultForm.faultDesc" rows="3" />
        </el-form-item>
        <el-form-item label="报修类型">
          <el-select v-model="faultForm.reportType" style="width: 100%">
            <el-option label="自主报修" value="自主报修" />
            <el-option label="电话报修" value="电话报修" />
          </el-select>
        </el-form-item>
        <el-form-item label="报修人">
          <el-input v-model="faultForm.reportedName" readonly />
        </el-form-item>
        <el-form-item label="联系电话">
          <el-input v-model="faultForm.reportedPhone" readonly />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleSubmit">提交报修</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, onMounted, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { getDeviceList, updateDeviceStatus, getDeviceStats } from '@/api/device'
import { addDeviceFault } from '@/api/deviceFault'
import { createRepairOrder } from '@/api/repairOrder'
// 引入 ECharts 组件
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { BarChart } from 'echarts/charts'
import {
  TitleComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent
} from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'

// 注册 ECharts 组件
use([
  BarChart,
  TitleComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent,
  CanvasRenderer
])

// 用户信息
const userInfo = ref({})
const userRole = computed(() => userInfo.value.role || '')

// 任务统计
const pendingTasks = ref(2)
const completedTasks = ref(5)

// 设备状态统计
const deviceStats = ref({
  normal: 0,
  fault: 0,
  maintenance: 0,
  scrapped: 0
})

// 图表配置
const chartOption = computed(() => {
  return {
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        type: 'shadow'
      }
    },
    grid: {
      left: '3%',
      right: '4%',
      bottom: '3%',
      containLabel: true
    },
    xAxis: {
      type: 'category',
      data: ['正常', '故障', '维护中', '报废'],
      axisTick: {
        alignWithLabel: true
      }
    },
    yAxis: {
      type: 'value',
      minInterval: 1 // 确保y轴刻度为整数
    },
    series: [
      {
        name: '设备数量',
        type: 'bar',
        barWidth: '60%',
        data: [
          {
            value: deviceStats.value.normal,
            itemStyle: { color: '#67C23A' }
          },
          {
            value: deviceStats.value.fault,
            itemStyle: { color: '#F56C6C' }
          },
          {
            value: deviceStats.value.maintenance,
            itemStyle: { color: '#E6A23C' }
          },
          {
            value: deviceStats.value.scrapped,
            itemStyle: { color: '#909399' }
          }
        ],
        label: {
          show: true,
          position: 'top',
          formatter: '{c}'
        }
      }
    ]
  }
})

// 监听设备统计数据变化,更新图表
watch(deviceStats, () => {
  // 数据变化时图表会自动更新
}, { deep: true })

// 获取设备状态统计
const fetchDeviceStats = async () => {
  try {
    const res = await getDeviceStats()
    if (res.code === 200) {
      deviceStats.value = res.data || {
        normal: 0,
        fault: 0,
        maintenance: 0,
        scrapped: 0
      }
    } else {
      console.error('获取设备统计失败:', res.msg)
    }
  } catch (error) {
    console.error('获取设备统计错误:', error)
  }
}

// 最近任务
const recentTasks = ref([])

// 获取待处理故障任务
const fetchPendingFaults = async () => {
  try {
    const res = await getPendingFaults()
    if (res.code === 200) {
      recentTasks.value = res.data || []
    } else {
      ElMessage.error(res.msg || '获取待处理故障任务失败')
    }
  } catch (error) {
    console.error('获取待处理故障任务错误:', error)
    ElMessage.error('获取待处理故障任务失败')
  }
}

// 故障列表相关
const faultList = ref([])
const deviceList = ref([])
const dialogVisible = ref(false)
const faultForm = ref({
  deviceId: '',
  faultDesc: '',
  reportType: '自主报修',
  reportedId: '',
  reportedName: '',
  reportedPhone: ''
})

// 获取故障列表
const fetchFaultList = async () => {
  try {
    const res = await getFaultList()
    if (res.code === 200) {
      faultList.value = res.data || []
    } else {
      ElMessage.error(res.msg || '获取故障列表失败')
    }
  } catch (error) {
    console.error('获取故障列表错误:', error)
    ElMessage.error('获取故障列表失败')
  }
}

// 获取设备列表
const fetchDeviceList = async () => {
  try {
    const res = await getDeviceList()
    if (res.code === 200) {
      deviceList.value = res.data || []
    }
  } catch (error) {
    console.error('获取设备列表失败:', error)
  }
}

// 获取状态标签类型
const getStatusType = (status) => {
  const statusMap = {
    '待处理': 'info',
    '处理中': 'warning',
    '进行中': 'warning',
    '已解决': 'success',
    '已完成': 'success',
    '待审批': 'warning',
    '已关闭': ''
  }
  return statusMap[status] || ''
}

// 设置默认用户信息
const setDefaultUserInfo = () => {
  const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
  faultForm.value.reportedId = userInfo.id || ''
  faultForm.value.reportedName = userInfo.username || ''
  faultForm.value.reportedPhone = userInfo.phone || ''
}

// 显示添加故障报修对话框
const showAddFaultDialog = () => {
  // 重置表单
  faultForm.value = {
    deviceId: '',
    faultDesc: '',
    reportType: '自主报修',
    reportedId: '',
    reportedName: '',
    reportedPhone: ''
  }
  // 设置用户信息
  setDefaultUserInfo()
  dialogVisible.value = true
}

// 提交报修
const handleSubmit = async () => {
  try {
    const res = await addDeviceFault(faultForm.value)
    if (res.code === 200) {
      ElMessage.success('报修成功')
      dialogVisible.value = false
      // 刷新故障列表
      fetchFaultList()
    } else {
      ElMessage.error(res.msg || '报修失败')
    }
  } catch (error) {
    console.error('报修失败:', error)
    ElMessage.error('报修失败')
  }
}

// 接取任务
import { getFaultList, getPendingFaults, updateFaultStatus } from '@/api/deviceFault'  // 添加 updateFaultStatus

const handleTakeTask = async (task) => {
  try {
    const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
    
    // 获取安全须知内容
    const safetyNotice = `一、安全防护
穿戴防护装备:工作服、手套、安全帽、防护眼镜等。
设备断电锁定:切断电源,锁定设备,挂警示牌。
环境安全:保持工作区域整洁,高处作业系安全带。
二、维修前准备
工具检查:确保工具齐全、绝缘良好。
备件准备:确认规格匹配,准备备用件。
查阅资料:熟悉设备结构和维修要点。
三、维修过程
故障诊断:观察现象,记录症状,逐步排查。
操作规范:按手册操作,标记拆卸部件。
数据备份:备份重要数据,确保安全。
清洁维护:清理灰尘,检查润滑部件。
四、维修后测试
功能测试:启动设备,检查功能是否正常。
性能检查:确认运行参数正常。
安全检查:检查外壳、防护装置是否牢固。
五、记录与总结
记录信息:记录设备信息、维修过程、测试结果。
总结经验:分析故障原因,提出改进措施。
六、其他注意
遵守制度:严格按流程操作,妥善处理废弃物。`

    const repairOrderData = {
      faultId: task.faultId,
      engineerId: userInfo.id,
      repairDesc: '',
      status: '处理中',
      safetyNotice: safetyNotice
    }
    
    const res = await createRepairOrder(repairOrderData)
    if (res.code === 200) {
      // 更新故障状态为处理中
      const updateFaultRes = await updateFaultStatus({
        faultId: task.faultId,
        status: '处理中'
      })
      
      if (updateFaultRes.code === 200) {
        // 更新设备状态为维护中
        try {
          const updateDeviceRes = await updateDeviceStatus({
            deviceId: task.deviceId,
            status: '维护中'
          })
          if (updateDeviceRes.code === 200) {
            ElMessage.success('维修任务接取成功')
            // 刷新数据
            fetchPendingFaults()
            fetchDeviceList()
            fetchFaultList()
          } else {
            ElMessage.warning('任务已接取,但设备状态更新失败')
          }
        } catch (error) {
          console.error('更新设备状态失败:', error)
          ElMessage.warning('任务已接取,但设备状态更新失败')
        }
      } else {
        ElMessage.warning('任务已接取,但故障状态更新失败')
      }
    } else {
      ElMessage.error(res.msg || '接取任务失败')
    }
  } catch (error) {
    console.error('接取任务失败:', error)
    ElMessage.error('接取任务失败')
  }
}

onMounted(() => {
  // 从localStorage获取用户信息
  const storedUserInfo = localStorage.getItem('userInfo')
  if (storedUserInfo) {
    userInfo.value = JSON.parse(storedUserInfo)
  }
  
  // 获取数据
  fetchFaultList()
  fetchDeviceList()
  fetchPendingFaults()
  fetchDeviceStats()  // 添加获取设备统计数据
})
</script>

<style scoped>
.home-container {
  padding: 20px;
}

.user-card {
  height: 100%;
}

.user-info {
  text-align: center;
  padding: 10px 0;
}

.user-info h3 {
  margin: 10px 0 5px;
}

.user-info p {
  margin: 5px 0;
  color: #666;
}

.stats {
  display: flex;
  justify-content: space-around;
  text-align: center;
}

.stat-item h4 {
  margin-bottom: 5px;
  font-weight: normal;
  color: #666;
}

.stat-value {
  font-size: 24px;
  font-weight: bold;
  color: #409EFF;
  margin: 0;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.task-container {
  height: 350px;
  overflow-y: auto;
}

.task-list {
  display: flex;
  flex-direction: column;
  gap: 15px;
  padding-right: 5px;
}

/* 自定义滚动条样式 */
.task-container::-webkit-scrollbar {
  width: 6px;
}

.task-container::-webkit-scrollbar-thumb {
  background-color: #dcdfe6;
  border-radius: 3px;
}

.task-container::-webkit-scrollbar-track {
  background-color: #f5f7fa;
}

.task-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.task-info h4 {
  margin: 0 0 10px 0;
  font-size: 16px;
  color: #333;
}

.task-info p {
  margin: 5px 0;
  color: #666;
}

.task-action {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
}

.task-action .el-button {
  margin-left: 0;
}

.status-box {
  text-align: center;
  padding: 15px;
  border-radius: 4px;
  color: white;
}

.status-box h3 {
  margin: 0 0 10px 0;
  font-size: 16px;
}

.status-box p {
  margin: 0;
  font-size: 24px;
  font-weight: bold;
}

.normal {
  background-color: #67C23A;
}

.fault {
  background-color: #F56C6C;
}

.maintenance {
  background-color: #E6A23C;
}

.scrapped {
  background-color: #909399;
}

.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.table-container {
  width: 100%;
  overflow: hidden;
}

.el-table {
  margin-bottom: 10px;
}

/* 添加图表容器样式 */
.chart-container {
  width: 100%;
  height: 300px;
}

.chart {
  width: 100%;
  height: 100%;
}

</style>

DeviceFault:

package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("device_faults")
public class DeviceFault {
    @TableId(type = IdType.AUTO)
    private Integer faultId;
    private String deviceId;
    private String faultDesc;
    private String reportType;
    private String reportedId;
    private String reportedName;
    private String reportedPhone;
    private String status;

    public Integer getFaultId() {
        return faultId;
    }

    public void setFaultId(Integer faultId) {
        this.faultId = faultId;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }

    public String getFaultDesc() {
        return faultDesc;
    }

    public void setFaultDesc(String faultDesc) {
        this.faultDesc = faultDesc;
    }

    public String getReportType() {
        return reportType;
    }

    public void setReportType(String reportType) {
        this.reportType = reportType;
    }

    public String getReportedId() {
        return reportedId;
    }

    public void setReportedId(String reportedId) {
        this.reportedId = reportedId;
    }

    public String getReportedName() {
        return reportedName;
    }

    public void setReportedName(String reportedName) {
        this.reportedName = reportedName;
    }

    public String getReportedPhone() {
        return reportedPhone;
    }

    public void setReportedPhone(String reportedPhone) {
        this.reportedPhone = reportedPhone;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}

DeviceFaultController:

package com.example.demo.controller;

import com.example.demo.common.Result;
import com.example.demo.entity.DeviceFault;
import com.example.demo.service.DeviceFaultService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/*
 * Qi
 * 25/4/22
 * 设备的报修
 * */
@RestController
@RequestMapping("/device-fault")
public class DeviceFaultController {

    @Autowired
    private DeviceFaultService deviceFaultService;

    @GetMapping("/list")
    public Result getAllFaults() {
        return Result.success(deviceFaultService.getAllFaults());
    }

    @GetMapping("/pending")
    public Result getPendingFaults() {
        List<DeviceFault> pendingFaults = deviceFaultService.getPendingFaults();
        return Result.success(pendingFaults);
    }

    @GetMapping("/{faultId}")
    public Result getFault(@PathVariable Integer faultId) {
        DeviceFault fault = deviceFaultService.getFaultById(faultId);
        return fault != null ? Result.success(fault) : Result.fail("故障记录不存在");
    }

    @PostMapping("/add")
    public Result addFault(@RequestBody DeviceFault deviceFault) {
        if (deviceFaultService.addFault(deviceFault)) {
            return Result.success(deviceFault);
        }
        return Result.fail("添加故障记录失败,请确认设备是否存在");
    }

    @PutMapping("/update")
    public Result updateFault(@RequestBody DeviceFault deviceFault) {
        if (deviceFaultService.updateFault(deviceFault)) {
            return Result.success(deviceFault);
        }
        return Result.fail("更新故障记录失败");
    }

    @PutMapping("/status")
    public Result updateFaultStatus(@RequestBody DeviceFault deviceFault) {
        if (deviceFaultService.updateFaultStatus(deviceFault.getFaultId(), deviceFault.getStatus())) {
            return Result.success(deviceFault);
        }
        return Result.fail("更新故障状态失败");
    }
}

DeviceFaultMapper:

package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.DeviceFault;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DeviceFaultMapper extends BaseMapper<DeviceFault> {
}

DeviceFaultService:

package com.example.demo.service;

import com.example.demo.entity.DeviceFault;

import java.util.List;
/*
 * Qi
 * 25/4/22
 * 设备的报修
 * */
public interface DeviceFaultService {
    List<DeviceFault> getAllFaults();
    DeviceFault getFaultById(Integer faultId);
    boolean addFault(DeviceFault deviceFault);
    boolean updateFault(DeviceFault deviceFault);
    List<DeviceFault> getPendingFaults();
    boolean updateFaultStatus(Integer faultId, String status);
}

DeviceFaultServiceImpl:

package com.example.demo.service.impl;

import com.example.demo.entity.Device;
import com.example.demo.entity.DeviceFault;
import com.example.demo.mapper.DeviceFaultMapper;
import com.example.demo.mapper.DeviceMapper;
import com.example.demo.service.DeviceFaultService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
/*
 * Qi
 * 25/4/22
 * 设备的报修
 * */
@Service
public class DeviceFaultServiceImpl implements DeviceFaultService {

    @Autowired
    private DeviceFaultMapper deviceFaultMapper;
    
    @Autowired
    private DeviceMapper deviceMapper;

    @Override
    public List<DeviceFault> getAllFaults() {
        return deviceFaultMapper.selectList(null);
    }

    @Override
    public DeviceFault getFaultById(Integer faultId) {
        return deviceFaultMapper.selectById(faultId);
    }

    @Override
    @Transactional
    public boolean addFault(DeviceFault deviceFault) {
        // 检查设备是否存在
        Device device = deviceMapper.selectById(deviceFault.getDeviceId());
        if (device == null) {
            return false;
        }
        
        // 设置初始状态
        deviceFault.setStatus("待处理");
        
        // 添加故障记录
        int result = deviceFaultMapper.insert(deviceFault);
        
        if (result > 0) {
            // 更新设备状态为故障
            device.setStatus("故障");
            deviceMapper.updateById(device);
            return true;
        }
        return false;
    }

    @Override
    public boolean updateFault(DeviceFault deviceFault) {
        return deviceFaultMapper.updateById(deviceFault) > 0;
    }

    @Override
    public boolean updateFaultStatus(Integer faultId, String status) {
        DeviceFault deviceFault = deviceFaultMapper.selectById(faultId);
        if (deviceFault != null) {
            deviceFault.setStatus(status);
            return deviceFaultMapper.updateById(deviceFault) > 0;
        }
        return false;
    }

    @Override
    public List<DeviceFault> getPendingFaults() {
        QueryWrapper<DeviceFault> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("status", "待处理");
        return deviceFaultMapper.selectList(queryWrapper);
    }
}

deviceFault.js:

import request from '@/utils/request'

export function getFaultList() {
  return request({
    url: '/device-fault/list',
    method: 'get'
  })
}

export function getFault(faultId) {
  return request({
    url: `/device-fault/${faultId}`,
    method: 'get'
  })
}

export function addDeviceFault(data) {
  return request({
    url: '/device-fault/add',
    method: 'post',
    data
  })
}

export function updateDeviceFault(data) {
  return request({
    url: '/device-fault/update',
    method: 'put',
    data
  })
}

export function updateFaultStatus(data) {
  return request({
    url: '/device-fault/status',
    method: 'put',
    data
  })
}

export function getPendingFaults() {
  return request({
    url: '/device-fault/pending',
    method: 'get'
  })
}
posted @ 2025-04-18 23:27  三拍  阅读(12)  评论(0)    收藏  举报