1.06 #8

Merged
zzh merged 2 commits from test into master 2026-10-19 17:04:23 +08:00
5 changed files with 237 additions and 56 deletions
Showing only changes of commit 108083d332 - Show all commits

View File

@ -1475,6 +1475,9 @@
manualColumnResize: true,
manualColumnMove: props.enableColumnReorder,
stretchH: props.stretchColumns ? 'all' : 'none',
// 列宽由列定义给出。开启后 loadData 会走 GhostTable 测量,
// 单元格节点缺失时抛 getBoundingClientRect,并中断外层页面更新。
autoColumnSize: false,
// stretch 时锁定操作/序号/复选框列,避免被均分拉宽
beforeStretchingColumnWidth: lockFixedColumnStretchWidth,
...(props.rowHeight != null && props.rowHeight > 0
@ -2362,6 +2365,8 @@
hotInstance.loadData([]);
hotInstance.render();
publishSelection([], new Set());
} catch (error) {
console.warn('[HandsontablePro] clear failed', error);
} finally {
selectionLock -= 1;
}
@ -2393,6 +2398,8 @@
} else {
applySelectedRowStyles();
}
} catch (error) {
console.warn('[HandsontablePro] refresh failed', error);
} finally {
selectionLock -= 1;
}

View File

@ -1079,9 +1079,25 @@ export default {
'order.platformStoreOrder.createFulfillWms.success': 'Created {count} fulfill order(s)',
'order.platformStoreOrder.pull.store': 'Store',
'order.platformStoreOrder.pull.storePlaceholder': 'Select stores',
'order.platformStoreOrder.pull.storeAll': 'All stores',
'order.platformStoreOrder.pull.status': 'Status',
'order.platformStoreOrder.pull.statusAll': 'All',
'order.platformStoreOrder.pull.statusClear': 'Clear',
'order.platformStoreOrder.pull.statusCount': '{count} selected',
'order.platformStoreOrder.pull.time': 'Time',
'order.platformStoreOrder.pull.timeHint': 'Defaults to today',
'order.platformStoreOrder.pull.timeHint': 'Filter by order created time. Defaults to today.',
'order.platformStoreOrder.pull.today': 'Today',
'order.platformStoreOrder.pull.last7': '7 days',
'order.platformStoreOrder.pull.last15': '15 days',
'order.platformStoreOrder.pull.status.UNPAID': 'Unpaid',
'order.platformStoreOrder.pull.status.ON_HOLD': 'On hold',
'order.platformStoreOrder.pull.status.PARTIALLY_SHIPPING': 'Partially shipping',
'order.platformStoreOrder.pull.status.AWAITING_SHIPMENT': 'Awaiting shipment',
'order.platformStoreOrder.pull.status.AWAITING_COLLECTION': 'Awaiting collection',
'order.platformStoreOrder.pull.status.IN_TRANSIT': 'In transit',
'order.platformStoreOrder.pull.status.DELIVERED': 'Delivered',
'order.platformStoreOrder.pull.status.COMPLETED': 'Completed',
'order.platformStoreOrder.pull.status.CANCELLED': 'Cancelled',
'order.platformStoreOrder.message.loadFailed': 'Failed to load orders',
'order.platformStoreOrder.message.detailFailed': 'Failed to load detail',
'order.platformStoreOrder.message.matchSuccess': 'Inventory matched',

View File

@ -1070,9 +1070,25 @@ export default {
'order.platformStoreOrder.createFulfillWms.success': '成功创建 {count} 张代发订单',
'order.platformStoreOrder.pull.store': '店铺',
'order.platformStoreOrder.pull.storePlaceholder': '请选择店铺',
'order.platformStoreOrder.pull.status': '类型',
'order.platformStoreOrder.pull.storeAll': '全选店铺',
'order.platformStoreOrder.pull.status': '状态',
'order.platformStoreOrder.pull.statusAll': '全选',
'order.platformStoreOrder.pull.statusClear': '清空',
'order.platformStoreOrder.pull.statusCount': '已选 {count} 个',
'order.platformStoreOrder.pull.time': '时间',
'order.platformStoreOrder.pull.timeHint': '默认时间为当天',
'order.platformStoreOrder.pull.timeHint': '按订单创建时间拉取,默认当天',
'order.platformStoreOrder.pull.today': '今天',
'order.platformStoreOrder.pull.last7': '近7天',
'order.platformStoreOrder.pull.last15': '近15天',
'order.platformStoreOrder.pull.status.UNPAID': '未付款',
'order.platformStoreOrder.pull.status.ON_HOLD': '待处理',
'order.platformStoreOrder.pull.status.PARTIALLY_SHIPPING': '部分发货',
'order.platformStoreOrder.pull.status.AWAITING_SHIPMENT': '待发货',
'order.platformStoreOrder.pull.status.AWAITING_COLLECTION': '待揽收',
'order.platformStoreOrder.pull.status.IN_TRANSIT': '运输中',
'order.platformStoreOrder.pull.status.DELIVERED': '已送达',
'order.platformStoreOrder.pull.status.COMPLETED': '已完成',
'order.platformStoreOrder.pull.status.CANCELLED': '已取消',
'order.platformStoreOrder.message.loadFailed': '加载订单失败',
'order.platformStoreOrder.message.detailFailed': '加载详情失败',
'order.platformStoreOrder.message.matchSuccess': '库存匹配成功',

View File

@ -9,6 +9,7 @@ import { pickField } from '@/utils/warehouseListMapper'
const props = defineProps<{
visible: boolean
customerId?: number
platformCode?: string
}>()
const emit = defineEmits<{
@ -16,51 +17,77 @@ const emit = defineEmits<{
success: []
}>()
/** TikTok 订单搜索状态,与平台 order_status 一致 */
const TIKTOK_PULL_STATUSES = [
'UNPAID',
'ON_HOLD',
'PARTIALLY_SHIPPING',
'AWAITING_SHIPMENT',
'AWAITING_COLLECTION',
'IN_TRANSIT',
'DELIVERED',
'COMPLETED',
'CANCELLED',
] as const
type StoreOption = { label: string; value: number; platform: string }
const { t } = useI18n()
const loading = ref(false)
const storeLoading = ref(false)
const storeOptions = ref<Array<{ label: string; value: number }>>([])
const storeOptions = ref<StoreOption[]>([])
const storeIds = ref<number[]>([])
const orderStatuses = ref<string[]>([
'unpaid',
'pending',
'canceled',
'ready_to_ship',
'delivered',
'returned',
'shipped',
'failed',
'topack',
'toship',
'shipping',
'lost',
])
const dateRange = ref<[string, string] | undefined>()
const statusOptions = computed(() =>
[
'unpaid',
'pending',
'canceled',
'ready_to_ship',
'delivered',
'returned',
'shipped',
'failed',
'topack',
'toship',
'shipping',
'lost',
].map((value) => ({ label: value, value })),
)
const orderStatuses = ref<string[]>([...TIKTOK_PULL_STATUSES])
const dateRange = ref<[string, string]>(todayRange())
const modalVisible = computed({
get: () => props.visible,
set: (v: boolean) => emit('update:visible', v),
})
const visibleStoreOptions = computed(() => {
const platform = (props.platformCode || '').trim().toLowerCase()
if (!platform || platform === 'all') return storeOptions.value
return storeOptions.value.filter((item) => item.platform === platform)
})
const allStatusesChecked = computed(
() => orderStatuses.value.length === TIKTOK_PULL_STATUSES.length,
)
const allStoresChecked = computed(
() =>
visibleStoreOptions.value.length > 0
&& storeIds.value.length === visibleStoreOptions.value.length,
)
const canSubmit = computed(
() => storeIds.value.length > 0 && orderStatuses.value.length > 0 && !storeLoading.value,
)
function todayRange(): [string, string] {
return [dayjs().format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')]
}
function applyRange(days: number) {
dateRange.value = [
dayjs().subtract(days - 1, 'day').format('YYYY-MM-DD'),
dayjs().format('YYYY-MM-DD'),
]
}
function toggleAllStatuses() {
orderStatuses.value = allStatusesChecked.value ? [] : [...TIKTOK_PULL_STATUSES]
}
function toggleAllStores() {
storeIds.value = allStoresChecked.value
? []
: visibleStoreOptions.value.map((item) => item.value)
}
async function loadStoreOptions() {
storeIds.value = []
orderStatuses.value = [...TIKTOK_PULL_STATUSES]
dateRange.value = todayRange()
storeLoading.value = true
try {
const { items } = await getPlatformStoreList({
@ -72,12 +99,12 @@ async function loadStoreOptions() {
.map((raw: Record<string, unknown>) => {
const id = Number(pickField(raw, 'id', 'Id'))
const name = String(pickField(raw, 'storeNickname', 'StoreNickname') ?? `#${id}`)
const platform = String(pickField(raw, 'platformCode', 'PlatformCode') ?? '')
return { label: platform ? `${name} (${platform})` : name, value: id }
const platform = String(pickField(raw, 'platformCode', 'PlatformCode') ?? '').toLowerCase()
return { label: platform ? `${name} (${platform})` : name, value: id, platform }
})
.filter((x) => x.value > 0)
if (!storeOptions.value.length) {
$baseMessage(t('order.platformStoreOrder.message.noStore'), 'warning')
.filter((item) => item.value > 0)
if (visibleStoreOptions.value.length === 1) {
storeIds.value = [visibleStoreOptions.value[0].value]
}
} catch (e: any) {
storeOptions.value = []
@ -145,38 +172,152 @@ async function handleSubmit(): Promise<boolean> {
<a-modal
v-model:visible="modalVisible"
:title="t('order.platformStoreOrder.actions.pullOrders')"
:width="720"
:ok-loading="loading"
:ok-text="t('order.platformStoreOrder.actions.startPull')"
:ok-button-props="{ disabled: !canSubmit }"
unmount-on-close
:on-before-ok="handleSubmit"
>
<a-form :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 16 }">
<a-form class="pull-form" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 16 }">
<a-form-item :label="t('order.platformStoreOrder.pull.store')" required>
<div class="pull-block">
<div class="pull-row">
<a-select
v-model="storeIds"
class="pull-grow"
multiple
allow-clear
:loading="storeLoading"
:options="storeOptions"
:options="visibleStoreOptions"
:placeholder="t('order.platformStoreOrder.pull.storePlaceholder')"
:max-tag-count="3"
:max-tag-count="2"
/>
<a-button
type="text"
size="small"
:disabled="!visibleStoreOptions.length"
@click="toggleAllStores"
>
{{ allStoresChecked ? t('order.platformStoreOrder.pull.statusClear') : t('order.platformStoreOrder.pull.storeAll') }}
</a-button>
</div>
<div v-if="!storeLoading && !visibleStoreOptions.length" class="hint">
{{ t('order.platformStoreOrder.message.noStore') }}
</div>
</div>
</a-form-item>
<a-form-item :label="t('order.platformStoreOrder.pull.status')" required>
<a-checkbox-group v-model="orderStatuses" :options="statusOptions" />
<div class="pull-block">
<div class="pull-row pull-row--between">
<span class="hint">
{{ t('order.platformStoreOrder.pull.statusCount', { count: orderStatuses.length }) }}
</span>
<a-button type="text" size="small" @click="toggleAllStatuses">
{{ allStatusesChecked ? t('order.platformStoreOrder.pull.statusClear') : t('order.platformStoreOrder.pull.statusAll') }}
</a-button>
</div>
<a-checkbox-group v-model="orderStatuses" class="status-grid">
<a-checkbox v-for="status in TIKTOK_PULL_STATUSES" :key="status" :value="status">
<span class="status-name">{{ t(`order.platformStoreOrder.pull.status.${status}`) }}</span>
<span class="status-code">{{ status }}</span>
</a-checkbox>
</a-checkbox-group>
</div>
</a-form-item>
<a-form-item :label="t('order.platformStoreOrder.pull.time')">
<a-range-picker v-model="dateRange" style="width: 100%" />
<div class="pull-block">
<a-range-picker
v-model="dateRange"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
<div class="pull-row">
<a-button type="text" size="small" @click="applyRange(1)">
{{ t('order.platformStoreOrder.pull.today') }}
</a-button>
<a-button type="text" size="small" @click="applyRange(7)">
{{ t('order.platformStoreOrder.pull.last7') }}
</a-button>
<a-button type="text" size="small" @click="applyRange(15)">
{{ t('order.platformStoreOrder.pull.last15') }}
</a-button>
</div>
<div class="hint">{{ t('order.platformStoreOrder.pull.timeHint') }}</div>
</div>
</a-form-item>
</a-form>
</a-modal>
</template>
<style scoped lang="less">
.hint {
margin-top: 8px;
.pull-form {
:deep(.arco-form-item) {
align-items: flex-start;
margin-bottom: 16px;
}
:deep(.arco-form-item-label-col) {
line-height: 32px;
}
:deep(.arco-form-item-content) {
min-width: 0;
}
}
.pull-block {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
width: 100%;
}
.pull-row {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.pull-row--between {
justify-content: space-between;
}
.pull-grow {
flex: 1;
min-width: 0;
}
.status-grid {
display: grid !important;
grid-template-columns: 1fr 1fr;
gap: 10px 12px;
width: 100%;
:deep(.arco-checkbox) {
margin: 0;
align-items: center;
line-height: 22px;
white-space: nowrap;
}
}
.status-name {
color: var(--color-text-1);
}
.status-code {
margin-left: 6px;
color: var(--color-text-3);
font-size: 12px;
}
.hint {
color: var(--color-text-3);
font-size: 12px;
line-height: 20px;
}
</style>

View File

@ -860,6 +860,7 @@ watch(
v-if="pullVisible"
v-model:visible="pullVisible"
:customer-id="withCustomerId({}).customerId"
:platform-code="platformCode"
@success="() => { loadSummary(); loadList() }"
/>