yyb
8 天以前 d176ab8e28af8f9e477a9be2a3de5f399f68a47e
src/pages/equipmentManagement/upkeep/add.vue
@@ -41,20 +41,78 @@
            </template>
         </u-form-item>
         <u-form-item label="保养人" prop="maintenancePerson" border-bottom>
         <u-form-item label="保养人" prop="maintenancePerson" required border-bottom>
            <u-input
               v-model="form.maintenancePerson"
               placeholder="请输入保养人"
               placeholder="请选择保养人"
               readonly
               @click="showPersonPicker"
               clearable
            />
            <template #right>
               <u-icon name="arrow-right" @click="showPersonPicker" />
            </template>
         </u-form-item>
         <u-form-item label="保养部位" prop="maintenanceLocation" required border-bottom>
            <u-input
               v-model="form.maintenanceLocation"
               placeholder="请输入保养部位"
               clearable
            />
         </u-form-item>
         <u-form-item label="保养项目" prop="maintenanceItems" border-bottom>
         <u-form-item label="保养内容" prop="maintenanceItems" required border-bottom>
            <u-input
               v-model="form.maintenanceItems"
               placeholder="请输入保养项目"
               placeholder="请输入保养内容"
               clearable
            />
         </u-form-item>
         <u-form-item label="附件" border-bottom>
            <view class="attachment-upload">
               <view class="upload-buttons">
                  <u-button
                     type="primary"
                     @click="chooseAttachment('camera')"
                     :loading="uploading"
                     :disabled="uploading"
                     :customStyle="{ marginRight: '10px', flex: 1 }"
                  >
                     <u-icon name="camera" size="18" color="#fff" style="margin-right: 5px;"></u-icon>
                     {{ uploading ? '上传中...' : '拍照' }}
                  </u-button>
                  <u-button
                     type="success"
                     @click="chooseAttachment('album')"
                     :loading="uploading"
                     :disabled="uploading"
                     :customStyle="{ flex: 1 }"
                  >
                     <u-icon name="photo" size="18" color="#fff" style="margin-right: 5px;"></u-icon>
                     {{ uploading ? '上传中...' : '相册' }}
                  </u-button>
               </view>
               <view v-if="attachmentList.length" class="attachment-list">
                  <view
                     v-for="(file, index) in attachmentList"
                     :key="file.id || index"
                     class="attachment-item"
                  >
                     <image
                        :src="getFileAccessUrl(file)"
                        mode="aspectFill"
                        class="attachment-preview"
                        @click="previewAttachment(index)"
                     />
                     <view class="attachment-delete" @click="removeAttachment(file, index)">
                        <u-icon name="close" size="12" color="#fff" />
                     </view>
                  </view>
               </view>
               <view v-else class="attachment-empty">暂无附件,可拍照或从相册选择</view>
            </view>
         </u-form-item>
         
         <!-- 提交按钮 -->
@@ -72,6 +130,14 @@
         @select="onDeviceConfirm"
         @close="showDevice = false"
      />
      <!-- 保养人选择器 -->
      <up-action-sheet
         :show="showPerson"
         :actions="personActions"
         title="选择保养人"
         @select="onPersonConfirm"
         @close="showPerson = false"
      />
<up-datetime-picker
         :show="showDate"
         v-model="pickerDateValue"
