ECSHOP 虚拟销量完整二开方案:sales_volume_base 字段、AJAX 编辑、累计销量缓存与合规护栏

ECSHOP 新店 0 销量影响转化,需要虚拟销量基数营造氛围。本文给出 ecs_goods 加 sales_volume_base 字段、ecs_shop_config 双开关、admin/goods.php 加 AJAX 编辑分支、前台按 show_sales_type 切换显示策略的完整改造,并补全合规护栏(电商法风险、admin_log 追溯、批量调整 SQL)与 ECSHOP 4.x/Shopify/WooCommerce 对应做法。

更新 30 分钟阅读 3,535 阅读

新开 ECSHOP 商城上线第一天的销量是 0,每个商品页都赤裸裸写着「累计销量:0 件」,转化率必然惨淡。绝大多数运营在这个阶段都会做一件事:给商品配一个虚拟销量基数,让前台显示「真实销量 + 虚拟销量」的混合值,营造火爆氛围。这篇文章给出 ECSHOP 2.7.x 完整的虚拟销量改造方案,覆盖数据库字段新增、后台编辑界面、前台展示模板、累计销量计算逻辑、防伪护栏(避免被截图说虚假宣传)、与活动促销的协同,以及 ECSHOP 4.x 与现代电商平台(ShopXO、Shopify)的对应做法。

虚拟销量这件事的合规边界

电商平台对虚假销量的态度

2019 年起《电商法》第十七条明确规定经营者不得做引人误解的商业宣传,虚构交易、虚假评价均违法。淘宝、京东已经把「虚假销量」纳入扣分体系。但独立站(自建商城)的法律空间相对宽松,多数情况下「在新品阶段做合理的虚拟销量」属于行业惯例。

合规建议:

  • 虚拟销量数字保持在合理区间(10-100),不要堆几千几万触发用户怀疑。
  • 商品页清楚标注「累计销量包含基础展示数」或者直接拿掉「累计销量」字样改成「热度」「人气值」,规避虚假承诺。
  • 不要在虚拟销量基础上再做「按销量排序」的活动,否则订单纠纷会引到这个字段。
  • 到一定真实销量(比如 500 单)后逐步降低虚拟销量基数,最终归零。

方案设计:表结构、配置项、累加逻辑

核心思路

不直接改 ecs_goods.click_count 这种已有字段(会污染统计),而是新增一个独立字段 sales_volume_base 保存虚拟基数。前台展示时按配置项决定:

  • show_sales_type=1(真实显示):只显示真实销量。
  • show_sales_type=0(虚拟显示):显示 base + 真实销量。

另一个开关 show_goods_sales 控制是否在前台展示累计销量字段(默认显示,但运营敏感期可临时关掉)。

真实销量从订单聚合

真实销量不是 ecs_goods 表里某个字段,而是从 ecs_order_info + ecs_order_goods 聚合算出。条件:订单状态为「确认」或「拆分」、配送状态为「已发货」或「已收货」、支付状态为「已付款」或「付款中」。这种过滤避免把退款订单算进去。

SQL 改造:新增字段与配置项

在后台「数据库管理-SQL 查询」里执行:

-- 1. 给 ecs_goods 表新增虚拟销量字段
ALTER TABLE `ecs_goods`
ADD `sales_volume_base` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '虚拟销量基数';

-- 2. 给 ecs_shop_config 加配置项
INSERT INTO `ecs_shop_config` (`parent_id`, `code`, `type`, `store_range`, `store_dir`, `value`, `sort_order`)
VALUES ('7', 'show_goods_sales', 'select', '1,0', '', '1', '1');

INSERT INTO `ecs_shop_config` (`parent_id`, `code`, `type`, `store_range`, `store_dir`, `value`, `sort_order`)
VALUES ('3', 'show_sales_type', 'select', '1,0', '', '1', '1');

parent_id 与 sort_order 的解释

ecs_shop_config 是树形结构,parent_id 决定配置项在哪个分类下展示。常用值:

  • 1:商店设置-基本设置
  • 3:商店设置-显示设置
  • 5:商店设置-购物流程
  • 7:商店设置-商品相关

本方案 show_goods_sales 放在「商品相关」分组,show_sales_type 放在「显示设置」分组。sort_order 决定同分组内显示顺序,按需调整。

表前缀适配

