add-patrol-order.vue 10.7 KB
<template>
  <view class="page-container">
    <view class="work-order-form-content commonPageLRpadding">
      <up-form label-position="left" :model="workOrderForm" ref="workOrderFormRef" labelWidth="200rpx">
        <!-- 1. 工单位置(地图选择) -->
        <up-form-item label="工单位置" prop="workLocation" border-bottom required
          @click="chooseWorkLocation(); hideKeyboard()">
          <up-input v-model="workOrderForm.workLocation" border="none" readonly suffix-icon="map-fill"
            placeholder="点击选择工单位置"></up-input>
        </up-form-item>

        <!-- 2. 问题类型(下拉框) -->
        <up-form-item label="问题类型" prop="orderName" border-bottom required
          @click="handleActionSheetOpen('orderName'); hideKeyboard()">
          <up-input v-model="workOrderForm.orderName" disabled disabled-color="#ffffff" placeholder="请选择问题类型"
            border="none"></up-input>
          <template #right>
            <up-icon name="arrow-right" size="16"></up-icon>
          </template>
        </up-form-item>

        <!-- 3. 情况描述(文本域) -->
        <up-form-item label="情况描述" prop="problemDesc" required>
          <up-textarea placeholder="请输入情况描述(最多200字)" v-model.trim="workOrderForm.problemDesc" count maxlength="200"
            rows="4" @blur="() => workOrderFormRef.validateField('problemDesc')"></up-textarea>
        </up-form-item>

        <!-- 4. 问题照片 -->
        <up-form-item label="问题照片" prop="problemImgs" required>
          <up-upload :file-list="problemImgs.imgList.value || []" @after-read="problemImgs.uploadImgs"
            @delete="problemImgs.deleteImg" multiple :width="70" :height="70"
            :max-count="problemImgs.uploadConfig.maxCount" :upload-text="problemImgs.uploadConfig.uploadText"
            :size-type="problemImgs.uploadConfig.sizeType"></up-upload>
        </up-form-item>

      </up-form>
    </view>

    <!-- 底部提交按钮 -->
    <view class="fixed-bottom-btn-wrap">
      <up-button type="primary" text="提交工单" @click="submitWorkOrder"></up-button>
    </view>

    <!-- 合并后的通用下拉弹窗 -->
    <up-action-sheet :show="showActionSheet" :actions="currentActionSheetData.list"
      :title="currentActionSheetData.title" @close="handleActionSheetClose"
      @select="handleActionSheetSelect"></up-action-sheet>

  </view>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { onReady, onShow, onLoad, onBackPress } from '@dcloudio/uni-app';
import { useUploadImgs } from '@/common/utils/useUploadImgs'
import { qyUniversalApproval, qyWorkorderCreate } from '@/api/regional-order-manage/regional-order-manage'
import { timeFormat } from '@/uni_modules/uview-plus'
import { nextStepMap } from '@/common/utils/common'
import { useUserStore } from '@/pinia/user';

// ========== 状态管理 ==========
const userStore = useUserStore();

// ========== 业务线相关状态 ==========
// 业务线映射表
const busiLineMap = ref({
  'yl': '园林',
  'sz': '市政',
  'wy': '物业',
  'zx': '秩序管理',
  'hj': '环境卫生',
  'yw': '常规运维',
  '园林': 'yl',
  '市政': 'sz',
  '物业': 'wy',
  '秩序管理': 'zx',
  '环境卫生': 'hj',
  '常规运维': 'yw'
});

// 业务线选项列表
const busiLineOptions = ref([]);
const formatBusiLineOptions = () => {
  if (!userStore.userInfo?.user?.busiLine) {
    busiLineOptions.value = [];
    return;
  }
  const rawBusiLines = userStore.userInfo.user.busiLine.split(',');
  busiLineOptions.value = rawBusiLines.map(item => ({
    name: busiLineMap.value[item.trim()]
  }));
};

// 工具方法:通过中文名称获取对应的英文标识
const getBusiLineEnByCn = (cnName) => {
  return busiLineMap.value[cnName] || '';
};

// ========== 表单Ref ==========
const workOrderFormRef = ref(null)

// ========== 公共上传逻辑复用 ==========
const problemImgs = useUploadImgs({
  maxCount: 3,
  uploadText: '选择问题照片',
  sizeType: ['compressed'],
  formRef: workOrderFormRef,
  fieldName: 'problemImgs'
})

if (!Array.isArray(problemImgs.rawImgList.value)) {
  problemImgs.rawImgList.value = [];
}

// ========== 页面状态 ==========
const showActionSheet = ref(false)
const currentActionSheetData = reactive({
  type: '',
  list: [],
  title: ''
})

// ========== 重新提交相关状态 ==========
const isRenew = ref(false);
const renewOrderData = ref(null);

// ========== 下拉列表数据 ==========
const orderNameList = ref([])

// ========== 工单表单数据 ==========
const workOrderForm = reactive({
  orderName: '',
  problemDesc: '',
  lat: 0,
  lon: 0,
  workLocation: '',
})

// ========== 表单校验规则 ==========
const workOrderFormRules = reactive({
  workLocation: [
    { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
  ],
  orderName: [
    { type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] }
  ],
  problemDesc: [
    { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] },
    { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] }
  ],
  problemImgs: [problemImgs.imgValidateRule]
})

