| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599 |
- <template>
- <view class="container">
- <!-- 设备头部信息区域 -->
- <view class="device-header">
- <view class="device-info-row">
- <view class="device-name-container">
- <text class="device-name">{{ deviceInfo.machineName }}</text>
- <view
- class="status-tag"
- :class="deviceInfo.onlineStatus == 1 ? 'status-online' : 'status-offline'"
- >
- <view class="status-dot" :class="{'offline-dot': deviceInfo.onlineStatus == 0}"></view>
- {{ deviceInfo.onlineStatus == 1 ? '在线' : '离线' }}
- </view>
- </view>
-
- <view class="refresh-btn" :class="{'refreshing': isRefreshing}" @tap="refreshData">
- <image src="/static/icons/refresh_icon.png" mode="aspectFit" style="width: 22px; height: 22px;"></image>
- </view>
- </view>
-
- <view class="device-meta-row">
- <view class="device-meta-item">
- <view class="meta-icon">
- <image src="/static/icons/device_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
- </view>
- <text class="meta-label">设备编号:</text>
- <text class="meta-value">{{ deviceInfo.machineCode }}</text>
- </view>
-
- <view class="device-meta-item">
- <view class="meta-icon">
- <image src="/static/icons/location_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
- </view>
- <text class="meta-label">工作区域:</text>
- <text class="meta-value">{{ deviceInfo.location }}</text>
- </view>
-
- <view class="device-meta-item">
- <view class="meta-icon">
- <image src="/static/icons/clock_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
- </view>
- <text class="meta-label">最近更新:</text>
- <text class="meta-value">{{ deviceInfo.updateTime }}</text>
- </view>
- </view>
- </view>
- <!-- 作业任务列表(竖向排列) -->
- <view class="task-section">
- <view class="section-title task-title-row">
- <view class="task-title-left">
- <text class="task-title-text">作业任务列表</text>
- <text class="task-title-sub">配置并选择当前设备的作业</text>
- </view>
- <view class="task-add-btn" @click.stop="goToCreateJob">
- <text class="task-add-plus">+</text>
- </view>
- </view>
- <view v-if="taskList.length" class="task-list">
- <view
- v-for="task in taskList"
- :key="task.id"
- class="task-card"
- :class="{ active: task.id === selectedTaskId }"
- @click="selectTask(task)"
- >
- <view class="task-header">
- <view class="task-icon">
- <image src="/static/icons/task_icon.png" mode="aspectFit" class="task-icon-img"></image>
- </view>
- <view class="task-title-wrap">
- <text class="task-title">{{ task.name }}</text>
- <text class="task-sub">{{ task.fieldArea }}</text>
- </view>
- <view class="task-area-type">{{ task.areaTypeText }}</view>
- <view class="task-radio">
- <view class="radio-outer" :class="{ checked: task.id === selectedTaskId }">
- <view class="radio-inner"></view>
- </view>
- </view>
- </view>
- <view class="task-meta">
- <text class="task-time">计划时间:{{ task.planTime }}</text>
- <view class="task-meta-right">
- <text class="task-status">{{ task.statusText }}</text>
- <button class="task-delete-btn" @click.stop="confirmDeleteTask(task)">删除</button>
- </view>
- </view>
- </view>
- </view>
- <view v-else class="task-empty">
- <text class="task-empty-text">当前设备暂无作业任务</text>
- </view>
- </view>
- <!-- 设备状态信息区域 -->
- <!-- <view class="status-section">
- <view class="section-title">
- <text>设备状态</text>
- </view>
-
- <view class="status-grid">
- <!-- 作业状态
- <view class="status-item" hover-class="status-item-hover">
- <text class="status-label">作业状态</text>
- <view class="status-value-container">
- <text class="status-value" :class="{
- 'status-working': deviceInfo.workStatus === 'working',
- 'status-idle': deviceInfo.workStatus === 'idle',
- 'status-error': deviceInfo.workStatus === 'error'
- }">{{ getWorkStatusText(deviceInfo.workStatus) }}</text>
- </view>
- <text class="status-time">{{ deviceInfo.statusUpdateTime || '1分钟前' }}</text>
- </view>
-
- <!-- 电量状态
- <view class="status-item" hover-class="status-item-hover">
- <text class="status-label">电量</text>
- <view class="status-value-container">
- <text class="status-value" :class="{
- 'value-warning': deviceInfo.battery < 30,
- 'value-alert': deviceInfo.battery < 15
- }">{{ deviceInfo.battery || 85 }}</text>
- <text class="status-unit">%</text>
- </view>
- <text class="status-time">{{ deviceInfo.batteryUpdateTime || '30秒前' }}</text>
- </view>
-
- <!-- GPS信号
- <view class="status-item" hover-class="status-item-hover">
- <text class="status-label">GPS信号</text>
- <view class="status-value-container">
- <text class="status-value" :class="{
- 'status-good': deviceInfo.gpsSignal >= 80,
- 'status-normal': deviceInfo.gpsSignal >= 60 && deviceInfo.gpsSignal < 80,
- 'status-weak': deviceInfo.gpsSignal < 60
- }">{{ getGpsSignalText(deviceInfo.gpsSignal) }}</text>
- </view>
- <text class="status-time">{{ deviceInfo.gpsUpdateTime || '15秒前' }}</text>
- </view>
-
- <!-- 工作时长
- <view class="status-item" hover-class="status-item-hover">
- <text class="status-label">今日工作</text>
- <view class="status-value-container">
- <text class="status-value">{{ deviceInfo.workDuration || '2.5' }}</text>
- <text class="status-unit">小时</text>
- </view>
- <text class="status-time">累计时长</text>
- </view>
-
- <!-- 作业面积
- <view class="status-item" hover-class="status-item-hover">
- <text class="status-label">今日面积</text>
- <view class="status-value-container">
- <text class="status-value">{{ deviceInfo.workArea || '15.6' }}</text>
- <text class="status-unit">亩</text>
- </view>
- <text class="status-time">已完成</text>
- </view>
-
- <!-- 当前速度
- <view class="status-item" hover-class="status-item-hover">
- <text class="status-label">当前速度</text>
- <view class="status-value-container">
- <text class="status-value">{{ deviceInfo.currentSpeed || '0' }}</text>
- <text class="status-unit">km/h</text>
- </view>
- <text class="status-time">{{ deviceInfo.speedUpdateTime || '实时' }}</text>
- </view>
- </view>
- </view> -->
- <!-- 设备控制区域
- <view class="control-section">
- <view class="section-title">
- <text>设备控制</text>
- <view class="engine-status" :class="{'engine-on': isEngineOn}">
- <text>{{ isEngineOn ? '引擎开启' : '引擎关闭' }}</text>
- </view>
- </view>
-
- <view class="control-container">
- <!-- 启动/停止按钮
- <view class="engine-control">
- <view
- class="engine-button"
- :class="{'engine-on': isEngineOn, 'disabled': deviceInfo.status === 'offline'}"
- @click="toggleEngine"
- >
- <view class="engine-icon">
- <image
- :src="isEngineOn ? '/static/icons/engine_stop.svg' : '/static/icons/engine_start.svg'"
- mode="aspectFit"
- style="width: 28px; height: 28px;"
- ></image>
- </view>
- <text class="engine-text">{{ isEngineOn ? '停止' : '启动' }}</text>
- </view>
- </view>
-
- <!-- 方向控制
- <view class="direction-control">
- <!-- 前进按钮
- <view class="control-btn control-forward"
- :class="{'disabled': !isEngineOn || deviceInfo.status === 'offline', 'active': activeControl === 'forward'}"
- @touchstart="startControl('forward')"
- @touchend="stopControl">
- <image src="/static/icons/arrow_up.svg" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- <text>前进</text>
- </view>
-
- <!-- 左右控制行
- <view class="control-row">
- <!-- 左转按钮
- <view class="control-btn control-left"
- :class="{'disabled': !isEngineOn || deviceInfo.status === 'offline', 'active': activeControl === 'left'}"
- @touchstart="startControl('left')"
- @touchend="stopControl">
- <image src="/static/icons/arrow_left.svg" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- <text>左转</text>
- </view>
-
- <!-- 中心状态显示
- <view class="control-center">
- <view class="speed-display">
- <text class="speed-value">{{ currentSpeed }}</text>
- <text class="speed-unit">km/h</text>
- </view>
- </view>
-
- <!-- 右转按钮
- <view class="control-btn control-right"
- :class="{'disabled': !isEngineOn || deviceInfo.status === 'offline', 'active': activeControl === 'right'}"
- @touchstart="startControl('right')"
- @touchend="stopControl">
- <image src="/static/icons/arrow_right.svg" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- <text>右转</text>
- </view>
- </view>
-
- <!-- 后退按钮
- <view class="control-btn control-backward"
- :class="{'disabled': !isEngineOn || deviceInfo.status === 'offline', 'active': activeControl === 'backward'}"
- @touchstart="startControl('backward')"
- @touchend="stopControl">
- <image src="/static/icons/arrow_down.svg" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- <text>后退</text>
- </view>
- </view>
-
- <!-- 快捷操作按钮
- <view class="quick-controls">
- <view class="quick-control-btn"
- :class="{'disabled': deviceInfo.status === 'offline'}"
- @click="emergencyStop">
- <view class="quick-icon emergency">
- <image src="/static/icons/emergency_stop.svg" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- </view>
- <text>紧急停止</text>
- </view>
-
- <view class="quick-control-btn"
- :class="{'disabled': deviceInfo.status === 'offline'}"
- @click="returnHome">
- <view class="quick-icon">
- <image src="/static/icons/home.svg" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- </view>
- <text>返回充电</text>
- </view>
-
- <view class="quick-control-btn"
- :class="{'disabled': deviceInfo.status === 'offline'}"
- @click="autoMode">
- <view class="quick-icon">
- <image src="/static/icons/auto_mode.svg" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- </view>
- <text>自动模式</text>
- </view>
- </view>
- </view>
- </view>-->
- <!-- 底部“开始作业”按钮 -->
- <view class="task-footer">
- <button
- class="start-btn"
- :class="{ disabled: !taskList.length || !selectedTaskId || deviceInfo.onlineStatus !== 1 }"
- @click="startWork"
- >
- 开始作业
- </button>
- </view>
- <!-- 告警信息列表 -->
- <view class="alerts-section">
- <view class="section-title">
- <text>告警信息</text>
- <view class="alert-badge" v-if="getUnhandledAlerts.length > 0">{{ getUnhandledAlerts.length }}</view>
- </view>
-
- <view class="alerts-list" v-if="getUnhandledAlerts.length > 0">
- <view
- v-for="(item, index) in getUnhandledAlerts"
- :key="index"
- class="alert-item"
- :class="{
- 'alert-urgent': item.level === 'high',
- 'alert-warning': item.level === 'medium',
- 'alert-info': item.level === 'low'
- }"
- @click="handleAlert(item)"
- >
- <view class="alert-item-icon">
- <image v-if="item.level === 'high'" src="/static/icons/warning_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- <image v-else-if="item.level === 'medium'" src="/static/icons/info_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- <image v-else src="/static/icons/success_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
- </view>
-
- <view class="alert-item-info">
- <text class="alert-item-type">{{ item.title }}</text>
- <text class="alert-item-level">
- {{ item.level === 'high' ? '紧急' : item.level === 'medium' ? '警告' : '提示' }}
- </text>
- </view>
-
- <view class="alert-item-time">{{ item.time }}</view>
- </view>
- </view>
-
- <view v-else class="empty-alert">
- <text class="empty-text">暂无告警信息</text>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, reactive, computed, onMounted } from 'vue'
- import { onLoad } from '@dcloudio/uni-app'
- import { machineAlarmRecordsList } from '@/api/services/machineAlarmRecords'
- import { deviceTasksList, startTask, deleteTask } from '@/api/services/job'
- // 响应式数据
- const deviceInfo = reactive({
- id: '',
- machineCode: '',
- machineName: '',
- onlineStatus: 0,
- updateTime: '',
- location: ''
- })
- // 作业任务列表
- const taskList = ref([])
- const selectedTaskId = ref(null)
- // 控制状态
- const isEngineOn = ref(false)
- const activeControl = ref('') // forward, backward, left, right
- const currentSpeed = ref(0)
- // 页面状态
- const isRefreshing = ref(false)
- const alarmTypeMap = {
- 0: '其他',
- 1: '发动机',
- 2: '燃油',
- 3: '温度',
- 4: '压力',
- 5: '定位',
- }
- // 告警数据
- const alerts = ref([])
- // 计算属性
- // 未处理的告警
- const getUnhandledAlerts = computed(() => {
- return alerts.value.filter(alert => !alert.handled)
- })
- // 方法
- // 加载设备告警数据
- const loadDeviceAlarmData = () => {
- return machineAlarmRecordsList({
- machineId: deviceInfo.id
- }).then(res => {
- console.log('设备告警数据:', res)
- if (res.data.code === 200 && res.data.rows) {
- alerts.value = res.data.rows.map(item => ({
- id: item.id,
- title: item.alarmDesc,
- level: item.alarmLevel === 3 ? 'high' : item.alarmLevel === 2 ? 'medium' : 'low',
- time: item.alarmTime
- }))
- }
- })
- }
- const formatAlarmType = (level) => {
- return alarmTypeMap[level] || '其他'
- }
- // 加载设备数据
- const loadDeviceData = () => {
- console.log('加载设备数据:', deviceInfo.deviceId)
- }
- // 加载当前设备的作业任务列表
- const loadTaskList = () => {
- const params = {
- pageNum: 1,
- pageSize: 10,
- deviceId: deviceInfo.id
- }
- return deviceTasksList(params)
- .then((res) => {
- console.log("res任务作业列表",res)
-
- const { data } = res || {}
- if (data && data.code === 200) {
- const list = (data.rows) ? data.rows : []
- const areaTypeTextMap = {
- 1: '回字形',
- 2: '弓字形',
- 3: '自定义',
- 4: '垄沟'
- }
- taskList.value = list.map((item) => ({
- id: item.id,
- name: item.taskName,
- fieldArea: item.workArea ? item.workArea.areaName : '',
- planTime: item.createTime,
- status: item.taskStatus,
- statusText: item.taskStatusDesc,
- areaType: item.workAreas && item.workAreas.areaType,
- areaTypeText: areaTypeTextMap[item.workAreas && item.workAreas.areaType || 0] || '未知'
- }))
- console.log('taskList',taskList.value)
- if (taskList.value.length) {
- const exists = selectedTaskId.value && taskList.value.some(t => t.id === selectedTaskId.value)
- selectedTaskId.value = exists ? selectedTaskId.value : taskList.value[0].id
- } else {
- selectedTaskId.value = null
- }
- } else {
- taskList.value = []
- selectedTaskId.value = null
- uni.showToast({
- title: (data && data.msg) ? data.msg : '获取作业任务失败',
- icon: 'none'
- })
- }
- })
- .catch((err) => {
- console.error('获取作业任务列表失败:', err)
- taskList.value = []
- selectedTaskId.value = null
- uni.showToast({
- title: '获取作业任务失败',
- icon: 'none'
- })
- })
- }
- // 刷新数据
- const refreshData = () => {
- isRefreshing.value = true
- Promise.all([
- loadTaskList(),
- loadDeviceAlarmData()
- ]).finally(() => {
- isRefreshing.value = false
- deviceInfo.lastUpdate = '刚刚'
- uni.showToast({
- title: '刷新成功',
- icon: 'success',
- duration: 1500
- })
- })
- }
- // 获取工作状态文本
- const getWorkStatusText = (status) => {
- const statusMap = {
- working: '工作中',
- idle: '空闲',
- error: '故障'
- }
- return statusMap[status] || '未知'
- }
- // 获取GPS信号文本
- const getGpsSignalText = (signal) => {
- if (signal >= 80) return '强'
- if (signal >= 60) return '中等'
- if (signal >= 40) return '弱'
- return '无信号'
- }
- // 切换引擎状态
- const toggleEngine = () => {
- if (deviceInfo.status === 'offline') {
- uni.showToast({
- title: '设备离线,无法操作',
- icon: 'none'
- })
- return
- }
-
- isEngineOn.value = !isEngineOn.value
-
- if (isEngineOn.value) {
- deviceInfo.workStatus = 'idle'
- uni.showToast({
- title: '引擎已启动',
- icon: 'success'
- })
- } else {
- deviceInfo.workStatus = 'idle'
- currentSpeed.value = 0
- stopAllControls()
- uni.showToast({
- title: '引擎已关闭',
- icon: 'success'
- })
- }
- }
- // 开始控制
- const startControl = (direction) => {
- if (!isEngineOn.value || deviceInfo.status === 'offline') {
- return
- }
-
- activeControl.value = direction
- deviceInfo.workStatus = 'working'
-
- switch (direction) {
- case 'forward':
- currentSpeed.value = 5
- break
- case 'backward':
- currentSpeed.value = 2
- break
- case 'left':
- case 'right':
- currentSpeed.value = 3
- break
- }
-
- sendControlCommand(direction, true)
- }
- // 停止控制
- const stopControl = () => {
- if (activeControl.value) {
- sendControlCommand(activeControl.value, false)
- }
-
- activeControl.value = ''
- currentSpeed.value = 0
- deviceInfo.workStatus = 'idle'
- }
- // 停止所有控制
- const stopAllControls = () => {
- activeControl.value = ''
- currentSpeed.value = 0
- if (isEngineOn.value) {
- deviceInfo.workStatus = 'idle'
- }
- }
- // 发送控制指令
- const sendControlCommand = (direction, isStart) => {
- console.log(`发送控制指令: ${direction}, 开始: ${isStart}`)
- }
- // 紧急停止
- const emergencyStop = () => {
- if (deviceInfo.status === 'offline') {
- uni.showToast({
- title: '设备离线,无法操作',
- icon: 'none'
- })
- return
- }
-
- uni.showModal({
- title: '紧急停止',
- content: '确定要执行紧急停止吗?设备将立即停止所有操作。',
- success: (res) => {
- if (res.confirm) {
- isEngineOn.value = false
- stopAllControls()
-
- uni.showToast({
- title: '紧急停止已执行',
- icon: 'success'
- })
- }
- }
- })
- }
- // 返回充电
- const returnHome = () => {
- if (deviceInfo.status === 'offline') {
- uni.showToast({
- title: '设备离线,无法操作',
- icon: 'none'
- })
- return
- }
-
- uni.showToast({
- title: '设备正在返回充电站',
- icon: 'success'
- })
- }
- // 自动模式
- const autoMode = () => {
- if (deviceInfo.status === 'offline') {
- uni.showToast({
- title: '设备离线,无法操作',
- icon: 'none'
- })
- return
- }
-
- uni.showToast({
- title: '已切换到自动模式',
- icon: 'success'
- })
- }
- // 跳转到新增作业页面
- const goToCreateJob = () => {
- uni.navigateTo({
- url: `/pages/device/job-create/index?machineCode=${deviceInfo.machineCode}&id=${deviceInfo.id}`
- })
- }
- // 选择作业任务
- const selectTask = (task) => {
- selectedTaskId.value = task.id
- }
- // 开始作业
- const startWork = () => {
- if (!taskList.value.length) {
- uni.showToast({
- title: '暂无可执行的作业任务',
- icon: 'none'
- })
- return
- }
- if (!selectedTaskId.value) {
- uni.showToast({
- title: '请先选择一个作业任务',
- icon: 'none'
- })
- return
- }
- if (deviceInfo.onlineStatus !== 1) {
- uni.showToast({
- title: '设备未在线,无法开始作业',
- icon: 'none'
- })
- return
- }
- const task = taskList.value.find(t => t.id === selectedTaskId.value)
- uni.showModal({
- title: '开始作业',
- content: `确定开始执行「${task.name}」吗?`,
- success: async (res) => {
- if (res.confirm) {
- try {
- uni.showLoading({ title: '启动中...' })
- const resp = await startTask(selectedTaskId.value)
- const { data } = resp || {}
- if (data && data.code === 200) {
- uni.showToast({
- title: '作业已启动',
- icon: 'success'
- })
- isEngineOn.value = true
- deviceInfo.workStatus = 'working'
- setTimeout(() => {
- uni.navigateTo({
- url: `/pages/device/job-detail/index?id=${selectedTaskId.value}&deviceId=${deviceInfo.machineCode}&deviceName=${deviceInfo.machineName}`
- })
- }, 300)
- } else {
- uni.showToast({
- title: (data && data.msg) ? data.msg : '启动失败',
- icon: 'none'
- })
- }
- } catch (e) {
- console.error('开始作业失败:', e)
- uni.showToast({
- title: '网络异常,启动失败',
- icon: 'none'
- })
- } finally {
- uni.hideLoading()
- }
- }
- }
- })
- }
- // 删除作业
- const confirmDeleteTask = (task) => {
- if (!task || !task.id) return
- uni.showModal({
- title: '确认删除',
- content: `确定删除作业「${task.name}」吗?删除后不可恢复。`,
- confirmText: '删除',
- confirmColor: '#F56C6C',
- success: async (res) => {
- if (!res.confirm) return
- try {
- uni.showLoading({ title: '删除中...' })
- const resp = await deleteTask(task.id)
- const { data } = resp || {}
- if (data && data.code === 200) {
- uni.showToast({ title: '删除成功', icon: 'success' })
- if (selectedTaskId.value === task.id) {
- selectedTaskId.value = null
- }
- await loadTaskList()
- } else {
- uni.showToast({
- title: (data && data.msg) ? data.msg : '删除失败',
- icon: 'none'
- })
- }
- } catch (e) {
- console.error('删除作业失败:', e)
- uni.showToast({ title: '网络异常,删除失败', icon: 'none' })
- } finally {
- uni.hideLoading()
- }
- }
- })
- }
- // 处理告警
- const handleAlert = (alert) => {
- uni.showModal({
- title: alert.title,
- content: alert.description + '\n\n是否标记为已处理?',
- success: (res) => {
- if (res.confirm) {
- const index = alerts.value.findIndex(item => item.id === alert.id)
- if (index !== -1) {
- alerts.value[index].handled = true
- }
-
- uni.showToast({
- title: '已标记为处理',
- icon: 'success'
- })
- }
- }
- })
- }
- // 生命周期钩子
- onMounted(() => {
- // uni-app 生命周期
- uni.$once('agriculturalMachinesData', (data) => {
- if (data) {
- deviceInfo.machineCode = data.machineCode
- deviceInfo.machineName = data.machineName
- deviceInfo.onlineStatus = data.onlineStatus
- deviceInfo.updateTime = data.updateTime
- }
- })
- uni.setNavigationBarTitle({
- title: '农机设备详情'
- })
-
- loadDeviceData()
- loadTaskList()
- loadDeviceAlarmData()
- })
- onLoad((options)=>{
- deviceInfo.id = options.id;
- })
- </script>
- <style scoped>
- .container {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
- background-color: #F8FCF9;
- padding-bottom: 30rpx;
- }
- /* 设备头部信息 */
- .device-header {
- background-color: #FFFFFF;
- border-radius: 20rpx;
- padding: 30rpx;
- margin: 30rpx 30rpx 30rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
- border: 1rpx solid rgba(210, 237, 217, 0.5);
- }
- .device-info-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 28rpx;
- }
- .device-name-container {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- }
- .device-name {
- font-size: 36rpx;
- color: #333333;
- font-weight: 600;
- margin-bottom: 12rpx;
- }
- .status-tag {
- padding: 6rpx 16rpx 6rpx 28rpx;
- border-radius: 30rpx;
- font-size: 24rpx;
- font-weight: 500;
- flex-shrink: 0;
- position: relative;
- overflow: hidden;
- }
- .status-online {
- background-color: rgba(76, 175, 80, 0.1);
- color: #3BB44A;
- }
- .status-offline {
- background-color: rgba(245, 108, 108, 0.1);
- color: #F56C6C;
- padding-left: 28rpx;
- }
- .status-dot {
- position: absolute;
- width: 6rpx;
- height: 6rpx;
- background-color: #3BB44A;
- border-radius: 50%;
- top: 50%;
- left: 12rpx;
- transform: translateY(-50%);
- box-shadow: 0 0 4rpx rgba(76, 175, 80, 0.8);
- animation: blink 1.5s infinite;
- display: inline-block;
- }
- .status-dot.offline-dot {
- background-color: #F56C6C;
- box-shadow: 0 0 4rpx rgba(245, 108, 108, 0.8);
- }
- .refresh-btn {
- width: 48rpx;
- height: 48rpx;
- background-color: rgba(76, 175, 80, 0.1);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 12rpx;
- transition: transform 0.3s ease;
- }
- .refresh-btn:active {
- transform: rotate(180deg);
- }
- .refresh-btn.refreshing {
- animation: spin 1.2s linear infinite;
- }
- @keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
- }
- @keyframes blink {
- 0% {
- opacity: 0.4;
- }
- 50% {
- opacity: 1;
- }
- 100% {
- opacity: 0.4;
- }
- }
- .device-meta-row {
- display: flex;
- flex-direction: column;
- background-color: #F9FCFA;
- padding: 20rpx 24rpx;
- border-radius: 16rpx;
- border: 1rpx solid rgba(210, 237, 217, 0.8);
- }
- .device-meta-item {
- display: flex;
- align-items: center;
- font-size: 28rpx;
- margin-top: 18rpx;
- }
- .device-meta-item:first-child {
- margin-top: 0;
- }
- .meta-icon {
- width: 36rpx;
- height: 36rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 12rpx;
- }
- .meta-label {
- color: #777777;
- min-width: 140rpx;
- font-size: 28rpx;
- }
- .meta-value {
- color: #333333;
- flex: 1;
- font-size: 28rpx;
- font-weight: 500;
- }
- /* 设备状态区域 */
- .status-section {
- background: #ffffff;
- margin: 0 24rpx 20rpx;
- padding: 32rpx 24rpx;
- border-radius: 20rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
- }
- .section-title {
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 32rpx;
- font-weight: 600;
- color: #333333;
- margin-bottom: 24rpx;
- padding-bottom: 16rpx;
- border-bottom: 2rpx solid #f0f0f0;
- }
- .status-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 20rpx;
- }
- .status-item {
- background: #f8f9fa;
- padding: 24rpx;
- border-radius: 16rpx;
- border: 2rpx solid transparent;
- transition: all 0.3s ease;
- }
- .status-item-hover {
- border-color: #40a9ff;
- box-shadow: 0 4rpx 12rpx rgba(64, 169, 255, 0.15);
- }
- .status-label {
- font-size: 24rpx;
- color: #999999;
- margin-bottom: 8rpx;
- display: block;
- }
- .status-value-container {
- display: flex;
- align-items: baseline;
- margin-bottom: 8rpx;
- }
- .status-value {
- font-size: 36rpx;
- font-weight: 600;
- color: #333333;
- }
- .status-unit {
- font-size: 24rpx;
- color: #666666;
- margin-left: 4rpx;
- }
- .status-time {
- font-size: 20rpx;
- color: #cccccc;
- }
- /* 状态颜色 */
- .status-working {
- color: #52c41a;
- }
- .status-idle {
- color: #1890ff;
- }
- .status-error {
- color: #ff4d4f;
- }
- .status-good {
- color: #52c41a;
- }
- .status-normal {
- color: #faad14;
- }
- .status-weak {
- color: #ff4d4f;
- }
- .value-warning {
- color: #faad14;
- }
- .value-alert {
- color: #ff4d4f;
- }
- /* 控制区域 */
- .control-section {
- background: #ffffff;
- margin: 0 24rpx 20rpx;
- padding: 32rpx 24rpx;
- border-radius: 20rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
- }
- .engine-status {
- padding: 8rpx 16rpx;
- border-radius: 20rpx;
- font-size: 24rpx;
- background-color: #fff1f0;
- color: #ff4d4f;
- }
- .engine-status.engine-on {
- background-color: #e8f5e8;
- color: #52c41a;
- }
- .control-container {
- display: flex;
- flex-direction: column;
- gap: 32rpx;
- }
- /* 引擎控制 */
- .engine-control {
- display: flex;
- justify-content: center;
- }
- .engine-button {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 24rpx;
- background: #ffffff;
- border-radius: 20rpx;
- border: 3rpx solid #4CAF50;
- transition: all 0.3s ease;
- width: 160rpx;
- box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.2);
- }
- .engine-button.engine-on {
- background: #F44336;
- border-color: #F44336;
- color: white;
- box-shadow: 0 4rpx 12rpx rgba(244, 67, 54, 0.3);
- }
- .engine-button:not(.engine-on) {
- background: #4CAF50;
- border-color: #4CAF50;
- color: white;
- }
- .engine-button.disabled {
- opacity: 0.3;
- pointer-events: none;
- background: #DDDDDD;
- border-color: #DDDDDD;
- }
- .engine-button:active {
- transform: scale(0.95);
- }
- .engine-icon {
- margin-bottom: 12rpx;
- }
- .engine-text {
- font-size: 28rpx;
- font-weight: 600;
- }
- /* 方向控制 */
- .direction-control {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 20rpx;
- }
- .control-row {
- display: flex;
- align-items: center;
- gap: 20rpx;
- }
- .control-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- width: 120rpx;
- height: 120rpx;
- background: #f8f9fa;
- border-radius: 12rpx;
- border: 2rpx solid #e0e0e0;
- transition: all 0.3s ease;
- font-size: 24rpx;
- color: #999999;
- gap: 8rpx;
- }
- .control-btn.active {
- background: rgba(76, 175, 80, 0.1);
- border-color: #4CAF50;
- color: #4CAF50;
- transform: scale(0.9);
- box-shadow: 0 2rpx 8rpx rgba(76, 175, 80, 0.3);
- }
- .control-btn.disabled {
- opacity: 0.3;
- pointer-events: none;
- background: #DDDDDD;
- border-color: #DDDDDD;
- }
- .control-center {
- width: 120rpx;
- height: 120rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background: #f0f0f0;
- border-radius: 20rpx;
- border: 2rpx solid #d0d0d0;
- }
- .speed-display {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .speed-value {
- font-size: 32rpx;
- font-weight: 600;
- color: #333333;
- }
- .speed-unit {
- font-size: 20rpx;
- color: #999999;
- }
- /* 快捷控制 */
- .quick-controls {
- display: flex;
- justify-content: space-around;
- padding-top: 20rpx;
- border-top: 2rpx solid #f0f0f0;
- }
- .quick-control-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8rpx;
- padding: 16rpx;
- border-radius: 16rpx;
- transition: all 0.3s ease;
- }
- .quick-control-btn.disabled {
- opacity: 0.5;
- pointer-events: none;
- }
- .quick-control-btn:active {
- background: #f0f0f0;
- }
- .quick-icon {
- width: 48rpx;
- height: 48rpx;
- background: #f8f9fa;
- border-radius: 12rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- border: 2rpx solid #e0e0e0;
- }
- .quick-icon.emergency {
- background: rgba(245, 108, 108, 0.1);
- border-color: #F56C6C;
- }
- .quick-control-btn text {
- font-size: 24rpx;
- color: #666666;
- }
- /* 告警信息 */
- .alerts-section {
- margin: 0 30rpx 30rpx;
- background-color: #FFFFFF;
- border-radius: 20rpx;
- padding: 30rpx 24rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
- border: 1rpx solid rgba(210, 237, 217, 0.5);
- }
- .alert-badge {
- background-color: #F56C6C;
- color: #FFFFFF;
- font-size: 22rpx;
- border-radius: 30rpx;
- padding: 2rpx 12rpx;
- margin-left: 12rpx;
- font-weight: normal;
- min-width: 32rpx;
- text-align: center;
- }
- .alerts-list {
- display: flex;
- flex-direction: column;
- gap: 16rpx;
- }
- .alert-item {
- display: flex;
- align-items: center;
- padding: 24rpx 20rpx;
- border-radius: 12rpx;
- margin-bottom: 16rpx;
- box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
- position: relative;
- border: 1rpx solid transparent;
- transition: transform 0.2s ease;
- }
- .alert-item:active {
- transform: scale(0.98);
- }
- .alert-urgent {
- background-color: #FEF3F3;
- border-left: 4rpx solid #F56C6C;
- border-color: rgba(245, 108, 108, 0.2);
- }
- .alert-warning {
- background-color: #FFF8E6;
- border-left: 4rpx solid #E6A23C;
- border-color: rgba(230, 162, 60, 0.2);
- }
- .alert-info {
- background-color: #F2FAF5;
- border-left: 4rpx solid #67C23A;
- border-color: rgba(103, 194, 58, 0.2);
- }
- .alert-item-icon {
- width: 50rpx;
- height: 50rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 16rpx;
- }
- .alert-item-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 8rpx;
- }
- .alert-item-type {
- font-size: 28rpx;
- color: #333333;
- font-weight: 500;
- margin-bottom: 8rpx;
- }
- .alert-item-level {
- font-size: 24rpx;
- color: #999999;
- }
- .alert-item-time {
- font-size: 24rpx;
- color: #999999;
- margin-left: 16rpx;
- min-width: 100rpx;
- text-align: right;
- }
- .empty-alert {
- padding: 60rpx 0;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .empty-text {
- font-size: 28rpx;
- color: #999999;
- }
- /* 作业任务列表 */
- .task-section {
- background: #ffffff;
- margin: 0 24rpx 20rpx;
- padding: 28rpx 24rpx 20rpx;
- border-radius: 20rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
- }
- .task-list {
- margin-top: 8rpx;
- }
- .task-card {
- background: #f9fbfa;
- border-radius: 18rpx;
- padding: 20rpx 18rpx;
- margin-bottom: 16rpx;
- border: 1rpx solid rgba(0, 0, 0, 0.03);
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
- }
- .task-card.active {
- border-color: #3bb44a;
- background: #f2fff4;
- box-shadow: 0 4rpx 14rpx rgba(59, 180, 74, 0.12);
- }
- .task-header {
- display: flex;
- align-items: center;
- }
- .task-icon {
- width: 60rpx;
- height: 60rpx;
- border-radius: 16rpx;
- background: linear-gradient(135deg, #66cc6a, #3bb44a);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 16rpx;
- }
- .task-icon-img {
- width: 34rpx;
- height: 34rpx;
- }
- .task-area-type {
- font-size: 22rpx;
- color: #888;
- margin-left: 8rpx;
- flex-shrink: 0;
- }
- .task-title-wrap {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- .task-title {
- font-size: 30rpx;
- font-weight: 600;
- color: #2c3e50;
- }
- .task-sub {
- margin-top: 6rpx;
- font-size: 24rpx;
- color: #8c9396;
- }
- .task-radio {
- margin-left: 12rpx;
- }
- .radio-outer {
- width: 32rpx;
- height: 32rpx;
- border-radius: 16rpx;
- border: 2rpx solid #c0c4cc;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .radio-outer.checked {
- border-color: #3bb44a;
- background: rgba(59, 180, 74, 0.1);
- }
- .radio-inner {
- width: 18rpx;
- height: 18rpx;
- border-radius: 9rpx;
- background: #3bb44a;
- opacity: 0;
- }
- .radio-outer.checked .radio-inner {
- opacity: 1;
- }
- .task-meta {
- margin-top: 12rpx;
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 24rpx;
- }
- .task-time {
- color: #909399;
- }
- .task-meta-right {
- display: flex;
- align-items: center;
- gap: 12rpx;
- }
- .task-status {
- color: #3bb44a;
- }
- .task-delete-btn {
- height: 52rpx;
- line-height: 52rpx;
- padding: 0 16rpx;
- border-radius: 26rpx;
- background-color: rgba(245, 108, 108, 0.12);
- color: #F56C6C;
- font-size: 22rpx;
- }
- .task-empty {
- padding: 40rpx 0 10rpx;
- display: flex;
- justify-content: center;
- }
- .task-empty-text {
- font-size: 26rpx;
- color: #999999;
- }
- .task-footer {
- padding: 12rpx 30rpx 24rpx;
- background: #f8fcf9;
- }
- .start-btn {
- width: 100%;
- height: 88rpx;
- line-height: 88rpx;
- border-radius: 44rpx;
- background: linear-gradient(135deg, #3bb44a, #66cc6a);
- color: #ffffff;
- font-size: 30rpx;
- font-weight: 600;
- }
- .start-btn.disabled {
- background: #dcdfe6;
- color: #ffffff;
- }
- /* 作业标题行加号按钮(与新增作业页保持一致) */
- .task-title-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .task-title-left {
- display: flex;
- flex-direction: column;
- }
- .task-title-text {
- font-size: 32rpx;
- font-weight: 600;
- color: #2c3e50;
- }
- .task-title-sub {
- margin-top: 4rpx;
- font-size: 22rpx;
- color: #8c9396;
- }
- .task-add-btn {
- width: 60rpx;
- height: 60rpx;
- border-radius: 30rpx;
- background: linear-gradient(135deg, #3bb44a, #66cc6a);
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 4rpx 12rpx rgba(59, 180, 74, 0.3);
- }
- .task-add-plus {
- font-size: 40rpx;
- color: #ffffff;
- line-height: 1;
- }
- </style>
|