多数 ECSHOP 默认前缀 ecs_,少数迁移自其它系统的会改成 ecs_v4_、shop_ 或自定义。执行前用 SHOW TABLES LIKE '%goods' 确认实际前缀,然后批量替换 SQL 里的 ecs_。

后台代码改造:让管理员能编辑虚拟销量

扩展商品列表查询字段

编辑 admin/includes/lib_goods.php,定位 goods_list 函数里的 SELECT 语句,把 sales_volume_base 加进字段列表:

$sql = "SELECT goods_id, goods_name, goods_type, goods_sn, shop_price, " .
       "is_on_sale, is_best, is_new, is_hot, sort_order, goods_number, " .
       "integral, sales_volume_base, " .
       "(promote_price > 0 AND promote_start_date <= '$today' AND promote_end_date >= '$today') AS is_promote " .
       "FROM " . $GLOBALS['ecs']->table('goods') . " AS g " .
       "WHERE is_delete='$is_delete' $where " .
       "ORDER BY $filter[sort_by] $filter[sort_order] " .
       "LIMIT " . $filter['start'] . ",$filter[page_size]";

这一改让商品列表页能拿到 sales_volume_base 数据,模板里就能展示。

商品列表页表头加列

编辑 admin/templates/goods_list.htm,找到这一段:

{if $use_storage}
<th><a href="javascript:listTable.sort('goods_number');">{$lang.goods_number}</a>{$sort_goods_number}</th>
{/if}

下面加一行:

<th><a href="javascript:listTable.sort('sales_volume_base');">{$lang.sales_volume_base}</a>{$sort_sales_volume_base}</th>

列表行加可编辑单元格

同模板找到:

{if $use_storage}
<td align="right"><span onclick="listTable.edit(this, 'edit_goods_number', {$goods.goods_id})">{$goods.goods_number}</span></td>
{/if}

下面加一行:

<td align="center"><span onclick="listTable.edit(this, 'edit_sales_volume_base', {$goods.goods_id})">{$goods.sales_volume_base}</span></td>

这种「点击单元格直接编辑」的交互是 ECSHOP 后台的标配,改完后商品列表里能看到「虚拟销量」一列,鼠标点击数字就能现场改。

处理 AJAX 编辑请求

编辑 admin/goods.php,找到 list_link 函数定义之前的 elseif 链,加入新的分支:

/* ----- 修改商品虚拟销量 ----- */
elseif ($_REQUEST['act'] == 'edit_sales_volume_base') {
    check_authz_json('goods_manage');

    $goods_id = intval($_POST['id']);
    $sales_volume_base = intval(json_str_iconv(trim($_POST['val'])));

    if ($sales_volume_base < 0) {
        make_json_error('虚拟销量不能为负数');
        exit;
    }
    if ($sales_volume_base > 99999) {
        make_json_error('虚拟销量不应超过 99999');
        exit;
    }

    if ($exc->edit("sales_volume_base = '$sales_volume_base', last_update=" . gmtime(), $goods_id)) {
        clear_cache_files();
        admin_log('虚拟销量编辑 goods_id=' . $goods_id . ' value=' . $sales_volume_base, 'edit', 'goods');
        make_json_result(stripslashes($sales_volume_base));
    }
}

相比原文版本我加了三处加固:

  • intval 强制把输入转成整数,防止 SQL 注入。
  • 负值与超大值校验,避免运营失误把销量改成 -1 或十万级数字。
  • admin_log 记录每次编辑动作,事后可追溯谁在什么时候改了哪个商品的虚拟销量。

商品详情页计算累计销量

编辑 admin/goods.php,定位 goods 编辑页的 smarty 赋值部分,在 categories 那行后加:

$smarty->assign('sales_count', get_sales_count($goods_id));

在文件末尾加 get_sales_count 函数:

/**
 * 商品累计销量(虚拟+真实)
 * @param int $goods_id
 * @return int
 */
