优化销售台账页面:新增产品信息的内联编辑功能,支持产品类别、规格型号、尺寸、数量、含税单价等字段的动态修改,提升用户交互体验
已修改1个文件
729 ■■■■■ 文件已修改
src/views/salesManagement/salesLedger/index.vue 729 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue
@@ -298,7 +298,7 @@
                </el-row>
                <el-row>
                    <el-form-item label="产品信息:" prop="entryDate">
                        <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">添加</el-button>
                        <el-button v-if="operationType !== 'view'" type="primary" @click="addProductInline">添加</el-button>
                        <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >删除</el-button>
                    </el-form-item>
                </el-row>
@@ -307,38 +307,376 @@
                    <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'"
                        :selectable="(row) => !isProductShipped(row)" />
                    <el-table-column align="center" label="序号" type="index" width="60" />
                    <el-table-column label="产品大类" prop="productCategory" />
                    <el-table-column label="规格型号" prop="specificationModel" />
                    <el-table-column label="产品大类" prop="productCategory" min-width="160">
                        <template #default="scope">
                            <el-tree-select
                                v-if="scope.row.__editing"
                                v-model="scope.row.__productCategoryId"
                                placeholder="请选择"
                                clearable
                                filterable
                                check-strictly
                                :data="productOptions"
                                :render-after-expand="false"
                                style="width: 100%"
                                :filter-node-method="filterProductCategoryNode"
                                @change="(val) => handleInlineProductCategoryChange(scope.row, val)"
                            />
                            <span v-else>{{ scope.row.productCategory ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="规格型号" prop="specificationModel" min-width="140">
                        <template #default="scope">
                            <el-select
                                v-if="scope.row.__editing"
                                v-model="scope.row.productModelId"
                                placeholder="请选择"
                                clearable
                                filterable
                                style="width: 140px"
                                @change="(val) => handleInlineProductModelChange(scope.row, val)"
                            >
                                <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
                            </el-select>
                            <span v-else>{{ scope.row.specificationModel ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="厚度" prop="thickness" min-width="90">
                        <template #default="scope">
                            {{ scope.row.thickness ?? "" }}
                            <el-input-number
                                v-if="scope.row.__editing"
                                v-model="scope.row.thickness"
                                :min="0"
                                :step="0.000000000000001"
                                :precision="15"
                                style="width: 110px"
                                placeholder="请输入"
                                clearable
                            />
                            <span v-else>{{ scope.row.thickness ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="宽(mm)" prop="width" min-width="80">
                        <template #default="scope">
                            {{ scope.row.width ?? "" }}
                            <el-input-number
                                v-if="scope.row.__editing"
                                v-model="scope.row.width"
                                :min="0"
                                :step="1"
                                :precision="2"
                                style="width: 110px"
                                placeholder="请输入"
                                clearable
                                @change="() => handleInlineSizeChange(scope.row)"
                            />
                            <span v-else>{{ scope.row.width ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="高(mm)" prop="height" min-width="80">
                        <template #default="scope">
                            {{ scope.row.height ?? "" }}
                            <el-input-number
                                v-if="scope.row.__editing"
                                v-model="scope.row.height"
                                :min="0"
                                :step="1"
                                :precision="2"
                                style="width: 110px"
                                placeholder="请输入"
                                clearable
                                @change="() => handleInlineSizeChange(scope.row)"
                            />
                            <span v-else>{{ scope.row.height ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="面积(m²)" prop="actualTotalArea" min-width="100">
                        <template #default="scope">
                            {{ scope.row.actualTotalArea ?? "" }}
                            <el-input-number
                                v-if="scope.row.__editing"
                                v-model="scope.row.actualTotalArea"
                                :min="0"
                                :step="0.00001"
                                :precision="5"
                                style="width: 120px"
                                placeholder="自动计算"
                                :disabled="true"
                            />
                            <span v-else>{{ scope.row.actualTotalArea ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="数量" prop="quantity" />
                    <el-table-column label="税率(%)" prop="taxRate" />
                    <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" />
                    <el-table-column label="数量" prop="quantity" min-width="90">
                        <template #default="scope">
                            <el-input-number
                                v-if="scope.row.__editing"
                                v-model="scope.row.quantity"
                                :step="0.1"
                                :min="0"
                                :precision="2"
                                style="width: 110px"
                                placeholder="请输入"
                                clearable
                                @change="() => handleInlineQuantityChange(scope.row)"
                            />
                            <span v-else>{{ scope.row.quantity ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" min-width="140">
                        <template #default="scope">
                            <el-input-number
                                v-if="scope.row.__editing"
                                :step="0.01"
                                :min="0"
                                :precision="2"
                                style="width: 120px"
                                v-model="scope.row.taxInclusiveUnitPrice"
                                placeholder="请输入"
                                clearable
                                @change="() => handleInlineUnitPriceChange(scope.row)"
                            />
                            <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice ?? 0) }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="税率(%)" prop="taxRate" min-width="90">
                        <template #default="scope">
                            <el-select
                                v-if="scope.row.__editing"
                                v-model="scope.row.taxRate"
                                placeholder="请选择"
                                clearable
                                style="width: 90px"
                                @change="() => handleInlineTaxRateChange(scope.row)"
                            >
                                <el-option label="1" value="1" />
                                <el-option label="3" value="3" />
                                <el-option label="6" value="6" />
                                <el-option label="9" value="9" />
                                <el-option label="13" value="13" />
                            </el-select>
                            <span v-else>{{ scope.row.taxRate ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" />
                    <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" />
                    <el-table-column fixed="right" label="操作" min-width="60" align="center" v-if="operationType !== 'view'">
                    <el-table-column label="发票类型" prop="invoiceType" min-width="120">
                        <template #default="scope">
                            <el-button link type="primary" size="small"
                                :disabled="isProductShipped(scope.row)"
                                @click="openProductForm('edit', scope.row,scope.$index)">编辑</el-button>
                            <el-select
                                v-if="scope.row.__editing"
                                v-model="scope.row.invoiceType"
                                placeholder="请选择"
                                clearable
                                style="width: 120px"
                            >
                                <el-option label="增普票" value="增普票" />
                                <el-option label="增专票" value="增专票" />
                            </el-select>
                            <span v-else>{{ scope.row.invoiceType ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="结算单片面积(㎡)" prop="settlePieceArea" min-width="140">
                        <template #default="scope">
                            <el-input-number
                                v-if="scope.row.__editing"
                                v-model="scope.row.settlePieceArea"
                                :min="0"
                                :step="0.00001"
                                :precision="5"
                                style="width: 140px"
                                placeholder="请输入"
                                clearable
                                @change="() => handleInlineSettleAreaChange(scope.row)"
                            />
                            <span v-else>{{ scope.row.settlePieceArea ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="加工要求" prop="processRequirement" min-width="160" show-overflow-tooltip>
                        <template #default="scope">
                            <el-input
                                v-if="scope.row.__editing"
                                v-model="scope.row.processRequirement"
                                placeholder="请输入"
                                clearable
                                style="width: 160px"
                            />
                            <span v-else>{{ scope.row.processRequirement ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip>
                        <template #default="scope">
                            <el-input
                                v-if="scope.row.__editing"
                                v-model="scope.row.remark"
                                placeholder="请输入"
                                clearable
                                style="width: 140px"
                            />
                            <span v-else>{{ scope.row.remark ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="楼层编号" prop="floorCode" min-width="140" show-overflow-tooltip>
                        <template #default="scope">
                            <el-input
                                v-if="scope.row.__editing"
                                v-model="scope.row.floorCode"
                                placeholder="请输入"
                                clearable
                                style="width: 140px"
                            />
                            <span v-else>{{ scope.row.floorCode ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column label="重箱" prop="heavyBox" min-width="100">
                        <template #default="scope">
                            <el-input
                                v-if="scope.row.__editing"
                                v-model="scope.row.heavyBox"
                                placeholder="请输入"
                                clearable
                                style="width: 110px"
                            />
                            <span v-else>{{ scope.row.heavyBox ?? "" }}</span>
                        </template>
                    </el-table-column>
                    <el-table-column fixed="right" label="操作" min-width="220" align="center" v-if="operationType !== 'view'">
                        <template #default="scope">
                            <template v-if="scope.row.__editing">
                                <el-button link type="primary" size="small" @click="saveProductInline(scope.row, scope.$index)">保存</el-button>
                                <el-button link type="danger" size="small" @click="cancelProductInline(scope.row, scope.$index)">取消</el-button>
                                <el-popover
                                    :width="420"
                                    trigger="click"
                                    :hide-after="0"
                                    v-model:visible="scope.row.__otherAmountPopoverVisible"
                                >
                                    <template #reference>
                                        <el-button
                                            link
                                            type="primary"
                                            size="small"
                                            @click="openOtherAmountInline(scope.row)"
                                        >
                                            其他金额({{ (scope.row.salesProductProcessList || []).length || 0 }})
                                        </el-button>
                                    </template>
                                    <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;">
                                        <div style="font-weight: 600; color:#303133;">
                                            其他金额
                                        </div>
                                        <el-button type="primary" plain size="small" @click="startAddOtherAmountForRow(scope.row)">
                                            新增
                                        </el-button>
                                    </div>
                                    <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0"
                                        style="display:flex; flex-direction:column; gap: 8px;"
                                    >
                                        <div
                                            v-for="(item, idx) in scope.row.salesProductProcessList"
                                            :key="String(item.id) + '_' + idx"
                                            style="display:flex; align-items:center; gap: 8px;"
                                        >
                                            <el-tag type="info" style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
                                                {{ item.processName }}
                                            </el-tag>
                                            <el-input-number
                                                v-model="item.quantity"
                                                :min="0"
                                                :step="1"
                                                :precision="0"
                                                style="width: 120px;"
                                                placeholder="数量"
                                                :disabled="operationType === 'view'"
                                                @change="handleOtherAmountQuantityChange(scope.row)"
                                            />
                                            <el-button type="danger" link size="small" @click="removeOtherAmountAtForRow(scope.row, idx)">
                                                删除
                                            </el-button>
                                        </div>
                                    </div>
                                    <div v-else style="color:#909399; font-size: 13px;">
                                        暂无其他金额
                                    </div>
                                </el-popover>
                            </template>
                            <template v-else>
                                <el-button
                                    link
                                    type="primary"
                                    size="small"
                                    :disabled="isProductShipped(scope.row)"
                                    @click="editProductInline(scope.row, scope.$index)"
                                >
                                    编辑
                                </el-button>
                                <el-popover
                                    :width="420"
                                    trigger="click"
                                    :hide-after="0"
                                    v-model:visible="scope.row.__otherAmountPopoverVisible"
                                >
                                    <template #reference>
                                        <el-button
                                            link
                                            type="primary"
                                            size="small"
                                            :disabled="isProductShipped(scope.row)"
                                            @click="openOtherAmountInline(scope.row)"
                                        >
                                            其他金额({{ (scope.row.salesProductProcessList || []).length || 0 }})
                                        </el-button>
                                    </template>
                                    <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;">
                                        <div style="font-weight: 600; color:#303133;">
                                            其他金额
                                        </div>
                                        <el-button
                                            type="primary"
                                            plain
                                            size="small"
                                            :disabled="isProductShipped(scope.row)"
                                            @click="startAddOtherAmountForRow(scope.row)"
                                        >
                                            新增
                                        </el-button>
                                    </div>
                                    <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0"
                                        style="display:flex; flex-direction:column; gap: 8px;"
                                    >
                                        <div
                                            v-for="(item, idx) in scope.row.salesProductProcessList"
                                            :key="String(item.id) + '_' + idx"
                                            style="display:flex; align-items:center; gap: 8px;"
                                        >
                                            <el-tag type="info" style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
                                                {{ item.processName }}
                                            </el-tag>
                                            <el-input-number
                                                v-model="item.quantity"
                                                :min="0"
                                                :step="1"
                                                :precision="0"
                                                style="width: 120px;"
                                                placeholder="数量"
                                                :disabled="operationType === 'view' || isProductShipped(scope.row)"
                                                @change="handleOtherAmountQuantityChange(scope.row)"
                                            />
                                            <el-button
                                                type="danger"
                                                link
                                                size="small"
                                                :disabled="isProductShipped(scope.row)"
                                                @click="removeOtherAmountAtForRow(scope.row, idx)"
                                            >
                                                删除
                                            </el-button>
                                        </div>
                                    </div>
                                    <div v-else style="color:#909399; font-size: 13px;">
                                        暂无其他金额
                                    </div>
                                </el-popover>
                            </template>
                        </template>
                    </el-table-column>
                </el-table>
@@ -506,17 +844,6 @@
                <!-- 每行三个:税率/含税单价/数量 -->
                <el-row :gutter="30">
                    <el-col :span="8">
                        <el-form-item label="税率(%):" prop="taxRate">
                            <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%">
                                <el-option label="1" value="1" />
                                <el-option label="3" value="3" />
                                <el-option label="6" value="6" />
                                <el-option label="9" value="9" />
                                <el-option label="13" value="13" />
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="8">
                        <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
                            <el-input-number
                                :step="0.01"
@@ -528,6 +855,17 @@
                                clearable
                                @change="calculateFromUnitPrice"
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="8">
                        <el-form-item label="税率(%):" prop="taxRate">
                            <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%">
                                <el-option label="1" value="1" />
                                <el-option label="3" value="3" />
                                <el-option label="6" value="6" />
                                <el-option label="9" value="9" />
                                <el-option label="13" value="13" />
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="8">
@@ -1090,6 +1428,319 @@
const { productForm, productRules } = toRefs(productFormData);
// 防止循环计算的标志
const isCalculating = ref(false);
// 产品行内编辑:只允许同时编辑一行
const editingProductRow = ref(null);
const ensureProductRowDefaults = (row) => {
    if (!row || typeof row !== "object") return;
    if (!Array.isArray(row.salesProductProcessList)) row.salesProductProcessList = [];
    if (row.__otherAmountPopoverVisible === undefined || row.__otherAmountPopoverVisible === null) row.__otherAmountPopoverVisible = false;
    if (row.width === undefined || row.width === null) row.width = 0;
    if (row.height === undefined || row.height === null) row.height = 0;
    if (row.perimeter === undefined || row.perimeter === null) row.perimeter = 0;
    if (row.actualPieceArea === undefined || row.actualPieceArea === null) row.actualPieceArea = 0;
    if (row.actualTotalArea === undefined || row.actualTotalArea === null) row.actualTotalArea = 0;
    if (row.settlePieceArea === undefined || row.settlePieceArea === null) row.settlePieceArea = 0;
    if (row.settleTotalArea === undefined || row.settleTotalArea === null) row.settleTotalArea = 0;
    if (row.processRequirement === undefined || row.processRequirement === null) row.processRequirement = "";
    if (row.remark === undefined || row.remark === null) row.remark = "";
    if (row.floorCode === undefined || row.floorCode === null) row.floorCode = "";
    if (row.invoiceType === undefined || row.invoiceType === null) row.invoiceType = "";
    if (row.taxRate === undefined || row.taxRate === null) row.taxRate = "";
    if (row.quantity === undefined || row.quantity === null) row.quantity = 0;
    if (row.taxInclusiveUnitPrice === undefined || row.taxInclusiveUnitPrice === null) row.taxInclusiveUnitPrice = 0;
    if (row.taxInclusiveTotalPrice === undefined || row.taxInclusiveTotalPrice === null) row.taxInclusiveTotalPrice = 0;
    if (row.taxExclusiveTotalPrice === undefined || row.taxExclusiveTotalPrice === null) row.taxExclusiveTotalPrice = 0;
};
const stopOtherEditingRows = () => {
    (productData.value || []).forEach((r) => {
        if (r && r.__editing) r.__editing = false;
    });
    editingProductRow.value = null;
};
const addProductInline = async () => {
    if (operationType.value === "view") return;
    // 已有行在编辑时,先取消其编辑状态,避免混乱
    stopOtherEditingRows();
    await getProductOptions();
    await fetchOtherAmountSelectOptions(true);
    const row = {
        id: null,
        __tempKey: `__temp_${Date.now()}_${Math.random().toString(16).slice(2)}`,
        __editing: true,
        __isNew: true,
        __productCategoryId: null,
        productCategory: "",
        productModelId: null,
        specificationModel: "",
        thickness: null,
        quantity: 0,
        taxInclusiveUnitPrice: 0,
        taxRate: "",
        taxInclusiveTotalPrice: 0,
        taxExclusiveTotalPrice: 0,
        invoiceType: "",
        width: 0,
        height: 0,
        perimeter: 0,
        actualPieceArea: 0,
        actualTotalArea: 0,
        settlePieceArea: 0,
        settleTotalArea: 0,
        processRequirement: "",
        remark: "",
        salesProductProcessList: [],
        processFlowConfigId: null,
        floorCode: "",
        heavyBox: "",
    };
    productData.value.push(row);
    editingProductRow.value = row;
    // 让现有的计算/其他金额逻辑复用当前行
    productForm.value = row;
};
const editProductInline = async (row, index) => {
    if (operationType.value === "view") return;
    if (!row) return;
    if (isProductShipped(row)) {
        proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑");
        return;
    }
    stopOtherEditingRows();
    await getProductOptions();
    await fetchOtherAmountSelectOptions(true);
    ensureProductRowDefaults(row);
    // 产品大类 tree-select 回显:名称 -> id
    row.__productCategoryId = findNodeIdByLabel(productOptions.value, row.productCategory);
    // 兼容后端字段命名(保持原逻辑)
    row.actualPieceArea = row?.actualPieceArea ?? row?.actual_piece_area ?? 0;
    row.actualTotalArea = row?.actualTotalArea ?? row?.actual_total_area ?? 0;
    row.settlePieceArea = row?.settlePieceArea ?? row?.settle_piece_area ?? 0;
    row.settleTotalArea = row?.settleTotalArea ?? row?.settle_total_area ?? 0;
    row.processRequirement = row?.processRequirement ?? row?.process_requirement ?? "";
    row.remark = row?.remark ?? row?.remarks ?? "";
    row.floorCode = row?.floorCode ?? row?.floor_code ?? "";
    row.processFlowConfigId = row?.processFlowConfigId ?? row?.process_flow_config_id ?? null;
    row.perimeter = row?.perimeter ?? row?.heavyBoxPerimeter ?? row?.heavyboxPerimeter ?? 0;
    row.thickness = row?.thickness;
    row.salesProductProcessList = normalizeOtherAmountsFromRow(row);
    mergeOtherAmountOptionsBySelection(row.salesProductProcessList);
    row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList);
    // 备份用于取消
    row.__backup = JSON.parse(JSON.stringify(row));
    row.__editing = true;
    editingProductRow.value = row;
    productForm.value = row;
    // 根据产品大类名称反查 tree 节点 id,并加载规格型号列表
    try {
        const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions();
        const categoryId = findNodeIdByLabel(options, row.productCategory);
        if (categoryId) {
            const models = await modelList({ id: categoryId });
            modelOptions.value = models || [];
            const currentModel = (modelOptions.value || []).find((m) => m.model === row.specificationModel);
            if (currentModel) row.productModelId = currentModel.id;
        }
    } catch (e) {
        console.error("加载产品规格型号失败", e);
    }
    // 同步计算一次
    recalcPerimeterFromWidthHeight();
    recalcAreaFromWidthHeight();
};
const validateInlineProductRow = (row) => {
    if (!row) return false;
    if (!row.productCategory) {
        proxy.$modal.msgWarning("请选择产品大类");
        return false;
    }
    if (!row.productModelId) {
        proxy.$modal.msgWarning("请选择规格型号");
        return false;
    }
    return true;
};
const saveProductInline = async (row, index) => {
    if (operationType.value === "view") return;
    if (!row) return;
    if (isProductShipped(row)) {
        proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑");
        return;
    }
    // 确保 productForm 指向当前行,以复用计算逻辑
    productForm.value = row;
    ensureProductRowDefaults(row);
    if (!validateInlineProductRow(row)) return;
    // 厚度精度处理
    if (row.thickness !== null && row.thickness !== undefined && row.thickness !== "") {
        row.thickness = Number(Number(row.thickness).toFixed(15));
    }
    // 提交前兜底计算一次(沿用原逻辑)
    recalcAreaTotals();
    // 规范化其他金额提交结构
    row.salesProductProcessList = (Array.isArray(row.salesProductProcessList) ? row.salesProductProcessList : [])
        .map((it) => ({
            id: it?.id,
            processName: it?.processName ?? "",
            unitPrice: Number(it?.unitPrice ?? 0) || 0,
            quantity: Number(it?.quantity ?? 0) || 0,
        }))
        .filter((it) => it.id !== null && it.id !== undefined && it.id !== "");
    // 规格型号:根据 productModelId 回填名称
    const model = (modelOptions.value || []).find((m) => String(m.id) === String(row.productModelId));
    if (model?.model) row.specificationModel = model.model;
    if (operationType.value === "edit") {
        // 台账已存在:走原接口保存到后端,再回拉刷新
        const payload = { ...row, salesLedgerId: currentId.value, type: 1 };
        delete payload.__backup;
        delete payload.__editing;
        delete payload.__isNew;
        delete payload.__productCategoryId;
        delete payload.__tempKey;
        await addOrUpdateSalesLedgerProduct(payload);
        proxy.$modal.msgSuccess("提交成功");
        await getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => {
            productData.value = res.productData;
        });
    } else {
        // 新增台账:仅在本地 productData 生效,最终随台账一起提交
        row.__isNew = false;
        row.__editing = false;
        delete row.__backup;
    }
    stopOtherEditingRows();
};
const cancelProductInline = (row, index) => {
    if (!row) return;
    if (row.__isNew) {
        productData.value.splice(index, 1);
    } else if (row.__backup) {
        const restored = JSON.parse(JSON.stringify(row.__backup));
        // 保留 id 与状态字段
        const keepId = row.id;
        Object.keys(row).forEach((k) => delete row[k]);
        Object.assign(row, restored);
        row.id = keepId;
        row.__editing = false;
        delete row.__backup;
    }
    stopOtherEditingRows();
};
const openOtherAmountInline = async (row) => {
    if (!row) return;
    if (operationType.value === "view") return;
    if (isProductShipped(row)) {
        proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑");
        return;
    }
    ensureProductRowDefaults(row);
    productForm.value = row;
    await fetchOtherAmountSelectOptions(true);
    mergeOtherAmountOptionsBySelection(row.salesProductProcessList);
    row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList);
    // 只做数据准备与打开浮层(新增由浮层内按钮触发)
    row.__otherAmountPopoverVisible = true;
};
const startAddOtherAmountForRow = async (row) => {
    if (!row) return;
    if (operationType.value === "view") return;
    if (isProductShipped(row)) {
        proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑");
        return;
    }
    ensureProductRowDefaults(row);
    productForm.value = row;
    await fetchOtherAmountSelectOptions(true);
    mergeOtherAmountOptionsBySelection(row.salesProductProcessList);
    row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList);
    startAddOtherAmount();
};
const removeOtherAmountAtForRow = (row, index) => {
    if (!row) return;
    if (operationType.value === "view") return;
    if (isProductShipped(row)) return;
    productForm.value = row;
    removeOtherAmountAt(index);
};
const handleOtherAmountQuantityChange = (row) => {
    if (!row) return;
    productForm.value = row;
    calculateFromUnitPrice(true);
};
const handleInlineProductCategoryChange = async (row, val) => {
    if (!row) return;
    productForm.value = row;
    // 复用原有逻辑:会写入 productCategory(名称)、重置规格/厚度并拉取型号
    await getModels(val);
    // 行内编辑时把选中的 id 记录下来,便于回显
    row.__productCategoryId = val;
};
const handleInlineProductModelChange = (row, val) => {
    if (!row) return;
    productForm.value = row;
    // 复用原有逻辑:会写入 specificationModel、厚度
    getProductModel(val);
};
const handleInlineSizeChange = (row) => {
    if (!row) return;
    productForm.value = row;
    recalcPerimeterFromWidthHeight();
    recalcAreaFromWidthHeight();
    recalcAreaTotals();
};
const handleInlineUnitPriceChange = (row) => {
    if (!row) return;
    productForm.value = row;
    calculateFromUnitPrice();
    recalcAreaTotals();
};
const handleInlineQuantityChange = (row) => {
    if (!row) return;
    productForm.value = row;
    calculateFromQuantity();
    recalcAreaTotals();
};
const handleInlineTaxRateChange = (row) => {
    if (!row) return;
    productForm.value = row;
    calculateFromTaxRate();
};
const handleInlineSettleAreaChange = (row) => {
    if (!row) return;
    productForm.value = row;
    recalcAreaTotals();
    calculateFromUnitPrice(true);
};
const upload = reactive({
    // 上传的地址
    url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
@@ -1844,8 +2495,19 @@
    proxy.$refs["formRef"].validate((valid) => {
        if (valid) {
            console.log('productData.value--', productData.value)
            // 行内编辑未保存时不允许提交,避免脏数据/临时字段进入后端
            const hasEditingRow = (productData.value || []).some((r) => r && r.__editing);
            if (hasEditingRow) {
                proxy.$modal.msgWarning("产品信息存在未保存的编辑行,请先保存或取消");
                return;
            }
            if (productData.value !== null && productData.value.length > 0) {
                form.value.productData = proxy.HaveJson(productData.value);
                const cleanedProducts = (productData.value || []).map((p) => {
                    if (!p || typeof p !== "object") return p;
                    const { __editing, __isNew, __backup, __productCategoryId, __tempKey, __otherAmountPopoverVisible, ...rest } = p;
                    return rest;
                });
                form.value.productData = proxy.HaveJson(cleanedProducts);
            } else {
                proxy.$modal.msgWarning("请添加产品信息");
                return;
@@ -2016,9 +2678,18 @@
    
    if (operationType.value === "add") {
        productSelectedRows.value.forEach((selectedRow) => {
            const index = productData.value.findIndex(
                (product) => product.id === selectedRow.id
            );
            const index = productData.value.findIndex((product) => {
                if (!product || !selectedRow) return false;
                // 新增行 id 为空时,用临时 key 定位
                if (product.id != null && selectedRow.id != null) {
                    return String(product.id) === String(selectedRow.id);
                }
                return (
                    product.__tempKey &&
                    selectedRow.__tempKey &&
                    String(product.__tempKey) === String(selectedRow.__tempKey)
                );
            });
            if (index !== -1) {
                productData.value.splice(index, 1);
            }