// ========== 生命周期 ==========
onLoad((options) => {
  // 初始化业务线选项
  formatBusiLineOptions();

  // 判断是否为重新提交状态
  if (options.isRenew == 1 && options.tempKey) {
    isRenew.value = true;
    const tempKey = options.tempKey;

    try {
      const orderData = uni.getStorageSync(tempKey);
      if (orderData && typeof orderData === 'object') {
        renewOrderData.value = orderData;
        echoOrderData(renewOrderData.value);
      } else {
        uni.showToast({ title: '工单数据不存在,无法重新提交', icon: 'none' });
        setTimeout(() => uni.navigateBack(), 1000);
        return;
      }
    } catch (error) {
      console.error('读取工单数据失败:', error);
      uni.showToast({ title: '数据读取异常,无法重新提交', icon: 'none' });
      setTimeout(() => uni.navigateBack(), 1000);
      return;
    } finally {
      uni.removeStorageSync(tempKey);
    }
  }
});

onReady(() => {
  if (workOrderFormRef.value) {
    workOrderFormRef.value.setRules(workOrderFormRules)
  }
  console.log('工单表单规则初始化完成')
})

onShow(() => {
  // 初始化工单名称列表
  orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))
})
// 新增页返回直接回列表页
onBackPress(() => {
  uni.redirectTo({
    url: '/pages-sub/problem/regional-order-manage/index'
  })
  return true;
})

// ========== 核心方法 ==========
const echoOrderData = (orderItem) => {
  // 回显基础字段
  workOrderForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';
  workOrderForm.orderName = orderItem.orderName || '';
  workOrderForm.problemDesc = orderItem.remark || '';
  workOrderForm.lat = orderItem.lat || 0;
  workOrderForm.lon = orderItem.lon || 0;

  // 回显图片
  if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
    const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({
      url: imgUrl,
      name: `renew_img_${index}`,
      status: 'success'
    }));
    problemImgs.imgList.value = imgList;
    problemImgs.rawImgList.value = imgList;
  }
};

// ========== 通用弹窗方法 ==========
const handleActionSheetOpen = (type) => {
  const configMap = {
    orderName: {
      title: '请选择工单名称',
      list: orderNameList.value
    }
  }

  currentActionSheetData.type = type
  currentActionSheetData.title = configMap[type].title
  currentActionSheetData.list = configMap[type].list
  showActionSheet.value = true
}

const handleActionSheetClose = () => {
  showActionSheet.value = false
  currentActionSheetData.type = ''
  currentActionSheetData.list = []
  currentActionSheetData.title = ''
}

const handleActionSheetSelect = (e) => {
  const { type } = currentActionSheetData
  switch (type) {
    case 'orderName':
      workOrderForm.orderName = e.name
      workOrderFormRef.value?.validateField('orderName')
      break
  }
  showActionSheet.value = false
}

const navigateBack = () => {
  uni.reLaunch({
    url: '/pages-sub/problem/work-order-manage/index',
    fail: () => {
      uni.navigateBack({ delta: 2 });
    }
  });
}

// 选择工单位置
const chooseWorkLocation = () => {
  uni.chooseLocation({
    success: async (res) => {
      workOrderForm.workLocation = res.name
      workOrderForm.lat = res.latitude
      workOrderForm.lon = res.longitude

      workOrderFormRef.value?.validateField('workLocation')
    },
    fail: (err) => {
      console.error('选择位置失败:', err)
      uni.showToast({ title: '选择位置失败:' + err.errMsg, icon: 'none' })
    }
  })
}

// 隐藏键盘
const hideKeyboard = () => {
  uni.hideKeyboard()
}

// 提交工单
const submitWorkOrder = async () => {
  try {
    await workOrderFormRef.value.validate()

    const commonSubmitData = {
      problemsImgs: problemImgs.getSuccessImgUrls(),
      remark: workOrderForm.problemDesc.trim(),
      latLonType: 2,
      lat: workOrderForm.lat,
      lon: workOrderForm.lon,
      lonLatAddress: workOrderForm.workLocation,
      orderName: workOrderForm.orderName,
      sourceId: 1
    }

    uni.showLoading({ title: '提交中...' })
    let res

    if (isRenew.value) {
      const renewSubmitData = {
        workerDataId: renewOrderData.value.id,
        taskKey: renewOrderData.value.taskKey,
        taskId: renewOrderData.value.taskId,
        operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',
        agree: 0,
        reason: workOrderForm.problemDesc.trim(),
        ...commonSubmitData
      }
      if (userStore.userInfo?.roles.includes('patrol_global')) { // 全域巡查员
        await qyUniversalApproval(renewSubmitData)
      }
    } else {
      await qyWorkorderCreate(commonSubmitData)
    }

    uni.hideLoading()
    uni.showToast({
      title: isRenew.value ? '重新提交成功' : '工单提交成功',
      icon: 'success',
      duration: 1000
    })

    setTimeout(() => {
      uni.reLaunch({
        url: '/pages-sub/problem/regional-order-manage/index'
      })
    }, 1000)
  } catch (error) {
    uni.hideLoading()

    if (!Array.isArray(error)) {
      console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error)
      uni.showToast({
        title: isRenew.value ? error.msg : error.msg,
        icon: 'none',
        duration: 2000
      })
    }
  }
}
</script>

<style lang="scss" scoped>
// 全局页面样式
.page-container {
  min-height: 100vh;
}

// 工单表单内容容器
.work-order-form-content {
  background: #fff;
}
</style>