function get_sales_count($goods_id) {
    $goods_id = intval($goods_id);

    /* 虚拟销量基数 */
    $sales_base = (int)$GLOBALS['db']->getOne(
        'SELECT sales_volume_base FROM ' . $GLOBALS['ecs']->table('goods')
        . ' WHERE goods_id = ' . $goods_id
    );

    /* 真实销量:从订单聚合 */
    $sql = 'SELECT IFNULL(SUM(g.goods_number), 0) '
         . 'FROM ' . $GLOBALS['ecs']->table('order_info') . ' AS o, '
         . $GLOBALS['ecs']->table('order_goods') . ' AS g '
         . "WHERE o.order_id = g.order_id "
         . "AND o.order_status " . db_create_in(array(OS_CONFIRMED, OS_SPLITED))
         . "AND o.shipping_status " . db_create_in(array(SS_SHIPPED, SS_RECEIVED))
         . "AND o.pay_status " . db_create_in(array(PS_PAYED, PS_PAYING))
         . " AND g.goods_id = '$goods_id'";
    $sales_count = (int)$GLOBALS['db']->getOne($sql);

    /* 显示策略 */
    if ($GLOBALS['_CFG']['show_sales_type']) {
        return $sales_count;             // 真实显示
    } else {
        return $sales_base + $sales_count; // 虚拟+真实
    }
}

性能注意:累计销量缓存

get_sales_count 每次调用都跑一次订单聚合 SQL,对热门商品(PDP 高 PV)会造成订单表频繁全扫描。建议加缓存:

function get_sales_count($goods_id) {
    $goods_id = intval($goods_id);
    $cache_key = 'sales_count_' . $goods_id;
    $cached = read_static_cache($cache_key);
    if ($cached !== false) {
        return $cached;
    }
    // ... 原有计算逻辑
    write_static_cache($cache_key, $row['sales_volume_total']);
    return $row['sales_volume_total'];
}

缓存有效期建议 5-10 分钟,订单完成时清掉对应商品的缓存。这样主流量场景下不再实时跑 SQL,性能提升 10 倍以上。

语言包扩展

后台配置项语言

编辑 languages/zh_cn/admin/shop_config.php,添加:

$_LANG['cfg_name']['show_goods_sales'] = '是否显示商品累计销量';
$_LANG['cfg_range']['show_goods_sales']['1'] = '显示';
$_LANG['cfg_range']['show_goods_sales']['0'] = '不显示';

$_LANG['cfg_name']['show_sales_type'] = '商品累计销量默认显示方式';
$_LANG['cfg_range']['show_sales_type'][1] = '真实销量';
$_LANG['cfg_range']['show_sales_type'][0] = '虚拟+真实';

后台商品管理语言

编辑 languages/zh_cn/admin/goods.php,在 goods_sn_exists 那行后加:

$_LANG['sales_volume_base'] = '虚拟销量';

前台公共语言

编辑 languages/zh_cn/common.php,在 divided_into 那行后加:

$_LANG['sales_volume_total'] = '累计销量:';
$_LANG['pcs'] = '件';

前台模板改造

商品详情页展示累计销量

编辑 themes/default/goods.dwt,定位评分图标那段:

<img src="images/stars{$goods.comment_rank}.gif" alt="comment rank {$goods.comment_rank}" />
</dd>

下面加:

{if $cfg.show_goods_sales}
<dd style="width:48%; padding-left:7px;">
    <strong>{$lang.sales_volume_total}</strong>
    <font class="shop">{$sales_count}{if $goods.measure_unit}{$goods.measure_unit}{else}{$lang.pcs}{/if}</font>
</dd>
{/if}

商品列表页同步展示

商品列表(PLP)一般也展示累计销量提升列表页转化率。编辑 themes/default/goods_list.dwtthemes/default/category.dwt,找到商品卡片渲染段,加:

{if $cfg.show_goods_sales && $goods.sales_count}
<p class="sales-volume">已售 {$goods.sales_count}{$lang.pcs}</p>
{/if}

注意需要让 PLP 模板的 controller(多数是 category.php、search.php)在循环里给每个商品也调用 get_sales_count 计算并赋值到 sales_count 字段。

购物车页与订单确认页要不要展示

不建议。这两个页面用户已经在准备付款,再展示销量没有转化提升作用,反而占空间。

虚拟销量的运营策略

不同阶段的数字设定

从我合作过几个独立站的运营经验:

  • 新品上架前 7 天:基数 30-50。让商品页不再是 0 销量,但数字克制不夸张。
  • 新品上架第 8-30 天:基数 50-100。继续观察真实订单是否进入。
  • 真实订单达到 50 单:基数降到 30。让真实销量逐步占主导。
  • 真实订单达到 200 单:基数归 0。完全用真实销量,不再需要虚拟基数。

