up
This commit is contained in:
@@ -131,6 +131,7 @@
|
||||
"no_photos": "No photos",
|
||||
"status": "Status",
|
||||
"created_at": "Created At",
|
||||
"updated_at": "Updated At",
|
||||
"filter_all": "All",
|
||||
"status_pending": "Pending",
|
||||
"status_checked": "Checked",
|
||||
@@ -171,6 +172,7 @@
|
||||
"remark": "Remark",
|
||||
"remark_placeholder": "Enter remark (optional)",
|
||||
"created_at": "Created At",
|
||||
"updated_at": "Updated At",
|
||||
"created_by": "Created By",
|
||||
"child_containers": "Sub-Containers",
|
||||
"items": "Items",
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
"no_photos": "暂无图片",
|
||||
"status": "状态",
|
||||
"created_at": "创建时间",
|
||||
"updated_at": "更新时间",
|
||||
"filter_all": "全部",
|
||||
"status_pending": "待处理",
|
||||
"status_checked": "已检查",
|
||||
@@ -171,6 +172,7 @@
|
||||
"remark": "备注",
|
||||
"remark_placeholder": "输入备注(可选)",
|
||||
"created_at": "创建日期",
|
||||
"updated_at": "更新日期",
|
||||
"created_by": "创建人",
|
||||
"child_containers": "子容器",
|
||||
"items": "物品",
|
||||
|
||||
@@ -390,6 +390,7 @@ onMounted(async () => {
|
||||
{{ usersStore.getUsernameFromUserID(container.CreatorID) }}
|
||||
</span>
|
||||
<span>{{ t('warehouse.created_at') }}: {{ fmtTs(container.CreatedAt) }}</span>
|
||||
<span v-if="container.UpdatedAt">{{ t('warehouse.updated_at') }}: {{ fmtTs(container.UpdatedAt) }}</span>
|
||||
<span>{{ t('warehouse.child_containers') }}: {{ container.ChildCount }}</span>
|
||||
<span>{{ t('warehouse.items') }}: {{ container.ItemCount }}</span>
|
||||
</div>
|
||||
@@ -428,13 +429,14 @@ onMounted(async () => {
|
||||
<th class="px-5 py-3 font-medium w-24 text-center">{{ t('warehouse.child_containers') }}</th>
|
||||
<th class="px-5 py-3 font-medium w-24 text-center">{{ t('warehouse.items') }}</th>
|
||||
<th class="px-5 py-3 font-medium whitespace-nowrap">{{ t('warehouse.created_at') }}</th>
|
||||
<th class="px-5 py-3 font-medium whitespace-nowrap">{{ t('warehouse.updated_at') }}</th>
|
||||
<th class="px-5 py-3 font-medium">{{ t('warehouse.created_by') }}</th>
|
||||
<th class="px-5 py-3 font-medium w-24 text-right">{{ t('warehouse.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loadingSub">
|
||||
<td colspan="6" class="px-5 py-8 text-center">
|
||||
<td colspan="7" class="px-5 py-8 text-center">
|
||||
<svg class="mx-auto h-5 w-5 animate-spin text-gray-400" viewBox="0 0 24 24" fill="none">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
||||
@@ -442,7 +444,7 @@ onMounted(async () => {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="subContainers.length === 0">
|
||||
<td colspan="6" class="px-5 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
<td colspan="7" class="px-5 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
{{ t('warehouse.no_containers') }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -470,6 +472,7 @@ onMounted(async () => {
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-3 text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">{{ fmtTs(c.CreatedAt) }}</td>
|
||||
<td class="px-5 py-3 text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">{{ fmtTs(c.UpdatedAt) }}</td>
|
||||
<td class="px-5 py-3">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<img
|
||||
@@ -552,15 +555,17 @@ onMounted(async () => {
|
||||
<tr class="border-b border-gray-200 bg-gray-50 text-gray-500 dark:border-dk-muted dark:bg-dk-base dark:text-gray-400">
|
||||
<th class="px-5 py-3 font-medium">{{ t('warehouse.item_name') }}</th>
|
||||
<th class="px-5 py-3 font-medium">{{ t('warehouse.serial_number') }}</th>
|
||||
<th class="px-5 py-3 font-medium">{{ t('warehouse.remark') }}</th>
|
||||
<th class="px-5 py-3 font-medium w-20 text-center">{{ t('warehouse.quantity') }}</th>
|
||||
<th class="px-5 py-3 font-medium whitespace-nowrap">{{ t('warehouse.created_at') }}</th>
|
||||
<th class="px-5 py-3 font-medium whitespace-nowrap">{{ t('warehouse.updated_at') }}</th>
|
||||
<th class="px-5 py-3 font-medium">{{ t('warehouse.created_by') }}</th>
|
||||
<th class="px-5 py-3 font-medium w-20 text-right">{{ t('warehouse.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loadingItems">
|
||||
<td colspan="6" class="px-5 py-8 text-center">
|
||||
<td colspan="8" class="px-5 py-8 text-center">
|
||||
<svg class="mx-auto h-5 w-5 animate-spin text-gray-400" viewBox="0 0 24 24" fill="none">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
||||
@@ -568,7 +573,7 @@ onMounted(async () => {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="items.length === 0">
|
||||
<td colspan="6" class="px-5 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
<td colspan="8" class="px-5 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
{{ t('warehouse.no_items') }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -586,8 +591,10 @@ onMounted(async () => {
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-3 text-xs text-gray-500 dark:text-gray-400 max-w-[140px] truncate">{{ item.serial_number || '—' }}</td>
|
||||
<td class="px-5 py-3 text-xs text-gray-500 dark:text-gray-400 max-w-[200px] truncate">{{ item.Remark || '—' }}</td>
|
||||
<td class="px-5 py-3 text-center text-sm">{{ item.Quantity }}</td>
|
||||
<td class="px-5 py-3 text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">{{ fmtTs(item.CreatedAt) }}</td>
|
||||
<td class="px-5 py-3 text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">{{ fmtTs(item.UpdatedAt) }}</td>
|
||||
<td class="px-5 py-3">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<img
|
||||
|
||||
@@ -52,6 +52,7 @@ const targetContainers = ref([])
|
||||
const targetSearch = ref('')
|
||||
const targetLoading = ref(false)
|
||||
const showTargetDropdown = ref(false)
|
||||
let targetDropdownTimer = null
|
||||
|
||||
// ── 删除确认 ──
|
||||
const showDeleteConfirm = ref(false)
|
||||
@@ -186,18 +187,39 @@ async function openMove() {
|
||||
showMove.value = true
|
||||
}
|
||||
|
||||
function onTargetFocus() {
|
||||
if (targetDropdownTimer) clearTimeout(targetDropdownTimer)
|
||||
showTargetDropdown.value = true
|
||||
loadTargetContainers(targetSearch.value)
|
||||
}
|
||||
|
||||
function onTargetInput() {
|
||||
if (targetDropdownTimer) clearTimeout(targetDropdownTimer)
|
||||
showTargetDropdown.value = true
|
||||
loadTargetContainers(targetSearch.value)
|
||||
}
|
||||
|
||||
function closeTargetDropdown() {
|
||||
targetDropdownTimer = setTimeout(() => {
|
||||
showTargetDropdown.value = false
|
||||
}, 150)
|
||||
}
|
||||
|
||||
async function loadTargetContainers(search = '') {
|
||||
targetLoading.value = true
|
||||
try {
|
||||
const isSearch = search.trim().length > 0
|
||||
const { errCode, data } = await warehouseApi.getContainers({
|
||||
search,
|
||||
entries: 50,
|
||||
all_levels: true,
|
||||
entries: isSearch ? 50 : 10,
|
||||
page: 1,
|
||||
})
|
||||
if (errCode === 0) {
|
||||
targetContainers.value = (data.containers ?? []).filter(
|
||||
(c) => c.ID !== itemId.value && c.ID !== item.value?.ContainerID
|
||||
const filtered = (data.containers ?? []).filter(
|
||||
(c) => c.ID !== item.value?.ContainerID
|
||||
)
|
||||
targetContainers.value = isSearch ? filtered : filtered.slice(0, 5)
|
||||
}
|
||||
} catch {
|
||||
targetContainers.value = []
|
||||
@@ -389,6 +411,7 @@ onMounted(() => {
|
||||
{{ usersStore.getUsernameFromUserID(item.CreatorID) }}
|
||||
</span>
|
||||
<span>{{ t('warehouse.created_at') }}: {{ fmtTs(item.CreatedAt) }}</span>
|
||||
<span v-if="item.UpdatedAt">{{ t('warehouse.updated_at') }}: {{ fmtTs(item.UpdatedAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -503,19 +526,21 @@ onMounted(() => {
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('warehouse.target_container') }}</label>
|
||||
<div class="relative">
|
||||
<div class="relative" @click.stop>
|
||||
<IconSearch class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" :size="15" />
|
||||
<input
|
||||
v-model="targetSearch"
|
||||
type="text"
|
||||
:placeholder="t('warehouse.search_container')"
|
||||
class="w-full rounded-lg border border-gray-300 bg-white py-2 pl-9 pr-3 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"
|
||||
@focus="loadTargetContainers(''); showTargetDropdown = true"
|
||||
@input="loadTargetContainers(targetSearch); showTargetDropdown = true"
|
||||
@focus="onTargetFocus"
|
||||
@input="onTargetInput"
|
||||
@blur="closeTargetDropdown"
|
||||
/>
|
||||
<div
|
||||
v-if="showTargetDropdown && (targetContainers.length > 0 || targetLoading)"
|
||||
v-if="showTargetDropdown"
|
||||
class="absolute z-10 mt-1 w-full rounded-lg border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card max-h-60 overflow-y-auto"
|
||||
@mousedown.prevent
|
||||
>
|
||||
<div v-if="targetLoading" class="px-3 py-2 text-xs text-gray-400">
|
||||
<svg class="inline h-3.5 w-3.5 animate-spin mr-1" viewBox="0 0 24 24" fill="none">
|
||||
|
||||
@@ -320,7 +320,7 @@ function formatDate(dateStr) {
|
||||
d = new Date(dateStr)
|
||||
}
|
||||
if (isNaN(d.getTime())) return '—'
|
||||
return d.toLocaleDateString(isEn.value ? 'en-US' : 'zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' })
|
||||
return d.toLocaleString(isEn.value ? 'en-US' : 'zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false })
|
||||
} catch { return dateStr }
|
||||
}
|
||||
|
||||
@@ -426,12 +426,13 @@ onMounted(() => {
|
||||
<th class="px-6 py-3 font-medium w-24 text-center">{{ t('warehouse.child_containers') }}</th>
|
||||
<th class="px-6 py-3 font-medium w-24 text-center">{{ t('warehouse.items') }}</th>
|
||||
<th class="px-6 py-3 font-medium whitespace-nowrap w-44">{{ t('warehouse.created_at') }}</th>
|
||||
<th class="px-6 py-3 font-medium whitespace-nowrap w-44">{{ t('warehouse.updated_at') }}</th>
|
||||
<th class="px-6 py-3 font-medium">{{ t('warehouse.created_by') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="containerLoading">
|
||||
<td colspan="7" class="px-6 py-8 text-center text-gray-400">
|
||||
<td colspan="8" class="px-6 py-8 text-center text-gray-400">
|
||||
<svg class="mx-auto mb-2 h-5 w-5 animate-spin text-gray-400" viewBox="0 0 24 24" fill="none">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
||||
@@ -440,7 +441,7 @@ onMounted(() => {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="containers.length === 0">
|
||||
<td colspan="7" class="px-6 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
<td colspan="8" class="px-6 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
{{ t('warehouse.no_containers') }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -472,6 +473,7 @@ onMounted(() => {
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-gray-500 dark:text-gray-400">{{ fmtTs(c.CreatedAt) }}</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-gray-500 dark:text-gray-400">{{ fmtTs(c.UpdatedAt) }}</td>
|
||||
<td class="px-6 py-3">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<img
|
||||
@@ -548,15 +550,17 @@ onMounted(() => {
|
||||
<tr class="border-b border-gray-200 bg-gray-50 text-gray-500 dark:border-dk-muted dark:bg-dk-base dark:text-gray-400">
|
||||
<th class="px-6 py-3 font-medium">{{ t('warehouse.item_name') }}</th>
|
||||
<th class="px-6 py-3 font-medium">{{ t('warehouse.serial_number') }}</th>
|
||||
<th class="px-6 py-3 font-medium">{{ t('warehouse.remark') }}</th>
|
||||
<th class="px-6 py-3 font-medium w-20 text-center">{{ t('warehouse.quantity') }}</th>
|
||||
<th class="px-6 py-3 font-medium">{{ t('warehouse.location') }}</th>
|
||||
<th class="px-6 py-3 font-medium whitespace-nowrap">{{ t('warehouse.created_at') }}</th>
|
||||
<th class="px-6 py-3 font-medium whitespace-nowrap">{{ t('warehouse.updated_at') }}</th>
|
||||
<th class="px-6 py-3 font-medium">{{ t('warehouse.created_by') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="itemLoading">
|
||||
<td colspan="6" class="px-6 py-8 text-center text-gray-400">
|
||||
<td colspan="8" class="px-6 py-8 text-center text-gray-400">
|
||||
<svg class="mx-auto mb-2 h-5 w-5 animate-spin text-gray-400" viewBox="0 0 24 24" fill="none">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
||||
@@ -565,7 +569,7 @@ onMounted(() => {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="items.length === 0">
|
||||
<td colspan="6" class="px-6 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
<td colspan="8" class="px-6 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
{{ t('warehouse.no_items') }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -577,6 +581,7 @@ onMounted(() => {
|
||||
>
|
||||
<td class="px-6 py-3 font-medium max-w-[200px] truncate">{{ item.Name }}</td>
|
||||
<td class="px-6 py-3 max-w-[160px] truncate text-xs text-gray-500 dark:text-gray-400">{{ item.SerialNumber || '—' }}</td>
|
||||
<td class="px-6 py-3 max-w-[200px] truncate text-xs text-gray-500 dark:text-gray-400">{{ item.Remark || '—' }}</td>
|
||||
<td class="px-6 py-3 text-center text-sm">{{ item.Quantity }}</td>
|
||||
<td class="px-6 py-3">
|
||||
<span v-if="item.ContainerID != null" class="inline-flex items-center gap-1 text-sm text-blue-600">
|
||||
@@ -588,6 +593,7 @@ onMounted(() => {
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-xs text-gray-400 dark:text-gray-500">{{ formatDate(item.CreatedAt) }}</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-xs text-gray-400 dark:text-gray-500">{{ formatDate(item.UpdatedAt) }}</td>
|
||||
<td class="px-6 py-3">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<img
|
||||
|
||||
@@ -393,7 +393,10 @@ onUnmounted(() => {
|
||||
{{ usersStore.getUsernameFromUserID(order.UserID) }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-sm text-gray-400">{{ formatDate(order?.CreatedAt) }}</span>
|
||||
<div class="text-sm text-gray-400 text-right">
|
||||
<div>{{ t('work_order.created_at') }}: {{ formatDate(order?.CreatedAt) }}</div>
|
||||
<div v-if="order?.UpdatedAt">{{ t('work_order.updated_at') }}: {{ formatDate(order.UpdatedAt) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态快捷切换(所有登录用户可见) -->
|
||||
|
||||
@@ -144,12 +144,13 @@ onMounted(fetchOrders)
|
||||
<th class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 w-16">No.</th>
|
||||
<th class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400">{{ t('work_order.title') }}</th>
|
||||
<th class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 whitespace-nowrap w-44">{{ t('work_order.created_at') }}</th>
|
||||
<th class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 whitespace-nowrap w-44">{{ t('work_order.updated_at') }}</th>
|
||||
<th class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 w-36">{{ t('work_order.status') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loading">
|
||||
<td colspan="4" class="px-6 py-8 text-center text-gray-400">
|
||||
<td colspan="5" class="px-6 py-8 text-center text-gray-400">
|
||||
<svg class="mx-auto mb-2 h-5 w-5 animate-spin text-gray-400" viewBox="0 0 24 24" fill="none">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
||||
@@ -158,7 +159,7 @@ onMounted(fetchOrders)
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="orders.length === 0">
|
||||
<td colspan="4" class="px-6 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
<td colspan="5" class="px-6 py-8 text-center text-gray-400 dark:text-gray-500">
|
||||
暂无工单
|
||||
</td>
|
||||
</tr>
|
||||
@@ -172,6 +173,7 @@ onMounted(fetchOrders)
|
||||
<td class="px-6 py-3 text-gray-500 dark:text-gray-400">{{ order.ID }}</td>
|
||||
<td class="px-6 py-3 font-medium text-gray-900 dark:text-white max-w-xs truncate">{{ order.Title }}</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-gray-500 dark:text-gray-400">{{ formatDate(order.CreatedAt) }}</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-gray-500 dark:text-gray-400">{{ formatDate(order.UpdatedAt) }}</td>
|
||||
<td class="px-6 py-3">
|
||||
<span
|
||||
class="inline-block rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||||
|
||||
Reference in New Issue
Block a user