车间能级提升-智能设备管理系统
baoshiwei
2025-06-27 0a27a9c0f9e1213dff16162d73a03dba0473b96e
eims-ui-mobile/src/pages/maint/maint-order.vue
@@ -2,25 +2,7 @@
{
  layout: 'default',
  needLogin: true,
  style: {
    navigationBarTitleText: '保养工单',
    'app-plus': {
      titleNView: {
        buttons: [
          {
            text: '提交',
            fontSize: '14px',
            color: '#FFFFFF',
          },
          {
            text: '',
            fontSize: '24px',
            color: '#FFFFFF',
          },
        ],
      },
    },
  },
  style: { navigationBarTitleText: '保养工单', navigationStyle: 'custom' },
}
</route>
@@ -30,9 +12,23 @@
    v-model="dataList"
    @query="queryList"
    :auto="false"
    refresher-only
    show-refresher-update-time
  >
    <template #top>
      <wd-navbar
        title="保养工单"
        left-arrow
        @click-left="goBack"
        right-text="提交"
        @click-right="handleClickRight"
        custom-style="background: #4D80F0;"
        safeAreaInsetTop
      >
        <template #right>
          <text v-if="maintSt.status === '0'" class="text-white">提交</text>
        </template>
      </wd-navbar>
      <wd-cell>
        <template #title>
          <text class="text-color-gray">设备</text>
@@ -42,37 +38,46 @@
      <wd-card type="rectangle">
        <template #title>
          <view class="flex justify-between">
            <view class="flex items-center menu-title-box">
              <view class="menu-indicator"></view>
              <view class="ml-1 text-sm align-center">{{ maintSt.equName }}</view>
              <view class="text-color-gray ml-2 text-mini">{{ maintSt.assetNo }}</view>
            <view class="items-center menu-title-box">
              <view class="flex justify-center ml-1 items-center align-center">
                <view class="menu-indicator"></view>
                <view class="ml-1"> {{ maintSt.equName }} </view>
              </view>
              <view class="text-color-gray ml-2 text-xs">{{ maintSt.assetNo }}</view>
            </view>
            <view class="flex items-center">
              <text class="text-color-gray text-mini">{{ maintSt.planTime }}</text>
              <text class="text-color-gray text-sm">{{ maintSt.planTime }}</text>
            </view>
          </view>
        </template>
        <view class="flex h-[140rpx]" items-center>
        <view class="flex" items-center>
          <image class="slot-img text-center" src="/static/images/camera.png" />
          <view class="flex-1">
            <view class="text-color-gray text-xs mt-1 flex">
            <view class="text-color-gray text-sm mt-1 flex">
              <text class="mr-3">工单总数: {{ maintSt.orderCount }}</text>
              |
              <text class="mx-3">已完成: {{ maintSt.wcCount }}</text>
            </view>
            <view class="text-color-gray text-xs mt-2 flex">
              <text class="mr-3">待保养: {{ maintSt.dbyCount }}</text>
              <text class="mx-3">已完成: {{ maintSt.dyzCount }}</text>
              |
              <text class="mx-3">保养中: {{ maintSt.byCount }}</text>
              |
              <text class="ml-3">待验证: {{ maintSt.dyzCount }}</text>
              <text class="mx-3">待保养: {{ maintSt.dbyCount }}</text>
            </view>
            <view class="text-color-gray text-xs mt-2 flex">
<!--            <view class="text-color-gray text-sm mt-2 flex">-->
<!--              <text class="mr-3">待保养: {{ maintSt.dbyCount }}</text>-->
<!--              |-->
<!--              <text class="mx-3">保养中: {{ maintSt.byCount }}</text>-->
<!--&lt;!&ndash;              |&ndash;&gt;-->
<!--&lt;!&ndash;              <text class="ml-3">待验证: {{ maintSt.dyzCount }}</text>&ndash;&gt;-->
<!--            </view>-->
            <view class="text-color-gray text-sm mt-2 flex">
              <text>状态:</text>
              <template v-if="maintSt.status === '1'">
                <wd-icon class="icon-color-success" name="check-outline" size="34rpx"></wd-icon>
                <text class="ml-1">已完成</text>
              </template>
              <template v-else-if="maintSt.status === '2'">
                <wd-icon class="icon-color-warning" name="check-outline" size="34rpx"></wd-icon>
                <text class="ml-1">已确认</text>
              </template>
              <template v-else>
                <wd-icon class="icon-color-base" name="detection" size="40rpx"></wd-icon>