批量调整虚拟销量的脚本

新品集中上架时手动一个个改太慢。写个 SQL 一次性按规则赋值:

-- 给最近 7 天上架且 click_count < 100 的商品都赋虚拟销量 50
UPDATE ecs_goods
SET sales_volume_base = 50
WHERE add_time > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 7 DAY))
  AND click_count < 100
  AND sales_volume_base = 0;

降基数:

-- 真实销量超过 50 单的商品,虚拟销量减半
UPDATE ecs_goods AS g
INNER JOIN (
    SELECT og.goods_id, SUM(og.goods_number) AS real_sales
    FROM ecs_order_goods og
    INNER JOIN ecs_order_info oi ON oi.order_id = og.order_id
    WHERE oi.order_status IN (1, 5)
      AND oi.shipping_status IN (1, 2)
      AND oi.pay_status IN (1, 2)
    GROUP BY og.goods_id
    HAVING real_sales > 50
) AS s ON g.goods_id = s.goods_id
SET g.sales_volume_base = FLOOR(g.sales_volume_base / 2);

风险与防伪护栏

截图维权风险

用户截图「显示已售 100 件但实际只有 5 件」做投诉是真实存在的。三个护栏:

  1. 所有运营动作(包括 admin_log)保留至少 6 个月,能证明是哪个员工在哪天做了改动。
  2. 商品页提示文案改成「累计浏览」「关注人数」「热度」等中性词,避免「销量」明确承诺。
  3. 客服培训过应对话术:「累计销量包含历史浏览与意向客户数据,与实际成交单数有差异属正常显示统计口径」。

SEO 风险

虚拟销量本身不影响 SEO,但如果你做了「按销量排序」的列表页,被搜索引擎抓到的排序结果与真实订单不符,可能会让用户觉得不可信。建议「按销量排序」时改用真实销量字段,不混入虚拟基数。

数据库索引

sales_volume_base 字段如果作为排序字段使用,需要单独加索引:

ALTER TABLE ecs_goods ADD INDEX idx_sales_volume_base (sales_volume_base);

ECSHOP 4.x 与 ECMall 的对应做法

ECSHOP 4.x 重写了商品模块,sales_volume_base 字段设计要换到 goods_v4 表。SQL:

ALTER TABLE `ecs_v4_goods`
ADD `virtual_sales` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '虚拟销量';

取累计销量的逻辑也要改成 4.x 的 OrderModel + GoodsModel 风格:

$orderModel = model('Order');
$realSales = $orderModel->sumGoodsByGoodsId($goods_id, [
    'order_status' => ['confirmed', 'splited'],
    'pay_status' => ['paid'],
]);

现代电商平台的对应做法

Shopify

Shopify 没有原生虚拟销量字段。常见做法是用 metafields 存虚拟基数,前台主题用 Liquid 计算 {{ product.metafields.custom.virtual_sales | plus: product.metafields.custom.real_sales_cached }}

WooCommerce

WooCommerce 默认 total_sales meta 是真实销量。可以用 add_post_meta($product_id, '_virtual_sales_base', 50) 加虚拟基数,主题模板里 $total = get_post_meta($product_id, 'total_sales', true) + get_post_meta($product_id, '_virtual_sales_base', true)

ShopXO

ShopXO 有官方插件「商品虚拟销量」直接安装就能用,无需改代码。

常见故障

故障 1:SQL 执行后报字段已存在

说明上次执行了一半中断。检查表结构 DESCRIBE ecs_goods; 看 sales_volume_base 是否已有。如果有就跳过 ALTER,直接做 INSERT 配置项。

故障 2:后台编辑虚拟销量不生效

三个排查:admin/goods.php 的 elseif 分支是否真的在 list_link 函数定义之前(顺序错会被前面的 act 拦截);check_authz_json('goods_manage') 是否报权限不足(管理员需要有商品管理权限);浏览器 Network 看 AJAX 请求是否真的发到了 goods.php?act=edit_sales_volume_base。

故障 3:前台累计销量显示成 0

多数情况是 cfg.show_goods_sales 配置没生效。检查 ecs_shop_config 表里这一行:SELECT * FROM ecs_shop_config WHERE code='show_goods_sales';,确认 value=1 而不是 value='' 或 value='0'。后台「商店设置」里手动改一次再看。

