You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1722 lines
48 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="inventory-bg">
<view class="inventory-card">
<view class="readonly-group">
<view class="readonly-item">
<text class="readonly-label">一级分类</text>
<text class="readonly-value">{{ firstCategory }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">二级分类</text>
<text class="readonly-value">{{ secondCategory }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">所属种类</text>
<text class="readonly-value">{{ categoryName }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">物资名称</text>
<text class="readonly-value">{{ materialName }}</text>
</view>
<!-- <view class="readonly-item">
<text class="readonly-label">物质代码</text>
<text class="readonly-value">{{ materialCode }}</text>
</view> -->
<view class="readonly-item">
<text class="readonly-label">物资类型</text>
<text class="readonly-value">{{ materialType }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">物资型号</text>
<text class="readonly-value">{{ materialSpec }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">物资规格</text>
<text class="readonly-value">{{ materialSize }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">单位</text>
<text class="readonly-value">{{ unit }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">入库批次</text>
<text class="readonly-value">{{ batchNumber }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">在库数量</text>
<text class="readonly-value">{{ stockQty }}{{ unit ? ' ' + unit : '' }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">顺序号</text>
<text class="readonly-value">{{ sequenceNumber }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">待出库</text>
<text class="readonly-value">{{ waitNum }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">生产日期</text>
<text class="readonly-value">{{ productionDate }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">入库日期</text>
<text class="readonly-value">{{ storageDate }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">物资状态</text>
<text class="readonly-value">{{ materialStatus }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">储备方式</text>
<text class="readonly-value">{{ reserveMethod }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">所在仓库</text>
<view class="readonly-value">{{ warehouseName }}</view>
</view>
<view class="readonly-item">
<text class="readonly-label">所在货架</text>
<text class="readonly-value">{{ shelfName }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">所在货架层</text>
<text class="readonly-value">{{ shelfLayer }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">等级分类</text>
<text class="readonly-value">{{ levelCategory }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">产权信息</text>
<text class="readonly-value">{{ propertyInfo }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">是否为固定资产</text>
<text class="readonly-value">{{ isFixedAsset }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">固定资产编码</text>
<text class="readonly-value">{{ fixedAssetCode }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">储备年限</text>
<text class="readonly-value">{{ reserveYears }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">存放要求</text>
<text class="readonly-value">{{ storageRequirement }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">维护要求</text>
<text class="readonly-value">{{ maintenanceRequirement }}</text>
</view>
<view class="readonly-item">
<text class="readonly-label">保养频次</text>
<text class="readonly-value">{{ maintenanceFrequency }}</text>
</view>
</view>
<!-- 盘点按钮 -->
<view class="inventory-action-section" v-if="!isViewMode">
<button class="inventory-action-btn" @click="showInventoryModal">盘点</button>
</view>
<!-- 运维记录 -->
<view class="maintenance-section">
<view class="section-title">运维记录</view>
<view class="maintenance-list" v-if="maintenanceRecords.length > 0">
<view class="maintenance-item" v-for="(record, index) in maintenanceRecords" :key="index">
<view class="maintenance-row">
<text class="maintenance-label">记录编号</text>
<text class="maintenance-value">{{record.no || '-'}}</text>
</view>
<view class="maintenance-row">
<text class="maintenance-label">计划维护日期</text>
<text class="maintenance-value">{{formatDate(record.planned_maintenance_date) || '-'}}</text>
</view>
<view class="maintenance-row">
<text class="maintenance-label">截止日期</text>
<text class="maintenance-value">{{formatDate(record.end_date) || '-'}}</text>
</view>
<view class="maintenance-row">
<text class="maintenance-label">负责人</text>
<text class="maintenance-value">{{record.responsible_admin?record.responsible_admin.name : '-'}}</text>
</view>
<view class="maintenance-row">
<text class="maintenance-label">运维内容</text>
<text class="maintenance-value">
<span v-if="record.equipment_maintain_config_id && record.equipment_maintain_config && record.equipment_maintain_config.name">
{{ record.equipment_maintain_config.name }}
</span>
<span v-else-if="record.content">
{{ record.content }}
</span>
<span v-else>-</span>
</text>
</view>
<view class="maintenance-row">
<text class="maintenance-label">状态:</text>
<text class="maintenance-value">{{record.status === 1 ? '已完成' : '待处理'}}</text>
</view>
<view class="maintenance-row">
<text class="maintenance-label">实际维护日期/状态:</text>
<view class="maintenance-value">
<text v-if="record.status === 1">{{formatDate(record.maintenance_date) || '-'}}</text>
<view v-else :class="['status-badge', getBadgeClass(record.end_date)]">
{{ getBadgeText(record.end_date) }}
</view>
</view>
</view>
<view class="maintenance-actions">
<button v-if="record.status === 0" class="action-btn complete-btn" @click="completeMaintenance(record)">完成维护</button>
<button v-if="record.status === 1" class="action-btn view-btn" @click="viewMaintenance(record)">查看</button>
</view>
</view>
</view>
<view class="empty-maintenance" v-else>
<text>暂无运维记录</text>
</view>
</view>
</view>
</view>
<!-- 盘点弹窗 -->
<view class="inventory-modal" v-if="showModal" @click="closeInventoryModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">物资盘点</text>
<view class="modal-close" @click="closeInventoryModal">
<text class="close-icon">×</text>
</view>
</view>
<view class="modal-body">
<view class="form-group">
<text class="form-label">盘点数量</text>
<input class="form-input" type="number" v-model="countQty" placeholder="请输入盘点数量" />
</view>
<view class="form-group">
<text class="form-label">盘点备注</text>
<textarea class="form-textarea" v-model="remark" placeholder="请输入备注信息" />
</view>
<view class="form-group">
<text class="form-label">照片上传</text>
<view class="photo-upload">
<view v-for="(photo, index) in photos" :key="index" class="photo-preview">
<image :src="photo" mode="aspectFill" class="photo-img" />
<view class="photo-del" @click="deletePhoto(index)">
<text class="delete-icon">×</text>
</view>
</view>
<button v-if="photos.length < 3" class="photo-btn" @click="choosePhoto">
<text class="iconfont icon-camera"></text>
<text class="btn-text">上传照片</text>
</button>
</view>
</view>
</view>
<view class="modal-footer">
<button class="modal-btn cancel-btn" @click="closeInventoryModal">取消</button>
<button class="modal-btn submit-btn" @click="submit">提交盘点</button>
</view>
</view>
</view>
<!-- 运维记录详情弹窗 -->
<view class="detail-modal" v-if="showDetailModal" @click="closeDetailModal">
<view class="detail-modal-content" @click.stop>
<view class="detail-modal-header">
<text class="detail-modal-title">运维记录详情</text>
<view class="detail-modal-close" @click="closeDetailModal">
<text class="close-icon">×</text>
</view>
</view>
<scroll-view class="detail-modal-body" scroll-y>
<view class="detail-form-group">
<text class="detail-form-label">计划维护日期:</text>
<text class="detail-form-value">{{ formatDate(currentRecord.planned_maintenance_date) || '-' }}</text>
</view>
<view class="detail-form-group">
<text class="detail-form-label">实际维护日期:</text>
<text class="detail-form-value">{{ formatDate(currentRecord.maintenance_date) || '-' }}</text>
</view>
<view class="detail-form-group">
<text class="detail-form-label">负责人:</text>
<text class="detail-form-value">{{ currentRecord.responsible_admin?currentRecord.responsible_admin.name : '-' }}</text>
</view>
<view class="detail-form-group">
<text class="detail-form-label">运维内容:</text>
<text class="detail-form-value">{{ currentRecord.content || '-' }}</text>
</view>
<view class="detail-form-group">
<text class="detail-form-label">维护备注:</text>
<text class="detail-form-value">{{ currentRecord.maintenance_content || '-' }}</text>
</view>
<view class="detail-form-group">
<text class="detail-form-label">维护照片:</text>
<view v-if="currentRecord.files && currentRecord.files.length" class="detail-photo-gallery">
<image
v-for="(file, idx) in currentRecord.files"
:key="'file-' + idx"
:src="file.url"
class="detail-photo-preview"
mode="aspectFill"
@click="previewImage(file.url, currentRecord.files.map(f => f.url))"
/>
</view>
<text v-else class="detail-form-value">-</text>
</view>
<view class="detail-form-group">
<text class="detail-form-label">签名照片:</text>
<view v-if="currentRecord.sign && currentRecord.sign.url" class="detail-photo-gallery">
<image
:src="currentRecord.sign.url"
class="detail-sign-preview"
mode="aspectFill"
@click="previewImage(currentRecord.sign.url, [currentRecord.sign.url])"
/>
</view>
<text v-else class="detail-form-value">-</text>
</view>
</scroll-view>
<view class="detail-modal-footer">
<button class="modal-btn submit-btn" @click="closeDetailModal">关闭</button>
</view>
</view>
</view>
<!-- 完成维护弹窗 -->
<view class="complete-modal" v-if="showCompleteModal" @click="closeCompleteModal">
<view class="complete-modal-content" @click.stop>
<view class="complete-modal-header">
<text class="complete-modal-title">完成维护</text>
<view class="complete-modal-close" @click="closeCompleteModal">
<text class="close-icon">×</text>
</view>
</view>
<scroll-view class="complete-modal-body" scroll-y>
<view class="complete-form-group">
<text class="complete-form-label">实际维护日期</text>
<picker mode="date" :value="completeForm.actual_date" @change="onDateChange">
<view class="complete-form-input">
<text :class="completeForm.actual_date ? 'input-value' : 'input-placeholder'">
{{ completeForm.actual_date || '请选择实际维护日期' }}
</text>
</view>
</picker>
</view>
<view class="complete-form-group">
<text class="complete-form-label">维护备注</text>
<textarea
class="complete-form-textarea"
v-model="completeForm.notes"
placeholder="请输入维护备注"
:maxlength="500"
/>
</view>
<view class="complete-form-group">
<text class="complete-form-label">上传图片</text>
<view class="complete-photo-upload">
<view v-for="(photo, index) in completeForm.photos" :key="index" class="complete-photo-preview">
<image :src="photo.url" mode="aspectFill" class="complete-photo-img" />
<view class="complete-photo-del" @click="removeCompletePhoto(index)">
<text class="delete-icon">×</text>
</view>
</view>
<button v-if="completeForm.photos.length < 9" class="complete-photo-btn" @click="chooseCompletePhoto">
<text class="iconfont icon-camera"></text>
<text class="btn-text">上传照片</text>
</button>
</view>
</view>
<view class="complete-form-group">
<text class="complete-form-label">签名</text>
<view class="signature-wrapper">
<canvas
canvas-id="signatureCanvas"
class="signature-canvas"
disable-scroll="true"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
></canvas>
<view class="signature-actions">
<button class="signature-btn" @click="clearSignature">清除签名</button>
</view>
</view>
</view>
</scroll-view>
<view class="complete-modal-footer">
<button class="modal-btn cancel-btn" @click="closeCompleteModal">取消</button>
<button class="modal-btn submit-btn" @click="submitCompleteMaintenance"></button>
</view>
</view>
</view>
</template>
<script>
import { getMaterialInfo, saveInventoryCheck, uploadFile, getMaintenanceRecord, getMaintenanceRecordDetail, submitMaintenanceRecord } from '@/api.js'
export default {
data() {
return {
isViewMode: false,
stockQty: '',
countQty: '',
remark: '',
photo: '',
photos: [],
material_info_id:'',
// 物资信息
firstCategory: '-',
secondCategory: '-',
categoryName: '-',
materialName: '-',
materialCode: '-',
materialType: '-',
materialSpec: '-',
materialSize: '-',
unit: '-',
// 库存信息
batchNumber: '-',
sequenceNumber: '-',
waitNum: '-',
// 物资明细
productionDate: '-',
storageDate: '-',
materialStatus: '-',
reserveMethod: '-',
warehouseName: '-',
shelfName: '-',
shelfLayer: '-',
levelCategory: '-',
propertyInfo: '-',
isFixedAsset: '-',
fixedAssetCode: '-',
reserveYears: '-',
storageRequirement: '-',
maintenanceRequirement: '-',
maintenanceFrequency: '-',
material_infos_plan_id: '',
materialId: '',
// 运维记录
maintenanceRecords: [],
// 弹窗控制
showModal: false,
// 查看详情
showDetailModal: false,
currentRecord: {},
// 完成维护弹窗
showCompleteModal: false,
completeForm: {
actual_date: '',
notes: '',
photos: [], // [{id: xxx, url: xxx}]
signature: '' // 签名图片路径
},
currentMaintenanceRecord: null, // 当前要完成的维护记录
signatureCtx: null, // 签名画布上下文
signaturePoints: [], // 签名点数组
isDrawing: false // 是否正在绘制签名
}
},
onLoad(options) {
this.isViewMode = options.view === '1';
this.date = this.getToday();
this.materialId = options.code;
console.log("materialId:", this.materialId);
if (this.materialId) {
getMaterialInfo(this.materialId).then(response => {
console.log("response:", response);
if (response.data) {
const data = response.data;
this.material_info_id = data.wuzibianma_material_infos_wuzibianma_relation.id;
const relation = data.wuzibianma_material_infos_wuzibianma_relation || {};
const fenleiDetail = relation.fenlei_detail || {};
const wuzizhuangtaiDetail = data.wuzizhuangtai_detail || {};
const chubeifangshiDetail = data.chubeifangshi_detail || {};
const dengjifenleiDetail = data.dengjifenlei_detail || {};
const chanquanxinxiDetail = data.chanquanxinxi_detail || {};
const materialstorages = data.materialstorages || {};
const shelfs = data.shelfs || {};
const equipmentMaintainConfig = data.equipment_maintain_config || {};
// 物资信息
// 一级分类、二级分类 - 从 material_info_type 分割
const materialInfoType = data.material_info_type || '';
if (materialInfoType) {
const parts = materialInfoType.split('-');
this.firstCategory = parts[0] || '-';
this.secondCategory = parts.length > 1 ? parts[1] : parts[0] || '-';
}
// 所属种类
this.categoryName = fenleiDetail.name || '-';
// 物资名称
this.materialName = data.zichanmingcheng || '-';
// 物质代码
this.materialCode = data.wuzibianma || '-';
// 物资类型
this.materialType = data.wuzileixing || '-';
// 物资型号
this.materialSpec = relation.guigexinghao || data.guigexinghao || '-';
// 物资规格
this.materialSize = relation.wuziguige || '-';
// 单位
this.unit = relation.jiliangdanwei || data.jiliangdanwei || '-';
// 库存信息
// 入库批次
this.batchNumber = data.rukupici || '-';
// 在库数量
this.stockQty = data.zaikushuliang || data.inventorys_total || '0';
// 顺序号
this.sequenceNumber = data.shunxuhao || '-';
// 待出库
this.waitNum = data.wait_num || '-';
// 物资明细
// 生产日期
this.productionDate = data.shengchanriqi || '-';
// 入库日期
this.storageDate = data.rukuriqi || '-';
// 物资状态
this.materialStatus = wuzizhuangtaiDetail.value || '-';
// 储备方式
this.reserveMethod = chubeifangshiDetail.value || '-';
// 所在仓库
this.warehouseName = materialstorages.cangkumingcheng || '-';
// 所在货架
this.shelfName = shelfs.huojiamingcheng || '-';
// 所在货架层
this.shelfLayer = data.huojiaceng || '-';
// 等级分类
this.levelCategory = dengjifenleiDetail.value || '-';
// 产权信息
this.propertyInfo = chanquanxinxiDetail.value || '-';
// 是否为固定资产
this.isFixedAsset = data.shifouweigudingzichan || '-';
// 固定资产编码
this.fixedAssetCode = data.gudingzichanbianma || '-';
// 储备年限
this.reserveYears = data.chubeinianxian || '-';
// 存放要求
this.storageRequirement = data.cunfangyaoqiu || '-';
// 维护要求
this.maintenanceRequirement = data.weihuyaoqiu || '-';
// 保养频次
this.maintenanceFrequency = equipmentMaintainConfig.name || '-';
// material_infos_plan_id
this.material_infos_plan_id = data.material_infos_plan_id || '';
} else {
uni.showToast({ title: '未获取到物资信息', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '获取物资信息失败', icon: 'none' })
})
// 获取运维记录
this.getMaintenanceRecords()
}
},
methods: {
// 显示盘点弹窗
showInventoryModal() {
this.showModal = true;
},
// 关闭盘点弹窗
closeInventoryModal() {
this.showModal = false;
},
getToday() {
const now = new Date();
const y = now.getFullYear();
const m = String(now.getMonth() + 1).padStart(2, '0');
const d = String(now.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
},
choosePhoto() {
if (this.photos.length >= 3) {
uni.showToast({ title: '最多上传3张照片', icon: 'none' });
return;
}
uni.chooseImage({
count: 3 - this.photos.length,
success: (res) => {
this.photos = [...this.photos, ...res.tempFilePaths];
}
});
},
deletePhoto(index) {
this.photos.splice(index, 1);
},
async submit() {
if (!this.countQty) {
uni.showToast({ title: '请输入盘点数量', icon: 'none' });
return;
}
if (!/^(0|[1-9][0-9]*)$/.test(this.countQty)) {
uni.showToast({ title: '盘点数量必须为0或正整数', icon: 'none' });
return;
}
uni.showLoading({ title: '提交中...' });
// 1. 上传所有照片,收集 file_id
let file_ids = [];
for (let i = 0; i < this.photos.length; i++) {
try {
const res = await uploadFile(this.photos[i]);
if (res && res.id) {
file_ids.push(res.id);
}
} catch (e) {
uni.hideLoading();
uni.showToast({ title: '图片上传失败', icon: 'none' });
return;
}
}
// 2. 组装盘点数据
const data = {
status: '1',
inventorys_id:this.materialId,
check_num: this.countQty,
remark: this.remark,
file_ids
// 其他参数如 material_infos_plan_id、status、check_date 可按需补充
};
console.log("data:", data);
// 3. 提交盘点
saveInventoryCheck(data).then(res => {
console.log("res:", res);
uni.hideLoading();
if (res && (!res.data || res.data.errcode === undefined)) {
uni.showToast({ title: '盘点提交成功', icon: 'success' });
setTimeout(() => {
uni.reLaunch({ url: '/pages/index/index' });
}, 1200);
} else {
uni.showToast({ title: res.data.errmsg || '提交失败', icon: 'none' });
}
}).catch(() => {
uni.hideLoading();
uni.showToast({ title: '提交失败', icon: 'none' });
});
},
// 获取运维记录
async getMaintenanceRecords() {
if (!this.materialId) return;
try {
const params = {
page: 1,
page_size: 999,
'filter[0][key]': 'inventorys_id',
'filter[0][op]': 'eq',
'filter[0][value]': this.materialId
};
const res = await getMaintenanceRecord(params);
console.log("运维记录响应:", res);
if (res.data && res.data.list && res.data.list.data) {
this.maintenanceRecords = res.data.list.data;
} else {
this.maintenanceRecords = [];
}
} catch (error) {
console.error('获取运维记录失败:', error);
this.maintenanceRecords = [];
}
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '-';
// 如果已经是 YYYY-MM-DD 格式,直接返回
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
return dateStr;
}
const date = new Date(dateStr);
if (isNaN(date.getTime())) {
return dateStr; // 如果无法解析,返回原值
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
// 获取日期差值(天数)
getDateDifferenceInDays(dateString) {
if (!dateString) return 0;
const plannedDate = new Date(dateString);
const today = new Date();
// 设置时间为当天的 00:00:00
today.setHours(0, 0, 0, 0);
plannedDate.setHours(0, 0, 0, 0);
// 计算日期差值(毫秒)
const diffTime = plannedDate - today;
// 转换为天数
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
},
// 获取状态徽章文本
getBadgeText(endDate) {
if (!endDate) {
return '-';
}
const diffDays = this.getDateDifferenceInDays(endDate);
return diffDays >= 0 ? `还有 ${diffDays}` : `已超期 ${Math.abs(diffDays)}`;
},
// 获取状态徽章样式类
getBadgeClass(endDate) {
if (!endDate) {
return 'status-default';
}
const diffDays = this.getDateDifferenceInDays(endDate);
if (diffDays < 0) {
return 'status-overdue'; // 已超期
} else if (diffDays <= 3) {
return 'status-urgent'; // 紧急3天内
} else {
return 'status-normal'; // 正常
}
},
// 完成维护
completeMaintenance(record) {
console.log('完成维护:', record);
this.currentMaintenanceRecord = record;
// 重置表单
this.completeForm = {
actual_date: this.getToday(),
notes: '',
photos: [],
signature: ''
};
this.showCompleteModal = true;
// 初始化签名画布
this.$nextTick(() => {
this.initSignatureCanvas();
});
},
// 关闭完成维护弹窗
closeCompleteModal() {
this.showCompleteModal = false;
this.currentMaintenanceRecord = null;
this.completeForm = {
actual_date: '',
notes: '',
photos: [],
signature: ''
};
},
// 日期选择
onDateChange(e) {
this.completeForm.actual_date = e.detail.value;
},
// 选择完成维护的图片
chooseCompletePhoto() {
if (this.completeForm.photos.length >= 9) {
uni.showToast({ title: '最多上传9张照片', icon: 'none' });
return;
}
uni.chooseImage({
count: 9 - this.completeForm.photos.length,
success: async (res) => {
uni.showLoading({ title: '上传中...' });
try {
for (let i = 0; i < res.tempFilePaths.length; i++) {
const uploadRes = await uploadFile(res.tempFilePaths[i]);
if (uploadRes && uploadRes.id) {
this.completeForm.photos.push({
id: uploadRes.id,
url: uploadRes.url || res.tempFilePaths[i]
});
}
}
uni.hideLoading();
} catch (error) {
uni.hideLoading();
uni.showToast({ title: '图片上传失败', icon: 'none' });
}
}
});
},
// 删除完成维护的图片
removeCompletePhoto(index) {
this.completeForm.photos.splice(index, 1);
},
// 初始化签名画布
initSignatureCanvas() {
this.$nextTick(() => {
const ctx = uni.createCanvasContext('signatureCanvas', this);
ctx.setStrokeStyle('#000000');
ctx.setLineWidth(3);
ctx.setLineCap('round');
ctx.setLineJoin('round');
this.signatureCtx = ctx;
this.signaturePoints = [];
this.isDrawing = false;
});
},
// 触摸开始
onTouchStart(e) {
if (!this.signatureCtx) {
this.initSignatureCanvas();
return;
}
this.isDrawing = true;
const touch = e.touches[0];
const point = {
x: touch.x,
y: touch.y
};
this.signaturePoints = [point];
this.signatureCtx.beginPath();
this.signatureCtx.moveTo(point.x, point.y);
},
// 触摸移动
onTouchMove(e) {
if (!this.isDrawing || !this.signatureCtx) return;
e.preventDefault();
const touch = e.touches[0];
const point = {
x: touch.x,
y: touch.y
};
if (this.signaturePoints.length > 0) {
const prevPoint = this.signaturePoints[this.signaturePoints.length - 1];
this.signatureCtx.moveTo(prevPoint.x, prevPoint.y);
this.signatureCtx.lineTo(point.x, point.y);
this.signatureCtx.stroke();
this.signatureCtx.draw(true);
}
this.signaturePoints.push(point);
},
// 触摸结束
onTouchEnd() {
this.isDrawing = false;
// 保存签名图片
this.saveSignature();
},
// 保存签名
saveSignature() {
if (!this.signatureCtx) return;
uni.canvasToTempFilePath({
canvasId: 'signatureCanvas',
success: (res) => {
this.completeForm.signature = res.tempFilePath;
},
fail: (err) => {
console.error('保存签名失败:', err);
}
}, this);
},
// 清除签名
clearSignature() {
if (this.signatureCtx) {
// 清除整个画布(使用足够大的值)
this.signatureCtx.clearRect(0, 0, 1000, 1000);
this.signatureCtx.draw(true);
this.completeForm.signature = '';
this.signaturePoints = [];
this.isDrawing = false;
}
},
// 提交完成维护
async submitCompleteMaintenance() {
if (!this.completeForm.actual_date) {
uni.showToast({ title: '请选择实际维护日期', icon: 'none' });
return;
}
if (!this.currentMaintenanceRecord || !this.currentMaintenanceRecord.id) {
uni.showToast({ title: '维护记录信息错误', icon: 'none' });
return;
}
uni.showLoading({ title: '提交中...' });
try {
// 上传签名图片
let sign_id = null;
if (this.completeForm.signature) {
const signRes = await uploadFile(this.completeForm.signature);
if (signRes && signRes.id) {
sign_id = signRes.id;
}
}
// 组装提交数据
const data = {
id: this.currentMaintenanceRecord.id,
maintenance_date: this.formatDate(this.completeForm.actual_date),
maintenance_content: this.completeForm.notes,
file_ids: this.completeForm.photos.map(photo => photo.id),
sign_id: sign_id,
status: 1
};
console.log('提交完成维护数据:', data);
// return
const res = await submitMaintenanceRecord(data);
console.log('提交完成维护响应:', res);
uni.hideLoading();
if (res && (!res.data || res.data.errcode === undefined)) {
uni.showToast({ title: '完成维护成功', icon: 'success' });
this.closeCompleteModal();
// 刷新运维记录列表
this.getMaintenanceRecords();
} else {
uni.showToast({ title: res.data?.errmsg || '提交失败', icon: 'none' });
}
} catch (error) {
console.error('提交完成维护失败:', error);
uni.hideLoading();
uni.showToast({ title: '提交失败', icon: 'none' });
}
},
// 查看维护记录
async viewMaintenance(record) {
console.log('查看维护记录:', record);
if (!record.id) {
uni.showToast({
title: '记录ID不存在',
icon: 'none'
});
return;
}
uni.showLoading({ title: '加载中...' });
try {
const res = await getMaintenanceRecordDetail(record.id);
console.log('运维记录详情:', res);
if (res.data) {
this.currentRecord = res.data;
this.showDetailModal = true;
} else {
uni.showToast({
title: '获取详情失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取运维记录详情失败:', error);
uni.showToast({
title: '获取详情失败',
icon: 'none'
});
} finally {
uni.hideLoading();
}
},
// 关闭详情弹窗
closeDetailModal() {
this.showDetailModal = false;
this.currentRecord = {};
},
// 预览图片
previewImage(currentUrl, urls) {
if (!urls || urls.length === 0) {
urls = [currentUrl];
}
uni.previewImage({
current: currentUrl,
urls: urls
});
}
}
}
</script>
<style>
.inventory-bg {
min-height: 100vh;
background: #f5f6f7;
padding: 24rpx;
}
.inventory-card {
background: #fff;
border-radius: 24rpx;
padding: 32rpx 24rpx;
margin-bottom: 24rpx;
}
.readonly-group {
margin-bottom: 32rpx;
padding: 24rpx;
background: #f8f9fa;
border-radius: 16rpx;
}
.readonly-item {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.readonly-item:last-child {
margin-bottom: 0;
}
.readonly-label {
color: #666;
font-size: 28rpx;
width:20%;
}
.readonly-value {
color: #333;
font-size: 28rpx;
font-weight: 500;
text-align: right;
width:75%;
}
.form-group {
margin-bottom: 32rpx;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.form-input {
height: 88rpx;
background: #f8f9fa;
border: none;
border-radius: 16rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #333;
}
.form-textarea {
min-height: 160rpx;
background: #f8f9fa;
border: none;
border-radius: 16rpx;
padding: 20rpx 24rpx;
font-size: 28rpx;
color: #333;
}
.photo-upload {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.photo-preview {
position: relative;
width: 160rpx;
height: 160rpx;
}
.photo-btn {
width: 160rpx;
height: 160rpx;
background: #f8f9fa;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0;
}
.photo-btn .iconfont {
font-size: 48rpx;
color: #666;
margin-bottom: 8rpx;
}
.btn-text {
font-size: 24rpx;
color: #666;
}
.photo-preview {
position: relative;
}
.photo-img {
width: 160rpx;
height: 160rpx;
border-radius: 16rpx;
}
.photo-del {
position: absolute;
top: -16rpx;
right: -16rpx;
width: 40rpx;
height: 40rpx;
background: rgba(0,0,0,0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
color: #ff4d4f;
font-size: 32rpx;
font-weight: bold;
line-height: 1;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: #409eff;
color: #fff;
font-size: 32rpx;
font-weight: 500;
border-radius: 44rpx;
margin-top: 48rpx;
}
.submit-btn:active {
opacity: 0.9;
}
/* 运维记录样式 */
.maintenance-section {
margin-bottom: 32rpx;
padding: 24rpx;
background: #f8f9fa;
border-radius: 16rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 700;
color: #333;
margin-bottom: 24rpx;
}
.maintenance-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.maintenance-item {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
border: 1px solid #e0e0e0;
}
.maintenance-row {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
line-height: 1.6;
}
.maintenance-row:last-child {
margin-bottom: 0;
}
.maintenance-label {
font-size: 26rpx;
color: #666;
width: 240rpx;
flex-shrink: 0;
}
.maintenance-value {
font-size: 26rpx;
color: #333;
flex: 1;
word-break: break-all;
}
.maintenance-actions {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: flex-end;
}
.action-btn {
padding: 12rpx 24rpx;
border-radius: 16rpx;
font-size: 24rpx;
font-weight: 500;
border: none;
margin-left: 16rpx;
}
.complete-btn {
background: linear-gradient(135deg, #52c41a, #73d13d);
color: #fff;
box-shadow: 0 2px 8px rgba(82,196,26,0.3);
}
.complete-btn:active {
opacity: 0.9;
transform: scale(0.98);
}
.view-btn {
background: linear-gradient(135deg, #409eff, #66b1ff);
color: #fff;
box-shadow: 0 2px 8px rgba(64,158,255,0.3);
}
.view-btn:active {
opacity: 0.9;
transform: scale(0.98);
}
.status-badge {
display: inline-block;
padding: 6rpx 12rpx;
border-radius: 12rpx;
font-size: 22rpx;
font-weight: 500;
white-space: nowrap;
}
.status-badge.status-normal {
background: #e6f7ff;
color: #1890ff;
border: 1px solid #91d5ff;
}
.status-badge.status-urgent {
background: #fff7e6;
color: #fa8c16;
border: 1px solid #ffd591;
}
.status-badge.status-overdue {
background: #fff1f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.status-badge.status-default {
background: #f5f5f5;
color: #999;
border: 1px solid #d9d9d9;
}
.empty-maintenance {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
/* 盘点按钮区域 */
.inventory-action-section {
margin-top: 32rpx;
margin-bottom: 32rpx;
display: flex;
justify-content: center;
}
.inventory-action-btn {
width: 200rpx;
height: 80rpx;
background: linear-gradient(135deg, #409eff, #66b1ff);
color: #fff;
border: none;
border-radius: 40rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 4px 12px rgba(64,158,255,0.3);
transition: all 0.3s ease;
}
.inventory-action-btn:active {
transform: scale(0.95);
box-shadow: 0 2px 8px rgba(64,158,255,0.4);
}
/* 盘点弹窗样式 */
.inventory-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
padding: 40rpx;
box-sizing: border-box;
}
.modal-content {
background: #fff;
border-radius: 24rpx;
width: 100%;
/* max-width: 600rpx; */
max-height: 90vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx 24rpx;
border-bottom: 1px solid #e0e0e0;
}
.modal-title {
font-size: 36rpx;
font-weight: 700;
color: #333;
}
.modal-close {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f5f5f5;
cursor: pointer;
}
.modal-close:active {
background: #e0e0e0;
}
.close-icon {
font-size: 48rpx;
color: #666;
line-height: 1;
}
.modal-body {
flex: 1;
padding: 32rpx 24rpx;
overflow-y: auto;
}
.modal-footer {
display: flex;
gap: 24rpx;
padding: 24rpx;
border-top: 1px solid #e0e0e0;
align-items: center;
}
.modal-btn {
flex: 1;
height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
transition: all 0.3s ease;
margin-top:0;
}
.cancel-btn {
background: #f5f5f5;
color: #666;
}
.cancel-btn:active {
background: #e0e0e0;
transform: scale(0.98);
}
.modal-footer .submit-btn {
background: linear-gradient(135deg, #409eff, #66b1ff);
color: #fff;
box-shadow: 0 4px 12px rgba(64,158,255,0.3);
}
.modal-footer .submit-btn:active {
opacity: 0.9;
transform: scale(0.98);
}
/* 运维记录详情弹窗样式 */
.detail-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
padding: 40rpx;
box-sizing: border-box;
}
.detail-modal-content {
background: #fff;
border-radius: 24rpx;
width: 100%;
max-width: 700rpx;
max-height: 90vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.detail-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx 24rpx;
border-bottom: 1px solid #e0e0e0;
}
.detail-modal-title {
font-size: 36rpx;
font-weight: 700;
color: #333;
}
.detail-modal-close {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f5f5f5;
cursor: pointer;
}
.detail-modal-close:active {
background: #e0e0e0;
}
.detail-modal-body {
flex: 1;
padding: 32rpx 24rpx;
overflow-y: auto;
}
.detail-form-group {
margin-bottom: 32rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.detail-form-group:last-child {
margin-bottom: 0;
}
.detail-form-label {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
.detail-form-value {
font-size: 28rpx;
color: #333;
line-height: 1.6;
word-break: break-all;
}
.detail-photo-gallery {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 12rpx;
}
.detail-photo-preview {
width: 200rpx;
height: 200rpx;
border-radius: 16rpx;
background: #f5f5f5;
}
.detail-sign-preview {
width: 300rpx;
height: 200rpx;
border-radius: 16rpx;
background: #f5f5f5;
}
.detail-modal-footer {
display: flex;
padding: 24rpx;
border-top: 1px solid #e0e0e0;
align-items: center;
}
.detail-modal-footer .submit-btn {
width: 100%;
margin-top: 0;
}
/* 完成维护弹窗样式 */
.complete-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 998;
padding: 40rpx;
box-sizing: border-box;
}
.complete-modal-content {
background: #fff;
border-radius: 24rpx;
width: 100%;
max-width: 700rpx;
max-height: 90vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.complete-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx 24rpx;
border-bottom: 1px solid #e0e0e0;
}
.complete-modal-title {
font-size: 36rpx;
font-weight: 700;
color: #333;
}
.complete-modal-close {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f5f5f5;
cursor: pointer;
}
.complete-modal-close:active {
background: #e0e0e0;
}
.complete-modal-body {
flex: 1;
padding: 32rpx 24rpx;
overflow-y: auto;
}
.complete-form-group {
margin-bottom: 32rpx;
}
.complete-form-group:last-child {
margin-bottom: 0;
}
.complete-form-label {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 16rpx;
display: block;
}
.complete-form-input {
height: 88rpx;
background: #f8f9fa;
border: none;
border-radius: 16rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
font-size: 28rpx;
}
.input-value {
color: #333;
}
.input-placeholder {
color: #999;
}
.complete-form-textarea {
min-height: 160rpx;
background: #f8f9fa;
border: none;
border-radius: 16rpx;
padding: 20rpx 24rpx;
font-size: 28rpx;
color: #333;
width: 100%;
box-sizing: border-box;
}
.complete-photo-upload {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.complete-photo-preview {
position: relative;
width: 160rpx;
height: 160rpx;
}
.complete-photo-img {
width: 160rpx;
height: 160rpx;
border-radius: 16rpx;
}
.complete-photo-del {
position: absolute;
top: -16rpx;
right: -16rpx;
width: 40rpx;
height: 40rpx;
background: rgba(0,0,0,0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.complete-photo-btn {
width: 160rpx;
height: 160rpx;
background: #f8f9fa;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0;
border: none;
}
.complete-photo-btn .iconfont {
font-size: 48rpx;
color: #666;
margin-bottom: 8rpx;
}
/* 签名区域样式 */
.signature-wrapper {
background: #fff;
border: 2px solid #e0e0e0;
border-radius: 16rpx;
padding: 20rpx;
}
.signature-canvas {
width: 100%;
height: 300rpx;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8rpx;
touch-action: none;
box-sizing: border-box;
}
.signature-actions {
margin-top: 16rpx;
display: flex;
justify-content: flex-end;
}
.signature-btn {
padding: 12rpx 24rpx;
background: #f5f5f5;
color: #666;
border: none;
border-radius: 16rpx;
font-size: 24rpx;
}
.signature-btn:active {
background: #e0e0e0;
}
.complete-modal-footer {
display: flex;
gap: 24rpx;
padding: 24rpx;
border-top: 1px solid #e0e0e0;
align-items: center;
}
.complete-modal-footer .cancel-btn {
flex: 1;
margin-top: 0;
}
.complete-modal-footer .submit-btn {
flex: 1;
margin-top: 0;
}
</style>