| | |
| | | <template> |
| | | <view class="device-repair"> |
| | | <view class="sales-account"> |
| | | <!-- 使用通用页面头部组件 --> |
| | | <PageHeader title="设备报修" @back="goBack" /> |
| | | |
| | | <!-- 搜索区域 --> |
| | | <view class="search-filter-section"> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <input |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请输入设备名称" |
| | | placeholder="请输入设备名称搜索" |
| | | v-model="searchKeyword" |
| | | confirm-type="search" |
| | | @confirm="getList" |
| | | @change="getList" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="getList"> |
| | |
| | | </view> |
| | | |
| | | <!-- 设备报修列表 --> |
| | | <view class="repair-list" v-if="repairList.length > 0"> |
| | | <view class="ledger-list" v-if="repairList.length > 0"> |
| | | <view v-for="(item, index) in repairList" :key="index"> |
| | | <view class="repair-item"> |
| | | <view class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | |
| | | <text class="item-id">设备名称:{{ item.deviceName }}</text> |
| | | </view> |
| | | <view class="status-tag"> |
| | | <!-- 替换标签和按钮 --> |
| | | <!-- 状态标签 --> |
| | | <u-tag v-if="item.status === 1" type="success">完结</u-tag> |
| | | <u-tag v-if="item.status === 0" type="error">待维修</u-tag> |
| | | |
| | | <!-- 操作按钮 --> |
| | | <view class="action-buttons"> |
| | | <u-button |
| | | type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | @click="openForm('add')" |
| | | > |
| | | 新增报修 |
| | | </u-button> |
| | | <u-button |
| | | type="warning" |
| | | size="small" |
| | | class="action-btn" |
| | | @click="openForm('edit', item)" |
| | | > |
| | | 编辑 |
| | | </u-button> |
| | | <u-button |
| | | type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | @click="maintain(item)" |
| | | > |
| | | 维修 |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | |
| | | <text class="detail-value">{{ formatDate(item.repairTime) || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">报修人</text> |
| | | <text class="detail-label">维修人</text> |
| | | <text class="detail-value">{{ item.repairName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | |
| | | <text class="detail-value">{{ item.remark || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">维修人</text> |
| | | <text class="detail-label">报修人</text> |
| | | <text class="detail-value">{{ item.maintenanceName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | |
| | | |
| | | <!-- 按钮区域 --> |
| | | <view class="action-buttons"> |
| | | <van-button |
| | | <u-button |
| | | type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.status === 1" |
| | | @click="edit(item.id)" |
| | | > |
| | | 编辑 |
| | | </van-button> |
| | | <van-button |
| | | </u-button> |
| | | <u-button |
| | | type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | @click="viewAttachments(item)" |
| | | > |
| | | 查看附件 |
| | | </u-button> |
| | | <u-button |
| | | type="warning" |
| | | size="small" |
| | | class="action-btn" |
| | |
| | | @click="addMaintain(item.id)" |
| | | > |
| | | 新增维修 |
| | | </van-button> |
| | | <van-button |
| | | type="danger" |
| | | </u-button> |
| | | <u-button |
| | | type="error" |
| | | size="small" |
| | | plain |
| | | class="action-btn" |
| | | @click="delRepairByIds(item.id)" |
| | | > |
| | | 删除 |
| | | </van-button> |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | <view v-else class="no-data"> |
| | | <text>暂无设备报修数据</text> |
| | | </view> |
| | | |
| | | <!-- 浮动气泡按钮 --> |
| | | <view class="fab-button" @click="addRepair"> |
| | | <!-- 查看附件弹窗 --> |
| | | <view v-if="showAttachmentDialog" class="custom-modal-overlay" @click="closeAttachmentDialog"> |
| | | <view class="custom-modal-container" @click.stop> |
| | | <view class="attachment-popup-content"> |
| | | <view class="attachment-popup-header"> |
| | | <text class="attachment-popup-title">查看附件 - {{ currentViewRepair?.deviceName || '报修' }}</text> |
| | | <view class="close-btn-attachment" @click="closeAttachmentDialog"> |
| | | <u-icon name="close" size="16" color="#666"></u-icon> |
| | | </view> |
| | | </view> |
| | | <view class="attachment-popup-body"> |
| | | <view class="attachment-content"> |
| | | <view v-if="attachmentList.length > 0" class="attachment-list"> |
| | | <view |
| | | v-for="(file, index) in attachmentList" |
| | | :key="index" |
| | | class="attachment-item" |
| | | @click="previewAttachment(file)" |
| | | > |
| | | <view class="attachment-preview-container"> |
| | | <image |
| | | v-if="isImageFile(file)" |
| | | :src="formatFileUrl(file.url || file.downloadUrl)" |
| | | class="attachment-preview" |
| | | mode="aspectFill" |
| | | /> |
| | | <view v-else class="attachment-video-preview"> |
| | | <u-icon name="video" size="24" color="#409eff"></u-icon> |
| | | <text class="video-text">视频</text> |
| | | </view> |
| | | </view> |
| | | <view class="attachment-info"> |
| | | <text class="attachment-name">{{ file.originalFilename || file.bucketFilename || file.name || '附件' }}</text> |
| | | <text class="attachment-size">{{ formatFileSize(file.byteSize || file.size) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="attachment-empty"> |
| | | <text>暂无附件</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- 视频预览弹窗 --> |
| | | <view v-if="showVideoDialog" class="video-modal-overlay" @click="closeVideoPreview"> |
| | | <view class="video-modal-container" @click.stop> |
| | | <view class="video-modal-header"> |
| | | <text class="video-modal-title">{{ currentVideoFile?.originalFilename || currentVideoFile?.bucketFilename || '视频预览' }}</text> |
| | | <view class="close-btn-video" @click="closeVideoPreview"> |
| | | <u-icon name="close" size="16" color="#fff"></u-icon> |
| | | </view> |
| | | </view> |
| | | <view class="video-modal-body"> |
| | | <video |
| | | v-if="currentVideoFile" |
| | | :src="currentVideoFile.url || currentVideoFile.downloadUrl" |
| | | class="video-player" |
| | | controls |
| | | autoplay |
| | | @error="handleVideoError" |
| | | ></video> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- 浮动操作按钮 --> |
| | | <view class="fab-button" @click="addRepair"> |
| | | <up-icon name="plus" size="24" color="#ffffff"></up-icon> |
| | | </view> |
| | | </view> |
| | |
| | | import { ref, onMounted } from 'vue' |
| | | import { onShow } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { getRepairPage, delRepair } from '@/api/equipmentManagement/repair' |
| | | import { getRepairPage, delRepair, getRepairById } from '@/api/equipmentManagement/repair' |
| | | import config from '@/config' |
| | | import useUserStore from "@/store/modules/user" |
| | | // 替换 Vant 的 toast |
| | | // import { showToast } from 'vant'; |
| | | |
| | | // 替换 toast 方法 |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | |
| | | |
| | | // 设备报修数据 |
| | | const repairList = ref([]) |
| | | |
| | | // 查看附件 |
| | | const showAttachmentDialog = ref(false) |
| | | const attachmentList = ref([]) |
| | | const currentViewRepair = ref(null) |
| | | const showVideoDialog = ref(false) |
| | | const currentVideoFile = ref(null) |
| | | |
| | | const filePreviewBase = config.fileUrl || config.baseUrl || '' |
| | | |
| | | const formatFileUrl = (url) => { |
| | | if (!url) return '' |
| | | if (url.startsWith('http://') || url.startsWith('https://')) return url |
| | | const base = (filePreviewBase || '').replace(/\/$/, '') |
| | | const path = (url || '').replace(/^\/+/, '') |
| | | return path ? `${base}/${path}` : base |
| | | } |
| | | |
| | | const isImageFile = (file) => { |
| | | if (file?.contentType && file.contentType.startsWith('image/')) return true |
| | | if (file?.type === 'image') return true |
| | | const name = file?.bucketFilename || file?.originalFilename || file?.name || '' |
| | | const ext = name.split('.').pop()?.toLowerCase() |
| | | return ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext) |
| | | } |
| | | |
| | | const formatFileSize = (size) => { |
| | | if (!size) return '' |
| | | if (size < 1024) return size + 'B' |
| | | if (size < 1024 * 1024) return (size / 1024).toFixed(1) + 'KB' |
| | | return (size / (1024 * 1024)).toFixed(1) + 'MB' |
| | | } |
| | | |
| | | const viewAttachments = async (item) => { |
| | | try { |
| | | currentViewRepair.value = item |
| | | const { code, data } = await getRepairById(item.id) |
| | | if (code === 200 && data) { |
| | | attachmentList.value = Array.isArray(data.fileList) ? data.fileList : (Array.isArray(data.commonFileList) ? data.commonFileList : []) |
| | | showAttachmentDialog.value = true |
| | | } else { |
| | | showToast('获取附件失败') |
| | | } |
| | | } catch (e) { |
| | | showToast('获取附件失败') |
| | | } |
| | | } |
| | | |
| | | const closeAttachmentDialog = () => { |
| | | showAttachmentDialog.value = false |
| | | currentViewRepair.value = null |
| | | attachmentList.value = [] |
| | | } |
| | | |
| | | const previewAttachment = (file) => { |
| | | if (isImageFile(file)) { |
| | | const urls = attachmentList.value.filter((f) => isImageFile(f)).map((f) => formatFileUrl(f.url || f.downloadUrl)) |
| | | uni.previewImage({ |
| | | urls, |
| | | current: formatFileUrl(file.url || file.downloadUrl) |
| | | }) |
| | | } else { |
| | | currentVideoFile.value = file |
| | | showVideoDialog.value = true |
| | | } |
| | | } |
| | | |
| | | const closeVideoPreview = () => { |
| | | showVideoDialog.value = false |
| | | currentVideoFile.value = null |
| | | } |
| | | |
| | | const handleVideoError = () => { |
| | | uni.showToast({ title: '视频播放失败', icon: 'none' }) |
| | | } |
| | | |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | |
| | | |
| | | // 查询列表 |
| | | const getList = () => { |
| | | showLoadingToast('加载中...') |
| | | const params = { |
| | | current: -1, |
| | | size: -1, |
| | |
| | | getRepairPage(params) |
| | | .then((res) => { |
| | | repairList.value = res.records || res.data?.records || [] |
| | | closeToast() |
| | | }) |
| | | .catch(() => { |
| | | closeToast() |
| | | showToast('获取数据失败') |
| | | }) |
| | | } |
| | | |
| | | // 显示加载提示 |
| | | const showLoadingToast = (message) => { |
| | | uni.showLoading({ |
| | | title: message, |
| | | mask: true |
| | | }); |
| | | }; |
| | | |
| | | // 关闭提示 |
| | | const closeToast = () => { |
| | | uni.hideLoading(); |
| | | }; |
| | | |
| | | // 新增维修 - 跳转到维修页面 |
| | | const addMaintain = (id) => { |
| | |
| | | showToast('参数错误') |
| | | return |
| | | } |
| | | // 使用uni.setStorageSync存储id |
| | | uni.setStorageSync('repairId', id) |
| | | uni.navigateTo({ |
| | | url: `/pages/equipmentManagement/repair/maintain?id=${id}` |
| | | url: '/pages/equipmentManagement/repair/maintain' |
| | | }) |
| | | } |
| | | |
| | |
| | | // 编辑 - 跳转到add页面,通过id区分新增还是编辑 |
| | | const edit = (id) => { |
| | | if (!id) return |
| | | // 使用uni.setStorageSync存储id |
| | | uni.setStorageSync('repairId', id) |
| | | uni.navigateTo({ |
| | | url: `/pages/equipmentManagement/repair/add?id=${id}` |
| | | url: '/pages/equipmentManagement/repair/add' |
| | | }) |
| | | } |
| | | |
| | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .u-divider { |
| | | margin: 0 !important; |
| | | } |
| | | @import '@/styles/sales-common.scss'; |
| | | |
| | | .device-repair { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | position: relative; |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | .search-filter-section { |
| | | padding: 10px 20px; |
| | | background: #ffffff; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | background: #f5f5f5; |
| | | border-radius: 24px; |
| | | padding: 10px 16px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .search-text { |
| | | flex: 1; |
| | | font-size: 14px; |
| | | color: #333; |
| | | background: transparent; |
| | | border: none; |
| | | outline: none; |
| | | } |
| | | |
| | | .search-text::placeholder { |
| | | color: #999; |
| | | } |
| | | |
| | | .filter-button { |
| | | width: 40px; |
| | | height: 40px; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .repair-list { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .repair-item { |
| | | background: #ffffff; |
| | | border-radius: 12px; |
| | | margin-bottom: 16px; |
| | | overflow: hidden; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | padding: 0 16px; |
| | | } |
| | | |
| | | .item-header { |
| | | padding: 16px 0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .document-icon { |
| | | width: 24px; |
| | | height: 24px; |
| | | background: #2979ff; |
| | | border-radius: 4px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .item-id { |
| | | font-size: 14px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | // 设备维修特有样式 |
| | | .sales-account { |
| | | padding-bottom: 80px; // 为浮动按钮留出空间 |
| | | } |
| | | |
| | | .status-tag { |
| | |
| | | align-items: center; |
| | | } |
| | | |
| | | .item-details { |
| | | padding: 16px 0; |
| | | } |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | align-items: flex-end; |
| | | justify-content: space-between; |
| | | margin-bottom: 8px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | .detail-label { |
| | | font-size: 12px; |
| | | color: #777777; |
| | | min-width: 60px; |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 12px; |
| | | color: #000000; |
| | | text-align: right; |
| | | flex: 1; |
| | | margin-left: 16px; |
| | | } |
| | | |
| | | .detail-value.highlight { |
| | | color: #2979ff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .no-data { |
| | | padding: 40px 0; |
| | | text-align: center; |
| | | color: #999; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | gap: 8px; |
| | | padding: 0 0 16px 0; |
| | | justify-content: space-between; |
| | | gap: 8px; // 与公共样式中的 12px 不同 |
| | | } |
| | | |
| | | .action-btn { |
| | | /* 查看附件弹窗 */ |
| | | .custom-modal-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | z-index: 100; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .custom-modal-container { |
| | | width: 100%; |
| | | max-width: 500px; |
| | | max-height: 80vh; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .attachment-popup-content { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | width: 100%; |
| | | min-height: 300px; |
| | | max-height: 70vh; |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); |
| | | } |
| | | |
| | | .attachment-popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 15px 20px; |
| | | border-bottom: 1px solid #eee; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .attachment-popup-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .close-btn-attachment { |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 50%; |
| | | background: #f5f5f5; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .attachment-popup-body { |
| | | flex: 1; |
| | | padding: 15px 20px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .attachment-content { |
| | | min-height: 200px; |
| | | } |
| | | |
| | | .attachment-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .attachment-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 8px; |
| | | border: 1px solid #e9ecef; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); |
| | | width: calc(33.33% - 8px); |
| | | min-width: 100px; |
| | | } |
| | | |
| | | .attachment-preview-container { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .attachment-preview { |
| | | width: 80px; |
| | | height: 80px; |
| | | border-radius: 8px; |
| | | object-fit: cover; |
| | | border: 2px solid #f0f0f0; |
| | | } |
| | | |
| | | .attachment-video-preview { |
| | | width: 80px; |
| | | height: 80px; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
| | | border-radius: 8px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border: 2px solid #f0f0f0; |
| | | } |
| | | |
| | | .attachment-info { |
| | | text-align: center; |
| | | width: 100%; |
| | | } |
| | | |
| | | .attachment-name { |
| | | font-size: 12px; |
| | | color: #333; |
| | | display: block; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | max-width: 80px; |
| | | } |
| | | |
| | | .attachment-size { |
| | | font-size: 10px; |
| | | color: #999; |
| | | margin-top: 2px; |
| | | display: block; |
| | | } |
| | | |
| | | .attachment-empty { |
| | | text-align: center; |
| | | padding: 60px 20px; |
| | | color: #999; |
| | | font-size: 14px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | border: 2px dashed #ddd; |
| | | } |
| | | |
| | | /* 视频预览弹窗 */ |
| | | .video-modal-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: rgba(0, 0, 0, 0.8); |
| | | z-index: 10001; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .video-modal-container { |
| | | width: 90%; |
| | | max-width: 800px; |
| | | max-height: 80vh; |
| | | background: #000; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); |
| | | } |
| | | |
| | | .video-modal-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 15px 20px; |
| | | background: rgba(0, 0, 0, 0.7); |
| | | color: #fff; |
| | | } |
| | | |
| | | .video-modal-title { |
| | | font-size: 16px; |
| | | color: #fff; |
| | | } |
| | | |
| | | .close-btn-video { |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 50%; |
| | | background: rgba(255, 255, 255, 0.2); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .video-modal-body { |
| | | background: #000; |
| | | } |
| | | |
| | | .video-player { |
| | | width: 100%; |
| | | height: auto; |
| | | max-height: 60vh; |
| | | display: block; |
| | | } |
| | | |
| | | .video-text { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin-top: 4px; |
| | | } |
| | | </style> |