up
This commit is contained in:
@@ -41,3 +41,16 @@
|
|||||||
- `AddEditWorkOrder.vue`: 编辑模式加载时回填 `selectedItems` 和 `selectedCustomers`
|
- `AddEditWorkOrder.vue`: 编辑模式加载时回填 `selectedItems` 和 `selectedCustomers`
|
||||||
- `AddEditWorkOrder.vue`: 编辑提交时发送 `item_ids` 和 `customer_ids`
|
- `AddEditWorkOrder.vue`: 编辑提交时发送 `item_ids` 和 `customer_ids`
|
||||||
- `apiWorkOrder.go`: `/update` 接口新增 `ItemIDs` 和 `CustomerIDs` 字段,重建物品/客户关联绑定
|
- `apiWorkOrder.go`: `/update` 接口新增 `ItemIDs` 和 `CustomerIDs` 字段,重建物品/客户关联绑定
|
||||||
|
- 仓库物品添加页支持关联客户
|
||||||
|
- `binds.go`: 新增 `TabWarehouseItemCustomerBind` 关联表(物品-客户多对多)
|
||||||
|
- `apiWarehouse.go`: `/add_item` 接口新增 `CustomerIDs` 字段,仅新建物品时创建客户关联绑定
|
||||||
|
- `WarehouseAddItem.vue`: 添加客户搜索选择组件(多选),提交时发送 `customer_ids`
|
||||||
|
- 使用已有的 `customerApi.list()` 搜索客户
|
||||||
|
- 仓库物品详情页显示关联客户
|
||||||
|
- `apiWarehouse.go`: `/get_item` 接口返回 `customers` 列表(包含客户 ID、姓名、称呼)
|
||||||
|
- `WarehouseItemDetail.vue`: 新增"关联客户" Tab,显示关联客户列表(头像、姓名、称呼),点击可跳转到客户详情页
|
||||||
|
- i18n: 添加 `warehouse.customers` 和 `warehouse.no_customers` 翻译
|
||||||
|
- 仓库物品编辑页支持关联客户
|
||||||
|
- `apiWarehouse.go`: `/update_item` 接口新增 `CustomerIDs` 字段,重建客户关联绑定
|
||||||
|
- `WarehouseItemEdit.vue`: 添加客户搜索选择组件(多选),加载时回填已关联客户,提交时发送 `customer_ids`
|
||||||
|
- i18n: 添加 `warehouse.linked_customers`、`warehouse.linked_customer_placeholder`、`warehouse.linked_customer_not_found` 等翻译到 `warehouse` 节点
|
||||||
|
|||||||
@@ -544,6 +544,7 @@ func ApiWarehouse(r *gin.RouterGroup) {
|
|||||||
Quantity int `json:"quantity"`
|
Quantity int `json:"quantity"`
|
||||||
ContainerID *uint `json:"container_id"`
|
ContainerID *uint `json:"container_id"`
|
||||||
Photos []string `json:"photos"`
|
Photos []string `json:"photos"`
|
||||||
|
CustomerIDs []uint `json:"customer_ids"`
|
||||||
}
|
}
|
||||||
var from FromAdd
|
var from FromAdd
|
||||||
if err := decodeJSON(data, &from); err != nil || from.Name == "" {
|
if err := decodeJSON(data, &from); err != nil || from.Name == "" {
|
||||||
@@ -673,6 +674,21 @@ func ApiWarehouse(r *gin.RouterGroup) {
|
|||||||
IP: ctx.ClientIP(),
|
IP: ctx.ClientIP(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 绑定客户关联(仅新建时)
|
||||||
|
if !exists {
|
||||||
|
for _, customerID := range from.CustomerIDs {
|
||||||
|
// 检查客户是否存在
|
||||||
|
var bindCustomer TabCustomer
|
||||||
|
if models.DB.First(&bindCustomer, customerID).Error == nil {
|
||||||
|
models.DB.Create(&TabWarehouseItemCustomerBind{
|
||||||
|
ItemID: itemID,
|
||||||
|
CustomerID: customerID,
|
||||||
|
CreatorID: user.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReturnJson(ctx, "apiOK", gin.H{"id": itemID, "updated": exists})
|
ReturnJson(ctx, "apiOK", gin.H{"id": itemID, "updated": exists})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -685,12 +701,13 @@ func ApiWarehouse(r *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FromUpdate struct {
|
type FromUpdate struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
SerialNumber string `json:"serial_number"`
|
SerialNumber string `json:"serial_number"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
Quantity int `json:"quantity"`
|
Quantity int `json:"quantity"`
|
||||||
Photos []string `json:"photos"`
|
Photos []string `json:"photos"`
|
||||||
|
CustomerIDs []uint `json:"customer_ids"`
|
||||||
}
|
}
|
||||||
var from FromUpdate
|
var from FromUpdate
|
||||||
if err := decodeJSON(data, &from); err != nil || from.ID == 0 || from.Name == "" {
|
if err := decodeJSON(data, &from); err != nil || from.ID == 0 || from.Name == "" {
|
||||||
@@ -738,6 +755,19 @@ func ApiWarehouse(r *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重建客户关联绑定
|
||||||
|
models.DB.Where("item_id = ?", from.ID).Delete(&TabWarehouseItemCustomerBind{})
|
||||||
|
for _, customerID := range from.CustomerIDs {
|
||||||
|
var bindCustomer TabCustomer
|
||||||
|
if models.DB.First(&bindCustomer, customerID).Error == nil {
|
||||||
|
models.DB.Create(&TabWarehouseItemCustomerBind{
|
||||||
|
ItemID: from.ID,
|
||||||
|
CustomerID: customerID,
|
||||||
|
CreatorID: user.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newContent, _ := json.Marshal(from)
|
newContent, _ := json.Marshal(from)
|
||||||
models.DB.Create(&TabWarehouseLog{
|
models.DB.Create(&TabWarehouseLog{
|
||||||
EntityType: "item",
|
EntityType: "item",
|
||||||
@@ -966,11 +996,35 @@ func ApiWarehouse(r *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关联客户
|
||||||
|
var customerBinds []TabWarehouseItemCustomerBind
|
||||||
|
models.DB.Where("item_id = ?", from.ID).Find(&customerBinds)
|
||||||
|
|
||||||
|
type CustomerInfo struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
var customers []CustomerInfo
|
||||||
|
for _, b := range customerBinds {
|
||||||
|
var c TabCustomer
|
||||||
|
if models.DB.Where("id = ?", b.CustomerID).First(&c).Error == nil {
|
||||||
|
customers = append(customers, CustomerInfo{
|
||||||
|
ID: c.ID,
|
||||||
|
FirstName: c.FirstName,
|
||||||
|
LastName: c.LastName,
|
||||||
|
Title: c.Title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReturnJson(ctx, "apiOK", gin.H{
|
ReturnJson(ctx, "apiOK", gin.H{
|
||||||
"item": item,
|
"item": item,
|
||||||
"photos": files,
|
"photos": files,
|
||||||
"commits": commitsWithBreadcrumb,
|
"commits": commitsWithBreadcrumb,
|
||||||
"work_orders": workOrders,
|
"work_orders": workOrders,
|
||||||
|
"customers": customers,
|
||||||
"canModifyItem": canModifyWarehouse(user.ID, item.CreatorID),
|
"canModifyItem": canModifyWarehouse(user.ID, item.CreatorID),
|
||||||
"container_breadcrumb": func() string {
|
"container_breadcrumb": func() string {
|
||||||
if item.ContainerID == nil {
|
if item.ContainerID == nil {
|
||||||
|
|||||||
@@ -73,6 +73,15 @@ type TabWarehouseContainerFileBind struct {
|
|||||||
CreatedAt time.Time `gorm:"type:datetime;autoCreateTime"`
|
CreatedAt time.Time `gorm:"type:datetime;autoCreateTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TabWarehouseItemCustomerBind 物品与客户关联表
|
||||||
|
type TabWarehouseItemCustomerBind struct {
|
||||||
|
ID uint `gorm:"primarykey"`
|
||||||
|
ItemID uint `gorm:"not null;index;comment:关联物品ID"`
|
||||||
|
CustomerID uint `gorm:"not null;index;comment:关联客户ID"`
|
||||||
|
CreatorID uint `gorm:"not null;comment:绑定人id"`
|
||||||
|
CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
func BindsInit() {
|
func BindsInit() {
|
||||||
models.DB.AutoMigrate(
|
models.DB.AutoMigrate(
|
||||||
&TabPurchaseFileBind{},
|
&TabPurchaseFileBind{},
|
||||||
@@ -83,6 +92,7 @@ func BindsInit() {
|
|||||||
&TabWorkOrderCommitFileBind{},
|
&TabWorkOrderCommitFileBind{},
|
||||||
&TabWorkOrderPurchaseOrderBind{},
|
&TabWorkOrderPurchaseOrderBind{},
|
||||||
&TabWorkOrderCustomerBind{},
|
&TabWorkOrderCustomerBind{},
|
||||||
|
&TabWarehouseItemCustomerBind{},
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { api } from './index'
|
import { api } from './index'
|
||||||
|
|
||||||
export const usersApi = {
|
export const usersApi = {
|
||||||
|
|
||||||
getUserInfoFromUserID(UserID) {
|
getUserInfoFromUserID(UserID) {
|
||||||
return api.get('/users/getuserinfo/'+UserID)
|
return api.get('/users/getuserinfo/'+UserID)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -222,8 +222,10 @@
|
|||||||
"move_item": "Move Item",
|
"move_item": "Move Item",
|
||||||
"move_history": "Move History",
|
"move_history": "Move History",
|
||||||
"work_orders": "Work Orders",
|
"work_orders": "Work Orders",
|
||||||
|
"customers": "Customers",
|
||||||
"no_move_history": "No move history",
|
"no_move_history": "No move history",
|
||||||
"no_work_orders": "No related work orders",
|
"no_work_orders": "No related work orders",
|
||||||
|
"no_customers": "No related customers",
|
||||||
"current_location": "Current Location",
|
"current_location": "Current Location",
|
||||||
"target_container": "Target Container",
|
"target_container": "Target Container",
|
||||||
"search_container": "Search container...",
|
"search_container": "Search container...",
|
||||||
@@ -234,7 +236,12 @@
|
|||||||
"delete_item_msg": "Are you sure you want to delete item \"{name}\"? This action cannot be undone.",
|
"delete_item_msg": "Are you sure you want to delete item \"{name}\"? This action cannot be undone.",
|
||||||
"items_in_containers": "Stored",
|
"items_in_containers": "Stored",
|
||||||
"total_items": "{count} items",
|
"total_items": "{count} items",
|
||||||
"search_item_placeholder": "Search by name or serial number"
|
"search_item_placeholder": "Search by name or serial number",
|
||||||
|
"linked_customers": "Linked Customers",
|
||||||
|
"linked_customer_placeholder": "Search customer by name or phone...",
|
||||||
|
"linked_customer_not_found": "No matching customers found",
|
||||||
|
"linked_customer_selected": "Selected",
|
||||||
|
"clear_linked_customer": "Clear"
|
||||||
},
|
},
|
||||||
"purchase_addorder": {
|
"purchase_addorder": {
|
||||||
"add_order": "Add Order",
|
"add_order": "Add Order",
|
||||||
@@ -554,6 +561,7 @@
|
|||||||
"created_at": "Created At",
|
"created_at": "Created At",
|
||||||
"primary": "Primary",
|
"primary": "Primary",
|
||||||
"set_primary": "Set Primary",
|
"set_primary": "Set Primary",
|
||||||
|
"primary_contact": "Primary Contact",
|
||||||
"label_mobile": "Mobile",
|
"label_mobile": "Mobile",
|
||||||
"label_work": "Work",
|
"label_work": "Work",
|
||||||
"label_home": "Home",
|
"label_home": "Home",
|
||||||
|
|||||||
@@ -222,8 +222,10 @@
|
|||||||
"move_item": "移动物品",
|
"move_item": "移动物品",
|
||||||
"move_history": "移动历史",
|
"move_history": "移动历史",
|
||||||
"work_orders": "关联工单",
|
"work_orders": "关联工单",
|
||||||
|
"customers": "关联客户",
|
||||||
"no_move_history": "暂无移动记录",
|
"no_move_history": "暂无移动记录",
|
||||||
"no_work_orders": "暂无关联工单",
|
"no_work_orders": "暂无关联工单",
|
||||||
|
"no_customers": "暂无关联客户",
|
||||||
"current_location": "当前位置",
|
"current_location": "当前位置",
|
||||||
"target_container": "目标容器",
|
"target_container": "目标容器",
|
||||||
"search_container": "搜索容器...",
|
"search_container": "搜索容器...",
|
||||||
@@ -234,7 +236,12 @@
|
|||||||
"delete_item_msg": "确定要删除物品「{name}」吗?此操作不可撤销。",
|
"delete_item_msg": "确定要删除物品「{name}」吗?此操作不可撤销。",
|
||||||
"items_in_containers": "已入库",
|
"items_in_containers": "已入库",
|
||||||
"total_items": "共 {count} 条",
|
"total_items": "共 {count} 条",
|
||||||
"search_item_placeholder": "搜索物品名称或序列号"
|
"search_item_placeholder": "搜索物品名称或序列号",
|
||||||
|
"linked_customers": "关联客户",
|
||||||
|
"linked_customer_placeholder": "搜索客户姓名或电话...",
|
||||||
|
"linked_customer_not_found": "未找到匹配的客户",
|
||||||
|
"linked_customer_selected": "已选择",
|
||||||
|
"clear_linked_customer": "清除"
|
||||||
},
|
},
|
||||||
"purchase_addorder": {
|
"purchase_addorder": {
|
||||||
"add_order": "添加订单",
|
"add_order": "添加订单",
|
||||||
@@ -554,6 +561,7 @@
|
|||||||
"created_at": "创建时间",
|
"created_at": "创建时间",
|
||||||
"primary": "主",
|
"primary": "主",
|
||||||
"set_primary": "设为主",
|
"set_primary": "设为主",
|
||||||
|
"primary_contact": "主要联系方式",
|
||||||
"label_mobile": "手机",
|
"label_mobile": "手机",
|
||||||
"label_work": "工作",
|
"label_work": "工作",
|
||||||
"label_home": "家庭",
|
"label_home": "家庭",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { customerApi } from '@/api/customer'
|
import { customerApi } from '@/api/customer'
|
||||||
@@ -17,6 +17,12 @@ const emails = ref([])
|
|||||||
const companies = ref([])
|
const companies = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 主要联系方式
|
||||||
|
const primaryPhone = computed(() => phones.value.find(p => p.is_primary) || null)
|
||||||
|
const primaryEmail = computed(() => emails.value.find(e => e.is_primary) || null)
|
||||||
|
const primaryCompany = computed(() => companies.value.find(c => c.is_primary) || null)
|
||||||
|
const hasPrimaryInfo = computed(() => primaryPhone.value || primaryEmail.value || primaryCompany.value)
|
||||||
|
|
||||||
const toast = ref({ show: false, message: '', type: 'success' })
|
const toast = ref({ show: false, message: '', type: 'success' })
|
||||||
|
|
||||||
// 获取客户详情
|
// 获取客户详情
|
||||||
@@ -51,9 +57,9 @@ async function fetchCustomerDetail() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回列表
|
// 返回上一页
|
||||||
function goBack() {
|
function goBack() {
|
||||||
router.push('/customer')
|
router.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
@@ -141,6 +147,40 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Primary Contact Info Card -->
|
||||||
|
<div v-if="hasPrimaryInfo" class="rounded-lg border border-amber-200 bg-amber-50 p-6 dark:border-amber-800 dark:bg-amber-900/20">
|
||||||
|
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-dk-text">{{ t('customer.primary_contact') }}</h2>
|
||||||
|
<div class="grid gap-4 sm:grid-cols-2">
|
||||||
|
<div v-if="primaryPhone">
|
||||||
|
<label class="text-sm text-gray-500 dark:text-dk-subtle">{{ t('customer.phone') }}</label>
|
||||||
|
<p class="font-medium text-gray-900 dark:text-dk-text">
|
||||||
|
+{{ primaryPhone.prefix }} {{ primaryPhone.phone }}
|
||||||
|
<span class="ml-1 rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-700 dark:bg-amber-900/30 dark:text-amber-400">
|
||||||
|
{{ getLabelText(primaryPhone.label) }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="primaryEmail">
|
||||||
|
<label class="text-sm text-gray-500 dark:text-dk-subtle">{{ t('customer.email') }}</label>
|
||||||
|
<p class="font-medium text-gray-900 dark:text-dk-text">
|
||||||
|
{{ primaryEmail.email }}
|
||||||
|
<span class="ml-1 rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-700 dark:bg-amber-900/30 dark:text-amber-400">
|
||||||
|
{{ getLabelText(primaryEmail.label) }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="primaryCompany" class="sm:col-span-2">
|
||||||
|
<label class="text-sm text-gray-500 dark:text-dk-subtle">{{ t('customer.company') }}</label>
|
||||||
|
<p class="font-medium text-gray-900 dark:text-dk-text">
|
||||||
|
{{ primaryCompany.company_name }}
|
||||||
|
</p>
|
||||||
|
<div v-if="primaryCompany.department || primaryCompany.position" class="mt-1 text-sm text-gray-500 dark:text-dk-subtle">
|
||||||
|
{{ primaryCompany.department }}{{ primaryCompany.department && primaryCompany.position ? ' · ' : '' }}{{ primaryCompany.position }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Phones Card -->
|
<!-- Phones Card -->
|
||||||
<div v-if="phones.length > 0" class="rounded-lg border border-gray-200 bg-white p-6 dark:border-dk-muted dark:bg-dk-card">
|
<div v-if="phones.length > 0" class="rounded-lg border border-gray-200 bg-white p-6 dark:border-dk-muted dark:bg-dk-card">
|
||||||
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-dk-text">{{ t('customer.phones') }}</h2>
|
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-dk-text">{{ t('customer.phones') }}</h2>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { useToastStore } from '@/stores/toast'
|
import { useToastStore } from '@/stores/toast'
|
||||||
import { usePageTitle } from '@/composables/usePageTitle'
|
import { usePageTitle } from '@/composables/usePageTitle'
|
||||||
import { warehouseApi } from '@/api/warehouse'
|
import { warehouseApi } from '@/api/warehouse'
|
||||||
|
import { customerApi } from '@/api/customer'
|
||||||
import useDropzone from '@/components/useDropzone.vue'
|
import useDropzone from '@/components/useDropzone.vue'
|
||||||
|
import { IconUser, IconX } from '@tabler/icons-vue'
|
||||||
|
|
||||||
usePageTitle('warehouse.add_item')
|
usePageTitle('warehouse.add_item')
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -32,7 +34,66 @@ function getPhotoHashes() {
|
|||||||
return dropzoneRef.value?.return_files().map((f) => f.hash) ?? []
|
return dropzoneRef.value?.return_files().map((f) => f.hash) ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 关联客户搜索(多选) ====================
|
||||||
|
const customerSearchQuery = ref('')
|
||||||
|
const customerSearchResults = ref([])
|
||||||
|
const customerSearchLoading = ref(false)
|
||||||
|
const showCustomerDropdown = ref(false)
|
||||||
|
const selectedCustomers = ref([])
|
||||||
|
|
||||||
|
let customerSearchTimer = null
|
||||||
|
|
||||||
|
function onCustomerSearchInput() {
|
||||||
|
clearTimeout(customerSearchTimer)
|
||||||
|
customerSearchTimer = setTimeout(async () => {
|
||||||
|
customerSearchLoading.value = true
|
||||||
|
showCustomerDropdown.value = true
|
||||||
|
try {
|
||||||
|
let res
|
||||||
|
if (customerSearchQuery.value.trim().length > 0) {
|
||||||
|
res = await customerApi.list({ search: customerSearchQuery.value.trim(), page: 1, page_size: 10 })
|
||||||
|
if (res.errCode === 0 && res.data) {
|
||||||
|
customerSearchResults.value = (res.data.customers || []).slice(0, 10)
|
||||||
|
} else {
|
||||||
|
customerSearchResults.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = await customerApi.list({ page: 1, page_size: 5 })
|
||||||
|
if (res.errCode === 0 && res.data) {
|
||||||
|
customerSearchResults.value = (res.data.customers || []).sort((a, b) => b.ID - a.ID)
|
||||||
|
} else {
|
||||||
|
customerSearchResults.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
customerSearchResults.value = []
|
||||||
|
} finally {
|
||||||
|
customerSearchLoading.value = false
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCustomer(customer) {
|
||||||
|
if (!selectedCustomers.value.find(c => c.id === customer.id)) {
|
||||||
|
selectedCustomers.value.push(customer)
|
||||||
|
}
|
||||||
|
customerSearchQuery.value = ''
|
||||||
|
customerSearchResults.value = []
|
||||||
|
showCustomerDropdown.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSelectedCustomer(customerId) {
|
||||||
|
selectedCustomers.value = selectedCustomers.value.filter(c => c.id !== customerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickOutside(e) {
|
||||||
|
if (!e.target.closest('.customer-search-wrapper')) {
|
||||||
|
showCustomerDropdown.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
document.addEventListener('click', handleClickOutside)
|
||||||
try {
|
try {
|
||||||
const { errCode, data } = await warehouseApi.getContainer(containerId.value)
|
const { errCode, data } = await warehouseApi.getContainer(containerId.value)
|
||||||
if (errCode === 0 && data?.container) {
|
if (errCode === 0 && data?.container) {
|
||||||
@@ -64,6 +125,7 @@ async function submit() {
|
|||||||
remark: form.remark.trim(),
|
remark: form.remark.trim(),
|
||||||
quantity: form.quantity > 0 ? form.quantity : 1,
|
quantity: form.quantity > 0 ? form.quantity : 1,
|
||||||
photos: hashes,
|
photos: hashes,
|
||||||
|
customer_ids: selectedCustomers.value.map(c => c.id),
|
||||||
})
|
})
|
||||||
if (errCode === 0) {
|
if (errCode === 0) {
|
||||||
toast.success(t('message.save_success'))
|
toast.success(t('message.save_success'))
|
||||||
@@ -167,6 +229,75 @@ async function submit() {
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 关联客户搜索(多选) -->
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
{{ t('warehouse.linked_customers') || '关联客户' }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- 已选择客户列表 -->
|
||||||
|
<div v-if="selectedCustomers.length > 0" class="mb-2 flex flex-wrap gap-2">
|
||||||
|
<div
|
||||||
|
v-for="customer in selectedCustomers"
|
||||||
|
:key="customer.id"
|
||||||
|
class="inline-flex items-center gap-1 rounded-full border border-blue-200 bg-blue-50 px-2.5 py-1 text-xs font-medium text-blue-700 dark:border-blue-800 dark:bg-blue-900/30 dark:text-blue-300"
|
||||||
|
>
|
||||||
|
<IconUser :size="12" />
|
||||||
|
{{ (customer.last_name || '') + (customer.first_name ? ' ' + customer.first_name : '') }}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-0.5 rounded-full p-0.5 transition-colors hover:bg-blue-200 dark:hover:bg-blue-800"
|
||||||
|
@click="removeSelectedCustomer(customer.id)"
|
||||||
|
>
|
||||||
|
<IconX :size="12" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="customer-search-wrapper relative">
|
||||||
|
<input
|
||||||
|
v-model="customerSearchQuery"
|
||||||
|
type="text"
|
||||||
|
:placeholder="t('warehouse.linked_customer_placeholder') || '搜索客户...'"
|
||||||
|
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"
|
||||||
|
@input="onCustomerSearchInput"
|
||||||
|
@focus="customerSearchQuery || onCustomerSearchInput()"
|
||||||
|
/>
|
||||||
|
<!-- 下拉结果 -->
|
||||||
|
<div
|
||||||
|
v-if="showCustomerDropdown && customerSearchResults.length > 0"
|
||||||
|
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="customer in customerSearchResults"
|
||||||
|
:key="customer.id"
|
||||||
|
class="cursor-pointer px-3 py-2 text-sm hover:bg-blue-50 dark:hover:bg-blue-900/30"
|
||||||
|
@click="selectCustomer(customer)"
|
||||||
|
>
|
||||||
|
<div class="font-medium text-gray-900 dark:text-white">{{ (customer.last_name || '') + (customer.first_name ? ' ' + customer.first_name : '') }}</div>
|
||||||
|
<div v-if="customer.primary_phone || customer.primary_company" class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ customer.primary_phone }}{{ customer.primary_phone && customer.primary_company ? ' · ' : '' }}{{ customer.primary_company }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<div
|
||||||
|
v-if="showCustomerDropdown && customerSearchLoading"
|
||||||
|
class="absolute z-10 mt-1 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm text-gray-500 shadow-lg dark:border-dk-muted dark:bg-dk-card"
|
||||||
|
>
|
||||||
|
{{ t('message.loading') }}
|
||||||
|
</div>
|
||||||
|
<!-- 无结果 -->
|
||||||
|
<div
|
||||||
|
v-if="showCustomerDropdown && !customerSearchLoading && customerSearchResults.length === 0 && customerSearchQuery.trim().length > 0"
|
||||||
|
class="absolute z-10 mt-1 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm text-gray-500 shadow-lg dark:border-dk-muted dark:bg-dk-card"
|
||||||
|
>
|
||||||
|
{{ t('warehouse.linked_customer_not_found') || '未找到客户' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 图片上传 -->
|
<!-- 图片上传 -->
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ const containerBreadcrumb = ref('')
|
|||||||
// ── Tab ──
|
// ── Tab ──
|
||||||
const activeTab = ref('work_orders')
|
const activeTab = ref('work_orders')
|
||||||
|
|
||||||
|
// ── 关联客户 ──
|
||||||
|
const customers = ref([])
|
||||||
|
|
||||||
// ── 移动弹窗 ──
|
// ── 移动弹窗 ──
|
||||||
const showMove = ref(false)
|
const showMove = ref(false)
|
||||||
const moveTarget = ref(null)
|
const moveTarget = ref(null)
|
||||||
@@ -77,6 +80,21 @@ function getStatusLabel(status) {
|
|||||||
return t(`work_order.status_${status}`) || status
|
return t(`work_order.status_${status}`) || status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 客户名称格式化 ──
|
||||||
|
function getCustomerDisplayName(customer) {
|
||||||
|
const lastName = customer.last_name || ''
|
||||||
|
const firstName = customer.first_name || ''
|
||||||
|
if (lastName && firstName) {
|
||||||
|
return `${lastName} ${firstName}`
|
||||||
|
}
|
||||||
|
return lastName || firstName || `Customer#${customer.id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCustomerTitleLabel(title) {
|
||||||
|
if (!title) return ''
|
||||||
|
return t(`customer.salutation_${title.toLowerCase()}`)
|
||||||
|
}
|
||||||
|
|
||||||
// ── 时间格式化 ──
|
// ── 时间格式化 ──
|
||||||
function fmtTs(ts) {
|
function fmtTs(ts) {
|
||||||
if (!ts) return '—'
|
if (!ts) return '—'
|
||||||
@@ -112,6 +130,7 @@ async function fetchItem() {
|
|||||||
photos.value = data.photos ?? []
|
photos.value = data.photos ?? []
|
||||||
commits.value = data.commits ?? []
|
commits.value = data.commits ?? []
|
||||||
workOrders.value = data.work_orders ?? []
|
workOrders.value = data.work_orders ?? []
|
||||||
|
customers.value = data.customers ?? []
|
||||||
canModifyItem.value = data.canModifyItem === true
|
canModifyItem.value = data.canModifyItem === true
|
||||||
containerBreadcrumb.value = data.container_breadcrumb ?? ''
|
containerBreadcrumb.value = data.container_breadcrumb ?? ''
|
||||||
loadContainerNames()
|
loadContainerNames()
|
||||||
@@ -175,6 +194,12 @@ function openLinkWorkOrder() {
|
|||||||
? `${item.value.Name}-${item.value.SerialNumber}`
|
? `${item.value.Name}-${item.value.SerialNumber}`
|
||||||
: item.value.Name,
|
: item.value.Name,
|
||||||
description: item.value.Remark || '',
|
description: item.value.Remark || '',
|
||||||
|
customers: customers.value.map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
first_name: c.first_name || '',
|
||||||
|
last_name: c.last_name || '',
|
||||||
|
primary_phone: c.primary_phone || '',
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
localStorage.setItem('prefill_work_order', JSON.stringify(prefillData))
|
localStorage.setItem('prefill_work_order', JSON.stringify(prefillData))
|
||||||
router.push('/work_order/add')
|
router.push('/work_order/add')
|
||||||
@@ -440,6 +465,15 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
{{ t('warehouse.work_orders') }} ({{ workOrders.length }})
|
{{ t('warehouse.work_orders') }} ({{ workOrders.length }})
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="px-4 py-1.5 text-sm rounded-md font-medium transition-colors"
|
||||||
|
:class="activeTab === 'customers'
|
||||||
|
? 'bg-white text-gray-900 shadow-sm dark:bg-dk-card dark:text-white'
|
||||||
|
: 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'"
|
||||||
|
@click="activeTab = 'customers'"
|
||||||
|
>
|
||||||
|
{{ t('warehouse.customers') }} ({{ customers.length }})
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
class="px-4 py-1.5 text-sm rounded-md font-medium transition-colors"
|
class="px-4 py-1.5 text-sm rounded-md font-medium transition-colors"
|
||||||
:class="activeTab === 'history'
|
:class="activeTab === 'history'
|
||||||
@@ -477,6 +511,38 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 关联客户 -->
|
||||||
|
<div v-if="activeTab === 'customers'">
|
||||||
|
<div v-if="customers.length === 0" class="rounded-xl border border-gray-200 bg-white px-5 py-8 text-center text-sm text-gray-400 dark:border-dk-muted dark:bg-dk-card">
|
||||||
|
{{ t('warehouse.no_customers') }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="space-y-2">
|
||||||
|
<RouterLink
|
||||||
|
v-for="c in customers"
|
||||||
|
:key="c.id"
|
||||||
|
:to="`/customer/detail/${c.id}`"
|
||||||
|
class="rounded-xl border border-gray-200 bg-white px-4 py-3 flex items-center justify-between gap-3 hover:shadow transition-shadow dark:border-dk-muted dark:bg-dk-card dark:hover:shadow-none"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/40 flex items-center justify-center flex-shrink-0">
|
||||||
|
<span class="text-sm font-medium text-blue-600 dark:text-blue-400">
|
||||||
|
{{ (c.last_name || c.first_name || 'C').charAt(0).toUpperCase() }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<span class="font-medium text-sm text-gray-900 truncate dark:text-white block">
|
||||||
|
{{ getCustomerDisplayName(c) }}
|
||||||
|
</span>
|
||||||
|
<span v-if="c.title" class="text-xs text-gray-400">
|
||||||
|
{{ getCustomerTitleLabel(c.title) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<IconArrowRight :size="14" class="text-gray-400 flex-shrink-0" />
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 移动历史 -->
|
<!-- 移动历史 -->
|
||||||
<div v-if="activeTab === 'history'">
|
<div v-if="activeTab === 'history'">
|
||||||
<div v-if="commits.length === 0" class="rounded-xl border border-gray-200 bg-white px-5 py-8 text-center text-sm text-gray-400 dark:border-dk-muted dark:bg-dk-card">
|
<div v-if="commits.length === 0" class="rounded-xl border border-gray-200 bg-white px-5 py-8 text-center text-sm text-gray-400 dark:border-dk-muted dark:bg-dk-card">
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { useToastStore } from '@/stores/toast'
|
import { useToastStore } from '@/stores/toast'
|
||||||
import { usePageTitle } from '@/composables/usePageTitle'
|
import { usePageTitle } from '@/composables/usePageTitle'
|
||||||
import { warehouseApi } from '@/api/warehouse'
|
import { warehouseApi } from '@/api/warehouse'
|
||||||
|
import { customerApi } from '@/api/customer'
|
||||||
import useDropzone from '@/components/useDropzone.vue'
|
import useDropzone from '@/components/useDropzone.vue'
|
||||||
|
import { IconUser, IconX } from '@tabler/icons-vue'
|
||||||
|
|
||||||
usePageTitle('warehouse.edit_item')
|
usePageTitle('warehouse.edit_item')
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -34,7 +36,66 @@ function getPhotoHashes() {
|
|||||||
return dropzoneRef.value?.return_files().map((f) => f.hash) ?? []
|
return dropzoneRef.value?.return_files().map((f) => f.hash) ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 关联客户搜索(多选) ====================
|
||||||
|
const customerSearchQuery = ref('')
|
||||||
|
const customerSearchResults = ref([])
|
||||||
|
const customerSearchLoading = ref(false)
|
||||||
|
const showCustomerDropdown = ref(false)
|
||||||
|
const selectedCustomers = ref([])
|
||||||
|
|
||||||
|
let customerSearchTimer = null
|
||||||
|
|
||||||
|
function onCustomerSearchInput() {
|
||||||
|
clearTimeout(customerSearchTimer)
|
||||||
|
customerSearchTimer = setTimeout(async () => {
|
||||||
|
customerSearchLoading.value = true
|
||||||
|
showCustomerDropdown.value = true
|
||||||
|
try {
|
||||||
|
let res
|
||||||
|
if (customerSearchQuery.value.trim().length > 0) {
|
||||||
|
res = await customerApi.list({ search: customerSearchQuery.value.trim(), page: 1, page_size: 10 })
|
||||||
|
if (res.errCode === 0 && res.data) {
|
||||||
|
customerSearchResults.value = (res.data.customers || []).slice(0, 10)
|
||||||
|
} else {
|
||||||
|
customerSearchResults.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = await customerApi.list({ page: 1, page_size: 5 })
|
||||||
|
if (res.errCode === 0 && res.data) {
|
||||||
|
customerSearchResults.value = (res.data.customers || []).sort((a, b) => b.ID - a.ID)
|
||||||
|
} else {
|
||||||
|
customerSearchResults.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
customerSearchResults.value = []
|
||||||
|
} finally {
|
||||||
|
customerSearchLoading.value = false
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCustomer(customer) {
|
||||||
|
if (!selectedCustomers.value.find(c => c.id === customer.id)) {
|
||||||
|
selectedCustomers.value.push(customer)
|
||||||
|
}
|
||||||
|
customerSearchQuery.value = ''
|
||||||
|
customerSearchResults.value = []
|
||||||
|
showCustomerDropdown.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSelectedCustomer(customerId) {
|
||||||
|
selectedCustomers.value = selectedCustomers.value.filter(c => c.id !== customerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickOutside(e) {
|
||||||
|
if (!e.target.closest('.customer-search-wrapper')) {
|
||||||
|
showCustomerDropdown.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
document.addEventListener('click', handleClickOutside)
|
||||||
try {
|
try {
|
||||||
const { errCode, data } = await warehouseApi.getItem(itemId.value)
|
const { errCode, data } = await warehouseApi.getItem(itemId.value)
|
||||||
if (errCode === 0 && data?.item) {
|
if (errCode === 0 && data?.item) {
|
||||||
@@ -44,6 +105,16 @@ onMounted(async () => {
|
|||||||
form.quantity = data.item.Quantity ?? 1
|
form.quantity = data.item.Quantity ?? 1
|
||||||
existingPhotos.value = data.photos ?? []
|
existingPhotos.value = data.photos ?? []
|
||||||
|
|
||||||
|
// 回填已关联的客户
|
||||||
|
if (data.customers && data.customers.length > 0) {
|
||||||
|
selectedCustomers.value = data.customers.map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
first_name: c.first_name,
|
||||||
|
last_name: c.last_name,
|
||||||
|
title: c.title
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// 获取容器名称
|
// 获取容器名称
|
||||||
if (data.item.ContainerID) {
|
if (data.item.ContainerID) {
|
||||||
const { errCode: cErr, data: cData } = await warehouseApi.getContainer(data.item.ContainerID)
|
const { errCode: cErr, data: cData } = await warehouseApi.getContainer(data.item.ContainerID)
|
||||||
@@ -80,6 +151,7 @@ async function submit() {
|
|||||||
remark: form.remark.trim(),
|
remark: form.remark.trim(),
|
||||||
quantity: form.quantity > 0 ? form.quantity : 1,
|
quantity: form.quantity > 0 ? form.quantity : 1,
|
||||||
photos: hashes,
|
photos: hashes,
|
||||||
|
customer_ids: selectedCustomers.value.map(c => c.id),
|
||||||
})
|
})
|
||||||
if (errCode === 0) {
|
if (errCode === 0) {
|
||||||
toast.success(t('message.save_success'))
|
toast.success(t('message.save_success'))
|
||||||
@@ -202,6 +274,75 @@ async function submit() {
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 关联客户搜索(多选) -->
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
{{ t('warehouse.linked_customers') }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- 已选择客户列表 -->
|
||||||
|
<div v-if="selectedCustomers.length > 0" class="mb-2 flex flex-wrap gap-2">
|
||||||
|
<div
|
||||||
|
v-for="customer in selectedCustomers"
|
||||||
|
:key="customer.id"
|
||||||
|
class="inline-flex items-center gap-1 rounded-full border border-blue-200 bg-blue-50 px-2.5 py-1 text-xs font-medium text-blue-700 dark:border-blue-800 dark:bg-blue-900/30 dark:text-blue-300"
|
||||||
|
>
|
||||||
|
<IconUser :size="12" />
|
||||||
|
{{ (customer.last_name || '') + (customer.first_name ? ' ' + customer.first_name : '') }}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-0.5 rounded-full p-0.5 transition-colors hover:bg-blue-200 dark:hover:bg-blue-800"
|
||||||
|
@click="removeSelectedCustomer(customer.id)"
|
||||||
|
>
|
||||||
|
<IconX :size="12" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="customer-search-wrapper relative">
|
||||||
|
<input
|
||||||
|
v-model="customerSearchQuery"
|
||||||
|
type="text"
|
||||||
|
:placeholder="t('warehouse.linked_customer_placeholder')"
|
||||||
|
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"
|
||||||
|
@input="onCustomerSearchInput"
|
||||||
|
@focus="customerSearchQuery || onCustomerSearchInput()"
|
||||||
|
/>
|
||||||
|
<!-- 下拉结果 -->
|
||||||
|
<div
|
||||||
|
v-if="showCustomerDropdown && customerSearchResults.length > 0"
|
||||||
|
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="customer in customerSearchResults"
|
||||||
|
:key="customer.id"
|
||||||
|
class="cursor-pointer px-3 py-2 text-sm hover:bg-blue-50 dark:hover:bg-blue-900/30"
|
||||||
|
@click="selectCustomer(customer)"
|
||||||
|
>
|
||||||
|
<div class="font-medium text-gray-900 dark:text-white">{{ (customer.last_name || '') + (customer.first_name ? ' ' + customer.first_name : '') }}</div>
|
||||||
|
<div v-if="customer.primary_phone || customer.primary_company" class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ customer.primary_phone }}{{ customer.primary_phone && customer.primary_company ? ' · ' : '' }}{{ customer.primary_company }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<div
|
||||||
|
v-if="showCustomerDropdown && customerSearchLoading"
|
||||||
|
class="absolute z-10 mt-1 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm text-gray-500 shadow-lg dark:border-dk-muted dark:bg-dk-card"
|
||||||
|
>
|
||||||
|
{{ t('message.loading') }}
|
||||||
|
</div>
|
||||||
|
<!-- 无结果 -->
|
||||||
|
<div
|
||||||
|
v-if="showCustomerDropdown && !customerSearchLoading && customerSearchResults.length === 0 && customerSearchQuery.trim().length > 0"
|
||||||
|
class="absolute z-10 mt-1 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm text-gray-500 shadow-lg dark:border-dk-muted dark:bg-dk-card"
|
||||||
|
>
|
||||||
|
{{ t('warehouse.linked_customer_not_found') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 图片上传 -->
|
<!-- 图片上传 -->
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
|||||||
@@ -205,6 +205,17 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果有客户信息,自动选中
|
||||||
|
if (prefill.customers && prefill.customers.length > 0) {
|
||||||
|
selectedCustomers.value = prefill.customers.map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
first_name: c.first_name,
|
||||||
|
last_name: c.last_name,
|
||||||
|
primary_phone: c.primary_phone,
|
||||||
|
}))
|
||||||
|
linkedCustomerIds.value = prefill.customers.map(c => c.id)
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.removeItem('prefill_work_order')
|
localStorage.removeItem('prefill_work_order')
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
|
|||||||
Reference in New Issue
Block a user