订单比对加上状态

This commit is contained in:
wuweihua
2026-10-26 11:59:19 +08:00
parent 4bee108661
commit ab6216922a
12 changed files with 337 additions and 61 deletions
@@ -157,7 +157,7 @@ public class TabOrderMonitorController extends BaseController
customerKeywordResolver.resolve(query);
String asyncTaskId = UUID.randomUUID().toString().replace("-", "");
tabOrderCompareAsyncRunner.put(asyncTaskId, TabOrderCompareTask.STATUS_PENDING, 0, "queued",
0, 0, 0, null, null);
0, 0, 0, 0, null, null, null);
tabOrderCompareAsyncRunner.run(asyncTaskId, query);
Map<String, Object> data = new HashMap<String, Object>();
data.put("asyncTaskId", asyncTaskId);
@@ -3,10 +3,12 @@ package com.bms.price.domain.vo;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 订单比对异步任务进度(Redis)。
* ok=本地已有;fail/缺失=上游有而本地无(仅转单号)。
* missing = 上游有转单号、本地无;
* statusMismatch = 本地有转单号但 order_status 与上游不一致。
*/
public class TabOrderCompareTask implements Serializable
{
@@ -27,9 +29,16 @@ public class TabOrderCompareTask implements Serializable
private int matched;
/** 本地缺失 */
private int missing;
/** 本地有但订单状态不一致 */
private int statusMismatch;
private String error;
/** 缺失转单号样本(有上限) */
private List<String> missingTransferNos = new ArrayList<String>();
/**
* 状态不一致样本(有上限)。
* 每项:transferNo / upstreamStatus / localStatus
*/
private List<Map<String, Object>> statusMismatchSamples = new ArrayList<Map<String, Object>>();
public String getTaskId() { return taskId; }
public void setTaskId(String taskId) { this.taskId = taskId; }
@@ -45,6 +54,8 @@ public class TabOrderCompareTask implements Serializable
public void setMatched(int matched) { this.matched = matched; }
public int getMissing() { return missing; }
public void setMissing(int missing) { this.missing = missing; }
public int getStatusMismatch() { return statusMismatch; }
public void setStatusMismatch(int statusMismatch) { this.statusMismatch = statusMismatch; }
public String getError() { return error; }
public void setError(String error) { this.error = error; }
public List<String> getMissingTransferNos() { return missingTransferNos; }
@@ -53,4 +64,10 @@ public class TabOrderCompareTask implements Serializable
this.missingTransferNos = missingTransferNos == null
? new ArrayList<String>() : missingTransferNos;
}
public List<Map<String, Object>> getStatusMismatchSamples() { return statusMismatchSamples; }
public void setStatusMismatchSamples(List<Map<String, Object>> statusMismatchSamples)
{
this.statusMismatchSamples = statusMismatchSamples == null
? new ArrayList<Map<String, Object>>() : statusMismatchSamples;
}
}
@@ -30,7 +30,7 @@ public interface OracleUpstreamOrderMapper
@Param("endRow") long endRow);
/**
* 订单比对专用(异步分页):仅返回 TRANSFER_ORDER_ID。
* 订单比对专用(异步分页):返回 TRANSFER_ORDER_ID + ORDER_STATUS。
*/
List<Map<String, Object>> selectTabOrderCompareKeyPage(
@Param("orderTable") String orderTable,
@@ -43,6 +43,11 @@ public interface PrcOrderBillMapper
List<String> selectExistingTransferNos(@Param("transferNos") List<String> transferNos,
@Param("customerCodes") List<String> customerCodes);
/** 订单比对:按转单号批量查本地 transfer_no + order_status。 */
List<java.util.Map<String, Object>> selectTransferOrderStatuses(
@Param("transferNos") List<String> transferNos,
@Param("customerCodes") List<String> customerCodes);
/** 订单号下拉搜索(带数据权限) */
public List<String> selectOrderNoOptions(@Param("query") PrcOrderBill query, @Param("limit") int limit);
@@ -295,10 +295,38 @@ public class TabOrderMonitorService
}
/**
* 订单比对异步分页:专用 SQL 仅返回当前页转单号。
* 订单比对异步分页:返回当前页转单号(兼容旧调用)。
*/
@DataSource(DataSourceType.SLAVE)
public List<String> selectCompareTransferPage(TabOrderMonitorQuery query, int pageNum, int pageSize)
{
List<Map<String, Object>> rows = selectCompareKeyPage(query, pageNum, pageSize);
if (rows == null || rows.isEmpty())
{
return Collections.emptyList();
}
List<String> out = new ArrayList<String>(rows.size());
for (Map<String, Object> row : rows)
{
Object v = row == null ? null : row.get("transferNo");
if (v == null)
{
continue;
}
String tn = String.valueOf(v).trim();
if (!tn.isEmpty())
{
out.add(tn);
}
}
return out;
}
/**
* 订单比对异步分页:返回 transferNo + orderStatus(上游 ORDER_STATUS)。
*/
@DataSource(DataSourceType.SLAVE)
public List<Map<String, Object>> selectCompareKeyPage(TabOrderMonitorQuery query, int pageNum, int pageSize)
{
// 比对与回填列表完全隔离:先清单号再 validate,仅走客户+提货时间
if (query != null)
@@ -334,7 +362,7 @@ public class TabOrderMonitorService
{
return Collections.emptyList();
}
List<String> out = new ArrayList<String>(rows.size());
List<Map<String, Object>> out = new ArrayList<Map<String, Object>>(rows.size());
for (Map<String, Object> row : rows)
{
Object v = value(row, "TRANSFER_ORDER_ID");
@@ -343,10 +371,14 @@ public class TabOrderMonitorService
continue;
}
String tn = String.valueOf(v).trim();
if (!tn.isEmpty())
if (tn.isEmpty())
{
out.add(tn);
continue;
}
Map<String, Object> one = new LinkedHashMap<String, Object>();
one.put("transferNo", tn);
one.put("orderStatus", integer(row, "ORDER_STATUS"));
out.add(one);
}
return out;
}
@@ -497,6 +497,13 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
productCode, productName, cc, cn, zone.zoneCode, weight, bizDate);
if (hit != null)
{
// Billing requires product catalog price (产品价); package hit alone is not enough.
if (request instanceof TmsPriceBillRequest && !hasProductCatalogPrice(catalog))
{
missCode = Math.max(missCode,
diagnoseCatalogMiss(productCode, cc, cn, zone.zoneCode, bizDate));
continue;
}
stampRequestMeta(request, hit);
return hit;
}
@@ -504,7 +511,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
}
else if (TYPE_DISCOUNT.equals(setType))
{
if (catalog == null)
if (!hasProductCatalogPrice(catalog))
{
missCode = Math.max(missCode, diagnoseCatalogMiss(productCode, cc, cn, zone.zoneCode, bizDate));
continue;
@@ -747,7 +754,13 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
return out;
}
/** Catalog match for product_amount / DISCOUNT base; null is allowed for FIXED sales. */
/** True when product catalog row has a usable freight price (产品价). */
private static boolean hasProductCatalogPrice(BmsProductCountry catalog)
{
return catalog != null && catalog.getFreightPrice() != null;
}
/** Catalog match for product_amount / DISCOUNT base; quote FIXED may still hit without catalog. */
private BmsProductCountry softMatchCatalog(String productCode, String countryCode, String countryName,
String zoneCode, BigDecimal weight, Date bizDate, PrcZone resolvedZone)
{
@@ -1,12 +1,11 @@
package com.bms.price.task;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -23,7 +22,7 @@ import com.bms.price.search.IBillSearchStore;
import com.bms.price.search.TabOrderSyncEsLog;
/**
* 订单比对异步任务:分页拉上游转单号,与本地 transfer_no 比对差异。
* 订单比对异步任务:分页拉上游转单号+状态,与本地 transfer_no / order_status 比对。
*/
@Component
public class TabOrderCompareAsyncRunner
@@ -37,6 +36,8 @@ public class TabOrderCompareAsyncRunner
public static final int SCAN_HARD_LIMIT = 50000;
/** 进度/抽屉中保留的缺失转单号上限 */
public static final int MISSING_SAMPLE_LIMIT = 5000;
/** 状态不一致样本上限 */
public static final int STATUS_MISMATCH_SAMPLE_LIMIT = 5000;
@Autowired
private TabOrderMonitorService tabOrderMonitorService;
@@ -62,11 +63,14 @@ public class TabOrderCompareAsyncRunner
query.setOrgOrderId(null);
query.setTransferOrderId(null);
}
put(asyncTaskId, TabOrderCompareTask.STATUS_RUNNING, 0, "starting", 0, 0, 0, null, null);
put(asyncTaskId, TabOrderCompareTask.STATUS_RUNNING, 0, "starting",
0, 0, 0, 0, null, null, null);
int scanned = 0;
int matched = 0;
int missing = 0;
int statusMismatch = 0;
List<String> missingSample = new ArrayList<String>();
List<Map<String, Object>> statusMismatchSample = new ArrayList<Map<String, Object>>();
int pageNum = 1;
while (true)
{
@@ -77,43 +81,72 @@ public class TabOrderCompareAsyncRunner
}
int remain = SCAN_HARD_LIMIT - scanned;
int pageSize = Math.min(PAGE_SIZE, remain);
List<String> transferNos = tabOrderMonitorService.selectCompareTransferPage(query, pageNum, pageSize);
if (transferNos == null || transferNos.isEmpty())
List<Map<String, Object>> pageRows =
tabOrderMonitorService.selectCompareKeyPage(query, pageNum, pageSize);
if (pageRows == null || pageRows.isEmpty())
{
break;
}
Set<String> existing = findExisting(transferNos, query == null ? null : query.getCustomerCodes());
for (String tn : transferNos)
List<String> transferNos = new ArrayList<String>(pageRows.size());
for (Map<String, Object> row : pageRows)
{
if (StringUtils.isEmpty(tn))
Object tn = row == null ? null : row.get("transferNo");
if (tn != null && StringUtils.isNotEmpty(String.valueOf(tn).trim()))
{
transferNos.add(String.valueOf(tn).trim());
}
}
Map<String, Integer> localStatusByTn =
findLocalStatuses(transferNos, query == null ? null : query.getCustomerCodes());
for (Map<String, Object> row : pageRows)
{
if (row == null)
{
continue;
}
scanned++;
String key = tn.trim().toUpperCase(Locale.ROOT);
if (existing.contains(key))
Object tnObj = row.get("transferNo");
if (tnObj == null || StringUtils.isEmpty(String.valueOf(tnObj).trim()))
{
matched++;
continue;
}
else
String tn = String.valueOf(tnObj).trim();
scanned++;
String key = tn.toUpperCase(Locale.ROOT);
Integer upstreamStatus = toInteger(row.get("orderStatus"));
if (!localStatusByTn.containsKey(key))
{
missing++;
if (missingSample.size() < MISSING_SAMPLE_LIMIT)
{
missingSample.add(tn.trim());
missingSample.add(tn);
}
continue;
}
matched++;
Integer localStatus = localStatusByTn.get(key);
if (!statusEquals(upstreamStatus, localStatus))
{
statusMismatch++;
if (statusMismatchSample.size() < STATUS_MISMATCH_SAMPLE_LIMIT)
{
Map<String, Object> one = new LinkedHashMap<String, Object>();
one.put("transferNo", tn);
one.put("upstreamStatus", upstreamStatus);
one.put("localStatus", localStatus);
statusMismatchSample.add(one);
}
}
}
int percent = Math.min(99, (int) ((scanned * 100L) / Math.max(scanned + pageSize, 1)));
// 最后一页不满页时近乎完成;否则按已扫描量给一个缓慢增长的百分比
if (transferNos.size() < pageSize)
if (pageRows.size() < pageSize)
{
percent = 99;
}
put(asyncTaskId, TabOrderCompareTask.STATUS_RUNNING, percent,
"scanning page=" + pageNum + " scanned=" + scanned + " missing=" + missing,
scanned, matched, missing, null, missingSample);
if (transferNos.size() < pageSize)
"scanning page=" + pageNum + " scanned=" + scanned
+ " missing=" + missing + " statusMismatch=" + statusMismatch,
scanned, matched, missing, statusMismatch, null, missingSample, statusMismatchSample);
if (pageRows.size() < pageSize)
{
break;
}
@@ -124,14 +157,19 @@ public class TabOrderCompareAsyncRunner
resp.put("scanned", Integer.valueOf(scanned));
resp.put("matched", Integer.valueOf(matched));
resp.put("missing", Integer.valueOf(missing));
resp.put("statusMismatch", Integer.valueOf(statusMismatch));
resp.put("missingTransferNos", missingSample);
resp.put("statusMismatchSamples", statusMismatchSample);
resp.put("missingTruncated", Boolean.valueOf(missing > missingSample.size()));
resp.put("statusMismatchTruncated",
Boolean.valueOf(statusMismatch > statusMismatchSample.size()));
put(asyncTaskId, TabOrderCompareTask.STATUS_SUCCESS, 100,
"done scanned=" + scanned + " missing=" + missing,
scanned, matched, missing, null, missingSample);
"done scanned=" + scanned + " missing=" + missing + " statusMismatch=" + statusMismatch,
scanned, matched, missing, statusMismatch, null, missingSample, statusMismatchSample);
TabOrderSyncEsLog.save(billSearchStore, asyncTaskId, "COMPARE_DONE", query, resp, "SUCCESS", null);
log.info("orderSource compare done asyncTaskId={} scanned={} matched={} missing={}",
asyncTaskId, Integer.valueOf(scanned), Integer.valueOf(matched), Integer.valueOf(missing));
log.info("orderSource compare done asyncTaskId={} scanned={} matched={} missing={} statusMismatch={}",
asyncTaskId, Integer.valueOf(scanned), Integer.valueOf(matched),
Integer.valueOf(missing), Integer.valueOf(statusMismatch));
}
catch (Exception e)
{
@@ -141,33 +179,101 @@ public class TabOrderCompareAsyncRunner
{
msg = e.getClass().getSimpleName();
}
put(asyncTaskId, TabOrderCompareTask.STATUS_FAIL, 100, "failed", 0, 0, 0, msg, null);
put(asyncTaskId, TabOrderCompareTask.STATUS_FAIL, 100, "failed",
0, 0, 0, 0, msg, null, null);
TabOrderSyncEsLog.save(billSearchStore, asyncTaskId, "COMPARE_DONE", query, null, "FAIL", msg);
}
}
private Set<String> findExisting(List<String> transferNos, List<String> customerCodes)
/** transferNo(upper) -> local order_status (null value means local row exists with null status). */
private Map<String, Integer> findLocalStatuses(List<String> transferNos, List<String> customerCodes)
{
Set<String> found = new HashSet<String>();
Map<String, Integer> found = new HashMap<String, Integer>();
if (transferNos == null || transferNos.isEmpty())
{
return found;
}
List<String> hit = prcOrderBillMapper.selectExistingTransferNos(transferNos, cleanCodes(customerCodes));
if (hit == null)
List<Map<String, Object>> rows =
prcOrderBillMapper.selectTransferOrderStatuses(transferNos, cleanCodes(customerCodes));
if (rows == null)
{
return found;
}
for (String s : hit)
for (Map<String, Object> row : rows)
{
if (StringUtils.isNotEmpty(s))
if (row == null)
{
found.add(s.trim().toUpperCase(Locale.ROOT));
continue;
}
Object tn = first(row, "transferNo", "TRANSFERNO", "transfer_no");
if (tn == null || StringUtils.isEmpty(String.valueOf(tn).trim()))
{
continue;
}
String key = String.valueOf(tn).trim().toUpperCase(Locale.ROOT);
Integer st = toInteger(first(row, "orderStatus", "ORDERSTATUS", "order_status"));
// Keep first hit; duplicate transfer_no rare.
if (!found.containsKey(key))
{
found.put(key, st);
}
}
return found;
}
private static Object first(Map<String, Object> row, String... keys)
{
if (row == null || keys == null)
{
return null;
}
for (String k : keys)
{
if (row.containsKey(k) && row.get(k) != null)
{
return row.get(k);
}
}
return null;
}
private static Integer toInteger(Object v)
{
if (v == null || "".equals(String.valueOf(v).trim()))
{
return null;
}
if (v instanceof Integer)
{
return (Integer) v;
}
if (v instanceof Number)
{
return Integer.valueOf(((Number) v).intValue());
}
try
{
return Integer.valueOf(String.valueOf(v).trim());
}
catch (Exception e)
{
return null;
}
}
private static boolean statusEquals(Integer a, Integer b)
{
if (a == null && b == null)
{
return true;
}
if (a == null || b == null)
{
return false;
}
return a.intValue() == b.intValue();
}
private static List<String> cleanCodes(List<String> codes)
{
if (codes == null || codes.isEmpty())
@@ -186,7 +292,8 @@ public class TabOrderCompareAsyncRunner
}
public void put(String taskId, String status, int percent, String message,
int scanned, int matched, int missing, String error, List<String> missingSample)
int scanned, int matched, int missing, int statusMismatch, String error,
List<String> missingSample, List<Map<String, Object>> statusMismatchSample)
{
TabOrderCompareTask task = new TabOrderCompareTask();
task.setTaskId(taskId);
@@ -196,11 +303,16 @@ public class TabOrderCompareAsyncRunner
task.setScanned(scanned);
task.setMatched(matched);
task.setMissing(missing);
task.setStatusMismatch(statusMismatch);
task.setError(error);
if (missingSample != null)
{
task.setMissingTransferNos(new ArrayList<String>(missingSample));
}
if (statusMismatchSample != null)
{
task.setStatusMismatchSamples(new ArrayList<Map<String, Object>>(statusMismatchSample));
}
redisCache.setCacheObject(CACHE_KEY_PREFIX + taskId, task, TTL_MINUTES, TimeUnit.MINUTES);
}
@@ -163,14 +163,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<!--
订单比对专用 SQL(异步分页):仅返回转单号,
订单比对专用 SQL(异步分页):返回转单号 + 订单状态,
筛选与列表相同;排除空转单号。
-->
<select id="selectTabOrderCompareKeyPage" resultType="java.util.HashMap">
SELECT TRANSFER_ORDER_ID FROM (
SELECT inner_q.TRANSFER_ORDER_ID AS TRANSFER_ORDER_ID, ROWNUM AS rn
SELECT TRANSFER_ORDER_ID, ORDER_STATUS FROM (
SELECT inner_q.TRANSFER_ORDER_ID AS TRANSFER_ORDER_ID,
inner_q.ORDER_STATUS AS ORDER_STATUS,
ROWNUM AS rn
FROM (
SELECT o.TRANSFER_ORDER_ID, o.PICKUP_DATE, o.ORDER_ID
SELECT o.TRANSFER_ORDER_ID, o.ORDER_STATUS, o.PICKUP_DATE, o.ORDER_ID
FROM ${orderTable} o
WHERE
<include refid="tabOrderMonitorWhere"/>
@@ -220,6 +220,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND p.transfer_no IS NOT NULL AND p.transfer_no != ''
</select>
<!-- 订单比对:本地转单号 + 订单状态(用于状态一致性比对) -->
<select id="selectTransferOrderStatuses" resultType="java.util.HashMap">
SELECT p.transfer_no AS transferNo, p.order_status AS orderStatus
FROM prc_order_bill p
WHERE p.transfer_no IN
<foreach collection="transferNos" item="tn" open="(" separator="," close=")">
#{tn}
</foreach>
<if test="customerCodes != null and customerCodes.size() > 0">
AND p.customer_code IN
<foreach collection="customerCodes" item="ccode" open="(" separator="," close=")">
#{ccode}
</foreach>
</if>
AND p.transfer_no IS NOT NULL AND p.transfer_no != ''
</select>
<!-- 订单比对:上游单号中已落库本地的 order_no/tracking_no/transfer_no -->
<select id="selectExistingOrderIdentifiers" resultType="String">
SELECT DISTINCT hit.idn
@@ -31,6 +31,7 @@
已扫描 {{ progress.scanned || 0 }}
/ 本地已有 {{ progress.matched || 0 }}
/ 本地缺失 {{ progress.missing || 0 }}
/ 状态不一致 {{ progress.statusMismatch || 0 }}
</div>
<div v-if="progress.message" class="msg-line">{{ progress.message }}</div>
<div v-if="progress.error" class="err-line">{{ progress.error }}</div>
@@ -40,7 +41,7 @@
type="info"
:closable="false"
show-icon
title="仅比对转单号:上游 TRANSFER_ORDER_ID 有、本地 transfer_no 没有。异步分页扫描,结果已写入操作记录。"
title="比对转单号 + 订单状态:1) 上游有转单号、本地没有;2) 两端都有但 order_status 不一致。异步分页扫描,结果已写入操作记录。"
/>
<div class="json-panel__head">
@@ -57,6 +58,18 @@
<el-table-column prop="transferNo" label="转单号" min-width="220" show-overflow-tooltip />
</el-table>
<el-empty v-if="!running && !(progress.missingTransferNos || []).length" description="暂无缺失转单号" />
<div class="json-panel__head">
<span>状态不一致(样本)</span>
</div>
<el-table :data="statusMismatchRows" border size="small" max-height="280" class="mb12">
<el-table-column type="index" width="55" label="#" />
<el-table-column prop="transferNo" label="转单号" min-width="180" show-overflow-tooltip />
<el-table-column prop="upstreamStatusLabel" label="上游状态" width="110" />
<el-table-column prop="localStatusLabel" label="本地状态" width="110" />
</el-table>
<el-empty v-if="!running && !(progress.statusMismatchSamples || []).length" description="暂无状态不一致" />
</div>
</el-drawer>
</template>
@@ -121,6 +134,35 @@ const progressStatus = computed(() => {
return undefined
})
const orderStatusMap = {
'0': '待提货',
'1': '已提货',
'2': '已收货',
'3': '运输中',
'4': '派送中',
'5': '已签收',
'6': '已完成',
'7': '已取消',
'8': '异常',
'9': '退件中',
'10': '已退件'
}
function labelStatus(v) {
const k = v == null ? '' : String(v).trim()
if (!k) return '-'
return orderStatusMap[k] || k
}
const statusMismatchRows = computed(() => {
const list = progress.value?.statusMismatchSamples || []
return list.map(row => ({
transferNo: row.transferNo || row.transfer_no || '',
upstreamStatusLabel: labelStatus(row.upstreamStatus ?? row.upstream_status),
localStatusLabel: labelStatus(row.localStatus ?? row.local_status)
}))
})
const missingRows = computed(() => {
const list = progress.value?.missingTransferNos || []
return list.map(tn => ({ transferNo: tn }))
@@ -375,7 +375,7 @@ function openOrderCompare() {
return
}
proxy.$modal.confirm(
'将按当前客户与提货时间,异步分页比对上游转单号与本地订单库(仅转单号),确认继续?',
'将按当前客户与提货时间,异步分页比对上游转单号与订单状态(缺失转单号 + 状态不一致),确认继续?',
'二次确认'
).then(() => {
// 比对只按客户+时间;不带单号框内容(回填后可能有数千转单号,带上会把比对拖死)
+49 -13
View File
@@ -83,6 +83,32 @@
remote-search
/>
</template>
<template #search-orderStatuses="{ model }">
<el-select
v-model="model.orderStatuses"
multiple
filterable
clearable
collapse-tags
collapse-tags-tooltip
placeholder="请选择"
style="width: 100%"
>
<template #header>
<el-checkbox
:model-value="isAllSelected(model, 'orderStatuses', orderStatusAllValues)"
:indeterminate="isIndeterminate(model, 'orderStatuses', orderStatusAllValues)"
@change="(checked) => toggleAllSelected(model, 'orderStatuses', orderStatusAllValues, checked)"
>全选</el-checkbox>
</template>
<el-option
v-for="opt in orderStatusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</template>
<template #search-billingFailStatuses="{ model }">
<el-select
v-model="model.billingFailStatuses"
@@ -96,9 +122,9 @@
>
<template #header>
<el-checkbox
:model-value="isAllBillingFailSelected(model)"
:indeterminate="isBillingFailIndeterminate(model)"
@change="(checked) => toggleAllBillingFail(model, checked)"
:model-value="isAllSelected(model, 'billingFailStatuses', billingFailAllValues)"
:indeterminate="isIndeterminate(model, 'billingFailStatuses', billingFailAllValues)"
@change="(checked) => toggleAllSelected(model, 'billingFailStatuses', billingFailAllValues, checked)"
>全选</el-checkbox>
</template>
<el-option
@@ -259,27 +285,37 @@ const billingFailStatusOptions = [
{ label: '未知失败', value: 22 }
]
const orderStatusAllValues = computed(() => orderStatusOptions.map(o => o.value))
const billingFailAllValues = computed(() => billingFailStatusOptions.map(o => o.value))
function selectedBillingFailStatuses(model) {
return Array.isArray(model?.billingFailStatuses) ? model.billingFailStatuses : []
/** 多选下拉「全选」复选框的通用逻辑,订单状态 / 计费状态共用 */
function selectedValues(model, key) {
const value = model?.[key]
return Array.isArray(value) ? value : []
}
function isAllBillingFailSelected(model) {
const selected = selectedBillingFailStatuses(model)
const all = billingFailAllValues.value
/** 模板中 computed 会自动解包为数组,script 内直接引用时是 ref,两种形态都兼容 */
function resolveAllValues(allValues) {
if (Array.isArray(allValues)) return allValues
const value = allValues?.value
return Array.isArray(value) ? value : []
}
function isAllSelected(model, key, allValues) {
const all = resolveAllValues(allValues)
const selected = selectedValues(model, key)
return all.length > 0 && selected.length === all.length
}
function isBillingFailIndeterminate(model) {
const selected = selectedBillingFailStatuses(model)
const all = billingFailAllValues.value
function isIndeterminate(model, key, allValues) {
const all = resolveAllValues(allValues)
const selected = selectedValues(model, key)
return selected.length > 0 && selected.length < all.length
}
function toggleAllBillingFail(model, checked) {
function toggleAllSelected(model, key, allValues, checked) {
if (!model) return
model.billingFailStatuses = checked ? [...billingFailAllValues.value] : []
model[key] = checked ? [...resolveAllValues(allValues)] : []
}
const searchColumns = [