gaoluyang
2025-09-16 2208346bff099670f2ab8608f2f774caa76d867e
标准作业指导
已添加1个文件
已修改2个文件
1397 ■■■■■ 文件已修改
src/pages.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/sop/index.vue 1380 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json
@@ -371,6 +371,13 @@
        "navigationBarTitleText": "智能派单",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/sop/index",
      "style": {
        "navigationBarTitleText": "标准作业指导",
        "navigationStyle": "custom"
      }
    }
  ],
  "subPackages": [
src/pages/equipmentManagement/sop/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1380 @@
<template>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="标准作业指导" @back="goBack" />
    <!-- ä»»åŠ¡ä¿¡æ¯å¡ç‰‡ -->
    <view class="task-info-card">
      <view class="task-header">
        <view class="task-left">
          <view class="task-icon">
            <up-icon name="file-text" size="20" color="#ffffff"></up-icon>
          </view>
          <view class="task-details">
            <text class="task-title">{{ currentTask.deviceName }}</text>
            <text class="task-subtitle">{{ currentTask.taskType }} - {{ currentTask.priority }}</text>
          </view>
        </view>
        <view class="task-status">
          <u-tag :type="getStatusType(currentTask.status)" size="small">
            {{ getStatusText(currentTask.status) }}
          </u-tag>
        </view>
      </view>
      <view class="task-meta">
        <view class="meta-item">
          <text class="meta-label">设备编号:</text>
          <text class="meta-value">{{ currentTask.deviceCode }}</text>
        </view>
        <view class="meta-item">
          <text class="meta-label">故障描述:</text>
          <text class="meta-value">{{ currentTask.faultDescription }}</text>
        </view>
        <view class="meta-item">
          <text class="meta-label">预计工时:</text>
          <text class="meta-value">{{ currentTask.estimatedHours }}小时</text>
        </view>
      </view>
    </view>
    <!-- åŠŸèƒ½å¯¼èˆª -->
    <view class="nav-tabs">
      <view
        v-for="(tab, index) in tabs"
        :key="index"
        class="nav-tab"
        :class="{ active: activeTab === tab.key }"
        @click="switchTab(tab.key)"
      >
        <up-icon :name="tab.icon" size="18" :color="activeTab === tab.key ? '#2979ff' : '#666'"></up-icon>
        <text class="tab-text" :class="{ active: activeTab === tab.key }">{{ tab.label }}</text>
      </view>
    </view>
    <!-- SOP标准作业程序 -->
    <view v-if="activeTab === 'sop'" class="content-section">
      <view class="section-header">
        <text class="section-title">标准作业程序</text>
        <view class="progress-info">
          <text class="progress-text">{{ completedSteps }}/{{ sopSteps.length }}</text>
          <up-icon name="checkmark-circle" size="16" color="#4caf50" v-if="completedSteps === sopSteps.length"></up-icon>
        </view>
      </view>
      <view class="sop-steps">
        <view
          v-for="(step, index) in sopSteps"
          :key="step.id"
          class="sop-step"
          :class="{ completed: step.completed, current: currentStepIndex === index }"
        >
          <view class="step-header" @click="toggleStep(index)">
            <view class="step-number">
              <text v-if="!step.completed" class="step-num">{{ index + 1 }}</text>
              <up-icon v-else name="checkmark" size="14" color="#ffffff"></up-icon>
            </view>
            <view class="step-content">
              <text class="step-title">{{ step.title }}</text>
              <text class="step-duration">预计{{ step.duration }}分钟</text>
            </view>
            <view class="step-toggle">
              <up-icon
                :name="step.expanded ? 'arrow-up' : 'arrow-down'"
                size="16"
                color="#999"
              ></up-icon>
            </view>
          </view>
          <view v-if="step.expanded" class="step-details">
            <view class="step-description">
              <text class="desc-text">{{ step.description }}</text>
            </view>
            <view v-if="step.warnings.length > 0" class="step-warnings">
              <text class="warning-title">⚠️ æ³¨æ„äº‹é¡¹</text>
              <view v-for="warning in step.warnings" :key="warning" class="warning-item">
                <text class="warning-text">• {{ warning }}</text>
              </view>
            </view>
            <view v-if="step.tools.length > 0" class="step-tools">
              <text class="tools-title">🔧 æ‰€éœ€å·¥å…·</text>
              <view class="tools-list">
                <u-tag
                  v-for="tool in step.tools"
                  :key="tool"
                  type="info"
                  size="mini"
                  class="tool-tag"
                >
                  {{ tool }}
                </u-tag>
              </view>
            </view>
            <view class="step-actions">
              <u-button
                v-if="!step.completed"
                type="success"
                size="small"
                @click="completeStep(index)"
              >
                å®Œæˆæ­¤æ­¥éª¤
              </u-button>
              <u-button
                v-else
                type="info"
                size="small"
                plain
                @click="uncompleteStep(index)"
              >
                å–消完成
              </u-button>
            </view>
          </view>
        </view>
      </view>
    </view>
    <!-- é…ä»¶æ¸…单 -->
    <view v-if="activeTab === 'parts'" class="content-section">
      <view class="section-header">
        <text class="section-title">配件清单</text>
        <view class="parts-summary">
          <text class="summary-text">共{{ partsList.length }}项配件</text>
        </view>
      </view>
      <view class="parts-list">
        <view v-for="part in partsList" :key="part.id" class="part-item">
          <view class="part-header">
            <view class="part-info">
              <text class="part-name">{{ part.name }}</text>
              <text class="part-spec">{{ part.specification }}</text>
            </view>
            <view class="part-status">
              <u-tag
                :type="part.available ? 'success' : 'error'"
                size="mini"
              >
                {{ part.available ? '库存充足' : '库存不足' }}
              </u-tag>
            </view>
          </view>
          <view class="part-details">
            <view class="detail-row">
              <text class="detail-label">配件编号:</text>
              <text class="detail-value">{{ part.partNumber }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">需要数量:</text>
              <text class="detail-value">{{ part.requiredQuantity }}{{ part.unit }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">库存数量:</text>
              <text class="detail-value" :class="{ danger: !part.available }">
                {{ part.stockQuantity }}{{ part.unit }}
              </text>
            </view>
            <view class="detail-row">
              <text class="detail-label">存放位置:</text>
              <text class="detail-value">{{ part.location }}</text>
            </view>
          </view>
          <view class="part-actions">
            <u-button
              type="primary"
              size="small"
              plain
              @click="requestPart(part)"
              :disabled="part.available"
            >
              ç”³è¯·é…ä»¶
            </u-button>
            <u-button
              type="info"
              size="small"
              plain
              @click="viewPartDetail(part)"
            >
              æŸ¥çœ‹è¯¦æƒ…
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <!-- å®‰å…¨æç¤º -->
    <view v-if="activeTab === 'safety'" class="content-section">
      <view class="section-header">
        <text class="section-title">安全提示</text>
        <view class="safety-level">
          <u-tag :type="getSafetyLevelType(safetyInfo.level)" size="small">
            {{ safetyInfo.level }}
          </u-tag>
        </view>
      </view>
      <!-- å®‰å…¨ç­‰çº§è¯´æ˜Ž -->
      <view class="safety-overview">
        <view class="safety-icon">
          <up-icon name="warning" size="24" color="#ff6b35"></up-icon>
        </view>
        <view class="safety-content">
          <text class="safety-title">{{ safetyInfo.title }}</text>
          <text class="safety-desc">{{ safetyInfo.description }}</text>
        </view>
      </view>
      <!-- ä¸ªäººé˜²æŠ¤è®¾å¤‡ -->
      <view class="safety-section">
        <text class="safety-section-title">🛡️ ä¸ªäººé˜²æŠ¤è®¾å¤‡</text>
        <view class="ppe-list">
          <view v-for="ppe in safetyInfo.ppe" :key="ppe.name" class="ppe-item">
            <view class="ppe-icon">
              <text class="ppe-emoji">{{ ppe.icon }}</text>
            </view>
            <view class="ppe-info">
              <text class="ppe-name">{{ ppe.name }}</text>
              <text class="ppe-desc">{{ ppe.description }}</text>
            </view>
            <view class="ppe-status">
              <up-icon
                :name="ppe.checked ? 'checkmark-circle' : 'close-circle'"
                :color="ppe.checked ? '#4caf50' : '#ccc'"
                size="20"
                @click="togglePPE(ppe)"
              ></up-icon>
            </view>
          </view>
        </view>
      </view>
      <!-- å®‰å…¨æ³¨æ„äº‹é¡¹ -->
      <view class="safety-section">
        <text class="safety-section-title">⚠️ å®‰å…¨æ³¨æ„äº‹é¡¹</text>
        <view class="safety-warnings">
          <view v-for="(warning, index) in safetyInfo.warnings" :key="index" class="safety-warning">
            <view class="warning-icon">
              <up-icon name="info-circle" size="16" color="#ff6b35"></up-icon>
            </view>
            <text class="warning-content">{{ warning }}</text>
          </view>
        </view>
      </view>
      <!-- åº”急处理 -->
      <view class="safety-section">
        <text class="safety-section-title">🚨 åº”急处理</text>
        <view class="emergency-procedures">
          <view v-for="(procedure, index) in safetyInfo.emergencyProcedures" :key="index" class="emergency-item">
            <view class="emergency-header">
              <text class="emergency-title">{{ procedure.situation }}</text>
            </view>
            <view class="emergency-steps">
              <view v-for="(step, stepIndex) in procedure.steps" :key="stepIndex" class="emergency-step">
                <text class="step-number">{{ stepIndex + 1 }}.</text>
                <text class="step-content">{{ step }}</text>
              </view>
            </view>
          </view>
        </view>
      </view>
      <!-- å®‰å…¨ç¡®è®¤ -->
      <view class="safety-confirmation">
        <view class="confirmation-header">
          <up-icon name="checkmark-circle" size="20" color="#4caf50"></up-icon>
          <text class="confirmation-title">安全确认</text>
        </view>
        <view class="confirmation-content">
          <text class="confirmation-text">我已仔细阅读并理解以上安全提示,将严格按照安全规程进行作业</text>
        </view>
        <view class="confirmation-actions">
          <u-button
            type="success"
            @click="confirmSafety"
            :disabled="safetyConfirmed"
          >
            {{ safetyConfirmed ? '已确认' : '确认并开始作业' }}
          </u-button>
        </view>
      </view>
    </view>
    <!-- åº•部操作按钮 -->
    <view class="bottom-actions">
      <u-button
        type="primary"
        size="large"
        @click="startWork"
        :disabled="!canStartWork"
      >
        å¼€å§‹ä½œä¸š
      </u-button>
      <u-button
        type="info"
        size="large"
        plain
        @click="saveProgress"
      >
        ä¿å­˜è¿›åº¦
      </u-button>
    </view>
  </view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
// å½“前任务信息
const currentTask = ref({
  id: 1,
  deviceName: '数控车床CK6140',
  deviceCode: 'CNC-001',
  taskType: '设备维修',
  priority: '紧急',
  status: 'in_progress',
  faultDescription: '主轴异响,切削精度下降',
  estimatedHours: 4,
  assignedTechnician: '李师傅',
  startTime: '2024-01-15 09:00:00'
})
// å¯¼èˆªæ ‡ç­¾
const tabs = ref([
  { key: 'sop', label: 'SOP', icon: 'list' },
  { key: 'parts', label: '配件', icon: 'grid' },
  { key: 'safety', label: '安全', icon: 'info' }
])
const activeTab = ref('sop')
// SOP步骤数据
const sopSteps = ref([
  {
    id: 1,
    title: '安全准备与设备断电',
    description: '确保设备完全断电,挂上安全标识牌,穿戴好个人防护设备,检查工作环境安全。',
    duration: 10,
    completed: false,
    expanded: false,
    warnings: [
      '必须确认设备完全断电后才能进行后续操作',
      '挂上"正在维修,禁止操作"的安全标识牌',
      '检查周围是否有其他人员,确保安全距离'
    ],
    tools: ['万用表', '安全标识牌', '个人防护设备']
  },
  {
    id: 2,
    title: '拆卸主轴防护罩',
    description: '使用专用工具小心拆卸主轴防护罩,注意保护罩的完整性,避免损坏密封件。',
    duration: 15,
    completed: false,
    expanded: false,
    warnings: [
      '拆卸时注意防护罩重量,避免掉落砸伤',
      '密封件容易老化,拆卸时要格外小心',
      '记录拆卸顺序,便于后续安装'
    ],
    tools: ['内六角扳手', '橡胶锤', '密封胶']
  },
  {
    id: 3,
    title: '检查主轴轴承状态',
    description: '仔细检查主轴轴承的磨损情况,测量轴承间隙,检查润滑油状态和轴承座的配合情况。',
    duration: 25,
    completed: false,
    expanded: false,
    warnings: [
      '轴承检查时避免用力过大,防止进一步损坏',
      '注意观察轴承表面是否有裂纹或异常磨损',
      '测量数据要准确记录,作为更换依据'
    ],
    tools: ['游标卡尺', '千分尺', '内窥镜', '润滑油检测仪']
  },
  {
    id: 4,
    title: '更换损坏轴承',
    description: '使用专用拉拔器拆卸损坏轴承,清洁轴承座,安装新轴承时确保配合精度。',
    duration: 45,
    completed: false,
    expanded: false,
    warnings: [
      '轴承拆卸时必须使用专用工具,避免损坏轴颈',
      '新轴承安装前要检查型号规格是否正确',
      '安装时要均匀用力,确保轴承完全就位'
    ],
    tools: ['轴承拉拔器', '轴承加热器', '专用安装工具', '润滑脂']
  },
  {
    id: 5,
    title: '调整主轴间隙',
    description: '根据技术要求调整主轴轴向和径向间隙,确保主轴运转平稳,精度符合标准。',
    duration: 30,
    completed: false,
    expanded: false,
    warnings: [
      '间隙调整要严格按照技术标准执行',
      '调整过程中要多次测量确认',
      '调整完成后要进行试运转检验'
    ],
    tools: ['百分表', '塞尺', '扭力扳手']
  },
  {
    id: 6,
    title: '安装防护罩并测试',
    description: '按照拆卸的逆序安装防护罩,加注润滑油,进行低速试运转,检查运转状态。',
    duration: 20,
    completed: false,
    expanded: false,
    warnings: [
      '安装时要确保所有密封件正确就位',
      '润滑油加注量要符合规定要求',
      '试运转时要密切观察设备状态'
    ],
    tools: ['润滑油', '密封胶', '清洁布']
  }
])
const currentStepIndex = ref(0)
// é…ä»¶æ¸…单数据
const partsList = ref([
  {
    id: 1,
    name: '主轴轴承',
    specification: 'NSK 7020C',
    partNumber: 'BRG-7020C-001',
    requiredQuantity: 2,
    stockQuantity: 5,
    unit: '个',
    available: true,
    location: '仓库A区-03货架',
    supplier: '日本NSK公司',
    price: 1250.00
  },
  {
    id: 2,
    name: '密封圈',
    specification: 'O型圈 Ï†45×3',
    partNumber: 'SEAL-045-003',
    requiredQuantity: 4,
    stockQuantity: 2,
    unit: '个',
    available: false,
    location: '仓库B区-12货架',
    supplier: '德国费斯托',
    price: 25.50
  },
  {
    id: 3,
    name: '润滑脂',
    specification: '高温轴承润滑脂 2#',
    partNumber: 'LUB-HT-002',
    requiredQuantity: 1,
    stockQuantity: 8,
    unit: '支',
    available: true,
    location: '仓库C区-05货架',
    supplier: '美孚石油',
    price: 180.00
  },
  {
    id: 4,
    name: '螺栓',
    specification: 'M8×25 å†…六角螺栓',
    partNumber: 'BOLT-M8-025',
    requiredQuantity: 8,
    stockQuantity: 50,
    unit: '个',
    available: true,
    location: '仓库A区-01货架',
    supplier: '标准件厂',
    price: 2.50
  },
  {
    id: 5,
    name: '垫片',
    specification: '调整垫片 Ï†40×0.1',
    partNumber: 'SHIM-040-01',
    requiredQuantity: 6,
    stockQuantity: 0,
    unit: '片',
    available: false,
    location: '仓库A区-08货架',
    supplier: '精密加工厂',
    price: 15.00
  }
])
// å®‰å…¨ä¿¡æ¯æ•°æ®
const safetyInfo = ref({
  level: '高风险',
  title: '机械设备维修安全规程',
  description: '本次维修涉及重型机械设备,存在机械伤害、电击等风险,请严格遵守安全操作规程。',
  ppe: [
    {
      name: '安全帽',
      icon: '⛑️',
      description: '防止头部受到撞击伤害',
      checked: false
    },
    {
      name: '安全眼镜',
      icon: '🥽',
      description: '防止金属屑飞溅伤眼',
      checked: false
    },
    {
      name: '防护手套',
      icon: '🧤',
      description: '防止手部割伤和烫伤',
      checked: false
    },
    {
      name: '安全鞋',
      icon: '👢',
      description: '防止足部被重物砸伤',
      checked: false
    },
    {
      name: '工作服',
      icon: '🦺',
      description: '防止衣物被机械卷入',
      checked: false
    }
  ],
  warnings: [
    '维修前必须确认设备完全断电,并挂上安全标识牌',
    '使用工具前要检查工具状态,确保完好无损',
    '拆卸重型部件时要使用起重设备,不得徒手操作',
    '工作区域要保持整洁,及时清理油污和杂物',
    '发现异常情况要立即停止作业,报告现场负责人',
    '严禁在疲劳状态下进行精密操作',
    '多人协作时要明确分工,加强沟通协调'
  ],
  emergencyProcedures: [
    {
      situation: '人员受伤',
      steps: [
        '立即停止所有作业活动',
        '评估伤情严重程度',
        '轻伤进行现场急救处理',
        '重伤立即拨打120急救电话',
        '通知安全管理人员和项目负责人',
        '保护现场,配合事故调查'
      ]
    },
    {
      situation: '设备故障',
      steps: [
        '立即按下急停按钮',
        '切断设备电源',
        '疏散周围人员到安全区域',
        '通知设备管理人员',
        '记录故障现象和时间',
        '等待专业人员处理'
      ]
    },
    {
      situation: '火灾事故',
      steps: [
        '立即切断电源',
        '使用适当的灭火器材',
        '疏散现场人员',
        '拨打119火警电话',
        '通知消防安全管理人员',
        '配合消防部门救援'
      ]
    }
  ]
})
const safetyConfirmed = ref(false)
// è®¡ç®—属性
const completedSteps = computed(() => {
  return sopSteps.value.filter(step => step.completed).length
})
const canStartWork = computed(() => {
  return safetyConfirmed.value && completedSteps.value > 0
})
// è¿”回上一页
const goBack = () => {
  uni.navigateBack()
}
// èŽ·å–ä»»åŠ¡çŠ¶æ€ç±»åž‹
const getStatusType = (status) => {
  const statusMap = {
    'pending': 'warning',
    'in_progress': 'primary',
    'completed': 'success',
    'paused': 'info'
  }
  return statusMap[status] || 'info'
}
// èŽ·å–ä»»åŠ¡çŠ¶æ€æ–‡æœ¬
const getStatusText = (status) => {
  const statusMap = {
    'pending': '待开始',
    'in_progress': '进行中',
    'completed': '已完成',
    'paused': '已暂停'
  }
  return statusMap[status] || '未知'
}
// åˆ‡æ¢æ ‡ç­¾
const switchTab = (tabKey) => {
  activeTab.value = tabKey
}
// åˆ‡æ¢æ­¥éª¤å±•开状态
const toggleStep = (index) => {
  sopSteps.value[index].expanded = !sopSteps.value[index].expanded
}
// å®Œæˆæ­¥éª¤
const completeStep = (index) => {
  sopSteps.value[index].completed = true
  showToast('步骤已完成')
  // è‡ªåŠ¨å±•å¼€ä¸‹ä¸€æ­¥
  if (index < sopSteps.value.length - 1) {
    currentStepIndex.value = index + 1
    sopSteps.value[index + 1].expanded = true
  }
}
// å–消完成步骤
const uncompleteStep = (index) => {
  sopSteps.value[index].completed = false
  showToast('已取消完成状态')
}
// ç”³è¯·é…ä»¶
const requestPart = (part) => {
  uni.showModal({
    title: '申请配件',
    content: `确认申请配件"${part.name}"?`,
    confirmText: '确认',
    cancelText: '取消',
    success: (res) => {
      if (res.confirm) {
        showToast('配件申请已提交')
        // è¿™é‡Œå¯ä»¥è°ƒç”¨API提交申请
      }
    }
  })
}
// æŸ¥çœ‹é…ä»¶è¯¦æƒ…
const viewPartDetail = (part) => {
  uni.showModal({
    title: part.name,
    content: `配件编号: ${part.partNumber}\n规格: ${part.specification}\n供应商: ${part.supplier}\n单价: Â¥${part.price}`,
    showCancel: false,
    confirmText: '知道了'
  })
}
// èŽ·å–å®‰å…¨ç­‰çº§ç±»åž‹
const getSafetyLevelType = (level) => {
  const levelMap = {
    '低风险': 'success',
    '中风险': 'warning',
    '高风险': 'error'
  }
  return levelMap[level] || 'info'
}
// åˆ‡æ¢PPE状态
const togglePPE = (ppe) => {
  ppe.checked = !ppe.checked
  if (ppe.checked) {
    showToast(`已确认佩戴${ppe.name}`)
  }
}
// ç¡®è®¤å®‰å…¨
const confirmSafety = () => {
  const uncheckedPPE = safetyInfo.value.ppe.filter(ppe => !ppe.checked)
  if (uncheckedPPE.length > 0) {
    showToast('请确认已佩戴所有个人防护设备')
    return
  }
  safetyConfirmed.value = true
  showToast('安全确认完成,可以开始作业')
}
// å¼€å§‹ä½œä¸š
const startWork = () => {
  if (!safetyConfirmed.value) {
    showToast('请先完成安全确认')
    return
  }
  if (completedSteps.value === 0) {
    showToast('请至少完成一个SOP步骤')
    return
  }
  uni.showModal({
    title: '开始作业',
    content: '确认开始正式作业?',
    confirmText: '开始',
    cancelText: '取消',
    success: (res) => {
      if (res.confirm) {
        currentTask.value.status = 'in_progress'
        showToast('作业已开始,请按照SOP执行')
      }
    }
  })
}
// ä¿å­˜è¿›åº¦
const saveProgress = () => {
  const progress = {
    taskId: currentTask.value.id,
    completedSteps: completedSteps.value,
    safetyConfirmed: safetyConfirmed.value,
    timestamp: new Date().toISOString()
  }
  // è¿™é‡Œå¯ä»¥è°ƒç”¨API保存进度
  showToast('进度已保存')
  console.log('保存的进度:', progress)
}
onMounted(() => {
  // é¡µé¢åŠ è½½æ—¶çš„åˆå§‹åŒ–é€»è¾‘
  // å¯ä»¥æ ¹æ®ä»»åŠ¡ID加载对应的SOP、配件清单和安全提示
})
onShow(() => {
  // é¡µé¢æ˜¾ç¤ºæ—¶çš„逻辑
})
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
// æ ‡å‡†ä½œä¸šæŒ‡å¯¼ç‰¹æœ‰æ ·å¼
.sales-account {
  padding-bottom: 100px;
}
// ä»»åŠ¡ä¿¡æ¯å¡ç‰‡
.task-info-card {
  margin: 20px;
  background: #ffffff;
  border-radius: 12px;
  padding: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.task-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.task-left {
  display: flex;
  align-items: center;
  gap: 12px;
}
.task-icon {
  width: 40px;
  height: 40px;
  background: #2979ff;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.task-details {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.task-title {
  font-size: 16px;
  font-weight: 500;
  color: #333;
}
.task-subtitle {
  font-size: 12px;
  color: #666;
}
.task-meta {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.meta-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.meta-label {
  font-size: 12px;
  color: #777;
  min-width: 70px;
}
.meta-value {
  font-size: 12px;
  color: #333;
  flex: 1;
  text-align: right;
}
// å¯¼èˆªæ ‡ç­¾
.nav-tabs {
  display: flex;
  background: #ffffff;
  margin: 0 20px;
  border-radius: 12px;
  padding: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.nav-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 12px 8px;
  border-radius: 8px;
  transition: all 0.3s ease;
  &.active {
    background: #f3f7ff;
  }
}
.tab-text {
  font-size: 12px;
  color: #666;
  &.active {
    color: #2979ff;
    font-weight: 500;
  }
}
// å†…容区域
.content-section {
  margin: 20px;
}
.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}
.section-title {
  font-size: 16px;
  font-weight: 500;
  color: #333;
}
.progress-info {
  display: flex;
  align-items: center;
  gap: 4px;
}
.progress-text {
  font-size: 12px;
  color: #666;
}
// SOP步骤样式
.sop-steps {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.sop-step {
  background: #ffffff;
  border-radius: 12px;
  border: 1px solid #f0f0f0;
  overflow: hidden;
  transition: all 0.3s ease;
  &.completed {
    border-color: #4caf50;
    background: #f8fff8;
  }
  &.current {
    border-color: #2979ff;
    box-shadow: 0 2px 8px rgba(41, 121, 255, 0.1);
  }
}
.step-header {
  display: flex;
  align-items: center;
  padding: 16px;
  cursor: pointer;
}
.step-number {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #f5f5f5;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 12px;
  .sop-step.completed & {
    background: #4caf50;
  }
}
.step-num {
  font-size: 12px;
  font-weight: 500;
  color: #666;
  .sop-step.completed & {
    color: #ffffff;
  }
}
.step-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.step-title {
  font-size: 14px;
  font-weight: 500;
  color: #333;
}
.step-duration {
  font-size: 12px;
  color: #666;
}
.step-details {
  padding: 0 16px 16px 16px;
  border-top: 1px solid #f0f0f0;
}
.step-description {
  margin: 16px 0;
}
.desc-text {
  font-size: 13px;
  color: #555;
  line-height: 1.5;
}
.step-warnings {
  margin: 16px 0;
  padding: 12px;
  background: #fff3e0;
  border-radius: 8px;
  border-left: 4px solid #ff9800;
}
.warning-title {
  font-size: 13px;
  font-weight: 500;
  color: #e65100;
  margin-bottom: 8px;
}
.warning-item {
  margin-bottom: 4px;
}
.warning-text {
  font-size: 12px;
  color: #bf360c;
  line-height: 1.4;
}
.step-tools {
  margin: 16px 0;
}
.tools-title {
  font-size: 13px;
  font-weight: 500;
  color: #333;
  margin-bottom: 8px;
}
.tools-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.tool-tag {
  margin: 0;
}
.step-actions {
  margin-top: 16px;
  display: flex;
  gap: 8px;
}
// é…ä»¶æ¸…单样式
.parts-summary {
  display: flex;
  align-items: center;
}
.summary-text {
  font-size: 12px;
  color: #666;
}
.parts-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.part-item {
  background: #ffffff;
  border-radius: 12px;
  padding: 16px;
  border: 1px solid #f0f0f0;
}
.part-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
}
.part-info {
  flex: 1;
}
.part-name {
  font-size: 14px;
  font-weight: 500;
  color: #333;
  display: block;
  margin-bottom: 4px;
}
.part-spec {
  font-size: 12px;
  color: #666;
}
.part-details {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 12px;
}
.detail-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.detail-label {
  font-size: 12px;
  color: #777;
  min-width: 70px;
}
.detail-value {
  font-size: 12px;
  color: #333;
  &.danger {
    color: #f44336;
    font-weight: 500;
  }
}
.part-actions {
  display: flex;
  gap: 8px;
}
// å®‰å…¨æç¤ºæ ·å¼
.safety-level {
  display: flex;
  align-items: center;
}
.safety-overview {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px;
  background: #fff3e0;
  border-radius: 12px;
  margin-bottom: 20px;
  border-left: 4px solid #ff6b35;
}
.safety-icon {
  margin-top: 2px;
}
.safety-content {
  flex: 1;
}
.safety-title {
  font-size: 14px;
  font-weight: 500;
  color: #e65100;
  display: block;
  margin-bottom: 4px;
}
.safety-desc {
  font-size: 12px;
  color: #bf360c;
  line-height: 1.4;
}
.safety-section {
  margin-bottom: 20px;
  background: #ffffff;
  border-radius: 12px;
  padding: 16px;
  border: 1px solid #f0f0f0;
}
.safety-section-title {
  font-size: 14px;
  font-weight: 500;
  color: #333;
  margin-bottom: 12px;
}
// PPE列表
.ppe-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ppe-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  background: #f8f9fa;
  border-radius: 8px;
}
.ppe-icon {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ppe-emoji {
  font-size: 20px;
}
.ppe-info {
  flex: 1;
}
.ppe-name {
  font-size: 13px;
  font-weight: 500;
  color: #333;
  display: block;
  margin-bottom: 2px;
}
.ppe-desc {
  font-size: 11px;
  color: #666;
}
// å®‰å…¨è­¦å‘Š
.safety-warnings {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.safety-warning {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 0;
}
.warning-icon {
  margin-top: 2px;
}
.warning-content {
  flex: 1;
  font-size: 12px;
  color: #555;
  line-height: 1.4;
}
// åº”急处理
.emergency-procedures {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.emergency-item {
  border: 1px solid #ffcdd2;
  border-radius: 8px;
  overflow: hidden;
}
.emergency-header {
  background: #ffebee;
  padding: 12px 16px;
  border-bottom: 1px solid #ffcdd2;
}
.emergency-title {
  font-size: 13px;
  font-weight: 500;
  color: #c62828;
}
.emergency-steps {
  padding: 12px 16px;
}
.emergency-step {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 8px;
  &:last-child {
    margin-bottom: 0;
  }
}
.step-number {
  font-size: 12px;
  font-weight: 500;
  color: #d32f2f;
  min-width: 16px;
}
.step-content {
  font-size: 12px;
  color: #555;
  line-height: 1.4;
}
// å®‰å…¨ç¡®è®¤
.safety-confirmation {
  background: #e8f5e8;
  border-radius: 12px;
  padding: 16px;
  border: 1px solid #c8e6c9;
}
.confirmation-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.confirmation-title {
  font-size: 14px;
  font-weight: 500;
  color: #2e7d32;
}
.confirmation-content {
  margin-bottom: 16px;
}
.confirmation-text {
  font-size: 12px;
  color: #388e3c;
  line-height: 1.4;
}
.confirmation-actions {
  display: flex;
  justify-content: center;
}
// åº•部操作按钮
.bottom-actions {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: #ffffff;
  padding: 16px 20px;
  border-top: 1px solid #f0f0f0;
  display: flex;
  gap: 12px;
  z-index: 100;
}
.bottom-actions .u-button {
  flex: 1;
}
</style>
src/pages/index.vue
@@ -275,6 +275,11 @@
        icon: 'flash',
        label: '智能派单',
        bgColor: '#ff6b35'
    },
    {
        icon: 'file-text',
        label: '作业指导',
        bgColor: '#4caf50'
    }
]);
@@ -377,6 +382,11 @@
                url: '/pages/equipmentManagement/smartDispatch/index'
            });
            break;
        case '作业指导':
            uni.navigateTo({
                url: '/pages/equipmentManagement/sop/index'
            });
            break;
        default:
            uni.showToast({
                title: `点击了${item.label}`,