@@ -80,7 +85,7 @@
              </template>
            </view>
            <view class="text-color-gray text-xs mt-2 flex">
            <view class="text-color-gray text-sm mt-2 flex">
              创建时间: {{ maintSt.createTime }}
            </view>
          </view>
@@ -92,168 +97,192 @@
      <view class="w-full h-[24rpx]"></view>
      <wd-cell class="mb-[2px]">
        <template #title>
          <text class="text-color-gray">保养项</text>
          <text class="text-color-gray text-sm">保养项</text>
        </template>
      </wd-cell>
      <wd-card type="rectangle" v-for="(item, index) in dataList" :key="item.id">
      <wd-card type="rectangle" v-for="(item, index) in dataList" :key="item.id" :class="['status-' + item.maintFun]">
        <template #title>
          <view class="flex justify-between">
            <view class="flex items-center menu-title-box">
              <view class="menu-indicator"></view>
              <view class="ml-1 text-sm align-center">
                <wd-text :text="item.maintName" :lines="1"></wd-text>
              </view>
          <view class="flex items-center">
            <view class="menu-indicator"></view>
            <view class="ml-1 text-sm align-center">
              <wd-text color="black" :text="item.maintName"></wd-text>
            </view>
            <view class="flex items-center w-[20%] justify-end">
              <text class="text-color-gray text-mini">
                {{ item?.planTime }}
              </text>
            <!-- 新增状态显示,绑定点击事件 -->
            <view
              v-if="item.status === '2'"
              class="ml-auto text-sm"
              style="width: 60px; text-align: end"
              :style="{ color: getStatusColor(item.maintFun) }"
              @click.stop="handleUndoAction(item)"
            >
              {{ getStatusText(item.maintFun) }}
            </view>
          </view>
        </template>
        <view class="flex h-[200rpx]" items-center>
          <image class="slot-img text-center" src="/static/ico/ico-platform.png" />
          <view class="flex-1 text-color-gray text-xs flex-row">
            <view class="mr-3 mt-2">保养单号: {{ item.maintCode }}</view>
            <view class="mr-3 mt-2">计划保养日期: {{ item.planTime }}</view>
            <view class="mr-3 mt-2">保养开始时间: {{ item.startTime }}</view>
            <view class="mr-3 mt-2">保养结束时间: {{ item.endTime }}</view>
            <view class="text-color-gray text-xs mt-2 flex">
              <text>状态:</text>
              <template v-if="item.status === '0'">
                <wd-icon class="icon-color-warning" name="books" size="34rpx"></wd-icon>
                <text class="ml-1">待保养</text>
              </template>
              <template v-else-if="item.status === '1'">
                <wd-icon class="icon-color-base" name="books" size="34rpx"></wd-icon>
                <text class="ml-1">保养中</text>
              </template>
              <template v-else-if="item.status === '2'">
                <wd-icon class="icon-color-purple" name="books" size="34rpx"></wd-icon>
                <text class="ml-1">待验证</text>
              </template>
              <template v-else-if="item.status === '3'">
                <wd-icon class="icon-color-success" name="check-outline" size="34rpx"></wd-icon>
                <text class="ml-1">已完成</text>
              </template>
              <text class="mx-3">|</text>
              <wd-icon class="icon-color-base" name="camera" size="30rpx"></wd-icon>
              <text class="ml-1">{{ item.maintUserName }}</text>
        <!-- 按钮区域 -->
        <view v-if="item.maintFun == null" class="flex justify-around mt-2">
          <wd-button
            type="primary"
            size="small"
            class="mr-2"
            @click.stop="handleAction(item, '0')"
          >
            检查
          </wd-button>
          <wd-button
            type="success"
            size="small"
            class="mr-2"
            @click.stop="handleAction(item, '1')"
          >
            保养
          </wd-button>
          <wd-button type="warning" size="small" @click.stop="handleAction(item, '2')">
            维修
          </wd-button>
        </view>
        <!-- 正文区域 -->
        <view v-else class="mt-2">
          <wd-upload v-model:file-list="item.fileList" :action="VITE_UPLOAD_BASEURL" @success="handleUploadSuccess" >
            <wd-button>上传图片</wd-button>
          </wd-upload>
          <!-- 保养说明区域 -->
          <view v-if="item.maintFun === '1'" class="mt-2">
            <wd-input
              v-model="item.maintDesc"
              placeholder="请输入保养说明"
              clearable
              :maxlength="200"
            />
          </view>
          <!-- 维修说明区域 -->
          <view v-if="item.maintFun === '2'" class="mt-2">
            <wd-input
              v-model="item.repairDesc"
              placeholder="请输入维修说明"
              clearable
              :maxlength="200"
            />
          </view>
          <!-- 备件信息录入区域 -->
          <view v-if="item.spareParts && item.spareParts.length > 0" class="mt-2">
            <view
              v-for="(part, partIndex) in item.spareParts"
              :key="partIndex"
              class="flex justify-between mt-1"
            >
              <wd-input
                v-model="part.name"
                label="名称:"
                label-width="100rpx"
                placeholder="备件名称"
              />
              <wd-input
                v-model="part.quantity"
                label="数量:"
                label-width="100rpx"
                placeholder="数量" type="number" :maxlength="5" />
            </view>
          </view>
          <!--操作工或维修工角色-->
          <template v-if="isOperatorOrRepair()">
            <view class="flex flex-col justify-between"  v-if="item.status === '0'">
              <wd-button
                size="small"
                icon="edit-outline"
                @click.stop="handleStartMaint(item)"
              >
                开始保养
              </wd-button>
          <!-- 维修说明区域 -->
          <view v-if="item.maintFun === '2'" class="mt-2">
            <wd-button type="info" size="small" @click.stop="addSparePart(item, index)">
              添加备件
            </wd-button>
          </view>
              <wd-button
                class="mt-3"
                size="small"
                icon="edit-outline"
                @click.stop="handleMaintFinish(item)"
              >
                一键保养
              </wd-button>
            </view>
            <wd-button
              v-if="item.status === '1'"
              size="small"
              icon="edit-outline"
              @click.stop="itemClick(item)"
            >
              保养中
            </wd-button>
            <wd-button
              v-if="item.status === '2'"
              size="small"
              icon="edit-outline"
              @click.stop="itemClick(item)"
            >
              待验证
            </wd-button>
            <wd-button
              v-if="item.status === '3'"
              size="small"
              icon="check-outline"
              @click.stop="itemClick(item)"
            >
              已完成
            </wd-button>
          </template>
          <!--管理员角色-->
          <template v-else-if="isLeader()">
            <wd-button v-if="item.status === '0'" size="small" icon="warn-bold" disabled>
              待保养
            </wd-button>
            <wd-button v-if="item.status === '1'" size="small" icon="warn-bold" disabled>
              保养中
            </wd-button>
            <wd-button
              v-if="item.status === '2'"
              size="small"
              icon="edit-outline"
              @click.stop="itemClick(item)"
            >
              待验证
            </wd-button>
            <wd-button
              v-if="item.status === '3'"
              size="small"
              icon="check-outline"
              @click.stop="itemClick(item)"
            >
              已完成
            </wd-button>
          </template>
          <!-- 保养人和保养时间区域 -->
          <view class="flex justify-between mt-2">
            <text>保养人: {{ item.maintUserName }}</text>
            <text>保养时间: {{ item.endTime }}</text>
          </view>
        </view>
      </wd-card>
      <wd-cell>
        <template #title>
          <text class="text-color-gray">其他信息</text>
        </template>
      </wd-cell>
      <view class="h-[2px] w-full bg-base"></view>
      <wd-textarea
        label="特记事项"
        label-width="200rpx"
        type="textarea"
        v-model="maintSt.specialNote"
        v-model="specialNote"
        auto-height
        :maxlength="200"
        show-word-limit
        placeholder="请输入特记事项"
        clearable
      />
      <!-- 新增确认完成按钮 -->
      <view class="flex justify-around">
        <wd-button type="primary" style="margin: 20px" block v-if="maintSt.status === '0'" @click="handleClickRight">提交</wd-button>
        <wd-button type="success" style="margin: 20px" block v-if="isLeader() && maintSt.status === '1'" @click="handleComplete">确认完成</wd-button>
      </view>
    </view>
    <!-- 新增提交按钮 -->
