<template>
  <div class="cart-page" v-loading="loading">
    <h1 class="page-title">购物车</h1>
    
    <div v-if="loading" class="loading">
      <i class="el-icon-loading" aria-label="加载中" role="img"></i>
      加载中...
    </div>

    <div v-else-if="isCartEmpty" class="empty-cart">
      <div class="empty-icon">🛒</div>
      <h2>购物车是空的</h2>
      <p>快去添加一些商品吧！</p>
      <router-link to="/productList" class="shop-now-btn">
        去购物
      </router-link>
    </div>

    <div v-else class="cart-content">
      <p style="color:#9E44F1;font-size: 14px;margin-bottom: 10px;">注意：各店铺支持多种支付方式，请选择店铺支付方式后提交订单结算</p>
      <!-- 外层：店铺列表 -->
      <el-table
        ref="shopTable"
        :data="shops"
        border
        style="width: 100%"
        :row-key="'id'"
        :expand-row-keys="expandedShopKeys"
        :header-cell-style="{ textAlign: 'left' }"
        :cell-style="{ textAlign: 'left' }"
        @expand-change="handleGuardExpand"
      >
        <el-table-column type="expand" width="46" :expandable="() => false">
          <template #default="shopScope">
            <!-- 机器列表（直接挂在店铺下） -->
            <el-table :data="shopScope.row.productMachineDtoList || []" size="small" border style="width: 100%"
              :row-key="'id'" reserve-selection
              :ref="'innerTable-' + shopScope.row.id"
              @selection-change="sels => handleShopInnerSelectionChange(shopScope.row, sels)"
              :row-class-name="getMachineRowClassName"
              :header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
              <el-table-column type="selection" width="46" :selectable="row => isRowSelectableByShop(shopScope.row, row)" />
              <el-table-column prop="name" label="商品名称" />
              <el-table-column prop="type" label="矿机种类" width="80">
                <template #default="scope">
                  <el-tag
                    disable-transitions
                    :class="['type-tag', typeClass(scope.row.type)]"
                  >
                    {{ formatMachineType(scope.row.type) }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="id" label="机器ID" width="80" />
              <el-table-column prop="algorithm" label="最大收益算法"  />
            
            
              <!-- <el-table-column prop="powerDissipation" label="功耗(kw/h)">
                <template #default="scope">
                  <span class="num-strong">
                    <el-tooltip
                      v-if="formatNum6(scope.row.powerDissipation).truncated"
                      :content="formatNum6(scope.row.powerDissipation).full"
                      placement="top"
                    >
                      <span>
                        {{ formatNum6(scope.row.powerDissipation).text }}
                        <i class="el-icon-more amount-more"></i>
                      </span>
                    </el-tooltip>
                    <span v-else>{{ formatNum6(scope.row.powerDissipation).text }}</span>
                  </span>
                </template>
              </el-table-column>
              <el-table-column prop="theoryPower" label="理论算力">
                <template #default="scope">
                  <span class="num-strong">
                    <el-tooltip
                      v-if="formatNum6(scope.row.theoryPower).truncated"
                      :content="formatNum6(scope.row.theoryPower).full"
                      placement="top"
                    >
                      <span>
                        {{ formatNum6(scope.row.theoryPower).text }}
                        <i class="el-icon-more amount-more"></i>
                      </span>
                    </el-tooltip>
                    <span v-else>{{ formatNum6(scope.row.theoryPower).text }}</span>
                  </span>
                  <span v-show="scope.row.theoryPower"> {{ scope.row.unit }} </span>
                </template>
              </el-table-column> -->
              
              <!-- <el-table-column prop="theoryUsdtIncome" label="单机理论收入(每日/USDT)">
                <template #default="scope">
                  <span class="num-strong">
                    <el-tooltip
                      v-if="formatNum6(scope.row.theoryUsdtIncome).truncated"
                      :content="formatNum6(scope.row.theoryUsdtIncome).full"
                      placement="top"
                    >
                      <span>
                        {{ formatNum6(scope.row.theoryUsdtIncome).text }}
                        <i class="el-icon-more amount-more"></i>
                      </span>
                    </el-tooltip>
                    <span v-else>{{ formatNum6(scope.row.theoryUsdtIncome).text }}</span>
                  </span>
                </template>
              </el-table-column> -->
              <!-- <el-table-column prop="state" label="状态" min-width="100" >
                <template #default="scope">
                  <el-tag :type="scope.row.state === 1 ? 'info' : 'success'">
                    {{ scope.row.state === 1 ? '下架' : '上架' }}
                  </el-tag>
                </template>
              </el-table-column> -->
     
           
          
       
              <el-table-column label="可售数量" min-width="40">
                <template #default="scope">
                  {{ Number(scope.row.canSaleNumbers || 0) }}
                </template>
              </el-table-column>
              <el-table-column label="购买数量" min-width="80">
                <template #default="scope">
                  <el-input-number
                    v-model="scope.row.numbers"
                    :min="1"
                    :max="getRowMaxPurchaseForCart(scope.row)"
                    :precision="0"
                    :step="1"
                    size="mini"
                    :controls="false"
                    :disabled="Number(scope.row.type) === 1"
                    @change="val => handleNumbersChange(scope.row, val)"
                  />
                </template>
              </el-table-column>
              <el-table-column label="最大可租(天)" min-width="50">
                <template #default="scope">{{ scope.row.maxLeaseDays != null ? scope.row.maxLeaseDays : '' }}</template>
              </el-table-column>
              <el-table-column label="租赁天数" width="145">
                <template #default="scope">
                  <el-input-number 
                    v-model="scope.row.leaseTime" 
                    :min="1" 
                    :max="getRowMaxLeaseDaysLocal(scope.row)" 
                    :precision="0"
                    :step="1"
                    size="mini" 
                    controls-position="right"
                    @change="handleLeaseTimeChange(scope.row)"
                    @input="handleLeaseTimeInput(scope.row, $event)"
                  />
                </template>
              </el-table-column>
              <el-table-column prop="price" width="120">
                <template #header>单价（{{ getSelectedCoinSymbolForShop(shopScope.row) || 'USDT' }}）</template>
                <template #default="scope">
                  <template v-if="getMachineUnitPriceBySelection(shopScope.row, scope.row) != null">
                    <span class="price-strong">
                      <el-tooltip
                        v-if="formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row), getSelectedCoinSymbolForShop(shopScope.row)).truncated"
                        :content="formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row), getSelectedCoinSymbolForShop(shopScope.row)).full"
                        placement="top"
                      >
                        <span>
                          {{ formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row), getSelectedCoinSymbolForShop(shopScope.row)).text }}
                          <i class="el-icon-more amount-more"></i>
                        </span>
                      </el-tooltip>
                      <span v-else>
                        {{ formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row), getSelectedCoinSymbolForShop(shopScope.row)).text }}
                      </span>
                    </span>
                  </template>
                  <template v-else>-</template>
                </template>
              </el-table-column>
                  <el-table-column label="机器状态" width="110">
                <template #default="scope">
                  <el-tag :type="(Number(scope.row.del) === 1 || Number(scope.row.state) === 1) ? 'info' : 'success'">
                    {{ (Number(scope.row.del) === 1 || Number(scope.row.state) === 1) ? '下架' : '上架' }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column min-width="120">
                <template #header>机器总价（{{ getSelectedCoinSymbolForShop(shopScope.row) || 'USDT' }}）</template>
                <template #default="scope">
                  <template v-if="getMachineUnitPriceBySelection(shopScope.row, scope.row) != null">
                    <span class="price-strong">
                      <el-tooltip
                        v-if="formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row) * Number(scope.row.leaseTime || 1), getSelectedCoinSymbolForShop(shopScope.row)).truncated"
                        :content="formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row) * Number(scope.row.leaseTime || 1), getSelectedCoinSymbolForShop(shopScope.row)).full"
                        placement="top"
                      >
                        <span>
                          {{ formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row) * Number(scope.row.leaseTime || 1), getSelectedCoinSymbolForShop(shopScope.row)).text }}
                          <i class="el-icon-more amount-more"></i>
                        </span>
                      </el-tooltip>
                      <span v-else>
                        {{ formatAmount(getMachineUnitPriceBySelection(shopScope.row, scope.row) * Number(scope.row.leaseTime || 1), getSelectedCoinSymbolForShop(shopScope.row)).text }}
                      </span>
                    </span>
                  </template>
                  <template v-else>-</template>
                </template>
              </el-table-column>
            </el-table>
          </template>
        </el-table-column>
        <el-table-column prop="name" label="店铺名称"  />
        <el-table-column prop="totalMachine" label="机器总数" />
 
        <el-table-column prop="totalPrice">
          <template #header>
            总价（{{ getSelectedCoinSymbolForShopHeader() }}）
          </template>
          <template #default="scope">
            <span class="price-strong">
              <el-tooltip
                v-if="formatAmount(displayShopTotalBySelection(scope.row), getSelectedCoinSymbolForShop(scope.row)).truncated"
                :content="formatAmount(displayShopTotalBySelection(scope.row), getSelectedCoinSymbolForShop(scope.row)).full"
                placement="top"
              >
                <span>
                  {{ formatAmount(displayShopTotalBySelection(scope.row), getSelectedCoinSymbolForShop(scope.row)).text }}
                  <i class="el-icon-more amount-more"></i>
                </span>
              </el-tooltip>
              <span v-else>
                {{ formatAmount(displayShopTotalBySelection(scope.row), getSelectedCoinSymbolForShop(scope.row)).text }}
              </span>
            </span>
          </template>
        </el-table-column>
  

        <el-table-column label="支付方式">
          <template #default="scope">
            <el-select
              v-model="paySelectionMap[scope.row.id]"
              placeholder="请选择"
              size="mini"
              style="min-width: 180px;"
              @change="val => handleShopPayChange(scope.row, val)"
            >
              <template #prefix>
                <img
                  v-if="getSelectedPayIcon(scope.row)"
                  :src="getSelectedPayIcon(scope.row)"
                  :alt="getSelectedCoinSymbolForShop(scope.row)"
                  style="width:16px;height:16px;margin-right:6px;border-radius:3px;"
                />
              </template>
              <el-option
                v-for="(opt, idx) in getShopPayOptions(scope.row)"
                :key="idx"
                :value="opt.value"
                :label="opt.label"
              >
                <div style="display:flex;align-items:center;gap:8px;">
                  <img :src="opt.icon" :alt="opt.label" style="width:18px;height:18px;border-radius:3px;" />
                  <span>{{ opt.label }}</span>
                </div>
              </el-option>
            </el-select>
          </template>
        </el-table-column>
        <!-- 操作列移除单店铺结算按钮，统一使用底部“结算选中机器” -->
      </el-table>
      <div class="summary-actions" style="margin-top:16px;display:flex;gap:12px;justify-content:flex-end;">
        <div class="summary-inline" style="color:#666;">
          已选机器：<b>{{ selectedMachineCount }}</b> 台
          <span style="margin-left:12px;">金额合计：</span>
          <template v-if="Object.keys(selectedTotalByCoin || {}).length">
            <span v-for="(amt, coin) in selectedTotalByCoin" :key="coin" style="margin-left:8px;">
              <span class="price-strong">
                <el-tooltip
                  v-if="formatAmount(amt, coin).truncated"
                  :content="formatAmount(amt, coin).full"
                  placement="top"
                >
                  <span>
                    {{ coin }}: {{ formatAmount(amt, coin).text }}
                    <i class="el-icon-more amount-more"></i>
                  </span>
                </el-tooltip>
                <span v-else>{{ coin }}: {{ formatAmount(amt, coin).text }}</span>
              </span>
            </span>
          </template>
          <template v-else>
            <span class="price-strong">-</span>
          </template>
        </div>
        <div class="actions-inline" style="display:flex;gap:12px;">
          <el-button type="danger" :disabled="!selectedMachineCount" @click="handleRemoveSelectedMachines">删除所选机器</el-button>
          <el-button type="warning" plain :loading="clearOffLoading" @click="handleClearOffShelf">清除已下架商品</el-button>
          <el-button type="primary" :disabled="!selectedMachineCount" @click="handleCheckoutSelected">结算选中机器</el-button>
        </div>
      </div>

      <el-dialog :visible.sync="confirmDialog.visible" width="80vw"  :close-on-click-modal="false" :title="`确认结算（共 ${confirmDialog.count} 台机器）`">
        <div>
          <div v-for="grp in confirmDialog.shops" :key="grp.shopId" style="margin-bottom: 18px;">
            <div style="display:flex;align-items:center;justify-content:space-between;margin:8px 0 6px 0;">
              <div style="font-weight:600;color:#2c3e50;">
                店铺：{{ grp.shopName || grp.shopId }}
                <span style="margin-left:12px;color:#666;font-weight:400;">支付方式：{{ grp.payLabel }}</span>
              </div>
              <div>
                <template v-if="grp.coinSymbol">
                  <span v-if="grp.enough"
                        style="color:#16a34a;font-weight:600;">
                    已满足起付额
                    {{ formatAmount(grp.deductibleAmount || 0, grp.coinSymbol).text }}
                  </span>
                  <span v-else
                        style="color:#ef4444;font-weight:600;">
                    金额不足最低起付额
                    {{ formatAmount(grp.deductibleAmount || 0, grp.coinSymbol).text }}
                    ，收取手续费
                    {{ formatAmount(grp.fee || 0, grp.coinSymbol).text }}
                  </span>
                </template>
              </div>
            </div>
            <el-table :data="grp.items" max-height="260" border stripe
              :header-cell-style="{ textAlign: 'left' }" :cell-style="{ textAlign: 'left' }">
              <el-table-column label="矿机种类" min-width="120">
                <template #default="scope">
                  <el-tag
                    disable-transitions
                    :class="['type-tag', typeClass(scope.row.type)]"
                    style="font-size: 14px;"
                  >
                    {{ formatMachineType(scope.row.type) }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="id" label="机器ID" min-width="160" />
              <el-table-column prop="algorithm" label="最大收益算法" min-width="140" />
              <el-table-column prop="unitPrice" min-width="140">
                <template #header>单价（{{ grp.coinSymbol || 'USDT' }}）</template>
                <template #default="scope">
                  <span class="price-strong">
                    <el-tooltip
                      v-if="formatAmount(scope.row.unitPrice, grp.coinSymbol).truncated"
                      :content="formatAmount(scope.row.unitPrice, grp.coinSymbol).full"
                      placement="top"
                    >
                      <span>
                        {{ formatAmount(scope.row.unitPrice, grp.coinSymbol).text }}
                        <i class="el-icon-more amount-more"></i>
                      </span>
                    </el-tooltip>
                    <span v-else>{{ formatAmount(scope.row.unitPrice, grp.coinSymbol).text }}</span>
                  </span>
                </template>
              </el-table-column>
              <el-table-column prop="leaseTime" label="租赁天数" min-width="120" />
              <el-table-column prop="numbers" label="购买数量" min-width="120" />
              <el-table-column prop="subtotal" min-width="140">
                <template #header>小计（{{ grp.coinSymbol || 'USDT' }}）</template>
                <template #default="scope">
                  <span class="price-strong">
                    <el-tooltip
                      v-if="formatAmount(scope.row.subtotal, grp.coinSymbol).truncated"
                      :content="formatAmount(scope.row.subtotal, grp.coinSymbol).full"
                      placement="top"
                    >
                      <span>
                        {{ formatAmount(scope.row.subtotal, grp.coinSymbol).text }}
                        <i class="el-icon-more amount-more"></i>
                      </span>
                    </el-tooltip>
                    <span v-else>{{ formatAmount(scope.row.subtotal, grp.coinSymbol).text }}</span>
                  </span>
                </template>
              </el-table-column>
            </el-table>
          </div>
          <div style="margin-top:12px;text-align:right;">
            <span style="margin-right:8px;">总金额：</span>
            <template v-if="Object.keys(confirmDialog.totalsByCoin || {}).length">
              <span v-for="(amt, coin) in confirmDialog.totalsByCoin" :key="coin" style="margin-left:12px;">
                {{ coin }}：
                <span class="price-strong">
                  <el-tooltip
                    v-if="formatAmount(amt, coin).truncated"
                    :content="formatAmount(amt, coin).full"
                    placement="top"
                  >
                    <span>
                      {{ formatAmount(amt, coin).text }}
                      <i class="el-icon-more amount-more"></i>
                    </span>
                  </el-tooltip>
                  <span v-else>{{ formatAmount(amt, coin).text }}</span>
                </span>
              </span>
            </template>
            <template v-else>-</template>
          </div>
        </div>
        <template #footer>
          <el-button @click="handleConfirmDialogPrev">上一步</el-button>
          <el-button @click="confirmDialog.visible=false">取消</el-button>
          <el-button type="primary" @click="confirmPay">确认结算</el-button>
        </template>
      </el-dialog>

     

      <!-- 购物须知（测试版） - 必须勾选并等待5秒后才可关闭 -->
      <el-dialog :visible.sync="noticeDialog.visible" width="60vw" title="下单须知" :show-close="false" :close-on-click-modal="false" :close-on-press-escape="false">
        <div class="notice-content">
          <p class="notice-title">尊敬的客户，感谢您选择我们的服务。在您下单前，请务必仔细阅读并完全理解以下须知条款。一旦您点击" 同意并下单"或完成支付流程，即视为您已充分阅读、理解并同意接受本须知的全部内容约束。</p>
          <ol class="notice-list">
            <li><b>预授权冻结:</b>为保障订单顺利执行，在下单成功后，系统将立即对您数字钱包或账户中与订单全款总额等值的资金进行预授权冻结。此操作并非即时划转，而是为确保您有足够的资金用于每日支付。</li>
            <li><b>每日结算支付：</b>本服务采用"按日结算"模式。冻结的资金将根据租赁协议约定的每日费用，每日自动划转相应的金额给卖家。划转操作通常在每个UTC日结束时自动执行。</li>
            <li><b>资金解冻：</b>当租赁服务到期或因其他原因终止后，系统中剩余的、未被划转的冻结资金将立即解除冻结，并返还至您的可用余额中。</li>
            <li><b>订单生效：</b> 您的订单在支付流程完成且资金成功冻结后立即生效。系统将开始为您配置相应的矿机或算力资源。</li>
            <li><b>不可取消政策：</b> 鉴于算力服务一经提供即无法退回的特性，所有订单一旦生效，即不可取消、不可退款、不可转让。您无法在租赁期内单方面中止服务或要求退还已冻结及已支付的费用。</li>
            <li><b>免责声明：</b>因不可抗力（如自然灾害、政策变动等）导致订单延迟或无法履行，我们不承担相应责任。</li>
            <li><b>算力波动：</b>您所租赁的算力产生的收益取决于区块链网络难度、全球总算力、币价波动、矿池运气等多种外部因素。我们仅提供稳定的算力输出，不对您的最终收益做出任何承诺或保证。</li>
          </ol>
          <p class="notice-title">再次提醒：数字资产挖矿存在较高市场风险，收益波动巨大，过去业绩不代表未来表现。请根据自身的风险承受能力谨慎决策。您下单的行为即代表您已充分了解并自愿承担所有相关风险。</p>
          <div class="notice-ack">
            <el-checkbox style="color: #e74c3c;" v-model="noticeDialog.checked">我已阅读并同意上述注意事项</el-checkbox>
          </div>
        </div>
        <template #footer>
          <el-button type="primary" :disabled="noticeDialog.countdown > 0" @click="handleNoticeAcknowledge">
            同意并下单{{ noticeDialog.countdown > 0 ? `（${noticeDialog.countdown}s）` : '' }}
          </el-button>
        </template>
      </el-dialog>

      <!-- 配置弹窗：币种/算法二级选择 + 已选机器列表 -->
      <el-dialog :visible.sync="configDialog.visible" width="80vw" title="矿机配置"
                 :close-on-click-modal="false" :close-on-press-escape="false">
        <div>
          <!-- 未配置的机器配置区域 -->
          <div v-if="configDialog.selectedMachines && configDialog.selectedMachines.length > 0">
            <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
              <div style="font-weight:600;color:#2c3e50;">选择币种/算法</div>
              <el-cascader
                v-model="configDialog.coinAlgoValue"
                :options="configDialog.coinAlgoOptions"
                :props="{ checkStrictly: false }"
                clearable
                placeholder="请选择币种与算法"
                style="min-width: 320px;"
                size="small"
                @change="handleCoinAlgoChange"
              />
              <div style="font-weight:600;color:#2c3e50;white-space: nowrap;">选择矿池/模型</div>
              <el-cascader
                v-model="configDialog.poolValue"
                :options="configDialog.poolOptions"
                :props="{ checkStrictly: false }"
                clearable
                placeholder="请选择矿池与模型"
                style="width: 240px;"
                size="small"
                @change="handlePoolValueChange"
                :disabled="!configDialog.coinAlgoValue || configDialog.coinAlgoValue.length !== 2"
              />
            </div>
            <!-- 机器列表 -->
            <div v-if="unconfiguredMachinesList.length > 0">
              <div style="font-weight:600;color:#2c3e50;margin-bottom:8px;font-size:14px;">
                <template v-if="!configDialog.coinAlgoValue || configDialog.coinAlgoValue.length !== 2">
                  待配置机器（共 {{ unconfiguredMachinesList.length }} 台）
                </template>
                <template v-else>
                  支持该币种/算法的机器（共 {{ unconfiguredMachinesList.length }} 台）
                </template>
              </div>
              <el-table 
                        :data="unconfiguredMachinesList" 
                        max-height="300" 
                        border 
                        stripe
                        ref="configTable"
                        row-key="id"
                        reserve-selection
                        @selection-change="handleConfigSelectionChange"
                        :header-cell-style="{ textAlign: 'left', fontSize: '14px' }" 
                        :cell-style="{ textAlign: 'left', fontSize: '14px' }">
                <el-table-column type="selection" width="46" />
                <el-table-column label="矿机种类" width="120">
                  <template #default="scope">
                    <el-tag
                      disable-transitions
                      :class="['type-tag', typeClass(scope.row.type)]"
                      style="font-size: 14px;"
                    >
                      {{ formatMachineType(scope.row.type) }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="id" label="机器ID" width="120" />
                <el-table-column prop="algorithm" label="最大收益算法" min-width="140" />
                <!-- 矿工号列始终显示（只要选择了矿池/模型） -->
                <el-table-column 
                  v-if="getCurrentWalletMining() !== null" 
                  label="矿工号" 
                  min-width="160">
                  <template #default="scope">
                    <el-input
                      v-model="scope.row._workerId"
                      placeholder="矿工号"
                      size="mini"
                      clearable
                      maxlength="40"
                      @input="val => handleMachineWorkerIdInput(scope.row, val)"
                    />
                  </template>
                </el-table-column>
              </el-table>
            </div>
            <!-- 统一输入区域：根据 walletMining 显示钱包地址或挖矿账户 -->
            <div v-if="getCurrentWalletMining() !== null" style="margin-top: 16px;">
              <!-- 钱包地址输入（当 walletMining 为 true 时显示） -->
              <div v-if="getCurrentWalletMining() === true" style="display: flex; align-items: center; gap: 12px;">
                <label style="font-weight: 600; color: #2c3e50; min-width: 100px;">钱包地址：</label>
                <el-input
                  v-model="configDialog.walletAddress"
                  placeholder="请输入钱包地址"
                  clearable
                  maxlength="95"
                  style="flex: 1; max-width: 500px;"
                  @input="handleWalletAddressInput"
                />
              </div>
              <!-- 挖矿账户输入（当 walletMining 为 false 时显示） -->
              <div v-if="getCurrentWalletMining() === false" style="display: flex; align-items: center; gap: 12px;">
                <label style="font-weight: 600; color: #2c3e50; min-width: 100px;">挖矿账户：</label>
                <el-input
                  v-model="configDialog.miningAccount"
                  placeholder="请输入挖矿账户"
                  clearable
                  maxlength="40"
                  style="flex: 1; max-width: 500px;"
                  @input="handleMiningAccountInput"
                />
              </div>
            </div>
            <div v-else-if="configDialog.coinAlgoValue && configDialog.coinAlgoValue.length === 2 && getCurrentWalletMining() === null" style="margin-top: 16px; color: #909399; font-size: 14px;">
              请先选择矿池/模型
            </div>
            <div v-else-if="configDialog.coinAlgoValue && configDialog.coinAlgoValue.length === 2 && unconfiguredMachinesList.length === 0" style="margin-bottom:12px;color:#909399;font-size:14px;">
              当前选择的币种/算法下没有可配置的机器
            </div>
            <!-- 不支持的机器列表 -->
            <div v-if="unsupportedMachinesList.length > 0" style="margin-top: 16px;">
              <div style="font-weight:600;color:#f56c6c;margin-bottom:8px;font-size:14px;">
                不支持该币种/算法的机器（共 {{ unsupportedMachinesList.length }} 台）
              </div>
              <el-table 
                :data="unsupportedMachinesList" 
                max-height="200" 
                border 
                stripe
                :header-cell-style="{ textAlign: 'left', fontSize: '14px' }" 
                :cell-style="{ textAlign: 'left', fontSize: '14px' }">
                <el-table-column label="矿机种类" width="120">
                  <template #default="scope">
                    <el-tag
                      disable-transitions
                      :class="['type-tag', typeClass(scope.row.type)]"
                      style="font-size: 14px;"
                    >
                      {{ formatMachineType(scope.row.type) }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="id" label="机器ID" width="120" />
                <el-table-column prop="algorithm" label="最大收益算法" min-width="140" />
              </el-table>
            </div>
          </div>
          
          <!-- 已配置的机器表格 -->
          <div v-if="configDialog.configuredMachines.length > 0" style="margin-top: 24px;">
            <div style="font-weight:600;color:#2c3e50;margin-bottom:12px;font-size:16px;">已配置机器</div>
            <el-table :data="configDialog.configuredMachines" max-height="300" border stripe
                      :header-cell-style="{ textAlign: 'left', fontSize: '14px' }" 
                      :cell-style="{ textAlign: 'left', fontSize: '14px' }">
              <el-table-column label="矿机种类" width="120">
                <template #default="scope">
                  <el-tag
                    disable-transitions
                    :class="['type-tag', typeClass(scope.row.type)]"
                    style="font-size: 14px;"
                  >
                    {{ formatMachineType(scope.row.type) }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="id" label="机器ID" width="120" />
              <el-table-column prop="coin" label="币种" width="120" />
              <el-table-column prop="algorithm" label="算法" min-width="140" />
              <el-table-column prop="poolName" label="矿池" min-width="140" />
              <el-table-column prop="modelName" label="模型" min-width="140" />
              <el-table-column label="钱包地址" min-width="200">
                <template #default="scope">
                  <el-tooltip :content="scope.row.walletAddress" placement="top" v-if="scope.row.walletAddress">
                    <span class="mono-ellipsis" style="font-family: monospace;">{{ formatWalletAddress(scope.row.walletAddress) }}</span>
                  </el-tooltip>
                  <span v-else>-</span>
                </template>
              </el-table-column>
              <el-table-column 
                v-if="hasConfiguredMiningAccount" 
                prop="miningAccount" 
                label="挖矿账户" 
                min-width="140" />
              <el-table-column 
                v-if="hasConfiguredWorkerId" 
                prop="workerId" 
                label="矿工号" 
                min-width="140" />
            </el-table>
          </div>
        </div>
        <template #footer>
          <el-button @click="handleConfigPrev">上一步</el-button>
          <el-button @click="handleConfigCancel">取消</el-button>
          <el-button 
            type="primary" 
            @click="handleConfigConfirm"
          >
            {{ unconfiguredMachinesList.length > 0 ? '确认配置' : '下一步' }}
          </el-button>
        </template>
      </el-dialog>

      <!-- 谷歌验证码输入弹窗 -->
      <el-dialog :visible.sync="googleCodeDialog.visible" width="480px" title="安全验证" :show-close="false" :close-on-click-modal="false" :close-on-press-escape="false">
        <div class="google-code-content">
          <div class="verification-icon">
            <i class="el-icon-lock" style="font-size: 48px; color: #409EFF;"></i>
          </div>
          <div class="verification-title">
            <h3>请输入谷歌验证码</h3>
            <p class="verification-desc">为了保障您的账户安全，请输入您的谷歌验证器中的6位验证码</p>
          </div>
          <div class="code-input-wrapper">
            <el-input
              v-model="googleCodeDialog.code"
              placeholder="请输入6位验证码"
              maxlength="6"
              size="large"
              @keyup.enter.native="handleGoogleCodeSubmit"
              @input="handleGoogleCodeInput"
              ref="googleCodeInput"
              class="code-input"
            >
              <template #prepend>
                <i class="el-icon-key"></i>
              </template>
            </el-input>
          </div>
          <div class="code-error" v-if="googleCodeDialog.error">
            <i class="el-icon-warning"></i>
            <span>{{ googleCodeDialog.error }}</span>
          </div>
        </div>
        <template #footer>
          <div class="dialog-footer">
            <el-button @click="handleGoogleCodePrev">上一步</el-button>
            <el-button @click="handleGoogleCodeCancel">取消</el-button>
            <el-button 
              type="primary" 
              :loading="googleCodeDialog.loading"
              :disabled="!isGoogleCodeValid"
              @click="handleGoogleCodeSubmit"
            >
              {{ googleCodeDialog.loading ? '验证中...' : '确认验证' }}
            </el-button>
          </div>
        </template>
      </el-dialog>
    </div>
    <!-- 结算成功提示弹窗（全局，避免被父级条件渲染影响） -->
    <el-dialog :visible.sync="settlementSuccessfulVisible" width="480px" append-to-body :close-on-click-modal="false" :close-on-press-escape="false" @close="handleCloseSuccessDialog">
      <div style="text-align:center; padding: 20px 0;">
        <div style="font-size: 48px; color: #52c41a; margin-bottom: 16px;">✓</div>
        <div style="font-size: 18px; color: #333; margin-bottom: 12px;">请求结算处理成功</div>
        <div style="color: #666; line-height: 1.6;">
          请在订单列表页面查看结算状态<br>
          结算成功会自动更新钱包余额
        </div>
      </div>
      <template #footer>
        <el-button type="primary" @click="handleCloseSuccessDialog">已知晓</el-button>
      </template>
    </el-dialog>
  </div>
  
</template>

<script>
import { getGoodsList, deleteBatchGoods as apiDeleteBatchGoods ,deleteBatchGoodsForIsDeleteV2,deleteBatchGoodsV2,deleteBatchGoodsForIsDelete,getGoodsListV2} from '../../api/shoppingCart'
import { addOrders,cancelOrder,getOrdersByIds,getOrdersByStatus , getMachineSupportPool,getChainAndListForSeller,getCoinPrice,getMachineSupportCoinAndAlgorithm, addOrdersV2} from '../../api/order'
import { truncateAmountByCoin, truncateTo6 } from '../../utils/amount'
import { rsaEncrypt, rsaEncryptSync } from '../../utils/rsaEncrypt'

export default {
  name: 'Cart',
  data() {
    return {
      loading: false,
      shops: [], // 店铺数组，每个店铺下有 shoppingCartInfoDtoList
      groups: [], // 兼容旧结构，保留
      selectedGroups: [],
      // 新结构：按店铺选择机器 { shopId: Set(machineId) }
      selectedMachinesMap: {},
      confirmDialog: { visible: false, shops: [], count: 0, totalsByCoin: {} },
      expandedGroupKeys: [],
      expandedShopKeys: [],
      creatingOrder: false,
      successDialog: { visible: false },
      // 注意事项弹窗
      noticeDialog: { visible: false, checked: false, countdown: 5 },
      noticeTimer: null,
      // 待结算的店铺信息
      pendingCheckoutShop: null,
      // 多店铺统一结算的临时选择
      pendingCheckoutAll: null,
      // 谷歌验证码弹窗
      googleCodeDialog: { 
        visible: false, 
        code: '', 
        error: '', 
        loading: false 
      },
      // 币种/算法配置弹窗
      machineSupportData: [], // fetchGetMachineSupportCoinAndAlgorithm 返回的原始数据
      configDialog: {
        visible: false,
        coin: '',
        algorithm: '',
        coinOptions: [],
        algorithmOptions: [],
        coinAlgoOptions: [],
        coinAlgoValue: [],
        selectedMachines: [], // 展示用机器行（来自当前待结算选择）
        // 选择矿池/模型（二级下拉）
        poolOptions: [],
        poolValue: [], // [poolId, coinConfigId]
        poolRawData: [], // 保存 getMachineSupportPool 返回的原始数据，用于获取 walletMining
        // 勾选的机器（配置表格）
        selectedMachineIds: [],
        // 输入项：钱包地址、挖矿账户、矿工号
        walletAddress: '',
        miningAccount: '',
        workerId: '',
        // 已配置的机器列表：{ id, coin, algorithm, poolName, modelName, walletAddress, miningAccount, workerId, type, algorithm: originalAlgorithm }
        configuredMachines: []
      },
      // 支付选择
      options: [],
      payDialog: { visible: false, value: [], loading: false },
      selectedChain: '',
      selectedCoin: '',
      selectedPrice: 0,
      // 每个店铺的支付方式选择：map<shopId, 'chain|coin'>
      paySelectionMap: {}
      ,clearOffLoading: false,
      settlementSuccessfulVisible: false,
      paramsV2:{
        pageNum: 1,
        pageSize: 20,
       
      }
    }
  },
  computed: {
    isAllSelected() {
      return this.selectedGroups.length && this.selectedGroups.length === this.groups.length
    },
    isCartEmpty() {
      const hasShops = Array.isArray(this.shops) && this.shops.length > 0
      const hasGroups = Array.isArray(this.groups) && this.groups.length > 0
      return !hasShops && !hasGroups
    },
    selectedMachineCount() {
      return Object.values(this.selectedMachinesMap).reduce((s, set) => s + (set ? set.size : 0), 0)
    },
    selectedTotal() {
      // 保留用于兼容，返回所有币种的总和（USDT）
      const byCoin = this.selectedTotalByCoin
      let total = 0
      Object.values(byCoin || {}).forEach(amt => {
        total += Number(amt || 0)
      })
      return total
    },
    // 按币种分组计算已选机器的总金额
    selectedTotalByCoin() {
      const totalsByCoin = new Map()
      const shops = Array.isArray(this.shops) ? this.shops : []
      shops.forEach(shop => {
        const set = this.selectedMachinesMap[shop.id]
        if (!set || !set.size) return
        // 获取该店铺的支付方式
        const key = this.paySelectionMap[shop.id] || ''
        const [chain, coin] = String(key).split('|')
        const coinSymbol = this.toUpperText(coin || '')
        if (!coinSymbol) return
        const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
        list.forEach(m => {
          if (set.has(m.id)) {
            // 使用当前支付方式对应的单价
            const unitPrice = this.getMachineUnitPriceBySelection(shop, m)
            if (unitPrice != null) {
              const days = Math.max(1, Math.floor(Number(m.leaseTime || 1)))
              const subtotal = Number(unitPrice || 0) * days
              const prev = totalsByCoin.get(coinSymbol) || 0
              totalsByCoin.set(coinSymbol, prev + subtotal)
            }
          }
        })
      })
      // 转为对象格式
      const result = {}
      totalsByCoin.forEach((val, coin) => {
        result[coin] = val
      })
      return result
    },
    canCheckout() {
      return this.selectedMachineCount > 0 || this.selectedGroups.length > 0
    },
    // 验证谷歌验证码格式
    isGoogleCodeValid() {
      const code = this.googleCodeDialog.code
      return /^\d{6}$/.test(code)
    },
    // 支付币种展示用（大写）
    payCoinSymbol() {
      return (this.selectedCoin || '').toUpperCase()
    },
    // 未配置的机器列表（计算属性，根据选择的币种/算法和machineIds过滤）
    unconfiguredMachinesList() {
      const allMachines = this.configDialog.selectedMachines || []
      const configuredIds = new Set((this.configDialog.configuredMachines || []).map(m => m.id))
      const unconfigured = allMachines.filter(m => !configuredIds.has(m.id))
      
      // 如果未选择币种/算法，返回所有未配置的机器
      const coinAlgo = Array.isArray(this.configDialog.coinAlgoValue) ? this.configDialog.coinAlgoValue : []
      if (coinAlgo.length !== 2) {
        return unconfigured
      }
      
      const coin = coinAlgo[0]
      const algorithm = coinAlgo[1]
      
      // 从machineSupportData中查找对应的machineIds
      const coinData = (this.machineSupportData || []).find(it => String(it && it.coin) === String(coin))
      if (!coinData) {
        return []
      }
      
      const algoData = (coinData.algorithmList || []).find(a => String(a && a.algorithm) === String(algorithm))
      if (!algoData) {
        return []
      }
      
      const supportedMachineIds = new Set((algoData.machineIds || []).map(id => Number(id)))
      
      // 只返回在machineIds中的机器
      return unconfigured.filter(m => supportedMachineIds.has(Number(m.id)))
    },
    // 不支持的机器列表（不在当前选择的币种/算法的machineIds中）
    unsupportedMachinesList() {
      const allMachines = this.configDialog.selectedMachines || []
      const configuredIds = new Set((this.configDialog.configuredMachines || []).map(m => m.id))
      const unconfigured = allMachines.filter(m => !configuredIds.has(m.id))
      
      // 如果未选择币种/算法，返回空列表
      const coinAlgo = Array.isArray(this.configDialog.coinAlgoValue) ? this.configDialog.coinAlgoValue : []
      if (coinAlgo.length !== 2) {
        return []
      }
      
      const coin = coinAlgo[0]
      const algorithm = coinAlgo[1]
      
      // 从machineSupportData中查找对应的machineIds
      const coinData = (this.machineSupportData || []).find(it => String(it && it.coin) === String(coin))
      if (!coinData) {
        return []
      }
      
      const algoData = (coinData.algorithmList || []).find(a => String(a && a.algorithm) === String(algorithm))
      if (!algoData) {
        return []
      }
      
      const supportedMachineIds = new Set((algoData.machineIds || []).map(id => Number(id)))
      
      // 返回不在machineIds中的机器
      return unconfigured.filter(m => !supportedMachineIds.has(Number(m.id)))
    },
    /**
     * 检查是否可以点击按钮（计算属性，确保响应式更新）
     * 逻辑：
     * 1. 如果有未配置的机器：需要勾选机器 + 表单完整 → 显示"确认配置"按钮
     * 2. 如果全部配置完成：直接返回 true → 显示"下一步"按钮
     */
    canProceedToNextStep() {
      // 强制访问所有依赖项，确保响应式追踪
      const dialog = this.configDialog
      const unconfigured = this.unconfiguredMachinesList
      
      // 情况1：还有未配置的机器，需要保存当前配置
      if (unconfigured.length > 0) {
        // 必须勾选至少一台机器
        const selectedIds = dialog.selectedMachineIds || []
        const hasSelection = Array.isArray(selectedIds) && selectedIds.length > 0
        
        // 表单必须完整：币种/算法、矿池/模型、钱包地址、挖矿账户
        const coinAlgo = Array.isArray(dialog.coinAlgoValue) ? dialog.coinAlgoValue : []
        const pool = Array.isArray(dialog.poolValue) ? dialog.poolValue : []
        const wallet = String(dialog.walletAddress || '').trim()
        const account = String(dialog.miningAccount || '').trim()
        
        const coinAlgoValid = coinAlgo.length === 2
        const poolValid = pool.length === 2
        const walletValid = wallet.length > 0
        const accountValid = account.length > 0
        const formValid = coinAlgoValid && poolValid && walletValid && accountValid
        
        const result = hasSelection && formValid
        
        // 临时调试信息（可以删除）
        if (!result) {
          console.log('按钮禁用原因:', {
            hasSelection,
            selectedIds: selectedIds,
            coinAlgo: coinAlgo,
            coinAlgoValid,
            pool: pool,
            poolValid,
            wallet: wallet,
            walletValid,
            account: account,
            accountValid,
            formValid
          })
        }
        
        return result
      }
      
      // 情况2：没有未配置的机器，验证是否所有机器都已配置完成
      const allMachineIds = new Set((dialog.selectedMachines || []).map(m => m.id))
      const configuredIds = new Set((dialog.configuredMachines || []).map(m => m.id))
      const allConfigured = Array.from(allMachineIds).every(id => configuredIds.has(id))
      
      return allConfigured
    },
    // 判断已配置机器中是否有挖矿账户数据
    hasConfiguredMiningAccount() {
      const configured = this.configDialog.configuredMachines || []
      return configured.some(m => m.miningAccount && String(m.miningAccount).trim().length > 0)
    },
    // 判断已配置机器中是否有矿工号数据
    hasConfiguredWorkerId() {
      const configured = this.configDialog.configuredMachines || []
      return configured.some(m => m.workerId && String(m.workerId).trim().length > 0)
    }
  },
  mounted() {
    this.fetchGetGoodsListV2(this.paramsV2)
   
    
  },
  watch: {
    'noticeDialog.visible'(val) {
      if (val) {
        this.startNoticeCountdown()
        // 弹窗打开时也立即恢复子表勾选（避免视觉上丢失）
        this.$nextTick(() => this.reapplySelectionsForPendingShop())
      } else if (this.noticeTimer) {
        try { clearInterval(this.noticeTimer) } catch (e) { /* noop */ }
        this.noticeTimer = null
      }
    },
    'confirmDialog.visible'(val) {
      // 打开或关闭任一阶段都尝试恢复一次视觉勾选
      this.$nextTick(() => this.reapplySelectionsForPendingShop())
    },
    'payDialog.visible'(val) {
      this.$nextTick(() => this.reapplySelectionsForPendingShop())
    },
    'googleCodeDialog.visible'(val) {
      this.$nextTick(() => this.reapplySelectionsForPendingShop())
    },
    // 监听配置弹窗的所有关键数据变化，确保计算属性能够响应
    'configDialog.coinAlgoValue'() {
      this.$forceUpdate()
    },
    'configDialog.poolValue'() {
      this.$forceUpdate()
    },
    'configDialog.walletAddress'() {
      this.$forceUpdate()
    },
    'configDialog.miningAccount'() {
      this.$forceUpdate()
    },
    'configDialog.workerId'() {
      this.$forceUpdate()
    },
    'configDialog.selectedMachineIds'() {
      this.$forceUpdate()
    }
  },
  beforeDestroy() {
    try { if (this.noticeTimer) clearInterval(this.noticeTimer) } catch (e) { /* noop */ }
    this.noticeTimer = null
  },
  methods: {
    // 获取支持的矿池/模型列表，按币种和算法
    async fetchGetMachineSupportPool(coin, algorithm) {
      const payload = { coin: coin || '', algorithm: algorithm || '' }
      const res = await getMachineSupportPool(payload)
      if (res && (res.code === 0 || res.code === 200) && Array.isArray(res.data)) {
        // 保存原始数据，用于后续获取 walletMining
        this.configDialog.poolRawData = res.data || []
        // 转为 cascader 二级结构：pool -> model
        const opts = (res.data || []).map(p => ({
          label: p && p.poolName ? String(p.poolName) : '',
          value: p && p.poolId != null ? p.poolId : '',
          children: Array.isArray(p && p.miningCoinInfoList) ? p.miningCoinInfoList.map(m => ({
            label: m && m.modelName ? String(m.modelName) : '',
            value: m && m.coinConfigId != null ? m.coinConfigId : '',
            raw: m
          })) : []
        }))
        this.configDialog.poolOptions = opts
        // 重置选择
        this.configDialog.poolValue = []
      } else {
        this.configDialog.poolOptions = []
        this.configDialog.poolValue = []
        this.configDialog.poolRawData = []
      }
    },
    // 获取支持的算法币种（按机器ID列表）
    async fetchGetMachineSupportCoinAndAlgorithm(machineIdList = []) {
      const ids = Array.isArray(machineIdList) ? machineIdList.filter(id => id != null) : []
      const res = await getMachineSupportCoinAndAlgorithm(ids)
      console.log(res, 'res');
      if (res && (res.code === 0 || res.code === 200) && res.data) {
        // 保存到配置弹窗使用的数据，不干扰店铺支付 options
        this.machineSupportData = Array.isArray(res.data) ? res.data : []
      }
    },
    // 获取购物车列表（V2：按店铺结构）
    async fetchGetGoodsListV2(params) {
      try {
        const res = await getGoodsListV2(params)
        // 兼容两种返回：1) { code, data: { rows: [...] } } 2) { code, rows: [...] } 3) { code, data: [...] }
        const root = (res && res.data) ? res.data : res
        const rows = Array.isArray(root && root.rows) ? root.rows
          : (Array.isArray(root) ? root : [])
        // 规范化为内部结构：shops[].productMachineDtoList
        const normalized = rows.map((sp, idx) => {
          const machines = Array.isArray(sp && sp.cartMachineInfoDtoList)
            ? sp.cartMachineInfoDtoList
            : (Array.isArray(sp && sp.productMachineDtoList) ? sp.productMachineDtoList : [])
          // 记录每台机器原始租期，便于后续判断是否修改
          try { machines.forEach(m => { if (m && m._origLeaseTime == null) m._origLeaseTime = Number(m.leaseTime || 1) }) } catch (e) { /* noop */ }
          // 对机器列表进行排序：下架的排到最下面
          const sortedMachines = this.sortMachinesByShelfStatus(machines)
          return {
            ...sp,
            id: sp && sp.id != null ? String(sp.id) : `shop-${idx}`,
            productMachineDtoList: sortedMachines
          }
        })
        this.shops = normalized
        // 默认展开所有店铺
        try { this.expandedShopKeys = normalized.map(sp => String(sp.id)) } catch (e) { this.expandedShopKeys = [] }
        // 为每个店铺设置默认支付方式（防止为空导致价格列/总价无法展示）
        try { this.shops.forEach(sp => this.ensureDefaultPaySelection(sp)) } catch (e) { /* noop */ }
        // 同步头部购物车数量
        try {
          const count = normalized.reduce((s, sp) => s + ((Array.isArray(sp.productMachineDtoList) ? sp.productMachineDtoList.length : 0)), 0)
          window.dispatchEvent(new CustomEvent('cart-updated', { detail: { count } }))
        } catch (e) { /* noop */ }
      } catch (e) {
        this.shops = []
      }
    },
    /**
     * 金额格式化（不补0、不四舍五入），根据币种限定小数位
     * @param {number|string} value
     * @param {string} coin
     * @returns {{text:string,truncated:boolean,full:string}}
     */
    formatAmount(value, coin) {
      return truncateAmountByCoin(value, coin)
    },
    /**
     * 数值格式化（最多6位小数，截断不四舍五入，不补0）
     * @param {number|string} value
     * @returns {{text:string,truncated:boolean,full:string}}
     */
    formatNum6(value) {
      return truncateTo6(value)
    },
    /**
     * 将金额转为整分整数，避免浮点误差
     * @param {number|string} v
     * @returns {number}
     */
    toCents(v) {
      // 直接截取两位小数（不四舍五入），并转为“分”的整数
      if (v === null || v === undefined) return 0
      let s = String(v).trim()
      if (s === '') return 0
      let sign = 1
      if (s[0] === '-') { sign = -1; s = s.slice(1) }
      const parts = s.split('.')
      const intPart = parseInt(parts[0] || '0', 10) || 0
      const decRaw = (parts[1] || '').replace(/[^0-9]/g, '')
      const decTwo = (decRaw.length >= 2 ? decRaw.slice(0, 2) : decRaw.padEnd(2, '0'))
      const cents = intPart * 100 + (parseInt(decTwo || '0', 10) || 0)
      return sign * cents
    },
    /**
     * 整分转字符串，固定两位小数
     * @param {number} cents
     * @returns {string}
     */
    centsToText(cents) {
      const sign = cents < 0 ? '-' : ''
      const abs = Math.abs(Number(cents) || 0)
      const intPart = Math.floor(abs / 100)
      const decPart = String(abs % 100).padStart(2, '0')
      return `${sign}${intPart}.${decPart}`
    },
    // 选择框可选逻辑：下架(或删除)机器不可选择
    isRowSelectable(row, index) {
      return !(Number(row && row.del) === 1 || Number(row && row.state) === 1)
    },
      // 判断机器是否"上架"（用于未展开时的自动筛选）
      isOnShelf(row) {
        return !(Number(row && row.del) === 1 || Number(row && row.state) === 1)
      },
      /**
       * 对机器列表进行排序：下架的排到最下面
       * @param {Array} machines - 机器列表
       * @returns {Array} 排序后的机器列表
       */
      sortMachinesByShelfStatus(machines) {
        if (!Array.isArray(machines)) return []
        // 上架的机器排前面，下架的排后面
        return [...machines].sort((a, b) => {
          const aOnShelf = this.isOnShelf(a)
          const bOnShelf = this.isOnShelf(b)
          // 如果都是上架或都是下架，保持原有顺序
          if (aOnShelf === bOnShelf) return 0
          // 上架的排前面（返回-1），下架的排后面（返回1）
          return aOnShelf ? -1 : 1
        })
      },
      /**
       * 获取机器表格行的类名（用于样式）
       * @param {Object} param - 包含 row, rowIndex 的对象
       * @returns {string} 行类名
       */
      getMachineRowClassName({ row }) {
        // 如果机器已下架，返回下架样式类
        if (!this.isOnShelf(row)) {
          return 'off-shelf-row'
        }
        return ''
      },
    // 获取本地最大可租赁天数，默认 365
    getRowMaxLeaseDaysLocal(row) {
      const raw = row && row.maxLeaseDays
      const n = Number(raw)
      if (!Number.isFinite(n)) return 365
      if (n < 1) return 1
      if (n > 365) return 365
      return Math.floor(n)
    },
    /**
     * 精确裁剪数字到指定位数（不四舍五入）
     * @param {number|string} value - 原始值
     * @param {number} decimals - 小数位数，默认2
     * @returns {string}
     */
    formatTrunc(value, decimals = 2) {
      const num = Number(value)
      if (!Number.isFinite(num)) return '0'
      const d = Math.max(0, Number(decimals) || 0)
      const factor = Math.pow(10, d)
      const truncated = Math.trunc(num * factor) / factor
      // 使用 toLocaleString 保留精度但不四舍五入，通过补零对齐位数
      const str = String(truncated)
      if (d === 0) return str
      const [intPart, decPart = ''] = str.split('.')
      const padded = decPart.padEnd(d, '0')
      return `${intPart}.${padded}`
    },
    //获取支持的链和币种
    async fetchChainAndListForSeller(shopId) {
      if (!shopId) {
        this.options = []
        this.loading = false
        return        
      }
      this.loading = true;
      const res = await getChainAndListForSeller({ id: shopId });
      if (res && (res.code === 0 || res.code === 200) && res.data) {
        this.options = this.toUpperOptions(res.data);
      }

      this.loading = false;
    },
    // 将级联选项的显示文本统一转为大写（不修改传给后端的 value）
    toUpperOptions(list) {
      const arr = Array.isArray(list) ? list : []
      return arr.map(item => {
        const next = { ...item }
        const labelSrc = (item && (item.label != null ? item.label : item.value)) || ''
        next.label = String(labelSrc).toUpperCase()
        if (Array.isArray(item && item.children)) {
          next.children = this.toUpperOptions(item.children)
        }
        return next
      })
    },
    // 获取所有商品分组（兼容保留，现直接返回空，因已移除中间商品层）
    getAllGroups() { return [] },
    // 生成店铺的支付方式选项，基于 totalPriceList
    getShopPayOptions(shop) {
      // 下拉渲染严格以 payConfigList 为准（接口定义的可选支付方式）
      const cfg = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
      return cfg.map(c => {
        const chain = c && c.payChain ? String(c.payChain) : ''
        const coin = c && c.payCoin ? String(c.payCoin) : ''
        const key = `${chain}|${coin}`
        return {
          label: `${chain} - ${this.toUpperText(coin)}`,
          value: key,
          icon: c && c.payCoinImage ? c.payCoinImage : ''
        }
      })
    },
    // 判断某机器在当前店铺选择的支付方式下是否有可用单价
    hasMachinePriceForSelection(shop, machine) {
      if (!shop || !machine) return false
      const key = this.paySelectionMap[shop.id] || ''
      const [chain, coin] = String(key).split('|')
      const list = Array.isArray(machine.priceList) ? machine.priceList : []
      return list.some(it => String(it.chain) === chain && String(it.coin) === coin)
    },
    // 获取店铺当前选择的币种符号（大写）
    getSelectedCoinSymbolForShop(shop) {
      const key = this.paySelectionMap[shop ? shop.id : undefined]
      if (!key) return ''
      const parts = String(key).split('|')
      return this.toUpperText(parts[1])
    },
    // 外层表头：取第一个店铺的币种显示在“总价”标题后面
    getSelectedCoinSymbolForShopHeader() {
      const first = Array.isArray(this.shops) && this.shops.length ? this.shops[0] : null
      if (!first) return ''
      // 确保默认已设置
      this.ensureDefaultPaySelection(first)
      return this.getSelectedCoinSymbolForShop(first)
    },
    // 获取当前店铺选中的支付方式图标（用于 select prefix）
    getSelectedPayIcon(shop) {
      if (!shop) return ''
      this.ensureDefaultPaySelection(shop)
      const key = this.paySelectionMap[shop.id] || ''
      const [chain, coin] = String(key).split('|')
      const cfg = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
      const hit = cfg.find(c => String(c.payChain) === chain && String(c.payCoin) === coin)
      return hit && hit.payCoinImage ? hit.payCoinImage : ''
    },
    // 默认设置店铺支付方式为第一个选项
    ensureDefaultPaySelection(shop) {
      if (!shop) return
      const list = this.getShopPayOptions(shop)
      if (list.length && !this.paySelectionMap[shop.id]) {
        this.$set(this.paySelectionMap, shop.id, list[0].value)
      }
    },
    // 处理店铺支付方式变化
    handleShopPayChange(shop, val) {
      if (!shop) return
      this.$set(this.paySelectionMap, shop.id, val)
      // 清理已选择但在当前支付方式下无价格的机器，并刷新视觉勾选
      const set = this.selectedMachinesMap[shop.id]
      if (set && set.size) {
        const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
        list.forEach(m => {
          if (set.has(m.id) && !this.hasMachinePriceForSelection(shop, m)) {
            set.delete(m.id)
          }
        })
        // 重新应用到表格勾选
        this.$nextTick(() => this.applyInnerSelectionFromSet(shop))
      }
    },
    // 根据店铺选择展示其总价（来自 totalPriceList）
    displayShopTotalBySelection(shop) {
      if (!shop) return 0
      // 确保有默认选择
      this.ensureDefaultPaySelection(shop)
      // 若用户修改过任意机器的租赁天数，则按当前租期+所选币种实时汇总
      if (this.isShopLeaseChanged(shop)) {
        try {
          const machines = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
          let totalCents = 0
          machines.forEach(m => {
            const unit = this.getMachineUnitPriceBySelection(shop, m)
            if (unit != null) {
              const days = Math.max(1, Math.floor(Number(m.leaseTime || 1)))
              totalCents += this.toCents(unit) * days
            }
          })
          return totalCents / 100
        } catch (e) {
          /* noop fallthrough to backend value */
        }
      }
      // 未修改租期：优先使用后端 totalPriceList 当前币种价格
      const key = this.paySelectionMap[shop.id] || ''
      const [chain, coin] = key.split('|')
      const list = Array.isArray(shop.totalPriceList) ? shop.totalPriceList : []
      const hit = list.find(it => String(it.chain) === chain && String(it.coin) === coin)
      if (hit && hit.price != null) return Number(hit.price || 0)
      // 兜底：若没命中，返回总价或0
      return Number(shop.totalPrice || 0)
    },
    // 是否有任意机器的租期被用户修改
    isShopLeaseChanged(shop) {
      try {
        const list = Array.isArray(shop && shop.productMachineDtoList) ? shop.productMachineDtoList : []
        return list.some(m => {
          const orig = (m && m._origLeaseTime != null) ? Number(m._origLeaseTime) : Number(m && m.leaseTime)
          const cur = Math.max(1, Math.floor(Number(m && m.leaseTime) || 1))
          return orig !== cur
        })
      } catch (e) {
        return false
      }
    },
    // 根据店铺选择获取机器的单价（来自 priceList）
    getMachineUnitPriceBySelection(shop, machine) {
      if (!shop || !machine) return Number(machine.price || 0)
      this.ensureDefaultPaySelection(shop)
      const key = this.paySelectionMap[shop.id] || ''
      const [chain, coin] = key.split('|')
      const list = Array.isArray(machine.priceList) ? machine.priceList : []
      const hit = list.find(it => String(it.chain) === chain && String(it.coin) === coin)
      if (hit && hit.price != null) return Number(hit.price || 0)
      // 查不到则返回 null（用于界面展示和禁用）
      return null
    },
    // 对应店铺下勾选列是否可点：同时判断上下架与是否有价格
    isRowSelectableByShop(shop, row) {
      if (!this.isOnShelf(row)) return false
      return this.hasMachinePriceForSelection(shop, row)
    },
    // 店铺总价（精确到分）：优先用后端 totalPrice；若用户改动租期则实时按分计算
    computeShopTotal(shop) {
      if (!shop) return 0
      const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
      // 若没有子项，直接返回后端值
      if (!list.length) return Number(shop.totalPrice || 0)
      let totalCents = 0
      for (const m of list) {
        const priceCents = this.toCents(m && m.price)
        const days = Math.max(1, Math.floor(Number(m && m.leaseTime) || 1))
        totalCents += priceCents * days
      }
      return totalCents / 100
    },
    /**
     * 返回展示用字符串：两位小数且无精度丢失
     */
    computeShopTotalDisplay(shop) {
      const list = Array.isArray(shop && shop.productMachineDtoList) ? shop.productMachineDtoList : []
      const backendVal = Number(shop && shop.totalPrice)
      const hasBackend = Number.isFinite(backendVal)
      // 若未修改任一机器租期，优先展示后端总价
      let modified = false
      for (const m of list) {
        const orig = (m && m._origLeaseTime != null) ? Number(m._origLeaseTime) : Number(m && m.leaseTime)
        const cur = Math.max(1, Math.floor(Number(m && m.leaseTime) || 1))
        if (orig !== cur) { modified = true; break }
      }
      if ((hasBackend && !modified) || (!list.length && hasBackend)) return this.formatTrunc(backendVal, 2)
      let totalCents = 0
      for (const m of list) {
        const priceCents = this.toCents(m && m.price)
        const days = Math.max(1, Math.floor(Number(m && m.leaseTime) || 1))
        totalCents += priceCents * days
      }
      return this.centsToText(totalCents)
    },
    // 组装批量删除的请求体：数组 [{ cartInfoId }]
    buildDeletePayload() {
      const payload = []
      const shops = Array.isArray(this.shops) ? this.shops : []
      shops.forEach(shop => {
        const set = this.selectedMachinesMap[shop.id]
        if (!set || set.size === 0) return
        const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
        list.forEach(m => { 
          if (set.has(m.id) && m.cartInfoId != null) {
            payload.push({ cartInfoId: m.cartInfoId }) 
          }
        })
      })
      return payload.filter(it => it && (it.cartInfoId != null))
    },
    async fetchAddOrdersV2(orderInfoVoList, orderMiningInfoDtoList, googleCode) {
      try {
        // 按照新的传参结构：{code: 谷歌验证码, orderInfoVoList: 订单信息列表, orderMiningInfoDtoList: 挖矿配置列表}
        const payload = {
          code: googleCode,
          orderInfoVoList: orderInfoVoList,
          orderMiningInfoDtoList: orderMiningInfoDtoList
        }
        const res = await addOrdersV2(payload)
        return res
      } catch (e) {
        return { code: -1, msg: '网络异常', data: null }
      }
    },
    // 请求后端删除接口
    async fetchDeleteBatchGoods(payload) {
      try {
        const res = await deleteBatchGoodsV2(payload)
        return res
      } catch (e) {
        return { code: -1, msg: '网络异常' }
      }
    },
    // 清除已下架/删除商品
    async handleClearOffShelf() {
      if (this.clearOffLoading) return
      this.clearOffLoading = true
      try {
        const res = await deleteBatchGoodsForIsDeleteV2()
        if (res && Number(res.code) === 200) {
          this.$message({ message: '已清除下架商品', type: 'success', showClose: true })
          await this.fetchGetGoodsListV2(this.paramsV2)
        } else {
          this.$message({ message: (res && res.msg) || '清除失败', type: 'error', showClose: true })
        }
      } catch (e) {
        this.$message({ message: '网络异常', type: 'error', showClose: true })
      } finally {
        this.clearOffLoading = false
      }
    },
    /**
     * 将文本安全地转为大写
     * @param {string|number|null|undefined} value 原值
     * @returns {string} 大写字符串
     */
    toUpperText(value) {
      return value == null ? '' : String(value).toUpperCase()
    },
    // 外层展开/收起同步到 expandedGroupKeys，确保点击箭头生效
    handleOuterExpandChange(row, expandedRows) {
      // ElTable 会把当前已展开行数组给我们，这里只需要记录其 id 列表即可
      try {
        const keys = Array.isArray(expandedRows)
          ? expandedRows.map(r => r && (r.id != null ? String(r.id) : undefined)).filter(Boolean)
          : []
        this.expandedGroupKeys = keys
      } catch (e) {
        this.expandedGroupKeys = []
      }
    },
    handleShopExpandChange(row, expandedRows) {
      try {
        const keys = Array.isArray(expandedRows)
          ? expandedRows.map(r => r && (r.id != null ? String(r.id) : undefined)).filter(Boolean)
          : []
        this.expandedShopKeys = keys
        // 展开时恢复该店铺下已选中的机器勾选
        const isExpanded = keys.includes(String(row.id))
        if (isExpanded) {
          this.$nextTick(() => this.applyInnerSelectionFromSet(row))
        }
      } catch (e) {
        this.expandedShopKeys = []
      }
    },
    async fetchGetGoodsList(params) {
      try {
        this.loading = true
        const res = await getGoodsList(params)
        // 支持新结构（店铺->商品->机器）与旧结构的兼容
        const rawRows = Array.isArray(res && res.rows) ? res.rows
          : Array.isArray(res && res.data && res.data.rows) ? res.data.rows
          : Array.isArray(res && res.data) ? res.data : []

        // 如果数据为空，清空所有数据
        if (!rawRows || rawRows.length === 0) {
          this.shops = []
          this.groups = []
          this.expandedShopKeys = []
          this.expandedGroupKeys = []
          this.selectedMachinesMap = {}
          this.selectedGroups = []
          // 同步头部购物车数量为0
          window.dispatchEvent(new CustomEvent('cart-updated', { detail: { count: 0 } }))
          return
        }

        // 如果是店铺结构
        if (rawRows.length && rawRows[0].productMachineDtoList) {
          const withShopKeys = rawRows.map((shop, sIdx) => ({
            ...shop,
            id: shop.id != null ? String(shop.id) : `shop-${sIdx}`
          }))
          // 为每个店铺设置默认支付方式
          try {
            withShopKeys.forEach(sp => this.ensureDefaultPaySelection(sp))
          } catch (e) { /* noop */ }
          // 记录每台机器的原始租期，便于判断是否被修改，并对机器列表进行排序
          try {
            withShopKeys.forEach(sp => {
              const list = Array.isArray(sp.productMachineDtoList) ? sp.productMachineDtoList : []
              list.forEach(m => { if (m && m._origLeaseTime == null) m._origLeaseTime = Number(m.leaseTime || 1) })
              // 对机器列表进行排序：下架的排到最下面
              sp.productMachineDtoList = this.sortMachinesByShelfStatus(list)
            })
          } catch (e) { /* noop */ }
          this.shops = withShopKeys
          this.groups = []
          this.expandedGroupKeys = []
          // 默认展开所有店铺
          try {
            this.expandedShopKeys = withShopKeys.map(sp => String(sp.id))
          } catch (e) { this.expandedShopKeys = [] }
          const count = withShopKeys.reduce((s, shop) => s + ((Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList.length : 0)), 0)
          window.dispatchEvent(new CustomEvent('cart-updated', { detail: { count } }))
          return
        }

        // 旧结构：直接是商品列表
        const withKeys = rawRows.map((g, idx) => ({
          ...g,
          id: g && (g.id != null ? g.id : (g.productId != null ? g.productId : `g-${idx}`))
        }))
        this.groups = withKeys
        // 清空新结构数据
        this.shops = []
        this.expandedShopKeys = []
        this.expandedGroupKeys = (this.expandedGroupKeys || []).filter(k => withKeys.some(g => String(g.id) === String(k)))
        // 同步头部购物车数量（统计所有商品下机器条数）
        try {
          const count = withKeys.reduce((s, r) => s + ((Array.isArray(r.productMachineDtoList) ? r.productMachineDtoList.length : 0)), 0)
          window.dispatchEvent(new CustomEvent('cart-updated', { detail: { count } }))
        } catch (e) { /* ignore sync header count error */ }
      } catch (e) {
       console.log(e,"e");
       
      } finally {
        this.loading = false
      }
    },
    
    handleGroupSelectionChange() { /* 中间层已移除，保留空实现以兼容旧调用 */ },
    // 选中某店铺下的商品分组：自动展开并全选其机器；取消则收起并清空
    handleGroupSelectionChangeForShop() { /* 已移除商品层，保留空实现 */ },
    // 尝试对子表应用选择，若未渲染则重试几次
    applyInnerSelection(shop, shouldSelectAll, retry = 0) {
      const inner = this.$refs['innerTable-' + shop.id]
      const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
      if (inner && typeof inner.clearSelection === 'function') {
        try { inner.clearSelection() } catch (e) { /* ignore */ }
        if (shouldSelectAll) {
          list.forEach(m => { try { inner.toggleRowSelection(m, true) } catch (e) { /* ignore */ } })
        }
        return
      }
      if (retry >= 5) return
      this.$nextTick(() => this.applyInnerSelection(shop, shouldSelectAll, retry + 1))
    },
    /**
     * 根据已保存的 selectedMachinesMap 重新应用某店铺子表的勾选（仅勾选之前勾选的机器）
     */
    applyInnerSelectionFromSet(shop, retry = 0) {
      if (!shop) return
      const inner = this.$refs['innerTable-' + shop.id]
      const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
      const set = this.selectedMachinesMap[shop.id]
      if (inner && typeof inner.clearSelection === 'function') {
        try { inner.clearSelection() } catch (e) { /* ignore */ }
        if (set && set.size) {
          list.forEach(m => {
            if (set.has(m.id)) {
              try { inner.toggleRowSelection(m, true) } catch (e) { /* ignore */ }
            }
          })
        }
        return
      }
      if (retry >= 5) return
      this.$nextTick(() => this.applyInnerSelectionFromSet(shop, retry + 1))
    },
    /**
     * 在结算流程被用户中断/失败返回时，恢复列表中的勾选 UI 状态
     */
    reapplySelectionsForPendingShop() {
      const shop = this.pendingCheckoutShop && this.pendingCheckoutShop.shop
      if (shop) this.applyInnerSelectionFromSet(shop)
    },
    handleShopInnerSelectionChange(shop, selections) {
      const selIds = new Set((selections || []).map(s => s.id))
      this.$set(this.selectedMachinesMap, shop.id, selIds)
    },
    toggleSelectAll() {
      const table = this.$refs.outerTable
      if (!table) return
      if (this.isAllSelected) {
        table.clearSelection()
      } else {
        this.groups.forEach(row => table.toggleRowSelection(row, true))
      }
    },
    calcGroupTotal(group) {
      const list = Array.isArray(group && group.productMachineDtoList) ? group.productMachineDtoList : []
      return list.reduce((s, m) => {
        const price = Number(m.price || 0)
        const days = Number(m.leaseTime || 1)
        return s + (price * days)
      }, 0)
    },
    // 统计店铺下的机器总数
    countMachines(shop) {
      const list = Array.isArray(shop && shop.productMachineDtoList) ? shop.productMachineDtoList : []
      return list.length
    },
    /**
     * 结算当前店铺：
     * - 如果店铺未展开，则默认包含该店铺下的全部机器进入下一步流程；
     * - 如果店铺已展开，则仅在勾选了机器的情况下进入下一步，否则给出提示。
     * @param {Record<string, any>} shop 当前店铺行
     */
    async handleCheckoutShop(shop) {
      if (!shop) return
      const shopId = shop.id
      const machines = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
      if (machines.length === 0) {
        this.$message({ message: '该店铺暂无可结算的机器', type: 'warning', showClose: true })
        return
      }

      // 是否已展开：仅在已展开时校验是否有选中机器
      const isExpanded = Array.isArray(this.expandedShopKeys) && this.expandedShopKeys.includes(String(shopId))
      const payload = []

      if (isExpanded) {
        const selectedSet = this.selectedMachinesMap[shopId]
        if (!selectedSet || selectedSet.size === 0) {
          this.$message({
            message: '请先在该店铺下勾选要结算的机器',
            type: 'warning',
            showClose: true
          })
          return
        }
        machines.forEach(m => {
          if (selectedSet.has(m.id) && this.isOnShelf(m)) {
            payload.push({
              leaseTime: Number(m.leaseTime || 1),
              machineId: m.id,
              productId: m.productId,
              shopId: shopId
            })
          }
        })
        if (!payload.length) {
          this.$message({ message: '所选机器均已下架，无法结算', type: 'warning', showClose: true })
          return
        }
      } else {
        // 未展开：仅结算“上架”机器
        const onShelfMachines = machines.filter(m => this.isOnShelf(m))
        if (!onShelfMachines.length) {
          this.$message({ message: '该店铺暂无上架机器可结算', type: 'warning', showClose: true })
          return
        }
        onShelfMachines.forEach(m => {
          payload.push({
            leaseTime: Number(m.leaseTime || 1),
            machineId: m.id,
            productId: m.productId,
            shopId: shopId
          })
        })
      }

      // 先根据当前店铺请求可选支付链/币种
      await this.fetchChainAndListForSeller(shopId)

      // 保存待结算的店铺信息，弹出购物须知弹窗
      this.pendingCheckoutShop = { shop, payload }
      this.noticeDialog.visible = true
      this.noticeDialog.checked = false
      this.startNoticeCountdown()
    },
    // 实际执行结算的方法，返回是否成功
    async executeCheckout(googleCode) {
      if (!this.pendingCheckoutShop && !this.pendingCheckoutAll) return false
      
      // 获取已配置的机器信息
      const configuredMachines = this.configDialog.configuredMachines || []
      if (!configuredMachines.length) {
        this.$message.warning('请先完成机器配置')
        return false
      }
      
      // 构建订单信息列表 orderInfoVoList
      const orderInfoVoList = []
      // 构建挖矿配置列表 orderMiningInfoDtoList（每台机器一个配置项，使用 machineId 字符串）
      const orderMiningInfoDtoList = []
      
      // 处理单店铺或多店铺的机器
      const allMachines = []
      if (this.pendingCheckoutAll && this.pendingCheckoutAll.length) {
        this.pendingCheckoutAll.forEach(({ shop, items }) => {
          (items || []).forEach(m => {
            allMachines.push({ shop, machine: m })
          })
        })
      } else if (this.pendingCheckoutShop) {
        const { shop, payload } = this.pendingCheckoutShop
        const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
        const idSet = new Set(payload.map(p => p.machineId))
        list.forEach(m => {
          if (idSet.has(m.id)) {
            allMachines.push({ shop, machine: m })
          }
        })
      }
      
      // 构建订单信息列表和挖矿配置列表
      allMachines.forEach(({ shop, machine }) => {
        const sel = this.paySelectionMap[shop.id] || ''
        const [chain, coin] = String(sel).split('|')
        
        // 查找该机器的配置信息
        const config = configuredMachines.find(c => c.id === machine.id)
        if (!config) {
          console.warn(`机器 ${machine.id} 未找到配置信息`)
          return
        }
        
        // 添加到订单信息列表
        orderInfoVoList.push({
          chain: chain || '',
          coin: coin || '',
          leaseTime: Number(machine.leaseTime || 1),
          machineId: machine.id,
          numbers: Number(machine.numbers || 1),
          type: Number(machine.type || 0),
          shopId: shop.id
        })
        
        // 为每台机器单独创建挖矿配置项，使用 machineId（string）而不是 machineIds（array）
        const coinConfigId = config.coinConfigId
        if (!coinConfigId) {
          console.warn(`机器 ${machine.id} 的 coinConfigId 为空`)
          return
        }
        
        const walletAddress = String(config.walletAddress || '').trim()
        const poolUser = String(config.miningAccount || '').trim()
        const workerId = String(config.workerId || '').trim()
        
        // 为每台机器创建独立的配置项
        orderMiningInfoDtoList.push({
          coinConfigId: coinConfigId,
          coin: config.coin || '', // 用户选择的币种/算法框里的币种
          poolName: config.poolName || '',
          poolUser: poolUser,
          walletAddress: walletAddress,
          walletMining: config.walletMining || false,
          workerId: workerId,
          machineId: String(machine.id) // 使用 machineId（string）而不是 machineIds（array）
        })
      })

      /**
       * 结算提交前：对配置中的钱包地址进行 RSA 加密（字段名不变，仍为 walletAddress）
       * 规则：同步优先（rsaEncryptSync），失败再异步兜底（rsaEncrypt）；仍失败则提示并中断结算
       */
      for (const item of (orderMiningInfoDtoList || [])) {
        const plainWalletAddress = String(item && item.walletAddress ? item.walletAddress : '').trim()
        // 空地址无需加密（例如 walletMining=false 时可能仅提交 poolUser）
        if (!plainWalletAddress) continue

        /** @type {string|null} */
        let encryptedWalletAddress = rsaEncryptSync(plainWalletAddress)
        if (!encryptedWalletAddress) {
          encryptedWalletAddress = await rsaEncrypt(plainWalletAddress)
        }
        if (!encryptedWalletAddress) {
          this.$message.error('钱包地址加密失败，请重试')
          // 中断前恢复勾选 UI（与其它失败分支保持一致）
          this.reapplySelectionsForPendingShop()
          return false
        }
        item.walletAddress = encryptedWalletAddress
      }

      this.creatingOrder = true
      try {
        const res = await this.fetchAddOrdersV2(orderInfoVoList, orderMiningInfoDtoList, googleCode)
        let ok = false
        if (res && Number(res.code) === 200) {
          const dataStr = String(res.data || '')
          ok = dataStr.includes('成功')
          if (ok) {
            this.$message({
              message: `结算成功,订单状态请在订单列表中查看`,
              type: 'success',
              duration: 3000,
              showClose: true,
            })
            this.settlementSuccessfulVisible = true
            // 结算成功后重新获取购物车数据，更新购物车数量
            await this.fetchGetGoodsListV2(this.paramsV2)
            // 清空待结算信息
            this.pendingCheckoutShop = null
            this.pendingCheckoutAll = null
            return true
          } else {
          
            // 失败或未成功，恢复之前的勾选 UI
            this.reapplySelectionsForPendingShop()
            return false
          }
        } else {
          
          // 失败或未成功，恢复之前的勾选 UI
          this.reapplySelectionsForPendingShop()
          return false
        }
      } catch (e) {
        console.log('网络错误，请重试', e)
        this.$message({
          message: '网络错误，请重试',
          type: 'error',
          duration: 3000,
          showClose: true,
        })
        // 异常也恢复勾选
        this.reapplySelectionsForPendingShop()
        return false
      } finally {
        this.creatingOrder = false
      }
    },
    async handleCheckoutSelected() {
      if (!this.selectedMachineCount) {
        this.$message({ message: '请先勾选要结算的机器', type: 'warning', showClose: true })
        return
      }
      const shops = Array.isArray(this.shops) ? this.shops : []
      const picked = []
      shops.forEach(shop => {
        const set = this.selectedMachinesMap[shop.id]
        if (!set || !set.size) return
        const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
        const items = list.filter(m => set.has(m.id) && this.isOnShelf(m))
        if (items.length) picked.push({ shop, items })
      })
      if (!picked.length) {
        this.$message({ message: '未找到可结算的上架机器', type: 'warning', showClose: true })
        return
      }
      // 在点击"结算选中机器"时请求币种/算法支持数据
      try {
        const ids = []
        picked.forEach(({ items }) => {
          (items || []).forEach(m => {
            if (m && m.id != null) ids.push(m.id)
          })
        })
        if (ids.length) {
          await this.fetchGetMachineSupportCoinAndAlgorithm(ids)
          // 检查是否获取到数据
          if (!this.machineSupportData || this.machineSupportData.length === 0) {
            this.$message({
              message: '加载配置信息失败，请重试',
              type: 'error',
              showClose: true
            })
            return
          }
        }
      } catch (e) {
        this.$message({
          message: '加载配置信息失败，请重试',
          type: 'error',
          showClose: true
        })
        return
      }
      this.pendingCheckoutAll = picked
      // 打开须知弹窗
      this.noticeDialog.visible = true
      this.noticeDialog.checked = false
      this.startNoticeCountdown()
    },
    handleRemoveSelectedMachines() {
      const payload = this.buildDeletePayload()
      if (!payload.length) {
        this.$message({
          message: '请先勾选需要删除的机器或商品',
          type: 'warning',
          showClose: true
        })
        return
      }
      this.$confirm('确定删除所选机器吗？', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        const res = await this.fetchDeleteBatchGoods(payload)
        if (res && Number(res.code) === 200) {
        
          this.$message({
            message: `已删除选中的机器`,
            type: 'success',
            duration: 3000,
            showClose: true,
          })
          await this.fetchGetGoodsListV2(this.paramsV2)
          this.selectedMachinesMap = {}
          this.selectedGroups = []
          // 清空所有内层子表的选择状态
          try {
            this.getAllGroups().forEach(g => {
              const inner = this.$refs['innerTable-' + g.id]
              if (inner && typeof inner.clearSelection === 'function') {
                try { inner.clearSelection() } catch (e) { /* noop */ }
              }
            })
          } catch (e) { /* noop */ }
        } else {
          // this.$message.error(res && res.msg)
        }
      }).catch(() => { return null })
    },
    confirmPay() {
      // 关闭确认结算弹窗，显示谷歌验证码弹窗
      this.confirmDialog.visible = false
      this.showGoogleCodeDialog()
    },
    // 确认结算弹窗的上一步：返回到配置弹窗
    handleConfirmDialogPrev() {
      this.confirmDialog.visible = false
      // 重新打开配置弹窗
      this.configDialog.visible = true
    },
    // 关闭成功弹窗：跳转到订单列表的订单进行中状态
    handleCloseSuccessDialog() {
      try { this.settlementSuccessfulVisible = false } catch (e) { /* noop */ }
      this.$router.push({ path: '/account/orders', query: { status: '7' } })
    },
    // 注意事项：启动 5 秒倒计时
    startNoticeCountdown() {
      try { if (this.noticeTimer) clearInterval(this.noticeTimer) } catch (e) {}
      this.noticeDialog.countdown = 5
      this.noticeTimer = setInterval(() => {
        if (this.noticeDialog.countdown > 0) {
          this.noticeDialog.countdown -= 1
        } else {
          clearInterval(this.noticeTimer)
          this.noticeTimer = null
        }
      }, 1000)
    },
    // 注意事项：确认已知悉
    async handleNoticeAcknowledge() {
      if (this.noticeDialog.countdown > 0) return
      if (!this.noticeDialog.checked) {
        this.$message({
          message: '请先勾选"我已阅读并同意上述注意事项"',
          type: 'warning',
          showClose: true
        })
        return
      }
      this.noticeDialog.visible = false
      // 进入下一步前，确保一次恢复（避免刚关闭后消失）
      this.$nextTick(() => this.reapplySelectionsForPendingShop())
      // 勾选确认后，调用算法/币种支持查询接口，参数为勾选的机器ID数组
      // 请求时机已提前到点击“结算选中机器”时，这里不再重复请求
      // 打开“配置弹窗”，待配置完成后再进入确认结算弹窗
      this.openConfigDialog()
    },
    // 打开配置弹窗并初始化选项与机器列表
    openConfigDialog() {
      // 检查是否获取到配置数据
      if (!this.machineSupportData || this.machineSupportData.length === 0) {
        this.$message({
          message: '加载配置信息失败，请重试',
          type: 'error',
          showClose: true
        })
        return
      }
      // 重置已配置列表
      this.configDialog.configuredMachines = []
      // 初始化币种选项
      const coins = Array.isArray(this.machineSupportData) ? this.machineSupportData : []
      const coinOptions = coins.map(it => ({
        label: this.toUpperText(it && it.coin),
        value: it && it.coin
      }))
      this.configDialog.coinOptions = coinOptions
      // 构建币种/算法联级选项
      this.configDialog.coinAlgoOptions = (coins || []).map(c => ({
        label: this.toUpperText(c && c.coin),
        value: c && c.coin,
        children: Array.isArray(c && c.algorithmList) ? c.algorithmList.map(a => ({
          label: a && a.algorithm ? String(a.algorithm) : '',
          value: a && a.algorithm ? String(a.algorithm) : ''
        })) : []
      }))
      // 不默认选择币种和算法，等用户自己选择
      this.$set(this.configDialog, 'coin', '')
      this.$set(this.configDialog, 'algorithm', '')
      this.$set(this.configDialog, 'coinAlgoValue', [])
      // 构建展示用机器列表
      this.configDialog.selectedMachines = this.buildSelectedMachinesForConfig()
      // 重置选择状态
      this.$set(this.configDialog, 'selectedMachineIds', [])
      // 重置输入项
      this.$set(this.configDialog, 'walletAddress', '')
      this.$set(this.configDialog, 'miningAccount', '')
      this.$set(this.configDialog, 'workerId', '')
      this.$set(this.configDialog, 'poolValue', [])
      // 重置矿池选项
      this.$set(this.configDialog, 'poolOptions', [])
      this.$set(this.configDialog, 'poolRawData', [])
      // 打开弹窗
      this.configDialog.visible = true
    },
    // 根据当前选中币种刷新算法选项
    refreshAlgorithmOptions() {
      const coin = this.configDialog.coin
      const hit = (this.machineSupportData || []).find(it => String(it && it.coin) === String(coin))
      const algos = Array.isArray(hit && hit.algorithmList) ? hit.algorithmList : []
      this.configDialog.algorithmOptions = algos.map(a => ({
        label: a && a.algorithm ? String(a.algorithm) : '',
        value: a && a.algorithm ? String(a.algorithm) : '',
        machineIds: Array.isArray(a && a.machineIds) ? a.machineIds : []
      }))
      if (!this.configDialog.algorithm && this.configDialog.algorithmOptions.length) {
        this.configDialog.algorithm = this.configDialog.algorithmOptions[0].value
      } else if (this.configDialog.algorithm && !this.configDialog.algorithmOptions.some(o => o.value === this.configDialog.algorithm)) {
        this.configDialog.algorithm = this.configDialog.algorithmOptions.length ? this.configDialog.algorithmOptions[0].value : ''
      }
    },
    // 币种变化
    handleConfigCoinChange() {
      this.configDialog.algorithm = ''
      this.refreshAlgorithmOptions()
      // 币种变化后，如有算法则刷新矿池
      try {
        if (this.configDialog.coin && this.configDialog.algorithm) {
          this.fetchGetMachineSupportPool(this.configDialog.coin, this.configDialog.algorithm)
        } else {
          this.configDialog.poolOptions = []
          this.configDialog.poolValue = []
          this.configDialog.poolRawData = []
        }
      } catch (e) { /* noop */ }
    },
    // 算法变化
    handleConfigAlgorithmChange() {
      try {
        if (this.configDialog.coin && this.configDialog.algorithm) {
          this.fetchGetMachineSupportPool(this.configDialog.coin, this.configDialog.algorithm)
        } else {
          this.configDialog.poolOptions = []
          this.configDialog.poolValue = []
          this.configDialog.poolRawData = []
        }
      } catch (e) { /* noop */ }
    },
    // 币种/算法二级下拉变化
    handleCoinAlgoChange(val) {
      const arr = Array.isArray(val) ? val : []
      const coin = arr[0] || ''
      const algo = arr[1] || ''
      this.$set(this.configDialog, 'coin', coin)
      this.$set(this.configDialog, 'algorithm', algo)
      this.$set(this.configDialog, 'coinAlgoValue', arr)
      // 清空表格选择状态
      this.$set(this.configDialog, 'selectedMachineIds', [])
      this.$nextTick(() => {
        try {
          const tb = this.$refs.configTable
          if (tb && typeof tb.clearSelection === 'function') {
            tb.clearSelection()
          }
        } catch (e) { /* noop */ }
        this.$forceUpdate()
      })
      // 清空统一输入框和矿池/模型选择
      this.$set(this.configDialog, 'walletAddress', '')
      this.$set(this.configDialog, 'miningAccount', '')
      if (coin && algo) {
        this.fetchGetMachineSupportPool(coin, algo)
      } else {
        this.$set(this.configDialog, 'poolOptions', [])
        this.$set(this.configDialog, 'poolValue', [])
        this.$set(this.configDialog, 'poolRawData', [])
      }
    },
    // 矿池/模型选择变化
    handlePoolValueChange(val) {
      this.$set(this.configDialog, 'poolValue', val || [])
      // 获取当前选择的矿池/模型的 walletMining 值
      const poolValue = Array.isArray(val) ? val : []
      if (poolValue.length === 2) {
        const poolId = poolValue[0]
        const coinConfigId = poolValue[1]
        const poolOption = (this.configDialog.poolOptions || []).find(p => p.value === poolId)
        if (poolOption) {
          const modelOption = (poolOption.children || []).find(m => m.value === coinConfigId)
          if (modelOption && modelOption.raw) {
            const walletMining = modelOption.raw.walletMining || false
            // 清空统一输入框
            this.$set(this.configDialog, 'walletAddress', '')
            this.$set(this.configDialog, 'miningAccount', '')
            // 清空所有未配置机器的矿工号
            const unconfigured = this.unconfiguredMachinesList
            unconfigured.forEach(machine => {
              this.$set(machine, '_walletMining', walletMining)
              this.$set(machine, '_workerId', '')
            })
          }
        }
      } else {
        // 如果未选择矿池/模型，清空统一输入框
        this.$set(this.configDialog, 'walletAddress', '')
        this.$set(this.configDialog, 'miningAccount', '')
      }
      // 强制触发视图更新
      this.$nextTick(() => {
        this.$forceUpdate()
      })
    },
    // 获取当前选择的矿池/模型的 walletMining 值
    getCurrentWalletMining() {
      const poolValue = Array.isArray(this.configDialog.poolValue) ? this.configDialog.poolValue : []
      if (poolValue.length !== 2) {
        return null
      }
      const poolId = poolValue[0]
      const coinConfigId = poolValue[1]
      const poolOption = (this.configDialog.poolOptions || []).find(p => p.value === poolId)
      if (poolOption) {
        const modelOption = (poolOption.children || []).find(m => m.value === coinConfigId)
        if (modelOption && modelOption.raw) {
          return modelOption.raw.walletMining || false
        }
      }
      return null
    },
    // 处理机器钱包地址输入
    handleMachineWalletAddressInput(machine, val) {
      if (!val) {
        this.$set(machine, '_walletAddress', '')
        return
      }
      // 只过滤中文字符，其他字符都可以输入
      // 匹配中文字符范围：\u4e00-\u9fa5 (基本汉字), \u3400-\u4dbf (扩展A), \u3000-\u303f (CJK符号和标点), \uff00-\uffef (全角字符)
      let filtered = val.replace(/[\u4e00-\u9fa5\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g, '')
      // 限制最大长度95
      if (filtered.length > 95) {
        filtered = filtered.substring(0, 95)
      }
      this.$set(machine, '_walletAddress', filtered)
    },
    // 处理机器挖矿账户输入
    handleMachineMiningAccountInput(machine, val) {
      if (!val) {
        this.$set(machine, '_miningAccount', '')
        return
      }
      // 只限制最大长度40，不限制其他输入
      let filtered = String(val)
      if (filtered.length > 40) {
        filtered = filtered.substring(0, 40)
      }
      this.$set(machine, '_miningAccount', filtered)
    },
    // 处理机器矿工号输入
    handleMachineWorkerIdInput(machine, val) {
      if (!val) {
        this.$set(machine, '_workerId', '')
        return
      }
      // 只限制最大长度40，不限制其他输入
      let filtered = String(val)
      if (filtered.length > 40) {
        filtered = filtered.substring(0, 40)
      }
      this.$set(machine, '_workerId', filtered)
    },
    // 钱包地址输入（过滤中文，限制长度26-95）
    handleWalletAddressInput(val) {
      if (!val) {
        this.$set(this.configDialog, 'walletAddress', '')
        return
      }
      // 过滤中文和特殊字符，只保留英文、数字和常见符号（如-、_等）
      // 使用 Unicode 范围匹配中文字符和其他非ASCII字符，避免使用控制字符
      let filtered = val.replace(/[^\u0020-\u007E]/g, '') // 移除所有非ASCII字符（包括中文），只保留可打印ASCII字符
      // 只允许字母、数字、连字符、下划线
      filtered = filtered.replace(/[^a-zA-Z0-9\-_]/g, '')
      // 限制最大长度95
      if (filtered.length > 95) {
        filtered = filtered.substring(0, 95)
      }
      this.$set(this.configDialog, 'walletAddress', filtered)
      // 如果过滤后长度小于26，提示（但不阻止输入）
      if (filtered.length > 0 && filtered.length < 26) {
        // 可以在这里添加提示，但为了不打断用户输入，暂时不提示
      }
      // 强制触发视图更新
      this.$nextTick(() => {
        this.$forceUpdate()
      })
    },
    // 挖矿账户输入（过滤中文）
    handleMiningAccountInput(val) {
      if (!val) {
        this.$set(this.configDialog, 'miningAccount', '')
        return
      }
      // 过滤中文和特殊字符，只保留英文、数字和常见符号
      // 使用 Unicode 范围匹配中文字符和其他非ASCII字符，避免使用控制字符
      let filtered = val.replace(/[^\u0020-\u007E]/g, '') // 移除所有非ASCII字符（包括中文），只保留可打印ASCII字符
      // 只允许字母、数字、连字符、下划线
      filtered = filtered.replace(/[^a-zA-Z0-9\-_]/g, '')
      // 限制最大长度50
      if (filtered.length > 50) {
        filtered = filtered.substring(0, 50)
      }
      this.$set(this.configDialog, 'miningAccount', filtered)
      // 强制触发视图更新
      this.$nextTick(() => {
        this.$forceUpdate()
      })
    },
    // 配置表格勾选变化
    handleConfigSelectionChange(selection) {
      const ids = (selection || []).map(r => r && r.id).filter(id => id != null)
      this.$set(this.configDialog, 'selectedMachineIds', ids)
      // 强制触发视图更新
      this.$nextTick(() => {
        this.$forceUpdate()
      })
      // 调试信息
      console.log('机器选择变化:', {
        selectionCount: selection ? selection.length : 0,
        selectedIds: ids,
        allSelected: selection || []
      })
    },
    // 生成配置弹窗中展示的“已选择机器列表”
    buildSelectedMachinesForConfig() {
      const rows = []
      if (this.pendingCheckoutAll && this.pendingCheckoutAll.length) {
        this.pendingCheckoutAll.forEach(({ shop, items }) => {
          (items || []).forEach(m => {
            rows.push({
              id: m.id,
              product: shop && (shop.name || ''),
              user: m.user,
              miner: m.miner,
              type: m.type,
              algorithm: m.algorithm
            })
          })
        })
      } else if (this.pendingCheckoutShop && this.pendingCheckoutShop.shop && Array.isArray(this.pendingCheckoutShop.payload)) {
        const shop = this.pendingCheckoutShop.shop
        const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
        const idSet = new Set(this.pendingCheckoutShop.payload.map(p => p.machineId))
        list.forEach(m => {
          if (idSet.has(m.id)) {
            rows.push({
              id: m.id,
              product: shop && (shop.name || ''),
              user: m.user,
              miner: m.miner,
              type: m.type,
              algorithm: m.algorithm
            })
          }
        })
      }
      return rows
    },
    // 验证配置表单是否有效
    isConfigFormValid() {
      const coinAlgo = Array.isArray(this.configDialog.coinAlgoValue) ? this.configDialog.coinAlgoValue : []
      const pool = Array.isArray(this.configDialog.poolValue) ? this.configDialog.poolValue : []
      const wallet = String(this.configDialog.walletAddress || '').trim()
      const account = String(this.configDialog.miningAccount || '').trim()
      
      const coinAlgoValid = coinAlgo.length === 2
      const poolValid = pool.length === 2
      const walletValid = wallet.length > 0
      const accountValid = account.length > 0
      
      const isValid = coinAlgoValid && poolValid && walletValid && accountValid
      
      // 调试信息（开发时使用）
      if (!isValid) {
        console.log('表单验证失败:', {
          coinAlgo: coinAlgo,
          coinAlgoValid,
          pool: pool,
          poolValid,
          wallet: wallet,
          walletValid,
          account: account,
          accountValid
        })
      }
      
      return isValid
    },
    // 确认配置，进入“确认结算”弹窗
    handleConfigConfirm() {
      const unconfigured = this.unconfiguredMachinesList
      
      // 如果还有未配置的机器，先保存当前配置
      if (unconfigured.length > 0) {
        // 验证是否勾选了机器：优先从表格 ref 获取，如果失败则从 selectedMachineIds 获取
        let selectedIds = []
        try {
          const table = this.$refs.configTable
          if (table && typeof table.selection === 'function') {
            const selections = table.selection() || []
            selectedIds = selections.map(r => r && r.id).filter(id => id != null)
          } else if (table && Array.isArray(table.selection)) {
            selectedIds = table.selection.map(r => r && r.id).filter(id => id != null)
          }
        } catch (e) {
          // 如果从表格获取失败，使用 selectedMachineIds
          selectedIds = this.configDialog.selectedMachineIds || []
        }
        
        // 如果从表格获取的选择为空，尝试从 selectedMachineIds 获取
        if (selectedIds.length === 0) {
          selectedIds = this.configDialog.selectedMachineIds || []
        }
        
        if (!Array.isArray(selectedIds) || selectedIds.length === 0) {
          this.$message.warning('请至少勾选一台机器进行配置')
          return
        }
        
        // 验证表单完整性
        const coinAlgo = Array.isArray(this.configDialog.coinAlgoValue) ? this.configDialog.coinAlgoValue : []
        const pool = Array.isArray(this.configDialog.poolValue) ? this.configDialog.poolValue : []
        
        if (coinAlgo.length !== 2) {
          this.$message.warning('请选择币种和算法')
          return
        }
        
        if (pool.length !== 2) {
          this.$message.warning('请选择矿池和模型')
          return
        }
        
        // 获取选中的机器（使用上面验证时获取的 selectedIds）
        const selectedMachines = unconfigured.filter(m => 
          selectedIds.includes(m.id)
        )
        
        if (selectedMachines.length === 0) {
          this.$message.warning('所选机器已配置，请选择未配置的机器')
          return
        }
        
        // 获取矿池和模型名称以及 walletMining
        const poolValue = this.configDialog.poolValue || []
        const poolId = poolValue[0]
        const coinConfigId = poolValue[1]
        
        // 从 poolOptions 中查找名称和原始数据
        let poolName = ''
        let modelName = ''
        let walletMining = false
        const poolOption = (this.configDialog.poolOptions || []).find(p => p.value === poolId)
        if (poolOption) {
          poolName = poolOption.label || ''
          const modelOption = (poolOption.children || []).find(m => m.value === coinConfigId)
          if (modelOption && modelOption.raw) {
            modelName = modelOption.label || ''
            // 从原始数据中获取 walletMining
            walletMining = modelOption.raw.walletMining || false
          }
        }
        
        // 验证统一输入字段
        if (walletMining) {
          // 支持钱包地址挖矿，需要验证钱包地址
          const wallet = String(this.configDialog.walletAddress || '').trim()
          if (!wallet || wallet.length === 0) {
            this.$message.warning('请输入钱包地址')
            return
          }
          // 验证钱包地址长度（26-95个字符）
          if (wallet.length < 26 || wallet.length > 95) {
            this.$message.warning('钱包地址长度应在26-95个字符之间')
            return
          }
        } else {
          // 不支持钱包地址挖矿，需要验证挖矿账户
          const account = String(this.configDialog.miningAccount || '').trim()
          if (!account || account.length === 0) {
            this.$message.warning('请输入挖矿账户')
            return
          }
        }
        
        // 验证每台机器的矿工号
        for (const machine of selectedMachines) {
          const workerId = String(machine._workerId || '').trim()
          if (!workerId || workerId.length === 0) {
            this.$message.warning(`机器ID ${machine.id} 请输入矿工号`)
            return
          }
        }
        
        // 保存配置（使用上面已经验证过的 coinAlgo）
        const coin = this.toUpperText(coinAlgo[0] || '')
        const algorithm = String(coinAlgo[1] || '')
        // 统一输入框的值
        const walletAddress = walletMining ? String(this.configDialog.walletAddress || '').trim() : ''
        const miningAccount = walletMining ? '' : String(this.configDialog.miningAccount || '').trim()
        
        selectedMachines.forEach(machine => {
          this.configDialog.configuredMachines.push({
            id: machine.id,
            coin: coin,
            algorithm: algorithm,
            poolName: poolName,
            modelName: modelName,
            walletAddress: walletAddress,
            miningAccount: miningAccount,
            workerId: String(machine._workerId || '').trim(), // 矿工号从每行获取
            walletMining: walletMining,
            type: machine.type,
            // 保存原始数据用于后续提交
            poolId: poolId,
            coinConfigId: coinConfigId,
            originalAlgorithm: machine.algorithm,
            // 保存完整的机器信息用于后续提交
            machineData: machine
          })
        })
        
        // 计算剩下待配置的机器ID
        const allMachineIds = new Set((this.configDialog.selectedMachines || []).map(m => m.id))
        const configuredIds = new Set((this.configDialog.configuredMachines || []).map(m => m.id))
        const remainingMachineIds = Array.from(allMachineIds).filter(id => !configuredIds.has(id))
        
        // 如果有待配置的机器，重新请求币种/算法支持数据
        if (remainingMachineIds.length > 0) {
          this.fetchGetMachineSupportCoinAndAlgorithm(remainingMachineIds).then(() => {
            // 检查是否获取到数据
            if (!this.machineSupportData || this.machineSupportData.length === 0) {
              this.$message({
                message: '加载配置信息失败，请重试',
                type: 'error',
                showClose: true
              })
              return
            }
            // 更新币种/算法选项
            const coins = Array.isArray(this.machineSupportData) ? this.machineSupportData : []
            this.configDialog.coinAlgoOptions = (coins || []).map(c => ({
              label: this.toUpperText(c && c.coin),
              value: c && c.coin,
              children: Array.isArray(c && c.algorithmList) ? c.algorithmList.map(a => ({
                label: a && a.algorithm ? String(a.algorithm) : '',
                value: a && a.algorithm ? String(a.algorithm) : ''
              })) : []
            }))
            // 如果更新后没有选项，清空选项
            if (this.configDialog.coinAlgoOptions.length === 0) {
              this.configDialog.coinAlgoOptions = []
            }
          }).catch(e => {
            console.error('重新获取币种/算法支持数据失败:', e)
            this.$message({
              message: '加载配置信息失败，请重试',
              type: 'error',
              showClose: true
            })
          })
        } else {
          // 如果没有待配置的机器，清空币种/算法选项
          this.configDialog.coinAlgoOptions = []
        }
        
        // 清空当前输入，准备下一批配置
        this.$set(this.configDialog, 'selectedMachineIds', [])
        this.$set(this.configDialog, 'poolValue', [])
        this.$set(this.configDialog, 'coinAlgoValue', [])
        this.$set(this.configDialog, 'coin', '')
        this.$set(this.configDialog, 'algorithm', '')
        // 清空统一输入框
        this.$set(this.configDialog, 'walletAddress', '')
        this.$set(this.configDialog, 'miningAccount', '')
        
        // 清空所有未配置机器的矿工号
        const remainingUnconfigured = this.unconfiguredMachinesList
        remainingUnconfigured.forEach(machine => {
          this.$set(machine, '_workerId', '')
          this.$set(machine, '_walletMining', null)
        })
        
        // 清空表格选择
        this.$nextTick(() => {
          try {
            const tb = this.$refs.configTable
            if (tb && typeof tb.clearSelection === 'function') {
              tb.clearSelection()
            }
          } catch (e) { /* noop */ }
        })
        
        // 如果还有未配置的，提示用户继续配置
        const remaining = this.unconfiguredMachinesList
        if (remaining.length > 0) {
          this.$message.success(`已配置 ${selectedMachines.length} 台机器，还有 ${remaining.length} 台待配置`)
        }
      } else {
        // 验证是否所有机器都已配置
        const allMachineIds = new Set((this.configDialog.selectedMachines || []).map(m => m.id))
        const configuredIds = new Set((this.configDialog.configuredMachines || []).map(m => m.id))
        const allConfigured = Array.from(allMachineIds).every(id => configuredIds.has(id))
        
        if (!allConfigured) {
          this.$message.warning('请完成所有机器的配置')
          return
        }
        
        // 全部配置完成，进入确认结算弹窗
        this.configDialog.visible = false
        // 按现有逻辑继续进入确认结算
        if (this.pendingCheckoutAll && this.pendingCheckoutAll.length) {
          this.showConfirmDialogAll()
        } else {
          try {
            const shop = this.pendingCheckoutShop && this.pendingCheckoutShop.shop
            if (shop) {
              const key = this.paySelectionMap[shop.id] || ''
              const [chain, coin] = String(key).split('|')
              this.selectedChain = chain || ''
              this.selectedCoin = coin || ''
            } else {
              this.selectedChain = ''
              this.selectedCoin = ''
            }
          } catch (e) {
            this.selectedChain = ''
            this.selectedCoin = ''
          }
          this.showConfirmDialog()
        }
      }
    },
    // 取消配置
    handleConfigCancel() {
      this.configDialog.visible = false
      this.configDialog.configuredMachines = []
      this.configDialog.selectedMachineIds = []
      this.configDialog.walletAddress = ''
      this.configDialog.miningAccount = ''
      this.configDialog.workerId = ''
      this.configDialog.poolValue = []
      this.configDialog.poolRawData = []
      this.configDialog.coinAlgoValue = []
      // 恢复勾选状态
      this.reapplySelectionsForPendingShop()
    },
    // 配置弹窗的上一步：返回到下单须知弹窗
    handleConfigPrev() {
      this.configDialog.visible = false
      // 重新打开下单须知弹窗
      this.noticeDialog.visible = true
      this.noticeDialog.checked = false
      this.startNoticeCountdown()
    },
    openPaySelectDialog() {
      // 已取消支付方式选择步骤，此函数保留空实现以兼容旧调用
      return
    },
    
    // 显示确认结算弹窗（支持多店铺结构，这里按当前店铺构建一个分组）
    showConfirmDialog() {
      if (!this.pendingCheckoutShop) return
      
      const { shop, payload } = this.pendingCheckoutShop
      // 打开确认前，确保一次恢复
      this.$nextTick(() => this.reapplySelectionsForPendingShop())
      const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
      
      // 通过 payload 中 machineId 来还原确认明细（仅选中的机器）
      const selectedByGroupId = new Map()
      const selectedIds = new Set(payload.map(p => p.machineId))
      
      const items = []
      list.forEach(m => {
        if (selectedIds.has(m.id) && this.isOnShelf(m)) {
          // 使用购物车中已选支付方式对应的单价
          const baseUnit = this.getMachineUnitPriceBySelection(shop, m)
          if (baseUnit == null) return
          const leaseDays = Math.max(1, Math.floor(Number(m.leaseTime || 1)))
          const unitPrice = Number(baseUnit || 0)
          const subtotal = Number(unitPrice) * leaseDays
          items.push({
            id: m.id,
            type: m.type,
            algorithm: m.algorithm || '',
            unitPrice: Number(unitPrice || 0),
            leaseTime: leaseDays,
            numbers: Number(m.numbers || 1),
            subtotal: Number(subtotal || 0)
          })
        }
      })

      // 构建对话框分组数据
      const key = this.paySelectionMap[shop.id] || ''
      const [chain, coin] = String(key).split('|')
      const coinSymbol = this.toUpperText(coin || '')
      const payLabel = `${chain} - ${coinSymbol}`
      // 当前支付方式配置（起付额/手续费）
      const cfgList = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
      const cfgHit = cfgList.find(c => String(c && c.payChain).toUpperCase() === String(chain).toUpperCase()
        && String(c && c.payCoin).toUpperCase() === String(coin).toUpperCase())
      const deductibleAmount = Number((cfgHit && cfgHit.deductibleAmount) || 0)
      const fee = Number((cfgHit && cfgHit.fee) || 0)
      const groupSubtotal = items.reduce((sum, it) => sum + Number(it.subtotal || 0), 0)
      const enough = groupSubtotal >= deductibleAmount || deductibleAmount <= 0
      const grp = {
        shopId: shop.id,
        shopName: shop.name || '',
        coinSymbol,
        payLabel,
        items,
        deductibleAmount,
        fee,
        enough,
        groupSubtotal
      }
      this.confirmDialog.shops = [grp]
      this.confirmDialog.count = items.length
      // 汇总各币种合计
      const totals = {}
      const centsAdd = (acc, v) => (acc + this.toCents(v))
      if (coinSymbol) {
        let tCents = items.reduce((acc, it) => centsAdd(acc, it.subtotal || 0), 0)
        // 若未满足起付额，合计中加入手续费
        if (!enough && fee > 0) tCents = tCents + this.toCents(fee)
        totals[coinSymbol] = Number(this.centsToText(tCents))
      }
      this.confirmDialog.totalsByCoin = totals
      this.confirmDialog.visible = true
    },
    // 多店铺：生成确认明细与多币种总额
    showConfirmDialogAll() {
      // 以当前勾选状态实时构建，避免中途数据变化造成空白
      const groups = []
      const totalsCentsByCoin = new Map()
      let count = 0
      const shops = Array.isArray(this.shops) ? this.shops : []
      shops.forEach(shop => {
        const set = this.selectedMachinesMap[shop.id]
        if (!set || !set.size) return
        const key = this.paySelectionMap[shop.id] || ''
        const [chain, coin] = String(key).split('|')
        const coinSymbol = this.toUpperText(coin || '')
        const payLabel = `${chain} - ${coinSymbol}`
        const cfgList = Array.isArray(shop && shop.payConfigList) ? shop.payConfigList : []
        const cfgHit = cfgList.find(c => String(c && c.payChain).toUpperCase() === String(chain).toUpperCase()
          && String(c && c.payCoin).toUpperCase() === String(coin).toUpperCase())
        const deductibleAmount = Number((cfgHit && cfgHit.deductibleAmount) || 0)
        const fee = Number((cfgHit && cfgHit.fee) || 0)
        const rows = []
        const list = Array.isArray(shop.productMachineDtoList) ? shop.productMachineDtoList : []
        let groupSubtotal = 0
        list.forEach(m => {
          if (!set.has(m.id) || !this.isOnShelf(m)) return
          const baseUnit = this.getMachineUnitPriceBySelection(shop, m)
          if (baseUnit == null) return
          const leaseDays = Math.max(1, Math.floor(Number(m.leaseTime || 1)))
          const unitPrice = Number(baseUnit || 0)
          const subtotal = Number(unitPrice) * leaseDays
          rows.push({
            id: m.id,
            type: m.type,
            algorithm: m.algorithm || '',
            unitPrice,
            leaseTime: leaseDays,
            numbers: Number(m.numbers || 1),
            subtotal
          })
          groupSubtotal += subtotal
          const prev = totalsCentsByCoin.get(coinSymbol) || 0
          totalsCentsByCoin.set(coinSymbol, prev + this.toCents(subtotal))
          count += 1
        })
        if (rows.length) {
          const enough = groupSubtotal >= deductibleAmount || deductibleAmount <= 0
          // 未满足起付额：总额加上手续费
          if (!enough && fee > 0) {
            const prev = totalsCentsByCoin.get(coinSymbol) || 0
            totalsCentsByCoin.set(coinSymbol, prev + this.toCents(fee))
          }
          groups.push({
            shopId: shop.id,
            shopName: shop.name || '',
            coinSymbol,
            payLabel,
            items: rows,
            deductibleAmount,
            fee,
            enough,
            groupSubtotal
          })
        }
      })
      const totalsObj = {}
      totalsCentsByCoin.forEach((val, coin) => {
        totalsObj[coin] = Number(this.centsToText(val))
      })
      this.confirmDialog.shops = groups
      this.confirmDialog.count = count
      this.confirmDialog.totalsByCoin = totalsObj
      this.confirmDialog.visible = true
    },
    // 显示谷歌验证码输入框
    showGoogleCodeDialog() {
      this.googleCodeDialog.visible = true
      this.googleCodeDialog.code = ''
      this.googleCodeDialog.error = ''
      this.googleCodeDialog.loading = false
      // 聚焦到输入框
      this.$nextTick(() => {
        if (this.$refs.googleCodeInput) {
          this.$refs.googleCodeInput.focus()
        }
      })
    },
    // 处理谷歌验证码输入
    handleGoogleCodeInput(value) {
      // 只允许输入数字
      this.googleCodeDialog.code = value.replace(/\D/g, '')
      // 清除错误信息
      if (this.googleCodeDialog.error) {
        this.googleCodeDialog.error = ''
      }
    },
    // 提交谷歌验证码
    async handleGoogleCodeSubmit() {
      if (!this.isGoogleCodeValid) {
        this.googleCodeDialog.error = '请输入6位数字验证码'
        return
      }
      
      this.googleCodeDialog.loading = true
      this.googleCodeDialog.error = ''
      
      try {
        // 执行结算，传入谷歌验证码，返回是否成功
        const success = await this.executeCheckout(this.googleCodeDialog.code)
        if (success) {
          // 只有订单创建成功时才关闭验证码弹窗
          this.googleCodeDialog.visible = false
          this.googleCodeDialog.code = ''
          this.googleCodeDialog.error = ''
        } else {
          // 订单创建失败，不关闭弹窗，显示错误提示
          this.googleCodeDialog.error = '订单创建失败，请检查后重试'
        }
      } catch (e) {
        // 捕获异常，不关闭弹窗
        this.googleCodeDialog.error = '订单创建失败，请重试'
        console.error('订单创建异常:', e)
      } finally {
        this.googleCodeDialog.loading = false
      }
    },
    // 取消谷歌验证码输入
    handleGoogleCodeCancel() {
      this.googleCodeDialog.visible = false
      this.googleCodeDialog.code = ''
      this.googleCodeDialog.error = ''
      this.googleCodeDialog.loading = false
      // 清除待结算信息
      // 先恢复勾选，再清空
      this.reapplySelectionsForPendingShop()
      this.pendingCheckoutShop = null
    },
    // 谷歌验证码弹窗的上一步：返回到确认结算弹窗
    handleGoogleCodePrev() {
      this.googleCodeDialog.visible = false
      this.googleCodeDialog.code = ''
      this.googleCodeDialog.error = ''
      this.googleCodeDialog.loading = false
      // 重新打开确认结算弹窗
      if (this.pendingCheckoutAll && this.pendingCheckoutAll.length) {
        this.showConfirmDialogAll()
      } else {
        this.showConfirmDialog()
      }
    },
    // 处理租赁天数输入变化
    handleLeaseTimeChange(machine) {
      // 确保租赁天数是大于等于1的整数
      if (machine.leaseTime < 1) {
        machine.leaseTime = 1
      } else if (machine.leaseTime > 365) {
        machine.leaseTime = 365
      } else {
        // 确保是整数
        machine.leaseTime = Math.floor(machine.leaseTime)
      }
    },
    // 该行可购买的最大数量：基于 canSaleNumbers
    getRowMaxPurchaseForCart(row) {
      const n = Number(row && row.canSaleNumbers)
      if (!Number.isFinite(n) || n < 1) return 1
      return Math.floor(n)
    },
    // 处理购买数量变化：仅 ASIC 可改动，范围 [1, canSaleNumbers]
    handleNumbersChange(row, value) {
      if (!row) return
      // GPU 不可改动，恢复为原值或 1
      if (Number(row.type) === 1) {
        try { this.$set(row, 'numbers', Number(row.numbers || 1)) } catch (e) { row.numbers = 1 }
        return
      }
      let v = Number(value)
      if (!Number.isFinite(v)) v = 1
      if (v < 1) v = 1
      const max = this.getRowMaxPurchaseForCart(row)
      if (v > max) v = max
      v = Math.floor(v)
      try { this.$set(row, 'numbers', v) } catch (e) { row.numbers = v }
    },
    // 处理租赁天数输入过程中的验证
    handleLeaseTimeInput(machine, value) {
      // 只允许输入数字
      if (value === '' || value === null || value === undefined) {
        machine.leaseTime = 1
        return
      }
      
      // 转换为数字
      const numValue = Number(value)
      
      // 如果不是有效数字，重置为1
      if (isNaN(numValue)) {
        machine.leaseTime = 1
        return
      }
      
      // 如果是小数，取整数部分
      if (numValue % 1 !== 0) {
        machine.leaseTime = Math.floor(numValue)
        return
      }
      
      // 如果小于1，设置为1
      if (numValue < 1) {
        machine.leaseTime = 1
        return
      }
      
      // 如果大于最大值，设置为最大值
      if (numValue > 365) {
        machine.leaseTime = 365
        return
      }
      
      // 设置值
      machine.leaseTime = numValue
    },
    // 处理商品分组展开/收起：若该分组被选中，展开后自动勾选其子级机器
    handleProductExpandChange(shop, product, expandedRows) {
      const table = this.$refs['productTable-' + (shop && shop.id)]
      if (!table || !product) return
      const selectedRows = table.selection || []
      const isSelected = Array.isArray(selectedRows) && selectedRows.some(r => r && r.id === product.id)
      if (!isSelected) return
      // 仅在选中情况下，展开后恢复子级勾选
      this.$nextTick(() => {
        this.applyInnerSelection(product, true)
      })
    },
    // 判断某商品分组是否被其父表勾选（备用）
    isProductSelected(shop, product) {
      const table = this.$refs['productTable-' + (shop && shop.id)]
      if (!table || !product) return false
      const selectedRows = table.selection || []
      return Array.isArray(selectedRows) && selectedRows.some(r => r && r.id === product.id)
    },
    formatPayTooltip(item) {
      return `${item.payChain} - ${this.toUpperText(item.payCoin)}`
    },
    // 矿机种类格式化：0-ASIC，1-GPU
    formatMachineType(val) {
      const n = Number(val)
      if (n === 0) return 'ASIC'
      if (n === 1) return 'GPU'
      return ''
    },
    // 格式化钱包地址显示（中间省略）
    formatWalletAddress(address) {
      if (!address || typeof address !== 'string') return '-'
      const addr = address.trim()
      if (addr.length <= 20) return addr
      // 显示前10个字符和后10个字符，中间用...连接
      return `${addr.substring(0, 10)}...${addr.substring(addr.length - 10)}`
    },
    // 返回矿机类型标签样式类
    typeClass(val) {
      const n = Number(val)
      if (n === 1) return 'gpu'   // GPU
      if (n === 0) return 'asic'  // ASIC
      return ''
    }
  }
}
</script>

<style scoped>
.cart-page {
  max-width: 90vw;
  margin: 0 auto;
  padding: 20px;
  min-height: 80vh;
}

.page-title {
  text-align: center;
  color: #2c3e50;
  margin-bottom: 30px;
  font-size: 28px;
  font-weight: 600;
}

.loading {
  text-align: center;
  padding: 60px 20px;
  color: #666;
}

.empty-cart {
  text-align: center;
  padding: 80px 20px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.empty-icon {
  font-size: 64px;
  margin-bottom: 20px;
}

.empty-cart h2 {
  color: #2c3e50;
  margin-bottom: 12px;
  font-size: 24px;
}

.empty-cart p {
  color: #666;
  margin-bottom: 24px;
  font-size: 16px;
}

.shop-now-btn {
  display: inline-block;
  background: #42b983;
  color: white;
  text-decoration: none;
  padding: 12px 24px;
  border-radius: 8px;
  font-size: 16px;
  font-weight: 600;
  transition: background 0.3s ease;
}

.shop-now-btn:hover {
  background: #3aa876;
}

.cart-content { margin-top: 12px; }

.cart-items {
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

.cart-item {
  display: grid;
  grid-template-columns: auto 1fr auto auto auto;
  gap: 20px;
  align-items: center;
  padding: 20px;
  border-bottom: 1px solid #eee;
}

.cart-item:last-child {
  border-bottom: none;
}

.item-image img {
  width: 80px;
  height: 80px;
  object-fit: cover;
  border-radius: 8px;
}

.item-info {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.item-title {
  font-size: 16px;
  font-weight: 600;
  color: #2c3e50;
  margin: 0;
}

.item-price {
  font-size: 18px;
  font-weight: 700;
  color: #e74c3c;
}

.item-quantity {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
}

.quantity-label {
  font-size: 14px;
  color: #666;
}

.quantity-controls {
  display: flex;
  align-items: center;
  border: 1px solid #ddd;
  border-radius: 6px;
  overflow: hidden;
}

.quantity-btn {
  background: #f8f9fa;
  border: none;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 16px;
  font-weight: 600;
  color: #495057;
  transition: background 0.3s ease;
}

.quantity-btn:hover:not(:disabled) {
  background: #e9ecef;
}

.quantity-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.quantity-input {
  width: 60px;
  padding: 8px;
  border: none;
  text-align: center;
  font-size: 14px;
  outline: none;
}

.item-total {
  text-align: center;
}

.total-label {
  font-size: 14px;
  color: #666;
}

.total-price {
  font-size: 18px;
  font-weight: 700;
  color: #e74c3c;
}

.item-actions {
  text-align: center;
}

.remove-btn {
  background: #ff4757;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.3s ease;
}

.remove-btn:hover {
  background: #ff3742;
}

.cart-summary {
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  padding: 24px;
  height: fit-content;
  position: sticky;
  top: 20px;
}

.summary-title {
  font-size: 20px;
  font-weight: 600;
  color: #2c3e50;
  margin: 0 0 20px 0;
  text-align: center;
}

.summary-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 0;
  border-bottom: 1px solid #eee;
  font-size: 16px;
  color: #666;
}

.summary-row:last-of-type {
  border-bottom: none;
}

.total-row {
  font-size: 18px;
  font-weight: 600;
  color: #2c3e50;
  border-top: 2px solid #eee;
  padding-top: 16px;
  margin-top: 16px;
}

.total-amount {
  color: #e74c3c;
  font-size: 24px;
}

.summary-actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 24px;
}

.clear-cart-btn {
  background: #6c757d;
  color: white;
  border: none;
  padding: 12px 20px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 16px;
  transition: background 0.3s ease;
}

.clear-cart-btn:hover {
  background: #5a6268;
}

.checkout-btn {
  background: #42b983;
  color: white;
  text-decoration: none;
  padding: 16px 24px;
  border-radius: 8px;
  text-align: center;
  font-size: 18px;
  font-weight: 600;
  transition: all 0.3s ease;
}

.checkout-btn:hover:not(.disabled) {
  background: #3aa876;
  transform: translateY(-2px);
}

.checkout-btn.disabled {
  background: #ccc;
  cursor: not-allowed;
  transform: none;
}
.summary-inline{
  text-align: left;
}

.price-strong {
  font-weight: 700;
  color: #e74c3c;
}
.type-tag {
  border: 1px solid transparent;
  font-weight: 600;
  padding: 0px 10px;
}
.type-tag.gpu {
  color: #6d28d9;           /* 紫色文字 */
  background: #ede9fe;      /* 很浅的紫底 */
  border-color: #c4b5fd;
}
.type-tag.asic {
  color: #409EFF;           /* 深蓝青文字 */
  background: #e0f2fe;      /* 淡蓝底 */
  border-color: #93c5fd;    /* 浅蓝边框 */
}

/* 响应式设计 */
@media (max-width: 768px) { .cart-page { padding: 16px; } .page-title { font-size: 24px; margin-bottom: 24px; } }

.notice-content { text-align: left; color: #333; }
.notice-title { font-size: 15px; font-weight: 600; color: #333; margin: 0; margin-top: 18px; }
.notice-list { padding-left: 18px; line-height: 1.8;margin-top: 10px; }
.notice-list li { margin-bottom: 10px; }
.notice-ack { margin-top: 12px;color: #e74c3c; }

/* 谷歌验证码弹窗样式 */
.google-code-content {
  text-align: center;
  padding: 20px 0;
}

.verification-icon {
  margin-bottom: 20px;
}

.verification-title h3 {
  color: #333;
  font-size: 20px;
  font-weight: 600;
  margin: 0 0 8px 0;
}

.verification-desc {
  color: #666;
  font-size: 14px;
  line-height: 1.5;
  margin: 0 0 24px 0;
}

.code-input-wrapper {
  margin-bottom: 16px;
}

.code-input {
  width: 280px;
}

.code-input :deep(.el-input__inner) {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 2px;
  text-align: center;
}

.code-error {
  color: #f56c6c;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

.dialog-footer {
  text-align: center;
}

.amount-more {
  font-size: 12px;
  color: #94a3b8; /* slate-400 */
  margin-left: 4px;
}

.num-strong {
  font-weight: inherit;
  color: inherit;
}


::v-deep .el-input__prefix, .el-input__suffix{
  top:24%;
}
::v-deep .el-input--mini .el-input__icon{
  line-height: 0px;
}
/* 禁用展开箭头的点击（彻底防止用户折叠） */
::v-deep .el-table .el-table__expand-icon {
  pointer-events: none;
}

/* 钱包地址格式化显示样式 */
.mono-ellipsis {
  font-family: monospace;
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}

/* 下架商品行样式 */
:deep(.off-shelf-row) {
  background-color: #f5f5f5 !important;
}

:deep(.off-shelf-row:hover > td) {
  background-color: #e8e8e8 !important;
}
</style> 