@@ -85,10 +151,20 @@
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { onShow, onUnload } from '@dcloudio/uni-app';
import PageHeader from '@/components/PageHeader.vue';
import config from '@/config';
import { getToken } from '@/utils/auth';
import { getDeviceLedger } from '@/api/equipmentManagement/ledger';
import { addUpkeep, editUpkeep, getUpkeepById } from '@/api/equipmentManagement/upkeep';
import {
   addUpkeep,
   editUpkeep,
   getUpkeepById,
   listMaintenanceTaskFiles,
   addMaintenanceTaskFile,
   delMaintenanceTaskFile,
} from '@/api/equipmentManagement/upkeep';
import { userListNoPageByTenantId } from '@/api/system/user';
import useUserStore from '@/store/modules/user';
import dayjs from "dayjs";
import { formatDateToYMD } from '@/utils/ruoyi';
@@ -105,11 +181,21 @@
  })
}
const normalizeId = (raw) => {
   if (raw === null || raw === undefined) return undefined;
   const val = String(raw).trim();
   if (!val || val === 'undefined' || val === 'null') return undefined;
   return val;
};
// 表单引用
const formRef = ref(null);
const operationType = ref('add');
const loading = ref(false);
const uploading = ref(false);
const attachmentList = ref([]);
const showDevice = ref(false);
const showPerson = ref(false);
const showDate = ref(false);
const pickerDateValue = ref(Date.now());
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]);
@@ -117,12 +203,20 @@
// 设备选项
const deviceOptions = ref([]);
const deviceNameText = ref('');
// 保养人选项
const userOptions = ref([]);
// 转换为 action-sheet 需要的格式
const deviceActions = computed(() => {
   return deviceOptions.value.map(item => ({
      text: item.deviceName,
      value: item.id,
      data: item
   }));
});
const personActions = computed(() => {
   return userOptions.value.map(item => ({
      name: item.nickName,
      value: item.userId,
   }));
});
@@ -134,6 +228,9 @@
const formRules = {
   deviceLedgerId: [{ required: true, trigger: "change", message: "请选择设备名称" }],
   maintenancePlanTime: [{ required: true, trigger: "change", message: "请选择计划保养日期" }],
   maintenancePerson: [{ required: true, trigger: "change", message: "请选择保养人" }],
   maintenanceLocation: [{ required: true, trigger: "blur", message: "请输入保养部位" }],
   maintenanceItems: [{ required: true, trigger: "blur", message: "请输入保养内容" }],
};
// 使用 ref 声明表单数据
@@ -142,7 +239,8 @@
   deviceModel: undefined, // 规格型号
   maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // 计划保养日期
   maintenancePerson: userStore.nickName || undefined, // 保养人
   maintenanceItems: undefined, // 保养项目
   maintenanceLocation: undefined, // 保养部位
   maintenanceItems: undefined, // 保养内容
});
// 加载设备列表
@@ -153,6 +251,179 @@
   } catch (e) {
      showToast('获取设备列表失败');
   }
};
// 加载保养人列表
const loadUserOptions = async () => {
   try {
      const { data } = await userListNoPageByTenantId();
      userOptions.value = data || [];
   } catch (e) {
      showToast('获取保养人列表失败');
   }
};
// 附件相关
const getFileAccessUrl = (file = {}) => {
   const url = file.url || file.tempFilePath || file.path || '';
   if (!url) return '';
   if (String(url).startsWith('http') || String(url).startsWith('blob:') || String(url).startsWith('file:') || String(url).startsWith('wxfile:')) {
      return url;
   }
   const path = String(url).startsWith('/') ? url : `/${url}`;
   return `${config.fileUrl}${path}`;
};
const fetchAttachmentList = async (id) => {
   if (!id) {
      attachmentList.value = [];
      return;
   }
   try {
      const { code, data } = await listMaintenanceTaskFiles({
         current: 1,
         size: 100,
         deviceMaintenanceId: id,
      });
      if (code === 200) {
         attachmentList.value = data?.records || [];
      } else {
         attachmentList.value = [];
      }
   } catch (e) {
      attachmentList.value = [];
   }
};
const chooseAttachment = (sourceType) => {
   const source = sourceType === 'camera' ? ['camera'] : ['album'];
   uni.chooseImage({
      count: 9,
      sizeType: ['original', 'compressed'],
      sourceType: source,
      success: (res) => {
         const files = res.tempFiles || [];
         if (!files.length) return;
         const id = getPageId();
         if (id) {
            uploadAttachments(files, id);
            return;
         }
         const tempItems = files.map((file, idx) => {
            const filePath = file.path || res.tempFilePaths?.[idx];
            return {
               id: `temp_${Date.now()}_${idx}`,
               url: filePath,
               tempFilePath: filePath,
               name: file.name || `附件_${Date.now()}_${idx}.jpg`,
               isTemp: true,
            };
         });
         attachmentList.value = [...attachmentList.value, ...tempItems];
         showToast('已添加,保存计划后自动上传');
      },
      fail: () => {
         showToast('选择图片失败');
      },
   });
};
const uploadAttachments = async (files, maintenanceId) => {
   const commonId = normalizeId(maintenanceId);
   if (!commonId) {
      showToast('未获取到保养计划ID,上传失败');
      return;
   }
   const token = getToken();
   if (!token) {
      showToast('登录已失效,请重新登录');
      return;
   }
   uploading.value = true;
   try {
      for (const file of files) {
         const filePath = file.path || file.tempFilePath;
         if (!filePath) continue;
         await new Promise((resolve, reject) => {
            uni.uploadFile({
               url: `${config.baseUrl}/file/upload`,
               filePath,
               name: 'file',
               header: {
                  Authorization: `Bearer ${token}`,
               },
               success: (uploadRes) => {
                  try {
                     const parsed = JSON.parse(uploadRes.data || '{}');
                     if (uploadRes.statusCode === 200 && parsed.code === 200) {
                        const fileName = file.name || filePath.split('/').pop();
                        addMaintenanceTaskFile({
                           name: fileName,
                           deviceMaintenanceId: commonId,
                           url: parsed.data?.tempPath || parsed.data?.url || '',
                        })
                           .then((addRes) => {
                              if (addRes.code === 200) {
                                 resolve(addRes);
                              } else {
                                 reject(new Error(addRes.msg || '保存附件信息失败'));
                              }
                           })
                           .catch(reject);
                     } else {
                        reject(new Error(parsed.msg || '上传失败'));
                     }
                  } catch (err) {
                     reject(new Error('上传响应解析失败'));
                  }
               },
               fail: () => reject(new Error('上传失败')),
            });
         });
      }
      showToast('上传成功');
      await fetchAttachmentList(commonId);
   } catch (e) {
      showToast(e?.message || '上传失败');
   } finally {
      uploading.value = false;
   }
};
const previewAttachment = (index) => {
   const urls = attachmentList.value
      .map((item) => getFileAccessUrl(item))
      .filter(Boolean);
   if (!urls.length) return;
   uni.previewImage({
      urls,
      current: urls[index] || urls[0],
   });
};
const removeAttachment = (file, index) => {
   if (!file?.id || file?.isTemp) {
      attachmentList.value.splice(index, 1);
      return;
   }
   uni.showModal({
      title: '提示',
      content: '确认删除该附件吗?',
      success: async (res) => {
         if (!res.confirm) return;
         try {
            const { code } = await delMaintenanceTaskFile(file.id);
            if (code === 200) {
               attachmentList.value.splice(index, 1);
               showToast('删除成功');
            } else {
               showToast('删除失败');
            }
         } catch (e) {
            showToast('删除失败');
         }
      },
   });
};
// 加载表单数据(编辑模式)
@@ -166,12 +437,14 @@
            form.value.deviceModel = data.deviceModel;
            form.value.maintenancePlanTime = dayjs(data.maintenancePlanTime).format("YYYY-MM-DD");
            form.value.maintenancePerson = data.maintenancePerson;
            form.value.maintenanceItems = data.maintenanceItems || data.maintenanceLocation;
            form.value.maintenanceLocation = data.maintenanceLocation;
            form.value.maintenanceItems = data.maintenanceItems;
            // 设置设备名称显示
            const device = deviceOptions.value.find(item => item.id === data.deviceLedgerId);
            if (device) {
               form.value.deviceNameText = device.deviceName;
            }
            await fetchAttachmentList(id);
         }
      } catch (e) {
         showToast('获取详情失败');
@@ -179,6 +452,7 @@
   } else {
      // 新增模式
      operationType.value = 'add';
      attachmentList.value = [];
   }
};
@@ -247,6 +521,22 @@
   showDevice.value = true;
};
// 显示保养人选择器
const showPersonPicker = () => {
   if (!userOptions.value.length) {
      showToast('暂无可选保养人');
      return;
   }
   showPerson.value = true;
};
// 确认保养人选择
const onPersonConfirm = (selected) => {
   const user = userOptions.value.find(item => item.userId === selected.value);
   form.value.maintenancePerson = user?.nickName || selected.name || '';
   showPerson.value = false;
};
// 确认设备选择
const onDeviceConfirm = (selected) => {
   // selected 返回的是选中项
@@ -276,8 +566,9 @@
});
onMounted(() => {
   // 页面加载时获取设备列表和参数
   // 页面加载时获取设备列表、保养人列表和参数
   loadDeviceName();
   loadUserOptions();
   getPageParams();
});
@@ -305,13 +596,30 @@
         submitData.maintenancePlanTime = submitData.maintenancePlanTime + ' 00:00:00';
      }
      
      const { code } = id
      const result = id
         ? await editUpkeep({ id: id, ...submitData })
         : await addUpkeep(submitData);
      const { code, data } = result || {};
      
      if (code == 200) {
         if (!id) {
            const newId = data?.id || data?.maintenanceId || data;
            if (newId) {
               const tempFiles = attachmentList.value
                  .filter((item) => item?.isTemp && (item.tempFilePath || item.url))
                  .map((item) => ({
                     path: item.tempFilePath || item.url,
                     tempFilePath: item.tempFilePath || item.url,
                     name: item.name,
                  }));
               if (tempFiles.length) {
                  await uploadAttachments(tempFiles, newId);
               }
            }
         }
         showToast(`${id ? "编辑" : "新增"}计划成功`);
         setTimeout(() => {
            uni.removeStorageSync('repairId');
            uni.navigateBack();
         }, 1500);
      } else {
@@ -330,26 +638,26 @@
   uni.navigateBack();
};
// 获取页面ID
const getPageId = () => {
   return normalizeId(uni.getStorageSync('repairId'));
};
// 获取页面参数
const getPageParams = () => {
   // 从本地存储获取id
   const id = uni.getStorageSync('repairId');
   // 根据是否有id参数来判断是新增还是编辑
   const id = getPageId();
   if (id) {
      // 编辑模式,获取详情
      loadForm(id);
   } else {
      // 新增模式
      operationType.value = 'add';
      attachmentList.value = [];
      loadForm();
   }
};
// 获取页面ID
const getPageId = () => {
   // 从本地存储获取id
   return uni.getStorageSync('repairId');
};
onUnload(() => {
   uni.removeStorageSync('repairId');
});
</script>
<style scoped lang="scss">
@@ -413,4 +721,50 @@
   margin-left: 8px;
   cursor: pointer;
}
.attachment-upload {
   width: 100%;
}
.upload-buttons {
   display: flex;
   margin-bottom: 12px;
}
.attachment-list {
   display: flex;
   flex-wrap: wrap;
   gap: 10px;
}
.attachment-item {
   position: relative;
   width: 80px;
   height: 80px;
}
.attachment-preview {
   width: 80px;
   height: 80px;
   border-radius: 6px;
   background: #f5f5f5;
}
.attachment-delete {
   position: absolute;
   top: -6px;
   right: -6px;
   width: 18px;
   height: 18px;
   border-radius: 50%;
   background: rgba(0, 0, 0, 0.65);
   display: flex;
   align-items: center;
   justify-content: center;
}
.attachment-empty {
   font-size: 12px;
   color: #909399;
}
</style>