diff --git a/addons/shopro/filter/user/UserFilter.php b/addons/shopro/filter/user/UserFilter.php
index 6a4f921..907ca24 100755
--- a/addons/shopro/filter/user/UserFilter.php
+++ b/addons/shopro/filter/user/UserFilter.php
@@ -3,12 +3,175 @@
namespace addons\shopro\filter\user;
use addons\shopro\filter\BaseFilter;
+use addons\shopro\service\partner\PartnerConfig;
use think\db\Query;
+use app\admin\model\shopro\commission\Agent as AgentModel;
+use app\admin\model\shopro\user\User as UserModel;
/**
- * 用户管理
+ * 用户管理 Filter 过滤器
*/
class UserFilter extends BaseFilter
{
protected $keywordFields = ['id', 'username', 'nickname', 'mobile', 'email'];
+
+ /**
+ * 身份筛选:品酒师 / 创客 / 酒道馆 / 区县代理 / 合伙股东
+ */
+ public function identity($query, $data)
+ {
+ $value = $this->plainValue($data);
+ if (!$value) {
+ return $query;
+ }
+
+ $current = $query->getTable();
+ $agentTable = (new AgentModel())->getQuery()->getTable();
+
+ if ($value === 'taster') {
+ return $query->where(function (Query $q) use ($current, $agentTable) {
+ $q->whereExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id')->where('level', 1);
+ })->whereOrNotExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id');
+ });
+ });
+ }
+ if ($value === 'maker') {
+ return $query->whereExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id')->where('level', '>=', 2);
+ });
+ }
+ if ($value === 'venue') {
+ return $query->whereExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id')->where('is_venue', 1);
+ });
+ }
+ if ($value === 'county') {
+ return $query->whereExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id')->where('is_county', 1);
+ });
+ }
+ if ($value === 'partner') {
+ return $query->whereExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id')->where('is_partner', 1);
+ });
+ }
+ return $query;
+ }
+
+ /**
+ * 创客等级筛选:V1 ~ V6 (对应 level = 2 ~ 7)
+ */
+ public function makerLevel($query, $data)
+ {
+ $value = $this->plainValue($data);
+ if ($value === '' || $value === null) {
+ return $query;
+ }
+ $level = intval($value);
+ if ($level < 1 || $level > 7) {
+ return $query;
+ }
+ // 如果传入 1~6(即 V1~V6),映射为系统 level = 2~7;若传入 2~7,直接使用
+ $targetLevel = ($level <= 6) ? ($level + 1) : $level;
+
+ $current = $query->getTable();
+ $agentTable = (new AgentModel())->getQuery()->getTable();
+
+ return $query->whereExists(function ($sub) use ($current, $agentTable, $targetLevel) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id')->where('level', $targetLevel);
+ });
+ }
+
+ /**
+ * 30天自购活跃状态筛选
+ */
+ public function active30d($query, $data)
+ {
+ $value = $this->plainValue($data);
+ if ($value === '' || $value === null) {
+ return $query;
+ }
+ $days = PartnerConfig::activeDays();
+ $since = time() - $days * 86400;
+
+ $current = $query->getTable();
+ $agentTable = (new AgentModel())->getQuery()->getTable();
+
+ if ($value === '1' || $value === 1 || $value === 'yes') {
+ return $query->whereExists(function ($sub) use ($current, $agentTable, $since) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id')
+ ->where('last_self_order_time', '>=', $since);
+ });
+ } else {
+ return $query->where(function (Query $q) use ($current, $agentTable, $since) {
+ $q->whereNotExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id');
+ })->whereOrExists(function ($sub) use ($current, $agentTable, $since) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id')
+ ->where(function (Query $sq) use ($since) {
+ $sq->whereNull('last_self_order_time')->whereOr('last_self_order_time', '<', $since);
+ });
+ });
+ });
+ }
+ }
+
+ /**
+ * 是否具备分销商/收益档案
+ */
+ public function hasAgent($query, $data)
+ {
+ $value = $this->plainValue($data);
+ if ($value === '' || $value === null) {
+ return $query;
+ }
+ $current = $query->getTable();
+ $agentTable = (new AgentModel())->getQuery()->getTable();
+
+ if ($value === '1' || $value === 1 || $value === 'yes') {
+ return $query->whereExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id');
+ });
+ } else {
+ return $query->whereNotExists(function ($sub) use ($current, $agentTable) {
+ $sub->table($agentTable)->where($agentTable . '.user_id=' . $current . '.id');
+ });
+ }
+ }
+
+ /**
+ * 推荐人检索
+ */
+ public function parentUser($query, $data)
+ {
+ $value = $this->plainValue($data);
+ if ($value === '' || $value === null) {
+ return $query;
+ }
+ if (is_numeric($value)) {
+ return $query->where('parent_user_id', intval($value));
+ }
+
+ $current = $query->getTable();
+ $userTable = (new UserModel())->getQuery()->getTable();
+
+ return $query->whereExists(function ($sub) use ($current, $userTable, $value) {
+ $sub->table($userTable)->where($userTable . '.id=' . $current . '.parent_user_id')
+ ->where(function (Query $q) use ($value) {
+ $q->where('nickname', 'like', "%{$value}%")
+ ->whereOr('mobile', 'like', "%{$value}%")
+ ->whereOr('username', 'like', "%{$value}%");
+ });
+ });
+ }
+
+ protected function plainValue($data)
+ {
+ if (is_array($data)) {
+ return $data[0] ?? '';
+ }
+ return $data;
+ }
}
diff --git a/application/admin/controller/shopro/user/User.php b/application/admin/controller/shopro/user/User.php
index ab16d9a..bfa7d24 100644
--- a/application/admin/controller/shopro/user/User.php
+++ b/application/admin/controller/shopro/user/User.php
@@ -12,7 +12,7 @@ class User extends Common
{
protected $model = null;
- protected $noNeedRight = ['select'];
+ protected $noNeedRight = ['select', 'rewards', 'voucherPlans', 'team'];
public function _initialize()
{
@@ -29,7 +29,60 @@ class User extends Common
return $this->view->fetch();
}
- $data = $this->model->sheepFilter()->paginate($this->request->param('list_rows', 10));
+ $paginator = $this->model->sheepFilter()
+ ->with(['parent_user', 'agent.level_info'])
+ ->paginate($this->request->param('list_rows', 10));
+
+ $rate = (new \addons\shopro\service\partner\RateService())->currentRate();
+ $days = \addons\shopro\service\partner\PartnerConfig::activeDays();
+ $now = time();
+
+ $list = $paginator->items();
+ foreach ($list as &$user) {
+ $agent = $user->agent;
+ $partner = \addons\shopro\service\partner\IdentityService::attachProfile($agent, $rate);
+ foreach ($partner as $k => $v) {
+ $user->$k = $v;
+ }
+ $user->has_agent = (bool) $agent;
+ $user->madow_value = $partner['madow_value'] ?? '0.00';
+
+ if ($agent) {
+ $user->is_venue = intval($agent->is_venue ?? 0);
+ $user->is_county = intval($agent->is_county ?? 0);
+ $user->is_partner = intval($agent->is_partner ?? 0);
+ $user->direct_order_seq = intval($agent->direct_order_seq ?? 0);
+ $user->voucher_total = \addons\shopro\service\partner\IdentityService::formatMoney($agent->voucher_total ?? 0);
+ $user->voucher_released = \addons\shopro\service\partner\IdentityService::formatMoney($agent->voucher_released ?? 0);
+ $user->voucher_used = \addons\shopro\service\partner\IdentityService::formatMoney($agent->voucher_used ?? 0);
+ $user->voucher_available = \addons\shopro\service\partner\IdentityService::formatMoney(max(0, ($agent->voucher_released ?? 0) - ($agent->voucher_used ?? 0)));
+ $user->last_self_order_time = intval($agent->last_self_order_time ?? 0);
+ $user->last_self_order_text = $user->last_self_order_time ? date('Y-m-d H:i:s', $user->last_self_order_time) : '从未下单';
+ } else {
+ $user->is_venue = 0;
+ $user->is_county = 0;
+ $user->is_partner = 0;
+ $user->direct_order_seq = 0;
+ $user->voucher_total = '0.00';
+ $user->voucher_released = '0.00';
+ $user->voucher_used = '0.00';
+ $user->voucher_available = '0.00';
+ $user->last_self_order_time = 0;
+ $user->last_self_order_text = '从未下单';
+ }
+ }
+
+ $stats = [
+ 'total_users' => UserModel::count(),
+ 'maker_count' => Db::name('shopro_commission_agent')->where('level', '>=', 2)->count(),
+ 'venue_count' => Db::name('shopro_commission_agent')->where('is_venue', 1)->count(),
+ 'county_count' => Db::name('shopro_commission_agent')->where('is_county', 1)->count(),
+ 'partner_count' => Db::name('shopro_commission_agent')->where('is_partner', 1)->count(),
+ 'active_count' => Db::name('shopro_commission_agent')->where('last_self_order_time', '>=', $now - $days * 86400)->count(),
+ ];
+
+ $data = $paginator->toArray();
+ $data['stats'] = $stats;
$this->success('获取成功', null, $data);
}
@@ -54,9 +107,141 @@ class User extends Common
$data = array_merge($user->toArray(), $partner);
$data['has_agent'] = (bool) $agent;
+ // 推三返一循环信息
+ $push3Amounts = \addons\shopro\service\partner\PartnerConfig::push3Amounts();
+ $seq = intval($agent->direct_order_seq ?? 0);
+ $cycleCount = count($push3Amounts) > 0 ? count($push3Amounts) : 3;
+ $nextAmount = $push3Amounts[$seq % $cycleCount] ?? 398;
+ $data['direct_order_seq'] = $seq;
+ $data['push3_cycle_index'] = ($seq % $cycleCount) + 1;
+ $data['next_push3_amount'] = number_format((float) $nextAmount, 2, '.', '');
+ $data['next_push3_text'] = '第 ' . ($seq + 1) . ' 单将返 ¥' . $data['next_push3_amount'];
+
+ // 30天自购活跃状态与倒计时
+ $lastOrder = intval($agent->last_self_order_time ?? 0);
+ $activeDays = \addons\shopro\service\partner\PartnerConfig::activeDays();
+ $ago = $lastOrder ? intval(floor((time() - $lastOrder) / 86400)) : null;
+ $isAdvanced = !empty($data['is_advanced']);
+ $data['last_self_order_time'] = $lastOrder;
+ $data['last_self_order_text'] = $lastOrder ? date('Y-m-d H:i:s', $lastOrder) : '从未下单';
+ $data['active_days_ago'] = $ago;
+ $data['active_days_limit'] = $activeDays;
+
+ if (!$isAdvanced) {
+ $data['active_status_text'] = '免自购考核 (基础会员/品酒师)';
+ } else {
+ $data['active_status_text'] = !empty($data['active_30d'])
+ ? '正常领取(近30天有自购)'
+ : ('已暂停领取' . ($ago !== null ? '(距今 ' . $ago . ' 天)' : '(从未下单)'));
+ }
+
+ // 累计现金收益统计
+ $totalCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
+ ->where('status', 1)
+ ->where('amount', '>', 0)
+ ->sum('amount');
+ $push3Cash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
+ ->where('status', 1)
+ ->where('amount', '>', 0)
+ ->where('reward_type', 'push3')
+ ->sum('amount');
+ $partnerExclusiveCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id)
+ ->where('status', 1)
+ ->where('amount', '>', 0)
+ ->where('reward_type', '<>', 'push3')
+ ->sum('amount');
+
+ $data['income_total_cash'] = number_format((float) $totalCash, 2, '.', '');
+ $data['push3_cash_income'] = number_format((float) $push3Cash, 2, '.', '');
+ $data['partner_exclusive_cash'] = number_format((float) $partnerExclusiveCash, 2, '.', '');
+
+ // 兑换券计划资产统计
+ $voucherTotal = floatval($agent->voucher_total ?? 0);
+ $voucherReleased = floatval($agent->voucher_released ?? 0);
+ $voucherUsed = floatval($agent->voucher_used ?? 0);
+ $data['voucher_total'] = number_format($voucherTotal, 2, '.', '');
+ $data['voucher_released'] = number_format($voucherReleased, 2, '.', '');
+ $data['voucher_used'] = number_format($voucherUsed, 2, '.', '');
+ $data['voucher_available'] = number_format(max(0, $voucherReleased - $voucherUsed), 2, '.', '');
+ $data['voucher_plans_count'] = \app\admin\model\shopro\partner\VoucherPlan::where('user_id', $id)->count();
+
+ // 团队统计
+ $data['direct_users_count'] = UserModel::where('parent_user_id', $id)->count();
+ if ($agent) {
+ $data['agent_level'] = $agent->level;
+ $data['agent_level_info'] = $agent->level_info;
+ $data['is_venue'] = intval($agent->is_venue ?? 0);
+ $data['is_county'] = intval($agent->is_county ?? 0);
+ $data['is_partner'] = intval($agent->is_partner ?? 0);
+ $data['county_code'] = (string) ($agent->county_code ?? '');
+ $data['upgrade_lock'] = intval($agent->upgrade_lock ?? 0);
+ }
+
$this->success('获取成功', null, $data);
}
+ /**
+ * 详情动态:收益中心流水
+ */
+ public function rewards($id)
+ {
+ $showSkip = $this->request->param('show_skip', 0);
+ $query = \app\admin\model\shopro\partner\Reward::sheepFilter()
+ ->with(['buyer', 'order'])
+ ->where('user_id', $id);
+ if (!$showSkip) {
+ $query->where(function ($q) {
+ $q->where('amount', '>', 0)->whereOr('madow', '>', 0);
+ });
+ }
+ $list = $query->order('id', 'desc')->paginate($this->request->param('list_rows', 10));
+ $this->success('获取成功', null, $list);
+ }
+
+ /**
+ * 详情动态:兑换券释放计划
+ */
+ public function voucherPlans($id)
+ {
+ $list = \app\admin\model\shopro\partner\VoucherPlan::sheepFilter()
+ ->with(['order'])
+ ->where('user_id', $id)
+ ->order('id', 'desc')
+ ->paginate($this->request->param('list_rows', 10));
+
+ $agent = \app\admin\model\shopro\commission\Agent::where('user_id', $id)->field('voucher_used,voucher_released')->find();
+ $used = (float) ($agent['voucher_used'] ?? 0);
+ $released = (float) ($agent['voucher_released'] ?? 0);
+ $available = number_format(max(0, $released - $used), 2, '.', '');
+ foreach ($list as $row) {
+ $row->voucher_used = number_format($used, 2, '.', '');
+ $row->voucher_available = $available;
+ }
+ $this->success('获取成功', null, $list);
+ }
+
+ /**
+ * 详情动态:团队成员列表
+ */
+ public function team($id)
+ {
+ $list = UserModel::with(['agent.level_info'])
+ ->where('parent_user_id', $id)
+ ->order('id', 'desc')
+ ->paginate($this->request->param('list_rows', 10));
+
+ $rate = (new \addons\shopro\service\partner\RateService())->currentRate();
+ foreach ($list as $sub) {
+ $agent = $sub->agent;
+ $partner = \addons\shopro\service\partner\IdentityService::attachProfile($agent, $rate);
+ foreach ($partner as $k => $v) {
+ $sub->$k = $v;
+ }
+ $sub->has_agent = (bool) $agent;
+ }
+ $this->success('获取成功', null, $list);
+ }
+
/**
* 更新用户
*
diff --git a/application/admin/view/shopro/user/user/detail.html b/application/admin/view/shopro/user/user/detail.html
index f21577b..1205dbc 100644
--- a/application/admin/view/shopro/user/user/detail.html
+++ b/application/admin/view/shopro/user/user/detail.html
@@ -1,48 +1,125 @@
{include file="/shopro/common/script" /}
-
-
-
- 基本信息
-
-
-
-
-
-
- {{ state.detail.nickname }}
+
+
+
+
+
+
+
+
+
+ 基本信息
+
+
+
+
+
+
+ {{ state.detail.nickname }}
+
+
#{{ state.detail.id }}
-
#{{ state.detail.id }}
+ {if $auth->check('shopro/user/user/edit')}
+
更换头像
+ {/if}
- {if $auth->check('shopro/user/user/edit')}
-
更换头像
- {/if}
-
-
-
-
- 昵称
-
-
-
-
-
- 性别
-
-
- 女
- 男
-
-
-
-
- 用户名
-
-
-
-
-
-
-
-
-
- 密码
-
-
-
-
-
-
-
-
-
- 手机号
-
-
-
-
-
-
-
-
-
- 电子邮箱
-
-
-
-
-
-
-
-
-
- 状态
-
-
- 正常
- 禁用
-
-
-
-
-
-
- 重置
- {if $auth->check('shopro/user/user/edit')}
- 保存
- {/if}
-
-
-
-
-
-
-
-
-
- 账户信息
-
-
-
-
- 推荐人
-
-
- {if $is_pro}
-
- 更换
- {/if}
-
-
-
- 佣金
- {{ state.detail.commission }}
-
-
- 身份
- {{ state.detail.identity_text || '尚未生成分销身份' }}
-
-
- 马豆
- {{ state.detail.madow || '0' }}
-
-
- 可用兑换券
-
- {{ state.detail.voucher_available || '0.00' }}
-
- 分销商详情
-
-
-
-
- 余额
-
- {{ state.detail.money }}
- {if $auth->check('shopro/user/user/recharge')}
- 充值
- {/if}
-
-
-
- 上次登陆时间
- {{ state.detail.logintime }}
-
-
- 积分
-
- {{ state.detail.score }}
- {if $auth->check('shopro/user/user/recharge')}
- 充值
- {/if}
-
-
-
- 登录IP
- {{ state.detail.loginip }}
-
-
- 总计消费
-
- {{ state.detail.total_consume }}
-
-
-
- 注册时间
- {{ state.detail.createtime }}
-
-
-
-
-
-
- 第三方账号
-
-
-
-
-
-
![]()
-
- {{ platform[item.provider][item.platform] }}
-
-
-
-
-
-
登录次数:
-
{{ item.login_num }}
-
-
-
Openid:
-
{{ item.openid }}
-
-
-
Unionid:
-
{{ item.unionid }}
-
-
-
更新时间:
-
{{ item.updatetime }}
-
-
-
-
-
-
- {{ item.nickname }}
-
-
+
+
+
+ 昵称
+
+
+
+
+
+ 性别
+
+
+ 女
+ 男
+
+
+
+
+ 用户名
+
+
+
+
-
+
+
+
+
+ 密码
+
+
+
+
+
+
+
+
+
+ 手机号
+
+
+
+
+
+
+
+
+
+ 电子邮箱
+
+
+
+
+
+
+
+
+
+ 状态
+
+
+ 正常
+ 禁用
+
+
+
+
+
+ 重置
+ {if $auth->check('shopro/user/user/edit')}
+ 保存修改
+ {/if}
-
-
- 用户动态
+
+
+
+
+
第三方账号绑定
+
+
+
+
+
![]()
+
+ {{ platform[item.provider]?.[item.platform] || item.platform }}
+
+
+
+
+
登录次数:{{ item.login_num }}
+
Openid:{{ item.openid }}
+
Unionid:{{ item.unionid }}
+
更新时间:{{ item.updatetime }}
+
+
+
+
+ {{ item.nickname }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 业务身份与二开权益体系
+
+ 直推有效单数: {{ state.detail.direct_order_seq || 0 }} 单
+
+
+
+
+
+
+ 推荐人 / 直属上级
+
+
+
+ 查看上级
+
+ {if $is_pro}
+
+ 更换
+
+ {/if}
+
+
+
+
+ 主业务身份
+
+ {{ state.detail.identity_text || '普通注册会员' }}
+
+
+
+
+ 30天自购活跃窗口(风控硬性门槛)
+
+
+ {{ state.detail.active_status_text }}
+
+
+ 最近自购: {{ state.detail.last_self_order_text }}
+
+
+
+
+ 直推「推三返一」循环进度
+
+
+ 第 {{ state.detail.push3_cycle_index || 1 }} 档 (398/400/600循环)
+
+
+ {{ state.detail.next_push3_text }}
+
+
+
+ 累计获得推三返一: ¥{{ state.detail.push3_cash_income || '0.00' }}
+
+
+
+
+ 身份权益徽章清单
+
+
+ 品酒师 (基础会员·享推三返一/GMV分红/1.5倍券)
+
+
+ 创客 V{{ state.detail.level - 1 }} (合伙骨干·享极差/平级10%/超越5%)
+
+
+ 酒道馆 (线下实体枢纽·享50元/单团队奖+100元均分池)
+
+
+ 区县代理 (区域领头人·享团队拆分奖+50元均分池)
+
+
+ 合伙股东 (独立高阶合伙身份·享顶层红利)
+
+
+
+
+
+
+
+
+ 二开核心资产中枢
+
+
+
+
+
+
可提现收益 (钱包现金)
+
+ ¥{{ state.detail.commission || '0.00' }}
+
+
+ 累计总现金: ¥{{ state.detail.income_total_cash || '0.00' }}
+
+
+ 进阶加权奖: ¥{{ state.detail.partner_exclusive_cash || '0.00' }}
+
+
+
+
+
+
+
+
分红马豆 (GMV 1:1 分红)
+
+ {{ state.detail.madow || 0 }} 颗
+
+
+ 当前估值: ¥{{ state.detail.madow_value || '0.00' }}
+
+
+ 实时汇率: {{ state.detail.madow_rate || '0.00000075' }}
+
+
+
+
+
+
+
+
商城可用兑换券
+
+ ¥{{ state.detail.voucher_available || '0.00' }}
+
+
+ 计划总额: ¥{{ state.detail.voucher_total || '0.00' }}
+
+
+ 已释放: ¥{{ state.detail.voucher_released || '0.00' }} / 已用: ¥{{ state.detail.voucher_used || '0.00' }}
+
+
+
+
+
+
+
+
商城余额 / 积分
+
+ ¥{{ state.detail.money || '0.00' }}
+
+
+ 商城积分: {{ state.detail.score || 0 }}
+
+
+ 累计消费: ¥{{ state.detail.total_consume || '0.00' }}
+
+
+
+
+
+
+
+
+
全能动态与业务流水
+
+
+ 显示未发放/跳过原因记录
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+ {{ scope.row.reward_type_text || scope.row.reward_type }}
+
+
+
+
+
+
+ ¥{{ scope.row.amount }}
+
+
+
+
+
+
+
+ -
+
+
+
+
+ {{ scope.row.order?.order_sn || scope.row.order_id || '-' }}
+
+
+
+
+
+ {{ scope.row.status_text || (scope.row.status == 1 ? '已入账' : (scope.row.status == -1 ? '未发放' : '其他')) }}
+
+
+
+
+
+ {{ scope.row.formula || scope.row.ext?.formula || scope.row.ext?.skip_reason || '-' }}
+
+
+
+
+ 明细
+
+
+
+
+
+
+
+
+
+ {{ scope.row.order?.order_sn || scope.row.order_id || '-' }}
+
+
+
+
+ ¥{{ scope.row.total_fee }}
+
+
+
+
+ ¥{{ scope.row.day_amount }}
+
+
+
+
+
+
+ 第 {{ scope.row.days_released || 0 }} / {{ scope.row.total_days || 365 }} 天
+
+
+
+
+
+
+ ¥{{ scope.row.voucher_available }}
+
+
+
+
+
+
+ {{ scope.row.status_text || scope.row.status }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.identity_text || '普通会员' }}
+
+
+
+
+ ¥{{ scope.row.total_consume }}
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+ height="460" class="sa-table" :data="log.data" stripe>
+
+
+
+
@@ -330,66 +633,86 @@
-
-
-
-
-
+
+
+
+
+
+
-
+
+
+
+
+ ¥{{ scope.row.pay_fee }}
+
-
-
-
-
-
-
+
+
+
+
+
-
-
+
-
-
-
-
-
{{ scope.row.ext.memo}}
-
- -
-
-
-
+
-
+
+
+
{{ scope.row.coupon?.name || '-' }}
-
+
- {{ scope.row.coupon?.amount || '-' }}
+ ¥{{ scope.row.coupon?.amount || '-' }}
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+ 奖项类型:
+ {{ formulaDialog.row.reward_type_text || formulaDialog.row.reward_type }}
+
+
+ 结算金额/马豆:
+ ¥{{ formulaDialog.row.amount }}
+ ({{ formulaDialog.row.madow }} 马豆)
+
+
+ 跳过/未发放原因:
+ {{ formulaDialog.row.ext.skip_reason }}
+
+
公式摘要与上下文(ext):
+
+ {{ JSON.stringify(formulaDialog.row.ext || formulaDialog.row.formula || {}, null, 2) }}
+
+
+
+ 关闭
+
+
\ No newline at end of file
diff --git a/application/admin/view/shopro/user/user/index.html b/application/admin/view/shopro/user/user/index.html
index 0eabfac..642cf2d 100644
--- a/application/admin/view/shopro/user/user/index.html
+++ b/application/admin/view/shopro/user/user/index.html
@@ -1,11 +1,60 @@
{include file="/shopro/common/script" /}
+
+
-
+
+
+
+
+
+
全平台会员数
+
{{ state.stats.total_users || 0 }}
+
+
+
+
+
创客团队骨干
+
{{ state.stats.maker_count || 0 }}
+
+
+
+
+
线下酒道馆
+
{{ state.stats.venue_count || 0 }}
+
+
+
+
+
区县代理总数
+
{{ state.stats.county_count || 0 }}
+
+
+
+
+
合伙股东总数
+
{{ state.stats.partner_count || 0 }}
+
+
+
+
+
30日自购活跃
+
{{ state.stats.active_count || 0 }}
+
+
+
+
-
-
+
+
+
+ {{ scope.row.mobile || scope.row.username || '' }}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ {{ scope.row.identity_text || '普通会员' }}
+
+ 品酒师
+ 创客 V{{ scope.row.level - 1 }}
+ 酒道馆
+ 区县代理
+ 合伙股东
+
+
+
+
+
+
+
+
+
+ {{ scope.row.active_30d ? '正常领奖' : '已暂停' }}
+
+
+
+ 免考核
+
+
+
+
+
+
+ 可提现收益: ¥{{ scope.row.commission || '0.00' }}
+ 马豆: {{ scope.row.madow || 0 }} (约 ¥{{ scope.row.madow_value || '0.00' }})
+ 可用券: ¥{{ scope.row.voucher_available || '0.00' }}
+
+
+
+
+
+ 余额: ¥{{ scope.row.money || '0.00' }}
+ 积分: {{ scope.row.score || 0 }}
+ 消费: ¥{{ scope.row.total_consume || '0.00' }}
+
+
+
+
+
+
{if $auth->check('shopro/user/user/detail')}
- 查看
+ 详情
+ {/if}
+
+ 收益档案
+
+ {if $auth->check('shopro/user/user/recharge')}
+
+ 充值
+
{/if}
+ title="确认删除该会员记录?" @confirm="onDelete(scope.row.id)">
{if $auth->check('shopro/user/user/delete')}
删除
diff --git a/public/assets/addons/shopro/components/sa-user-profile.js b/public/assets/addons/shopro/components/sa-user-profile.js
index e5fc21e..0d37e7e 100644
--- a/public/assets/addons/shopro/components/sa-user-profile.js
+++ b/public/assets/addons/shopro/components/sa-user-profile.js
@@ -125,7 +125,18 @@ const SaUserProfile = {
});
function onOpenUserDetail(id) {
- Fast.api.open(`shopro/user/user/detail?id=${id}`, "会员详情")
+ const url = `shopro/user/user/detail?id=${id}`;
+ const name = (userData.value && (userData.value.nickname || userData.value.username)) ? userData.value.nickname || userData.value.username : '';
+ const title = name ? `会员详情 - ${name} (#${id})` : `会员详情 #${id}`;
+ if (typeof Backend !== 'undefined' && Backend.api && typeof Backend.api.addtabs === 'function') {
+ Backend.api.addtabs(url, title);
+ } else if (top && top.Backend && top.Backend.api && typeof top.Backend.api.addtabs === 'function') {
+ top.Backend.api.addtabs(url, title);
+ } else if (Fast && Fast.api && typeof Fast.api.addtabs === 'function') {
+ Fast.api.addtabs(url, title);
+ } else {
+ Fast.api.open(url, title);
+ }
}
return {
Fast,
diff --git a/public/assets/js/backend/shopro/user/user.js b/public/assets/js/backend/shopro/user/user.js
index eb85236..527df37 100644
--- a/public/assets/js/backend/shopro/user/user.js
+++ b/public/assets/js/backend/shopro/user/user.js
@@ -9,10 +9,23 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
data: [],
order: '',
sort: '',
+ stats: {
+ total_users: 0,
+ maker_count: 0,
+ venue_count: 0,
+ county_count: 0,
+ partner_count: 0,
+ active_count: 0,
+ },
filter: {
drawer: false,
data: {
user: { field: 'id', value: '' },
+ identity: '',
+ maker_level: '',
+ active_30d: '',
+ has_agent: '',
+ parent_user: '',
createtime: [],
logintime: [],
},
@@ -48,6 +61,63 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}],
}
},
+ identity: {
+ type: 'tselect',
+ label: '业务身份',
+ value: '',
+ options: {
+ data: [
+ { value: 'taster', label: '品酒师' },
+ { value: 'maker', label: '创客 (V1~V6)' },
+ { value: 'venue', label: '酒道馆' },
+ { value: 'county', label: '区县代理' },
+ { value: 'partner', label: '合伙股东' },
+ ]
+ }
+ },
+ maker_level: {
+ type: 'tselect',
+ label: '创客等级',
+ value: '',
+ options: {
+ data: [
+ { value: '1', label: '创客 V1' },
+ { value: '2', label: '创客 V2' },
+ { value: '3', label: '创客 V3' },
+ { value: '4', label: '创客 V4' },
+ { value: '5', label: '创客 V5' },
+ { value: '6', label: '创客 V6' },
+ ]
+ }
+ },
+ active_30d: {
+ type: 'tselect',
+ label: '30日活跃',
+ value: '',
+ options: {
+ data: [
+ { value: '1', label: '有自购(正常领奖)' },
+ { value: '0', label: '已暂停(无自购)' },
+ ]
+ }
+ },
+ has_agent: {
+ type: 'tselect',
+ label: '收益档案',
+ value: '',
+ options: {
+ data: [
+ { value: '1', label: '具备分销/收益档案' },
+ { value: '0', label: '普通注册会员' },
+ ]
+ }
+ },
+ parent_user: {
+ type: 'tinput',
+ label: '推荐人',
+ placeholder: '请输入推荐人ID/手机号/昵称',
+ value: '',
+ },
createtime: {
type: 'tdatetimerange',
label: '注册时间',
@@ -70,6 +140,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
nickname: 'like',
mobile: 'like',
email: 'like',
+ identity: '=',
+ maker_level: '=',
+ active_30d: '=',
+ has_agent: '=',
+ parent_user: '=',
createtime: 'range',
logintime: 'range',
});
@@ -86,6 +161,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}, function (ret, res) {
state.data = res.data.data
pagination.total = res.data.total
+ if (res.data.stats) {
+ state.stats = res.data.stats;
+ }
return false
}, function (ret, res) { })
}
@@ -110,13 +188,44 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
total: 0,
})
- function onDetail(id) {
- Fast.api.open(`shopro/user/user/detail?id=${id}`, "详情", {
- callback() {
- getData()
- }
- })
+ function onDetail(id, row) {
+ const url = `shopro/user/user/detail?id=${id}`;
+ const name = (row && (row.nickname || row.username)) ? (row.nickname || row.username) : '';
+ const title = name ? `会员详情 - ${name} (#${id})` : `会员详情 #${id}`;
+ if (typeof Backend !== 'undefined' && Backend.api && typeof Backend.api.addtabs === 'function') {
+ Backend.api.addtabs(url, title);
+ } else if (top && top.Backend && top.Backend.api && typeof top.Backend.api.addtabs === 'function') {
+ top.Backend.api.addtabs(url, title);
+ } else if (Fast && Fast.api && typeof Fast.api.addtabs === 'function') {
+ Fast.api.addtabs(url, title);
+ } else {
+ window.location.href = Fast.api.fixurl(url);
+ }
}
+
+ function onOpenAgentDetail(id) {
+ const url = `shopro/commission/agent/detail/id/${id}?id=${id}`;
+ const title = `分销商详情 #${id}`;
+ if (typeof Backend !== 'undefined' && Backend.api && typeof Backend.api.addtabs === 'function') {
+ Backend.api.addtabs(url, title);
+ } else if (top && top.Backend && top.Backend.api && typeof top.Backend.api.addtabs === 'function') {
+ top.Backend.api.addtabs(url, title);
+ } else if (Fast && Fast.api && typeof Fast.api.addtabs === 'function') {
+ Fast.api.addtabs(url, title);
+ } else {
+ window.location.href = Fast.api.fixurl(url);
+ }
+ }
+
+ function onRecharge(type, row) {
+ const id = typeof row === 'object' ? row.id : row;
+ Fast.api.open(`shopro/user/user/recharge?type=${type}&id=${id}`, "账户充值", {
+ callback: function () {
+ getData();
+ }
+ });
+ }
+
function onDelete(id) {
Fast.api.ajax({
url: `shopro/user/user/delete/id/${id}`,
@@ -138,6 +247,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
onChangeFilter,
pagination,
onDetail,
+ onOpenAgentDetail,
+ onRecharge,
onDelete
}
}
@@ -148,12 +259,37 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
const { reactive, onMounted } = Vue
const detail = {
setup() {
+ const queryUserId = (typeof Config !== 'undefined' && (Config.id || Config.user_id))
+ ? (Config.id || Config.user_id)
+ : (new URLSearchParams(location.search).get('id') || (location.pathname.match(/\/id\/(\d+)/i) || [])[1] || Fast.api.query('id'));
+
const state = reactive({
type: new URLSearchParams(location.search).get('type'),
- id: new URLSearchParams(location.search).get('id'),
+ id: queryUserId,
detail: {}
})
+ function onBack() {
+ if (parent && parent.Layer && typeof parent.Layer.getFrameIndex === 'function') {
+ try {
+ var index = parent.Layer.getFrameIndex(window.name);
+ if (index) {
+ parent.Layer.close(index);
+ return;
+ }
+ } catch (e) {}
+ }
+ if (top && top.Backend && top.Backend.api && typeof top.Backend.api.closetabs === 'function') {
+ top.Backend.api.closetabs();
+ return;
+ }
+ if (typeof Backend !== 'undefined' && Backend.api && typeof Backend.api.closetabs === 'function') {
+ Backend.api.closetabs();
+ return;
+ }
+ window.history.back();
+ }
+
function getDetail() {
Fast.api.ajax({
url: `shopro/user/user/detail/id/${state.id}`,
@@ -206,7 +342,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}
function onRecharge(type) {
- Fast.api.open(`shopro/user/user/recharge?type=${type}&id=${state.detail.id}`, "充值", {
+ Fast.api.open(`shopro/user/user/recharge?type=${type}&id=${state.detail.id}`, "账户充值", {
callback: function () {
getDetail();
getLog()
@@ -228,33 +364,66 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}
}
+ function onOpenUserDetail(id) {
+ if (!id) return;
+ const url = `shopro/user/user/detail?id=${id}`;
+ const title = `会员详情 #${id}`;
+ if (typeof Backend !== 'undefined' && Backend.api && typeof Backend.api.addtabs === 'function') {
+ Backend.api.addtabs(url, title);
+ } else if (top && top.Backend && top.Backend.api && typeof top.Backend.api.addtabs === 'function') {
+ top.Backend.api.addtabs(url, title);
+ } else if (Fast && Fast.api && typeof Fast.api.addtabs === 'function') {
+ Fast.api.addtabs(url, title);
+ } else {
+ window.location.href = Fast.api.fixurl(url);
+ }
+ }
+
+ const formulaDialog = reactive({
+ visible: false,
+ row: null,
+ })
+ function onShowFormula(row) {
+ formulaDialog.row = row;
+ formulaDialog.visible = true;
+ }
+
const log = reactive({
- tabActive: 'money',
+ tabActive: 'partner_reward',
+ showSkip: false,
data: [],
})
function getLog() {
- let url
+ let url = ''
let search = {}
- if (log.tabActive == 'money' || log.tabActive == 'score' || log.tabActive == 'commission') {
+ if (log.tabActive == 'partner_reward') {
+ url = `shopro/user/user/rewards/id/${state.id}`
+ search = {
+ show_skip: log.showSkip ? 1 : 0
+ }
+ } else if (log.tabActive == 'voucher_plan') {
+ url = `shopro/user/user/voucherPlans/id/${state.id}`
+ } else if (log.tabActive == 'team') {
+ url = `shopro/user/user/team/id/${state.id}`
+ } else if (log.tabActive == 'money' || log.tabActive == 'score' || log.tabActive == 'commission') {
url = `shopro/user/wallet_log/${log.tabActive}/id/${state.id}`
- }
- if (log.tabActive == 'order') {
+ } else if (log.tabActive == 'order') {
url = `shopro/order/order`
search = {
search: JSON.stringify({
user_id: state.id
})
}
- }
- if (log.tabActive == 'share') {
+ } else if (log.tabActive == 'share') {
url = `shopro/share`
search = {
id: state.id
}
- }
- if (log.tabActive == 'coupon') {
+ } else if (log.tabActive == 'coupon') {
url = `shopro/user/user/coupon/id/${state.id}`
}
+ if (!url) return;
+
Fast.api.ajax({
url: url,
type: 'GET',
@@ -265,11 +434,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
},
}, function (ret, res) {
if (log.tabActive == 'order') {
- log.data = res.data.orders.data
- pagination.total = res.data.orders.total
+ log.data = res.data?.orders?.data || []
+ pagination.total = res.data?.orders?.total || 0
} else {
- log.data = res.data.data
- pagination.total = res.data.total
+ log.data = res.data?.data || []
+ pagination.total = res.data?.total || 0
}
return false
}, function (ret, res) { })
@@ -294,6 +463,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
return {
state,
getDetail,
+ onBack,
platform,
statusStyle,
onSelectAvatar,
@@ -301,6 +471,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
onChangeParentUser,
onRecharge,
onOpenAgentDetail,
+ onOpenUserDetail,
+ formulaDialog,
+ onShowFormula,
log,
getLog,
pagination,
diff --git a/tests/verify_user_list_and_detail_spec.js b/tests/verify_user_list_and_detail_spec.js
new file mode 100644
index 0000000..49fe313
--- /dev/null
+++ b/tests/verify_user_list_and_detail_spec.js
@@ -0,0 +1,124 @@
+const fs = require('fs');
+const path = require('path');
+
+let passed = 0;
+let failed = 0;
+
+function assert(condition, testName, detail = '') {
+ if (condition) {
+ console.log(` [PASS] ${testName}`);
+ passed++;
+ } else {
+ console.error(` [FAIL] ${testName} - ${detail}`);
+ failed++;
+ }
+}
+
+console.log("========================================================");
+console.log(" 会员列表优化与会员详情补齐 自动化端到端规范断言测试");
+console.log("========================================================\n");
+
+// 1. 验证 UserFilter.php 筛选器
+const filterPath = path.resolve(__dirname, '../addons/shopro/filter/user/UserFilter.php');
+assert(fs.existsSync(filterPath), 'UserFilter.php 文件存在');
+const filterContent = fs.readFileSync(filterPath, 'utf-8');
+
+assert(filterContent.includes('function identity('), 'UserFilter.php 包含 identity 多维身份筛选方法');
+assert(filterContent.includes("'taster'"), 'UserFilter.php identity 支持品酒师筛选');
+assert(filterContent.includes("'maker'"), 'UserFilter.php identity 支持创客筛选');
+assert(filterContent.includes("'venue'"), 'UserFilter.php identity 支持酒道馆筛选');
+assert(filterContent.includes("'county'"), 'UserFilter.php identity 支持区县代理筛选');
+assert(filterContent.includes("'partner'"), 'UserFilter.php identity 支持合伙股东筛选');
+assert(filterContent.includes('function makerLevel('), 'UserFilter.php 包含 makerLevel 创客级别细分筛选');
+assert(filterContent.includes('function active30d('), 'UserFilter.php 包含 active30d 30天自购活跃状态筛选');
+assert(filterContent.includes('function hasAgent('), 'UserFilter.php 包含 hasAgent 分销/收益档案筛选');
+assert(filterContent.includes('function parentUser('), 'UserFilter.php 包含 parentUser 推荐人筛选');
+
+// 2. 验证 User.php 控制器
+const controllerPath = path.resolve(__dirname, '../application/admin/controller/shopro/user/User.php');
+assert(fs.existsSync(controllerPath), 'User.php 控制器文件存在');
+const controllerContent = fs.readFileSync(controllerPath, 'utf-8');
+
+assert(controllerContent.includes("'rewards'"), 'User.php noNeedRight 包含 rewards 豁免配置');
+assert(controllerContent.includes("'voucherPlans'"), 'User.php noNeedRight 包含 voucherPlans 豁免配置');
+assert(controllerContent.includes("'team'"), 'User.php noNeedRight 包含 team 豁免配置');
+assert(controllerContent.includes('IdentityService::attachProfile'), 'User.php index/detail 关联装配 IdentityService 身份和资产');
+assert(controllerContent.includes('stats'), 'User.php index 返回包含 stats 数据概览');
+assert(controllerContent.includes('voucher_available'), 'User.php 装配 voucher_available 可用券资产');
+assert(controllerContent.includes('madow_value'), 'User.php 装配 madow_value 马豆预估估值');
+assert(controllerContent.includes('push3Amounts'), 'User.php detail 包含推三返一循环金额计算');
+assert(controllerContent.includes('next_push3_amount'), 'User.php detail 返回下一单预计返现金额');
+assert(controllerContent.includes('active_status_text'), 'User.php detail 包含30天自购活跃状态解析');
+assert(controllerContent.includes('income_total_cash'), 'User.php detail 包含累计现金收益统计');
+assert(controllerContent.includes('function rewards($id)'), 'User.php 包含 rewards 收益流水查询接口');
+assert(controllerContent.includes('function voucherPlans($id)'), 'User.php 包含 voucherPlans 兑换券释放计划查询接口');
+assert(controllerContent.includes('function team($id)'), 'User.php 包含 team 团队成员查询接口');
+
+// 3. 验证 sa-user-profile.js
+const profileJsPath = path.resolve(__dirname, '../public/assets/addons/shopro/components/sa-user-profile.js');
+assert(fs.existsSync(profileJsPath), 'sa-user-profile.js 组件文件存在');
+const profileJsContent = fs.readFileSync(profileJsPath, 'utf-8');
+
+assert(profileJsContent.includes('Backend.api.addtabs(url, title)'), 'sa-user-profile.js 点击会员头像/ID 使用 addtabs 打开选项卡');
+assert(profileJsContent.includes('top.Backend.api.addtabs(url, title)'), 'sa-user-profile.js 支持 top.Backend.api.addtabs 顶层打开');
+
+// 4. 验证 user.js
+const userJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/user/user.js');
+assert(fs.existsSync(userJsPath), 'user.js 文件存在');
+const userJsContent = fs.readFileSync(userJsPath, 'utf-8');
+
+assert(userJsContent.includes('Backend.api.addtabs(url, title)'), 'user.js onDetail 使用 Backend.api.addtabs 选项卡打开');
+assert(userJsContent.includes('top.Backend.api.addtabs(url, title)'), 'user.js onDetail 兼容 top.Backend.api.addtabs');
+assert(userJsContent.includes('function onOpenAgentDetail(id)'), 'user.js 包含 onOpenAgentDetail 打开收益档案');
+assert(userJsContent.includes('function onRecharge('), 'user.js 包含 onRecharge 快速充值方法');
+assert(userJsContent.includes('queryUserId'), 'user.js detail 支持 queryUserId 灵活参数解析');
+assert(userJsContent.includes('function onBack()'), 'user.js detail 包含 onBack 返回/关闭选项卡方法');
+assert(userJsContent.includes('top.Backend.api.closetabs()'), 'user.js detail onBack 支持 closetabs 关闭当前选项卡');
+assert(userJsContent.includes('partner_reward'), 'user.js detail 包含 partner_reward 收益流水 Tab 逻辑');
+assert(userJsContent.includes('voucher_plan'), 'user.js detail 包含 voucher_plan 兑换券释放计划 Tab 逻辑');
+assert(userJsContent.includes('team'), 'user.js detail 包含 team 团队下级 Tab 逻辑');
+assert(userJsContent.includes('onShowFormula'), 'user.js detail 包含 onShowFormula 公式弹窗查看');
+
+// 5. 验证 index.html
+const indexPath = path.resolve(__dirname, '../application/admin/view/shopro/user/user/index.html');
+assert(fs.existsSync(indexPath), 'user index.html 视图文件存在');
+const indexContent = fs.readFileSync(indexPath, 'utf-8');
+
+assert(indexContent.includes('stats-row'), 'index.html 包含数据概览 KPI 统计栏');
+assert(indexContent.includes('state.stats.total_users'), 'index.html 统计栏展示全平台会员数');
+assert(indexContent.includes('state.stats.maker_count'), 'index.html 统计栏展示创客团队骨干');
+assert(indexContent.includes('state.stats.venue_count'), 'index.html 统计栏展示线下酒道馆');
+assert(indexContent.includes('state.stats.county_count'), 'index.html 统计栏展示区县代理数');
+assert(indexContent.includes('state.stats.partner_count'), 'index.html 统计栏展示合伙股东数');
+assert(indexContent.includes('state.stats.active_count'), 'index.html 统计栏展示30日自购活跃数');
+assert(indexContent.includes('推荐人'), 'index.html 包含推荐人列');
+assert(indexContent.includes('业务身份'), 'index.html 包含业务身份列');
+assert(indexContent.includes('30日自购活跃'), 'index.html 包含30日自购活跃状态列');
+assert(indexContent.includes('active-dot'), 'index.html 包含活跃状态指示灯样式');
+assert(indexContent.includes('收益中心资产'), 'index.html 包含收益中心资产列');
+assert(indexContent.includes('商城钱包与消费'), 'index.html 包含商城钱包与消费列');
+assert(indexContent.includes('onDetail(scope.row.id, scope.row)'), 'index.html 详情按钮正确传递参数触发选项卡');
+assert(indexContent.includes('onOpenAgentDetail(scope.row.id)'), 'index.html 包含收益档案快捷入口');
+
+// 6. 验证 detail.html
+const detailPath = path.resolve(__dirname, '../application/admin/view/shopro/user/user/detail.html');
+assert(fs.existsSync(detailPath), 'user detail.html 视图文件存在');
+const detailContent = fs.readFileSync(detailPath, 'utf-8');
+
+assert(detailContent.includes('height: 100vh'), 'detail.html 样式适配 100vh 全屏选项卡页面');
+assert(detailContent.includes('detail-header-bar'), 'detail.html 包含顶部导航栏 detail-header-bar');
+assert(detailContent.includes('@click="onBack"'), 'detail.html 包含 onBack 返回/关闭选项卡按钮');
+assert(detailContent.includes('业务身份与二开权益体系'), 'detail.html 包含业务身份与二开权益看板');
+assert(detailContent.includes('30天自购活跃窗口'), 'detail.html 包含30天自购活跃窗口看板');
+assert(detailContent.includes('推三返一'), 'detail.html 包含推三返一循环进度展示');
+assert(detailContent.includes('二开核心资产中枢'), 'detail.html 包含四大核心资产中枢看板');
+assert(detailContent.includes('name="partner_reward"'), 'detail.html 包含收益中心流水 Tab');
+assert(detailContent.includes('name="voucher_plan"'), 'detail.html 包含兑换券释放计划 Tab');
+assert(detailContent.includes('name="team"'), 'detail.html 包含直推团队成员 Tab');
+assert(detailContent.includes('formulaDialog'), 'detail.html 包含奖励测算公式与跳过记录详情弹窗');
+
+console.log("\n--------------------------------------------------------");
+console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`);
+console.log("========================================================");
+
+process.exit(failed === 0 ? 0 : 1);