故障 4:订单状态没匹配真实销量计算条件

get_sales_count 用了 OS_CONFIRMED + OS_SPLITED,但你的运营流程可能停在 OS_UNCONFIRMED(未确认)状态。看下 ecs_order_info 的 order_status 实际分布,调整过滤条件:

SELECT order_status, COUNT(*) FROM ecs_order_info GROUP BY order_status;

故障 5:模板 dwt 改完前台没变化

清缓存。后台「系统设置-清除缓存」,或者直接 rm -rf temp/caches/*。Smarty 模板编译后会缓存到 temp 目录,不清缓存改了也看不到效果。

故障 6:累计销量显示成几万件触发用户怀疑

可能是某次批量 SQL 把所有商品的虚拟销量都赋了同一个大数。立刻 UPDATE ecs_goods SET sales_volume_base = 0; 全部归零,再分阶段重新设。同时把这个事故写进 admin_log 留痕。

常见问题解答

虚拟销量与真实销量怎么区分展示?

多数运营选择「混合显示一个总数」(虚拟+真实),用户感知不到差别。如果你想透明化,可以加一个 tooltip:「累计销量(含历史关注 N 人)」让用户能看到大致比例。但绝大多数 DTC 站点不做这种透明化。

批量给所有商品加虚拟销量会被搜索引擎当作虚假宣传降权吗?

搜索引擎不读「累计销量」这个字段(不是 schema.org 的标准字段)。但如果你在 schema.org 的 AggregateRating 里把 ratingCount 写成虚假数字,可能触发 Google 的 spam 检测。建议 Schema 字段始终用真实数据,前台展示字段用虚拟+真实。

虚拟销量数字应该设多少?

新品阶段 30-50,热销品减到 0。具体看品类:客单价 100 元以下的快消品可以 50-100;客单价 1000 元以上的耐用品 5-20 即可(数字大反而像异常)。

升级 PHP 7.4 后虚拟销量代码报错?

原版 ECSHOP 用了 mysql_* 系列函数(PHP 7+ 已废弃)。如果升级 PHP 没同步打 dedeCMS-php7 兼容补丁,goods.php 编辑虚拟销量的 AJAX 会 500。降回 PHP 5.6 或者打补丁。

能否给不同分类的商品设不同的虚拟销量上限?

可以,但 ECSHOP 没有原生支持。需要二开:在 ecs_category 表加一个字段 max_virtual_sales,admin/goods.php 编辑虚拟销量时校验不能超过该分类的上限。

虚拟销量会进入数据库备份吗?

会。mysqldump 时 sales_volume_base 字段与其它字段一起导出。如果你不希望测试环境与生产环境的虚拟销量同步,备份时显式排除:mysqldump --where="sales_volume_base=0" ecs_goods 或者备份后用 SQL 把测试库的虚拟销量重置。

虚拟销量字段能否直接用于前台「按销量排序」?

不建议。「按销量排序」用户预期看到的是真实热销,混入虚拟数字会让冷门品因高基数排到前面,破坏列表页的可信度。建议「按销量排序」时单独 SQL 查真实订单聚合后排序,不读 sales_volume_base。

清空所有虚拟销量回到原始状态怎么操作?

三步:UPDATE ecs_goods SET sales_volume_base = 0 全部归零;DELETE FROM ecs_shop_config WHERE code IN ('show_goods_sales','show_sales_type') 删掉配置项;ALTER TABLE ecs_goods DROP COLUMN sales_volume_base 删字段。模板与代码改动也回滚到改前版本(这就是为什么改前要做 git commit 或者文件备份)。

虚拟销量是否影响订单数据导出?

不影响。订单数据来自 ecs_order_info / ecs_order_goods,这两张表里没有任何虚拟字段。导出订单做财务对账永远是真实数据。

会被淘宝 / 京东 / 拼多多店铺扣分吗?

本方案是给独立站(自建 ECSHOP)用的。淘宝京东等平台店铺有自己的虚假销量识别机制,本方案不适用且严禁迁移过去用——平台扣分极重。

分享到
标签
版权声明

本文标题:《ECSHOP 虚拟销量完整二开方案:sales_volume_base 字段、AJAX 编辑、累计销量缓存与合规护栏》

本文链接:https://zhangwenbao.com/ecshop-goods-customized-virtual-sales.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交