已修改2个文件
432 ■■■■■ 文件已修改
src/pages/equipmentManagement/upkeep/add.vue 421 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/add.vue
@@ -40,13 +40,79 @@
                    <u-icon name="arrow-right" @click="showDatePicker" />
                </template>
            </u-form-item>
            <u-form-item label="保养项目" prop="maintenanceLocation" border-bottom>
            <u-form-item label="保养人" prop="maintenancePerson" required border-bottom>
                <u-input
                    v-model="form.maintenanceLocation"
                    placeholder="请输入保养项目"
                    v-model="form.maintenancePerson"
                    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" required border-bottom>
                <u-input
                    v-model="form.maintenanceItems"
                    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>
            
            <!-- 提交按钮 -->
@@ -64,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"
@@ -77,12 +151,24 @@
<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,
    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';
const userStore = useUserStore();
defineOptions({
    name: "设备保养计划表单",
@@ -94,11 +180,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()]);
@@ -106,12 +202,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,
    }));
});
@@ -123,6 +227,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 声明表单数据
@@ -130,7 +237,10 @@
    deviceLedgerId: undefined, // 设备ID
    deviceModel: undefined, // 规格型号
    maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // 计划保养日期
    maintenanceLocation: undefined, // 保养项目
    maintenancePerson: userStore.nickName || undefined, // 保养人
    maintenanceLocation: undefined, // 保养部位
    maintenanceItems: undefined, // 保养内容
    tempFileIds: [], // 本次上传附件的临时文件ID,保存时提交
});
// 加载设备列表
@@ -143,6 +253,204 @@
    }
};
// 加载保养人列表
const loadUserOptions = async () => {
    try {
        const { data } = await userListNoPageByTenantId();
        userOptions.value = data || [];
    } catch (e) {
        showToast('获取保养人列表失败');
    }
};
// 附件相关
const extractTempFileId = (uploadedFile) => {
    if (!uploadedFile) return undefined;
    const data = Array.isArray(uploadedFile) ? uploadedFile[0] : uploadedFile;
    return data?.tempId ?? data?.tempFileId ?? data?.id;
};
const syncTempFileIds = () => {
    form.value.tempFileIds = attachmentList.value
        .filter((item) => item.isTempFile)
        .map((item) => item.tempId ?? item.tempFileId)
        .filter((v) => v !== undefined && v !== null && v !== '');
};
const getFileAccessUrl = (file = {}) => {
    if (file?._localPreviewUrl) return file._localPreviewUrl;
    const url = file.url || file.tempPath || 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 = [];
        form.value.tempFileIds = [];
        return;
    }
    try {
        const { code, data } = await listMaintenanceTaskFiles({
            current: 1,
            size: 100,
            deviceMaintenanceId: id,
        });
        if (code === 200) {
            const records = data?.records || [];
            attachmentList.value = records.map((file) => ({
                ...file,
                isTempFile: false,
            }));
        } else {
            attachmentList.value = [];
        }
    } catch (e) {
        attachmentList.value = [];
    }
    syncTempFileIds();
};
const chooseAttachment = (sourceType) => {
    const source = sourceType === 'camera' ? ['camera'] : ['album'];
    const remaining = 9 - attachmentList.value.length;
    if (remaining <= 0) {
        showToast('最多上传9张附件');
        return;
    }
    uni.chooseImage({
        count: Math.min(remaining, 9),
        sizeType: ['original', 'compressed'],
        sourceType: source,
        success: (res) => {
            const files = res.tempFiles || [];
            if (!files.length) return;
            uploadAttachments(files, res.tempFilePaths);
        },
        fail: () => {
            showToast('选择图片失败');
        },
    });
};
const handleUploadSuccess = (response, file) => {
    let uploadedFile = response?.data;
    if (Array.isArray(uploadedFile)) {
        uploadedFile = uploadedFile[0];
    }
    const tempId = extractTempFileId(uploadedFile);
    if (!tempId) {
        showToast('未获取到文件ID');
        return;
    }
    attachmentList.value.push({
        tempId,
        tempFileId: tempId,
        isTempFile: true,
        url: uploadedFile?.tempPath || uploadedFile?.url || uploadedFile?.downloadUrl || file.tempFilePath || file.path,
        tempPath: uploadedFile?.tempPath || '',
        name: uploadedFile?.originalName || uploadedFile?.originalFilename || file.name,
        _localPreviewUrl: file.tempFilePath || file.path || '',
    });
    syncTempFileIds();
};
const uploadAttachments = async (files, tempFilePaths = []) => {
    const token = getToken();
    if (!token) {
        showToast('登录已失效,请重新登录');
        return;
    }
    uploading.value = true;
    try {
        for (let i = 0; i < files.length; i++) {
            const file = files[i];
            const filePath = file.path || file.tempFilePath || tempFilePaths[i];
            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) {
                                handleUploadSuccess(parsed, {
                                    ...file,
                                    tempFilePath: filePath,
                                    path: filePath,
                                    name: file.name || `附件_${Date.now()}_${i}.jpg`,
                                });
                                resolve(parsed);
                            } else {
                                reject(new Error(parsed.msg || '上传失败'));
                            }
                        } catch (err) {
                            reject(new Error('上传响应解析失败'));
                        }
                    },
                    fail: () => reject(new Error('上传失败')),
                });
            });
        }
        showToast('上传成功');
    } 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?.isTempFile) {
        attachmentList.value.splice(index, 1);
        syncTempFileIds();
        return;
    }
    if (!file?.id) {
        attachmentList.value.splice(index, 1);
        syncTempFileIds();
        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('删除失败');
            }
        },
    });
};
// 加载表单数据(编辑模式)
const loadForm = async (id) => {
    if (id) {
@@ -153,12 +461,15 @@
                form.value.deviceLedgerId = data.deviceLedgerId;
                form.value.deviceModel = data.deviceModel;
                form.value.maintenancePlanTime = dayjs(data.maintenancePlanTime).format("YYYY-MM-DD");
                form.value.maintenancePerson = data.maintenancePerson;
                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('获取详情失败');
@@ -166,6 +477,8 @@
    } else {
        // 新增模式
        operationType.value = 'add';
        attachmentList.value = [];
        form.value.tempFileIds = [];
    }
};
@@ -234,6 +547,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 返回的是选中项
@@ -263,8 +592,9 @@
});
onMounted(() => {
    // 页面加载时获取设备列表和参数
    // 页面加载时获取设备列表、保养人列表和参数
    loadDeviceName();
    loadUserOptions();
    getPageParams();
});
@@ -285,20 +615,24 @@
        loading.value = true;
        const id = getPageId();
        
        syncTempFileIds();
        // 准备提交数据
        const submitData = { ...form.value };
        submitData.tempFileIds = form.value.tempFileIds || [];
        // 确保日期格式正确
        if (submitData.maintenancePlanTime && !submitData.maintenancePlanTime.includes(':')) {
            submitData.maintenancePlanTime = submitData.maintenancePlanTime + ' 00:00:00';
        }
        
        const { code } = id
        const result = id
            ? await editUpkeep({ id: id, ...submitData })
            : await addUpkeep(submitData);
        const { code } = result || {};
        
        if (code == 200) {
            showToast(`${id ? "编辑" : "新增"}计划成功`);
            setTimeout(() => {
                uni.removeStorageSync('repairId');
                uni.navigateBack();
            }, 1500);
        } else {
@@ -317,26 +651,27 @@
    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 = [];
        form.value.tempFileIds = [];
        loadForm();
    }
};
// 获取页面ID
const getPageId = () => {
    // 从本地存储获取id
    return uni.getStorageSync('repairId');
};
onUnload(() => {
    uni.removeStorageSync('repairId');
});
</script>
<style scoped lang="scss">
@@ -400,4 +735,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>
src/pages/equipmentManagement/upkeep/index.vue
@@ -55,8 +55,16 @@
              <text class="detail-value">{{ formatDate(item.maintenancePlanTime) || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">保养项目</text>
              <text class="detail-label">保养人</text>
              <text class="detail-value">{{ item.maintenancePerson || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">保养部位</text>
              <text class="detail-value">{{ item.maintenanceLocation || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">保养内容</text>
              <text class="detail-value">{{ item.maintenanceItems || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">录入人</text>
@@ -255,6 +263,7 @@
  // 新增计划 - 跳转到新增页面
  const addPlan = () => {
    uni.removeStorageSync("repairId");
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/add",
    });