yyb
8 天以前 e6080ac8ad345f8cd7378c7cfa5f0f70f80e3926
增加保养部件和附件
已修改2个文件
310 ■■■■■ 文件已修改
src/pages/equipmentManagement/upkeep/add.vue 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/add.vue
@@ -54,12 +54,65 @@
                </template>
            </u-form-item>
            
            <u-form-item label="保养项目" prop="maintenanceItems" border-bottom>
            <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>
            
            <!-- 提交按钮 -->
@@ -100,8 +153,17 @@
import { ref, computed, onMounted, onUnmounted } from 'vue';
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";
@@ -130,6 +192,8 @@
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);
@@ -165,6 +229,8 @@
    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 声明表单数据
@@ -173,6 +239,7 @@
    deviceModel: undefined, // 规格型号
    maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // 计划保养日期
    maintenancePerson: userStore.nickName || undefined, // 保养人
    maintenanceLocation: undefined, // 保养部位
    maintenanceItems: undefined, // 保养项目
});
@@ -196,6 +263,169 @@
    }
};
// 附件相关
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('删除失败');
            }
        },
    });
};
// 加载表单数据(编辑模式)
const loadForm = async (id) => {
    if (id) {
@@ -207,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('获取详情失败');
@@ -220,6 +452,7 @@
    } else {
        // 新增模式
        operationType.value = 'add';
        attachmentList.value = [];
    }
};
@@ -363,11 +596,27 @@
            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');
@@ -401,6 +650,7 @@
        loadForm(id);
    } else {
        operationType.value = 'add';
        attachmentList.value = [];
        loadForm();
    }
};
@@ -471,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>
src/pages/equipmentManagement/upkeep/index.vue
@@ -59,8 +59,12 @@
              <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 || item.maintenanceLocation || '-' }}</text>
              <text class="detail-value">{{ item.maintenanceItems || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">录入人</text>