<!--    <view class="flex justify-center mt-4">-->
<!--      <wd-button type="primary" block size="large" @click="handleClickRight">提交</wd-button>-->
<!--    </view>-->
  </z-paging>
  <!-- 备件选择弹出层 -->
  <wd-popup v-model="showSparePopup" position="bottom" height="33.33vh">
    <view class="flex justify-between p-2 bg-white">
      <wd-button type="text" @click="closeSparePopup">取消</wd-button>
      <wd-button type="text" @click="addOtherSparePart">其他</wd-button>
    </view>
    <wd-input
      v-model="searchKeyword"
      placeholder="请输入备件名称或型号"
      clearable
      @input="filterSpareParts"
    />
    <view class="p-2">
      <view
        v-for="(part, index) in sparePartsList"
        :key="index"
        class="flex justify-between items-center p-2 border-b"
        @click="selectFilteredSparePart(part)"
      >
        <text>{{ part.name }} ({{ part.code }})</text>
        <text>剩余: {{ part.actualStock }}</text>
      </view>
    </view>
  </wd-popup>
</template>
<script setup lang="ts">
import type { MaintStVO } from '@/service/maint.d'
import { getMaintSt, getMaintStOrderList, updateMaintOrder, updateMaintSt } from '@/service/maint'
import { getMaintSt, getMaintStOrderList, updateMaintOrder, updateMaintSt, updateMaintOrderBatch } from '@/service/maint'
import { ref, reactive } from 'vue'
import { useToast, useMessage } from 'wot-design-uni'
import { isLeader, isOperatorOrRepair } from '@/utils/RoleUtils'
import { isLeader, isLineOrRepair } from '@/utils/RoleUtils'
import { formatDate } from '@/utils/DateUtils'
import { useUserStore } from "@/store";
import { getSpareList } from '@/service/spare'
import { getEnvBaseUploadUrl } from "@/utils";
const VITE_UPLOAD_BASEURL = `${getEnvBaseUploadUrl()}`
const message = useMessage()
const toast = useToast()
const userStore = useUserStore()
const paging = ref(null)
const dataList = ref([])
const maintStId = ref('')
const showSparePopup = ref(false)
const selectedPartIndex = ref(-1)
const sparePartsList = ref([])
const searchKeyword = ref('')
const dataChange = ref(false)
const specialNote = ref('')
interface QueryParams {
  pageNum: number
  pageSize: number
@@ -274,18 +303,26 @@
  dyzCount: null,
  specialNote: '',
})
const queryList = (pageNum?: number, pageSize?: number) => {
const queryList = () => {
  const params: QueryParams = {
    pageNum,
    pageSize,
    maintCode: maintCode.value,
  }
  getMaintStOrderList(params)
    .then((res: any) => {
      // 将每一项的picture转换为fileList数组
      res.rows.forEach((item: any) => {
        item.fileList = item.picture?.split(',').map((url: string) => {
          return {
            url: url,
          }
        })
      })
      console.log(res)
      // 请勿在网络请求回调中给dataList赋值!!只需要调用complete就可以了
      paging.value.completeByTotal(res.rows, res.total)
      paging.value.complete(res.rows)
    })
    .catch((res) => {
      console.error(res)
      // 如果请求失败写paging.value.complete(false),会自动展示错误页面
      // 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
      // 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
@@ -312,6 +349,66 @@
  uni.navigateTo({
    url: `/pages/maint/order-detail?id=${item.id}`,
  })
}
// 备件选择相关逻辑
function addSparePart(item: any, index: number) {
  if (!item.spareParts) {
    item.spareParts = []
  }
  selectedPartIndex.value = index
  // item.spareParts.push({ name: '', quantity: '' })
  selectSparePart(item)
}
function selectSparePart(item: any) {
  showSparePopup.value = true
  loadSpareParts()
}
function loadSpareParts(value?: string) {
  getSpareList({ name: value, pageNum: 1, pageSize: 10 }).then((res: any) => {
    sparePartsList.value = res.rows
  })
}
function filterSpareParts() {
  if (!searchKeyword.value) {
    loadSpareParts()
  } else {
    loadSpareParts(searchKeyword.value)
  }
}
function selectFilteredSparePart(part: any) {
  dataList.value[selectedPartIndex.value].spareParts.push({
    id: part.id,
    name: part.name,
    quantity: '',
  })
  closeSparePopup()
}
function closeSparePopup() {
  showSparePopup.value = false
  selectedPartIndex.value = -1
  searchKeyword.value = ''
}
function confirmSpareSelection() {
  closeSparePopup()
}
function addOtherSparePart() {
  dataList.value[selectedPartIndex.value].spareParts.push({
    id:  '',
    name: '',
    quantity: '',
  })
  closeSparePopup();
}
/**
@@ -385,19 +482,45 @@
}
function handleUpdateMaintSt() {
  if (maintSt.orderCount !== maintSt.wcCount) {
    toast.info('请先完成所有保养项!')
  // if (maintSt.orderCount !== maintSt.wcCount) {
  //   toast.info('请先完成所有保养项!')
  //   return false
  // }
  // 先判断保养项是否有更改
  console.log('handleUPdateMaintst', dataChange.value)
  if (!dataChange.value) {
    message.alert('请操作后提交!')
    return false
  }
  // 过滤掉 maintFun为空的保养项
  const submitList = dataList.value.filter((item) => item.maintFun != null)
  // 如果maintFun为1的则判断保养说明不能为空,如果为2则判断维修说明不能为空
  if (submitList.some((item) => item.maintFun === '1' && !item.maintDesc)) {
    toast.info('请填写保养说明!')
    return false
  } else if (submitList.some((item) => item.maintFun === '2' && !item.repairDesc)) {
    toast.info('请填写维修说明!')
    return false
  }
  // 将submitList中每一项的fileList转换为以逗号分割的字符串
  submitList.forEach((item) => {
    console.log('item.fileList', item.fileList)
    const map = item.fileList?.map((file) => file.url)
    console.log('map', map)
    item.picture = map?.join(',')
  })
  const data: any = Object.assign(
    {},
    {
      id: maintSt.id,
      orderCount: maintSt.orderCount,
      wcCount: maintSt.wcCount,
      wcCount: submitList.length,
      status: maintSt.status,
      specialNote: maintSt.specialNote,
      specialNote: specialNote.value,
    },
  )
  if (data.orderCount === data.wcCount) {
@@ -408,11 +531,13 @@
      msg: '确定更新工单汇总数据?',
      title: '提示',
      beforeConfirm: ({ resolve }) => {
        updateMaintOrderBatch({maintOrderList:submitList})
        updateMaintSt(data)
          .then((res: any) => {
            resolve(true)
            if (res?.code === 200) {
              reloadData()
              uni.$emit('maint-st-refresh')
              goBack()
              toastSucces()
            }
          })
@@ -434,11 +559,16 @@
function reloadData() {
  initMaintSt(maintStId.value)
}
onNavigationBarButtonTap((e) => {
  if (e.index === 0) {
const goBack = () => {
  uni.navigateBack()
}
function handleClickRight() {
    handleUpdateMaintSt()
  }
})
}
onLoad(() => {
  uni.$on('maint-order-refresh', reloadData)
@@ -456,6 +586,131 @@
  maintStId.value = options.id
  reloadData()
})
watch(
  () => [...dataList.value, specialNote.value ], // 使用扩展运算符创建新数组以触发监听
  (newVal, oldVal) => {
    if (oldVal.length > 0) {
      console.log('dataChange',oldVal,  newVal)
      dataChange.value = true
    }
  },
  { deep: true },
)
/**
 * 处理按钮点击事件
 * @param item 保养项
 * @param action 操作类型(check/check/maintain/repair)
 */
function handleAction(item: any, action: number) {
  // 设置当前选中的操作类型
  item.maintFun = action
  item.status = '2'
  // 自动填充保养人和时间
  item.maintUserName = userStore?.userInfo?.realName || ''
  const now = new Date()
  item.endTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
  console.log('handleAction', item)
}
/**
 * 撤销保养操作
 * @param item 保养项
 */
function handleUndoAction(item: any) {
  message
    .confirm({
      msg: '确定撤销当前操作?',
      title: '提示',
      beforeConfirm: ({ resolve }) => {
        // 重置状态和相关字段
        item.maintFun = null;
        item.status = '0';
        item.maintDesc = '';
        item.repairDesc = '';
        item.spareParts = [];
        item.maintUserName = '';
        item.endTime = '';
        resolve(true);
      },
    })
    .then(() => {
      toast.success('操作已撤销');
      dataChange.value = true;
    })
    .catch((error) => {
      console.log(error);
    });
}
/**
 * 确认完成按钮点击事件
 */
function handleComplete() {
  if (!isLeader()) {
    toast.info('无权限操作');
    return;
  }
  const now = new Date();
  const data: any = Object.assign(
    {},
    {
      id: maintSt.id,
      status: '2',
      verifyUser: userStore?.userInfo?.userId ,
      verifyTime: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
    },
  )
  updateMaintSt(data)
    .then((res: any) => {
      if (res?.code === 200) {
        uni.$emit('maint-st-refresh')
        goBack()
        toastSucces()
      }
    })
    .catch((res) => {
      console.error(res)
    })
}
function handleUploadSuccess({ file, fileList }) {
  console.log('handleUploadSuccess', file)
  // 判断 file.response是不是 对象,不是对象将json字符串转换为对象
  if (typeof file.response === 'string') {
    file.response = JSON.parse(file.response)
    console.log('file.response', file.response)
    file.ossId = file.response.data.ossId
    file.url = file.response.data.url
  }
}
// 新增方法:获取状态文本
function getStatusText(maintFun: string): string {
  switch (maintFun) {
    case '0':
      return '已检查';
    case '1':
      return '已保养';
    case '2':
      return '已维修';
    default:
      return '';
  }
}
// 新增方法:获取状态颜色
function getStatusColor(maintFun: string): string {
  switch (maintFun) {
    case '0':
      return '#007bff'; // 检查按钮颜色
    case '1':
      return '#28a745'; // 保养按钮颜色
    case '2':
      return '#ffc107'; // 维修按钮颜色
    default:
      return '#000';
  }
}
</script>
<style scoped lang="scss">
@@ -471,7 +726,7 @@
}
.text-mini {
  font-size: 22rpx;
  font-size: 24rpx;
}
.menu-indicator {
@@ -493,7 +748,7 @@
}
:deep(.wd-navbar__text) {
  font-size: 26rpx;
  font-size: 28rpx;
  color: white;
}
@@ -501,6 +756,20 @@
:deep(.wd-navbar__title) {
  color: white;
  font-weight: 0;
  font-size: 32rpx;
  font-size: 34rpx;
}
// 新增样式:动态设置卡片背景色
:deep(.wd-card) {
  transition: background-color 0.3s ease;
  &.status-0 {
    background-color: #e6f7ff; // 检查状态背景色
  }
  &.status-1 {
    background-color: #f6ffed; // 保养状态背景色
  }
  &.status-2 {
    background-color: #fffbe6; // 维修状态背景色
  }
}
</style>