diff --git a/backend/my_work/routers/apiWorkOrder.go b/backend/my_work/routers/apiWorkOrder.go index 8ffb3cf..c4e1e12 100644 --- a/backend/my_work/routers/apiWorkOrder.go +++ b/backend/my_work/routers/apiWorkOrder.go @@ -418,6 +418,7 @@ func ApiWorkOrder(r *gin.RouterGroup) { ID uint `json:"ID"` Name string `json:"Name"` SerialNumber string `json:"SerialNumber"` + ContainerID *uint `json:"ContainerID"` } var linkedItems []LinkedItem var itemBinds []TabWarehouseItemWorkOrderBind @@ -434,6 +435,7 @@ func ApiWorkOrder(r *gin.RouterGroup) { ID: it.ID, Name: it.Name, SerialNumber: it.SerialNumber, + ContainerID: it.ContainerID, }) } } @@ -499,6 +501,34 @@ func ApiWorkOrder(r *gin.RouterGroup) { oldStatus := order.CurrentStatus models.DB.Model(&order).Update("current_status", from.Status) + // 如果状态变更为"已送还",移除关联物品的容器 + if from.Status == "returned" { + var itemBinds []TabWarehouseItemWorkOrderBind + models.DB.Where("work_order_id = ?", from.ID).Find(&itemBinds) + for _, bind := range itemBinds { + var item TabWarehouseItem + if models.DB.Where("id = ?", bind.ItemID).First(&item).Error == nil { + oldContainer := item.ContainerID + // 移除容器 + item.ContainerID = nil + models.DB.Save(&item) + // 记录移动 commit + models.DB.Create(&TabWarehouseItemCommit{ + ItemID: item.ID, + UserID: user.ID, + OldContainer: oldContainer, + NewContainer: nil, + Remark: "工单送还: " + from.Comment, + IP: ctx.ClientIP(), + }) + // 旧容器 ItemCount -1 + if oldContainer != nil { + models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *oldContainer).Update("item_count", models.DB.Raw("item_count - 1")) + } + } + } + } + comment := from.Comment if comment == "" { comment = "状态变更为: " + from.Status diff --git a/frontend/ops_vue_js/src/i18n/en.json b/frontend/ops_vue_js/src/i18n/en.json index 13d80a6..b10c009 100644 --- a/frontend/ops_vue_js/src/i18n/en.json +++ b/frontend/ops_vue_js/src/i18n/en.json @@ -122,6 +122,11 @@ "title_placeholder": "Enter work order title", "description": "Description", "description_placeholder": "Enter problem description", + "linked_item": "Linked Item", + "linked_item_placeholder": "Search item name or serial number...", + "linked_item_not_found": "No matching items found", + "linked_item_selected": "Selected", + "clear_linked_item": "Clear", "photos": "Photos", "no_photos": "No photos", "status": "Status", diff --git a/frontend/ops_vue_js/src/i18n/zh-CN.json b/frontend/ops_vue_js/src/i18n/zh-CN.json index 138729e..48e388a 100644 --- a/frontend/ops_vue_js/src/i18n/zh-CN.json +++ b/frontend/ops_vue_js/src/i18n/zh-CN.json @@ -122,6 +122,11 @@ "title_placeholder": "输入工单标题", "description": "问题描述", "description_placeholder": "输入问题描述", + "linked_item": "关联物品", + "linked_item_placeholder": "搜索物品名称或序列号...", + "linked_item_not_found": "未找到匹配的物品", + "linked_item_selected": "已选择", + "clear_linked_item": "清除", "photos": "图片", "no_photos": "暂无图片", "status": "状态", diff --git a/frontend/ops_vue_js/src/views/work_order/AddEditWorkOrder.vue b/frontend/ops_vue_js/src/views/work_order/AddEditWorkOrder.vue index bb4580f..268459d 100644 --- a/frontend/ops_vue_js/src/views/work_order/AddEditWorkOrder.vue +++ b/frontend/ops_vue_js/src/views/work_order/AddEditWorkOrder.vue @@ -3,14 +3,16 @@ * 工单新增/编辑页面 * - 路由有 :id 参数时为编辑模式,否则为新增模式 * - 支持图片上传(复用 useDropzone 组件) + * - 支持关联物品搜索 */ -import { reactive, ref, computed, onMounted, nextTick } from 'vue' +import { reactive, ref, computed, onMounted, nextTick, watch } from 'vue' import { useI18n } from 'vue-i18n' import { useRoute, useRouter } from 'vue-router' import { useToastStore } from '@/stores/toast' import { usePageTitle } from '@/composables/usePageTitle' import { useValidation } from '@/composables' import { workOrderApi } from '@/api/work_order' +import { warehouseApi } from '@/api/warehouse' import useDropzone from '@/components/useDropzone.vue' import ConfirmDialog from '@/components/ConfirmDialog.vue' @@ -31,6 +33,75 @@ const pageLoading = ref(false) const pageError = ref('') const showDeleteConfirm = ref(false) +// ==================== 关联物品搜索 ==================== +const itemSearchQuery = ref('') +const itemSearchResults = ref([]) +const itemSearchLoading = ref(false) +const showItemDropdown = ref(false) +const selectedItem = ref(null) + +let searchTimer = null + +function onItemSearchInput() { + clearTimeout(searchTimer) + searchTimer = setTimeout(async () => { + itemSearchLoading.value = true + showItemDropdown.value = true + try { + let res + if (itemSearchQuery.value.trim().length > 0) { + // 有搜索词:搜索匹配物品 + res = await warehouseApi.getItems({ search: itemSearchQuery.value.trim() }) + if (res.errCode === 0 && res.data) { + itemSearchResults.value = (res.data.items || []).slice(0, 10) + } else { + itemSearchResults.value = [] + } + } else { + // 无搜索词:显示最新5个物品 + res = await warehouseApi.getItems({ page: 1, page_size: 5 }) + if (res.errCode === 0 && res.data) { + // 按创建时间倒序(最新在前) + itemSearchResults.value = (res.data.items || []).sort((a, b) => { + const tsA = parseInt(a.CreatedAt || '0', 10) + const tsB = parseInt(b.CreatedAt || '0', 10) + return tsB - tsA + }) + } else { + itemSearchResults.value = [] + } + } + } catch { + itemSearchResults.value = [] + } finally { + itemSearchLoading.value = false + } + }, 300) +} + +function selectItem(item) { + selectedItem.value = item + linkedItemId.value = item.ID + itemSearchQuery.value = '' + itemSearchResults.value = [] + showItemDropdown.value = false +} + +function clearSelectedItem() { + selectedItem.value = null + linkedItemId.value = null +} + +function handleClickOutside(e) { + if (!e.target.closest('.item-search-wrapper')) { + showItemDropdown.value = false + } +} + +onMounted(() => { + document.addEventListener('click', handleClickOutside) +}) + // ==================== 表单数据 ==================== const form = reactive({ title: '', @@ -57,7 +128,20 @@ onMounted(async () => { const prefill = JSON.parse(prefillStr) form.title = prefill.title || '' form.description = prefill.description || '' - linkedItemId.value = prefill.itemId || null + + // 如果有物品ID,获取物品详情并自动选中 + if (prefill.itemId) { + try { + const itemRes = await warehouseApi.getItem(prefill.itemId) + if (itemRes.errCode === 0 && itemRes.data) { + selectedItem.value = itemRes.data.item + linkedItemId.value = prefill.itemId + } + } catch { + // 获取物品详情失败,忽略 + } + } + localStorage.removeItem('prefill_work_order') } catch { // ignore @@ -247,6 +331,72 @@ async function handleSubmit() { /> + +