feat(user): 重构后台会员列表与详情为全屏选项卡并补齐二开权益资产中心
这个提交包含在:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*
|
||||
|
||||
@@ -1,48 +1,125 @@
|
||||
{include file="/shopro/common/script" /}
|
||||
|
||||
<style>
|
||||
.user-detail .title {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
color: var(--sa-subtitle);
|
||||
margin-bottom: 8px;
|
||||
.user-detail {
|
||||
background: #fff;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-detail .bottom {
|
||||
.user-detail .panel-block {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.user-detail .detail-header-bar {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 12px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--sa-border);
|
||||
}
|
||||
|
||||
.user-detail .detail-title-text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-left: 12px;
|
||||
color: var(--sa-title);
|
||||
}
|
||||
|
||||
.user-detail .user-detail-scrollbar>.el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.user-detail .user-detail-scrollbar>.el-scrollbar__bar.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user-detail .card-title {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--sa-subtitle);
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.user-detail .card-section {
|
||||
padding: 16px;
|
||||
background: var(--sa-table-header-bg);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.user-detail .left {
|
||||
height: 12px;
|
||||
line-height: 12px;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--sa-subfont);
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.user-detail .right {
|
||||
line-height: 32px;
|
||||
line-height: 28px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--sa-subtitle);
|
||||
}
|
||||
|
||||
.user-detail .info {
|
||||
padding: 16px 16px 0;
|
||||
padding: 16px 16px 4px;
|
||||
background: var(--sa-background-assist);
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.user-detail .info .el-col {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.user-detail .log .bottom {
|
||||
.user-detail .asset-card-box {
|
||||
background: #fff;
|
||||
border: 1px solid var(--sa-border);
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.user-detail .asset-card-num {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--sa-title);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.user-detail .asset-card-label {
|
||||
font-size: 12px;
|
||||
color: var(--sa-subfont);
|
||||
}
|
||||
|
||||
.user-detail .asset-card-desc {
|
||||
font-size: 11px;
|
||||
color: var(--sa-subfont);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.user-detail .active-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.user-detail .active-dot.is-on {
|
||||
background: #52c41a;
|
||||
}
|
||||
.user-detail .active-dot.is-off {
|
||||
background: #c0c4cc;
|
||||
}
|
||||
|
||||
.user-detail .log .card-section {
|
||||
padding: 0;
|
||||
background: var(--sa-background-assist);
|
||||
}
|
||||
@@ -58,266 +135,492 @@
|
||||
}
|
||||
|
||||
.user-detail .third-oauth .provider-item {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.user-detail .third-oauth .provider-item:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.user-detail .formula-box {
|
||||
background: #f8fafc;
|
||||
border: 1px solid var(--sa-border);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="detail" class="user-detail" v-cloak>
|
||||
<el-container class="panel-block">
|
||||
<el-main>
|
||||
<el-row :gutter="24">
|
||||
<el-col class="sa-col-24" :xs="24" :sm="24" :md="6" :lg="6" :xl="6">
|
||||
<div class="title">基本信息</div>
|
||||
<div class="bottom">
|
||||
<div class="sa-flex sa-row-between mb-3">
|
||||
<div class="sa-flex sa-flex-1">
|
||||
<sa-image :url="state.detail.avatar" size="64" radius="32"></sa-image>
|
||||
<div class="ml-4 sa-flex-1">
|
||||
<div class="nickname mb-1 sa-table-line-1">
|
||||
{{ state.detail.nickname }}
|
||||
<el-main style="padding: 12px 16px;">
|
||||
<!-- 顶部导航条(用于选项卡或全屏页面操作) -->
|
||||
<div class="detail-header-bar sa-flex sa-row-between">
|
||||
<div class="sa-flex sa-col-center">
|
||||
<el-button icon="Back" @click="onBack">返回</el-button>
|
||||
<span class="detail-title-text">
|
||||
会员详情 - {{ state.detail.nickname || state.detail.username || '会员' }} (#{{ state.detail.id }})
|
||||
</span>
|
||||
<el-tag :type="state.detail.status == 'normal' ? 'success' : 'danger'" size="small" class="ml-2">
|
||||
{{ state.detail.status == 'normal' ? '正常' : '已禁用' }}
|
||||
</el-tag>
|
||||
<el-tag v-if="state.detail.identity_text" type="primary" size="small" class="ml-2">
|
||||
{{ state.detail.identity_text }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="sa-flex">
|
||||
<el-button v-if="state.detail.has_agent" type="success" @click="onOpenAgentDetail(state.detail.id)">
|
||||
收益中心分销商档案
|
||||
</el-button>
|
||||
{if $auth->check('shopro/user/user/recharge')}
|
||||
<el-button type="warning" @click="onRecharge('money')">账户充值</el-button>
|
||||
{/if}
|
||||
<el-button class="sa-button-refresh ml-2" icon="RefreshRight" @click="getDetail"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 可滚动核心区域 -->
|
||||
<el-scrollbar class="user-detail-scrollbar" style="height: calc(100vh - 76px);">
|
||||
<el-row :gutter="16">
|
||||
<!-- 左列:基本信息与账号安全 -->
|
||||
<el-col class="sa-col-24" :xs="24" :sm="24" :md="7" :lg="7" :xl="7">
|
||||
<div class="card-title">基本信息</div>
|
||||
<div class="card-section">
|
||||
<div class="sa-flex sa-row-between mb-3">
|
||||
<div class="sa-flex sa-flex-1">
|
||||
<sa-image :url="state.detail.avatar" size="60" radius="30"></sa-image>
|
||||
<div class="ml-3 sa-flex-1">
|
||||
<div class="nickname mb-1 sa-table-line-1" style="font-weight: 600;">
|
||||
{{ state.detail.nickname }}
|
||||
</div>
|
||||
<div class="id" style="color: var(--sa-subfont); font-size: 12px;">#{{ state.detail.id }}</div>
|
||||
</div>
|
||||
<div class="id">#{{ state.detail.id }}</div>
|
||||
</div>
|
||||
{if $auth->check('shopro/user/user/edit')}
|
||||
<el-button type="primary" link @click="onSelectAvatar">更换头像</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
{if $auth->check('shopro/user/user/edit')}
|
||||
<el-button type="primary" @click="onSelectAvatar">更换头像</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="info">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24">
|
||||
<div class="left">昵称</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.nickname" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24">
|
||||
<div class="left">性别</div>
|
||||
<div class="right">
|
||||
<el-radio-group v-model="state.detail.gender">
|
||||
<el-radio :label="0">女</el-radio>
|
||||
<el-radio :label="1">男</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24">
|
||||
<div class="left">用户名</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.username" placeholder="首位需为字母且不少五位">
|
||||
<template #suffix>
|
||||
<span v-html="statusStyle('username')"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24">
|
||||
<div class="left">密码</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.password" placeholder="不修改则留空">
|
||||
<template #suffix>
|
||||
<span v-html="statusStyle('password')"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24">
|
||||
<div class="left">手机号</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.mobile" type="number">
|
||||
<template #suffix>
|
||||
<span v-html="statusStyle('mobile')"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24">
|
||||
<div class="left">电子邮箱</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.email">
|
||||
<template #suffix>
|
||||
<span v-html="statusStyle('email')"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24">
|
||||
<div class="left">状态</div>
|
||||
<div class="right">
|
||||
<el-radio-group v-model="state.detail.status">
|
||||
<el-radio label="normal">正常</el-radio>
|
||||
<el-radio label="disabled">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24">
|
||||
<div class="label"></div>
|
||||
<div class="right sa-flex sa-row-right">
|
||||
<el-button @click="getDetail">重置</el-button>
|
||||
{if $auth->check('shopro/user/user/edit')}
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col class="sa-col-24" :xs="24" :sm="24" :md="18" :lg="18" :xl="18">
|
||||
<el-row :gutter="24">
|
||||
<el-col class="sa-col-24" :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<div class="title">账户信息</div>
|
||||
<div class="bottom">
|
||||
<div class="info">
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">推荐人</div>
|
||||
<div class="right sa-flex">
|
||||
<sa-user-profile type="agent" :user="state.detail.parent_user"
|
||||
:id="state.detail.parent_user_id"></sa-user-profile>
|
||||
{if $is_pro}
|
||||
<el-button class="ml-2" type="primary" link @click="onChangeParentUser">
|
||||
更换</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">佣金</div>
|
||||
<div class="right">{{ state.detail.commission }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">身份</div>
|
||||
<div class="right">{{ state.detail.identity_text || '尚未生成分销身份' }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">马豆</div>
|
||||
<div class="right">{{ state.detail.madow || '0' }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">可用兑换券</div>
|
||||
<div class="right sa-flex">
|
||||
{{ state.detail.voucher_available || '0.00' }}
|
||||
<el-button v-if="state.detail.has_agent" class="ml-2" type="primary" link
|
||||
@click="onOpenAgentDetail(state.detail.id)">
|
||||
分销商详情
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">余额</div>
|
||||
<div class="right sa-flex">
|
||||
{{ state.detail.money }}
|
||||
{if $auth->check('shopro/user/user/recharge')}
|
||||
<el-button class="ml-2" type="primary" link
|
||||
@click="onRecharge('money')">充值</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">上次登陆时间</div>
|
||||
<div class="right">{{ state.detail.logintime }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">积分</div>
|
||||
<div class="right sa-flex">
|
||||
{{ state.detail.score }}
|
||||
{if $auth->check('shopro/user/user/recharge')}
|
||||
<el-button class="ml-2" type="primary" link
|
||||
@click="onRecharge('score')">充值</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">登录IP</div>
|
||||
<div class="right">{{ state.detail.loginip }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">总计消费</div>
|
||||
<div class="right">
|
||||
{{ state.detail.total_consume }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="left">注册时间</div>
|
||||
<div class="right">{{ state.detail.createtime }}</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col v-if="state.detail.third_oauth?.length>0" class="third-oauth sa-col-24" :xs="24"
|
||||
:sm="24" :md="12" :lg="12" :xl="12">
|
||||
<div class="title">第三方账号</div>
|
||||
<div class="bottom">
|
||||
<el-row class="info">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<div class="provider-item sa-flex sa-row-between"
|
||||
v-for="item in state.detail.third_oauth" :key="item.id">
|
||||
<div class="sa-flex">
|
||||
<img class="provider-platform"
|
||||
:src="`/assets/addons/shopro/img/user/${item.provider}/${item.platform}.png`" />
|
||||
<span class="ml-2">
|
||||
{{ platform[item.provider][item.platform] }}
|
||||
</span>
|
||||
</div>
|
||||
<el-popover popper-class="sa-popper" placement="top-start" trigger="hover">
|
||||
<div>
|
||||
<div class="item sa-flex">
|
||||
<div class="left">登录次数:</div>
|
||||
<div class="right">{{ item.login_num }}</div>
|
||||
</div>
|
||||
<div class="item sa-flex">
|
||||
<div class="left">Openid:</div>
|
||||
<div class="right">{{ item.openid }}</div>
|
||||
</div>
|
||||
<div v-if="item.unionid" class="item sa-flex">
|
||||
<div class="left">Unionid:</div>
|
||||
<div class="right">{{ item.unionid }}</div>
|
||||
</div>
|
||||
<div class="item sa-flex">
|
||||
<div class="left">更新时间:</div>
|
||||
<div class="right">{{ item.updatetime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div class="sa-flex">
|
||||
<sa-image :url="item.avatar" size="32"></sa-image>
|
||||
<span class="name ml-2">
|
||||
{{ item.nickname }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<div class="left">昵称</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.nickname" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="left">性别</div>
|
||||
<div class="right">
|
||||
<el-radio-group v-model="state.detail.gender">
|
||||
<el-radio :label="0">女</el-radio>
|
||||
<el-radio :label="1">男</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="left">用户名</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.username" placeholder="首位需为字母且不少五位">
|
||||
<template #suffix>
|
||||
<span v-html="statusStyle('username')"></span>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="left">密码</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.password" placeholder="不修改则留空">
|
||||
<template #suffix>
|
||||
<span v-html="statusStyle('password')"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="left">手机号</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.mobile" type="number">
|
||||
<template #suffix>
|
||||
<span v-html="statusStyle('mobile')"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="left">电子邮箱</div>
|
||||
<div class="right">
|
||||
<el-input v-model="state.detail.email">
|
||||
<template #suffix>
|
||||
<span v-html="statusStyle('email')"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="left">状态</div>
|
||||
<div class="right">
|
||||
<el-radio-group v-model="state.detail.status">
|
||||
<el-radio label="normal">正常</el-radio>
|
||||
<el-radio label="disabled">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="right sa-flex sa-row-right">
|
||||
<el-button @click="getDetail">重置</el-button>
|
||||
{if $auth->check('shopro/user/user/edit')}
|
||||
<el-button type="primary" @click="onSave">保存修改</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col class="log sa-col-24" :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<div class="title sa-flex"> 用户动态 </div>
|
||||
</div>
|
||||
|
||||
<!-- 第三方账号绑定 -->
|
||||
<div v-if="state.detail.third_oauth?.length > 0">
|
||||
<div class="card-title">第三方账号绑定</div>
|
||||
<div class="card-section third-oauth">
|
||||
<div class="info">
|
||||
<div class="provider-item sa-flex sa-row-between"
|
||||
v-for="item in state.detail.third_oauth" :key="item.id">
|
||||
<div class="sa-flex">
|
||||
<img class="provider-platform"
|
||||
:src="`/assets/addons/shopro/img/user/${item.provider}/${item.platform}.png`" />
|
||||
<span class="ml-2">
|
||||
{{ platform[item.provider]?.[item.platform] || item.platform }}
|
||||
</span>
|
||||
</div>
|
||||
<el-popover popper-class="sa-popper" placement="top-start" trigger="hover">
|
||||
<div>
|
||||
<div>登录次数:{{ item.login_num }}</div>
|
||||
<div>Openid:{{ item.openid }}</div>
|
||||
<div v-if="item.unionid">Unionid:{{ item.unionid }}</div>
|
||||
<div>更新时间:{{ item.updatetime }}</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div class="sa-flex">
|
||||
<sa-image :url="item.avatar" size="28"></sa-image>
|
||||
<span class="name ml-2">{{ item.nickname }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右列:二开身份权益、四大资产看板、全能动态流水 -->
|
||||
<el-col class="sa-col-24" :xs="24" :sm="24" :md="17" :lg="17" :xl="17">
|
||||
<!-- 看板 1:业务身份与二开权益体系 -->
|
||||
<div class="card-title">
|
||||
<span>业务身份与二开权益体系</span>
|
||||
<span v-if="state.detail.has_agent" style="font-size: 12px; font-weight: normal; color: var(--sa-subfont);">
|
||||
直推有效单数: {{ state.detail.direct_order_seq || 0 }} 单
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-section">
|
||||
<div class="info">
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="12" :md="12">
|
||||
<div class="left">推荐人 / 直属上级</div>
|
||||
<div class="right sa-flex">
|
||||
<sa-user-profile type="agent" :user="state.detail.parent_user"
|
||||
:id="state.detail.parent_user_id"></sa-user-profile>
|
||||
<el-button v-if="state.detail.parent_user_id" class="ml-2" type="primary" link
|
||||
@click="onOpenUserDetail(state.detail.parent_user_id)">
|
||||
查看上级
|
||||
</el-button>
|
||||
{if $is_pro}
|
||||
<el-button class="ml-2" type="warning" link @click="onChangeParentUser">
|
||||
更换
|
||||
</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :sm="12" :md="12">
|
||||
<div class="left">主业务身份</div>
|
||||
<div class="right">
|
||||
<b style="font-size: 15px;">{{ state.detail.identity_text || '普通注册会员' }}</b>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :sm="12" :md="12">
|
||||
<div class="left">30天自购活跃窗口(风控硬性门槛)</div>
|
||||
<div class="right sa-flex sa-col-center">
|
||||
<i class="active-dot" :class="state.detail.active_30d ? 'is-on' : 'is-off'"></i>
|
||||
<span>{{ state.detail.active_status_text }}</span>
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
最近自购: {{ state.detail.last_self_order_text }}
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :sm="12" :md="12">
|
||||
<div class="left">直推「推三返一」循环进度</div>
|
||||
<div class="right">
|
||||
<span style="color: #e6a23c; font-weight: 600;">
|
||||
第 {{ state.detail.push3_cycle_index || 1 }} 档 (398/400/600循环)
|
||||
</span>
|
||||
<span class="ml-2" style="font-size: 12px; color: var(--sa-subfont);">
|
||||
{{ state.detail.next_push3_text }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
累计获得推三返一: ¥{{ state.detail.push3_cash_income || '0.00' }}
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<div class="left">身份权益徽章清单</div>
|
||||
<div class="right sa-flex sa-flex-wrap" style="gap: 8px;">
|
||||
<el-tag v-if="state.detail.level == 1" type="info">
|
||||
品酒师 (基础会员·享推三返一/GMV分红/1.5倍券)
|
||||
</el-tag>
|
||||
<el-tag v-if="state.detail.level >= 2" type="primary">
|
||||
创客 V{{ state.detail.level - 1 }} (合伙骨干·享极差/平级10%/超越5%)
|
||||
</el-tag>
|
||||
<el-tag v-if="state.detail.is_venue" type="success">
|
||||
酒道馆 (线下实体枢纽·享50元/单团队奖+100元均分池)
|
||||
</el-tag>
|
||||
<el-tag v-if="state.detail.is_county" type="warning">
|
||||
区县代理 (区域领头人·享团队拆分奖+50元均分池)
|
||||
</el-tag>
|
||||
<el-tag v-if="state.detail.is_partner" type="danger">
|
||||
合伙股东 (独立高阶合伙身份·享顶层红利)
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 看板 2:二开收益中心四大资产核心中枢 -->
|
||||
<div class="card-title">二开核心资产中枢</div>
|
||||
<div class="card-section">
|
||||
<el-row :gutter="12">
|
||||
<!-- 资产 1: 收益钱包 -->
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<div class="asset-card-box">
|
||||
<div class="asset-card-label">可提现收益 (钱包现金)</div>
|
||||
<div class="asset-card-num" style="color: #409eff;">
|
||||
¥{{ state.detail.commission || '0.00' }}
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
累计总现金: ¥{{ state.detail.income_total_cash || '0.00' }}
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
进阶加权奖: ¥{{ state.detail.partner_exclusive_cash || '0.00' }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 资产 2: 分红马豆 -->
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<div class="asset-card-box">
|
||||
<div class="asset-card-label">分红马豆 (GMV 1:1 分红)</div>
|
||||
<div class="asset-card-num" style="color: #67c23a;">
|
||||
{{ state.detail.madow || 0 }} <span style="font-size: 13px;">颗</span>
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
当前估值: ¥{{ state.detail.madow_value || '0.00' }}
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
实时汇率: {{ state.detail.madow_rate || '0.00000075' }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 资产 3: 商城兑换券 -->
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<div class="asset-card-box">
|
||||
<div class="asset-card-label">商城可用兑换券</div>
|
||||
<div class="asset-card-num" style="color: #e6a23c;">
|
||||
¥{{ state.detail.voucher_available || '0.00' }}
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
计划总额: ¥{{ state.detail.voucher_total || '0.00' }}
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
已释放: ¥{{ state.detail.voucher_released || '0.00' }} / 已用: ¥{{ state.detail.voucher_used || '0.00' }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 资产 4: 基础钱包与消费 -->
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<div class="asset-card-box">
|
||||
<div class="asset-card-label">商城余额 / 积分</div>
|
||||
<div class="asset-card-num" style="color: #303133;">
|
||||
¥{{ state.detail.money || '0.00' }}
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
商城积分: {{ state.detail.score || 0 }}
|
||||
</div>
|
||||
<div class="asset-card-desc">
|
||||
累计消费: ¥{{ state.detail.total_consume || '0.00' }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 看板 3:全能用户动态与流水明细 -->
|
||||
<div class="card-title sa-flex sa-row-between">
|
||||
<span>全能动态与业务流水</span>
|
||||
<div v-if="log.tabActive == 'partner_reward'" class="sa-flex sa-col-center">
|
||||
<el-checkbox v-model="log.showSkip" @change="onChangeTab">
|
||||
显示未发放/跳过原因记录
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-section log">
|
||||
<div class="bottom">
|
||||
<el-tabs v-model="log.tabActive" @tab-change="onChangeTab">
|
||||
<el-tab-pane label="收益中心流水" name="partner_reward"></el-tab-pane>
|
||||
<el-tab-pane label="兑换券释放计划" name="voucher_plan"></el-tab-pane>
|
||||
<el-tab-pane label="直推团队成员" name="team"></el-tab-pane>
|
||||
<el-tab-pane label="佣金记录" name="commission"></el-tab-pane>
|
||||
<el-tab-pane label="余额记录" name="money"></el-tab-pane>
|
||||
<el-tab-pane label="积分记录" name="score"></el-tab-pane>
|
||||
<el-tab-pane label="佣金记录" name="commission"></el-tab-pane>
|
||||
<el-tab-pane label="订单记录" name="order"></el-tab-pane>
|
||||
<el-tab-pane label="商城订单" name="order"></el-tab-pane>
|
||||
<el-tab-pane label="分享记录" name="share"></el-tab-pane>
|
||||
<el-tab-pane label="优惠券明细" name="coupon"></el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- Tab 1: 收益中心流水 (二开核心) -->
|
||||
<el-table v-if="log.tabActive == 'partner_reward'" height="460" class="sa-table" :data="log.data" stripe>
|
||||
<el-table-column prop="createtime" label="入账时间" width="165"></el-table-column>
|
||||
<el-table-column prop="reward_type_text" label="奖项类型" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-tag size="small">{{ scope.row.reward_type_text || scope.row.reward_type }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="role_text" label="受益角色" min-width="100"></el-table-column>
|
||||
<el-table-column prop="amount" label="结算金额" min-width="100">
|
||||
<template #default="scope">
|
||||
<b :style="{ color: scope.row.amount > 0 ? '#67c23a' : '#909399' }">
|
||||
¥{{ scope.row.amount }}
|
||||
</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="madow" label="结算马豆" min-width="90"></el-table-column>
|
||||
<el-table-column label="买家" min-width="140">
|
||||
<template #default="scope">
|
||||
<sa-user-profile v-if="scope.row.buyer" :user="scope.row.buyer" :id="scope.row.buyer_id"></sa-user-profile>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关联订单" min-width="150">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.order?.order_sn || scope.row.order_id || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status_text" label="状态" min-width="90">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="scope.row.status == 1 ? 'success' : (scope.row.status == -1 ? 'info' : 'danger')">
|
||||
{{ scope.row.status_text || (scope.row.status == 1 ? '已入账' : (scope.row.status == -1 ? '未发放' : '其他')) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="测算摘要 / 跳过原因" min-width="180" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.formula || scope.row.ext?.formula || scope.row.ext?.skip_reason || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="onShowFormula(scope.row)">明细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- Tab 2: 兑换券释放计划 (二开核心) -->
|
||||
<el-table v-if="log.tabActive == 'voucher_plan'" height="460" class="sa-table" :data="log.data" stripe>
|
||||
<el-table-column prop="id" label="计划ID" width="80"></el-table-column>
|
||||
<el-table-column label="订单号" min-width="170">
|
||||
<template #default="scope">
|
||||
{{ scope.row.order?.order_sn || scope.row.order_id || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="total_fee" label="计划总额" min-width="110">
|
||||
<template #default="scope">
|
||||
<b>¥{{ scope.row.total_fee }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="day_amount" label="每日释放" min-width="100">
|
||||
<template #default="scope">
|
||||
¥{{ scope.row.day_amount }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="释放进度" min-width="140">
|
||||
<template #default="scope">
|
||||
<el-progress :percentage="Math.min(100, Math.round(((scope.row.days_released || 0) / (scope.row.total_days || 365)) * 100))"></el-progress>
|
||||
<div style="font-size: 11px; color: var(--sa-subfont); margin-top: 2px;">
|
||||
第 {{ scope.row.days_released || 0 }} / {{ scope.row.total_days || 365 }} 天
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="released_amount" label="已释放金额" min-width="110"></el-table-column>
|
||||
<el-table-column prop="voucher_available" label="当前可用额" min-width="110">
|
||||
<template #default="scope">
|
||||
<span style="color: #67c23a; font-weight: 600;">¥{{ scope.row.voucher_available }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="start_date" label="开始日期" width="120"></el-table-column>
|
||||
<el-table-column prop="status_text" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="scope.row.status == 'running' ? 'success' : (scope.row.status == 'finished' ? 'info' : 'danger')">
|
||||
{{ scope.row.status_text || scope.row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- Tab 3: 直推团队成员 (二开核心) -->
|
||||
<el-table v-if="log.tabActive == 'team'" height="460" class="sa-table" :data="log.data" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column label="下级成员" min-width="160">
|
||||
<template #default="scope">
|
||||
<sa-user-profile :user="scope.row" :id="scope.row.id"></sa-user-profile>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mobile" label="手机号" min-width="120"></el-table-column>
|
||||
<el-table-column label="业务身份" min-width="160">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.identity_text || '普通会员' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="total_consume" label="累计消费" min-width="110">
|
||||
<template #default="scope">
|
||||
¥{{ scope.row.total_consume }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createtime" label="注册时间" width="165"></el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="onOpenUserDetail(scope.row.id)">
|
||||
查看详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 原生 Tab 4/5/6: 佣金/余额/积分 -->
|
||||
<el-table
|
||||
v-if="log.tabActive=='money' || log.tabActive=='score' || log.tabActive=='commission'"
|
||||
height="520" class="sa-table" :data="log.data" stripe>
|
||||
<el-table-column prop="createtime" label="交易时间" width="172">
|
||||
</el-table-column>
|
||||
<el-table-column prop="amount" label="变动余额" min-width="120">
|
||||
</el-table-column>
|
||||
<el-table-column prop="before" label="变更前" min-width="120">
|
||||
</el-table-column>
|
||||
<el-table-column prop="after" label="剩余余额" min-width="120">
|
||||
</el-table-column>
|
||||
height="460" class="sa-table" :data="log.data" stripe>
|
||||
<el-table-column prop="createtime" label="交易时间" width="172"></el-table-column>
|
||||
<el-table-column prop="amount" label="变动金额/积分" min-width="120"></el-table-column>
|
||||
<el-table-column prop="before" label="变动前" min-width="120"></el-table-column>
|
||||
<el-table-column prop="after" label="变动后" min-width="120"></el-table-column>
|
||||
<el-table-column label="操作人" min-width="160">
|
||||
<template #default="scope">
|
||||
<sa-user-profile type="oper" :user="scope.row.oper" :id="scope.row.oper_id">
|
||||
@@ -330,66 +633,86 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table v-if="log.tabActive=='order'" height="520" class="sa-table" :data="log.data"
|
||||
stripe>
|
||||
<el-table-column prop="createtime" label="下单时间" min-width="172"></el-table-column>
|
||||
<el-table-column prop="order_sn" label="订单号" min-width="280"></el-table-column>
|
||||
<el-table-column prop="platform_text" label="订单来源" min-width="120">
|
||||
</el-table-column>
|
||||
|
||||
<!-- 原生 Tab 7: 商城订单 -->
|
||||
<el-table v-if="log.tabActive=='order'" height="460" class="sa-table" :data="log.data" stripe>
|
||||
<el-table-column prop="createtime" label="下单时间" min-width="165"></el-table-column>
|
||||
<el-table-column prop="order_sn" label="订单号" min-width="200"></el-table-column>
|
||||
<el-table-column prop="platform_text" label="订单来源" min-width="100"></el-table-column>
|
||||
<el-table-column prop="type_text" label="订单类型" min-width="100"></el-table-column>
|
||||
<el-table-column prop="order_amount" label="订单总金额" min-width="140">
|
||||
<el-table-column prop="order_amount" label="总金额" min-width="110"></el-table-column>
|
||||
<el-table-column prop="total_discount_fee" label="优惠" min-width="90"></el-table-column>
|
||||
<el-table-column prop="pay_fee" label="实付金额" min-width="110">
|
||||
<template #default="scope">
|
||||
<b style="color: #67c23a;">¥{{ scope.row.pay_fee }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="total_discount_fee" label="优惠减免" min-width="140">
|
||||
</el-table-column>
|
||||
<el-table-column prop="pay_fee" label="实付金额" min-width="140"></el-table-column>
|
||||
<el-table-column prop="status_text" label="订单状态" min-width="100"></el-table-column>
|
||||
</el-table>
|
||||
<el-table v-if="log.tabActive=='share'" height="520" class="sa-table" :data="log.data"
|
||||
stripe>
|
||||
<el-table-column prop="createtime" label="分享时间" width="172"></el-table-column>
|
||||
<el-table-column label="被分享用户" min-width="180">
|
||||
|
||||
<!-- 原生 Tab 8: 分享记录 -->
|
||||
<el-table v-if="log.tabActive=='share'" height="460" class="sa-table" :data="log.data" stripe>
|
||||
<el-table-column prop="createtime" label="分享时间" width="165"></el-table-column>
|
||||
<el-table-column label="被分享用户" min-width="160">
|
||||
<template #default="scope">
|
||||
<sa-user-profile :user="scope.row.user" :id="scope.user_id">
|
||||
</sa-user-profile>
|
||||
<sa-user-profile :user="scope.row.user" :id="scope.row.user_id"></sa-user-profile>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="from_text" label="分享类型" min-width="120"></el-table-column>
|
||||
<el-table-column label="分享信息" min-width="180" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.ext" class="sa-flex">
|
||||
<sa-image :url="scope.row.ext.image" size="32"></sa-image>
|
||||
<div class="sa-table-line-1 ml-2">{{ scope.row.ext.memo}}</div>
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="platform_text" label="平台" min-width="130"></el-table-column>
|
||||
<el-table-column prop="platform_text" label="平台" min-width="110"></el-table-column>
|
||||
</el-table>
|
||||
<el-table v-if="log.tabActive=='coupon'" height="520" class="sa-table" :data="log.data"
|
||||
stripe>
|
||||
|
||||
<!-- 原生 Tab 9: 优惠券明细 -->
|
||||
<el-table v-if="log.tabActive=='coupon'" height="460" class="sa-table" :data="log.data" stripe>
|
||||
<el-table-column label="优惠券名称" min-width="160">
|
||||
<template #default="scope">
|
||||
<div class="sa-line-1">{{ scope.row.coupon?.name || '-' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="优惠券面额" min-width="120">
|
||||
<el-table-column label="面额" min-width="100">
|
||||
<template #default="scope">
|
||||
<div class="sa-line-1">{{ scope.row.coupon?.amount || '-' }}</div>
|
||||
<div>¥{{ scope.row.coupon?.amount || '-' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status_text" label="优惠券状态" min-width="120"></el-table-column>
|
||||
<el-table-column prop="createtime" label="领取时间" width="172"></el-table-column>
|
||||
<el-table-column prop="use_time" label="使用时间" width="172"></el-table-column>
|
||||
<el-table-column prop="status_text" label="状态" min-width="100"></el-table-column>
|
||||
<el-table-column prop="createtime" label="领取时间" width="165"></el-table-column>
|
||||
<el-table-column prop="use_time" label="使用时间" width="165"></el-table-column>
|
||||
</el-table>
|
||||
<el-footer class="sa-flex sa-row-right">
|
||||
<sa-pagination v-model="pagination" @pagination-change="getLog">
|
||||
</sa-pagination>
|
||||
|
||||
<el-footer class="sa-flex sa-row-right mt-2">
|
||||
<sa-pagination v-model="pagination" @pagination-change="getLog"></sa-pagination>
|
||||
</el-footer>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<!-- 奖励测算公式与跳过原因详情弹窗 -->
|
||||
<el-dialog v-model="formulaDialog.visible" title="奖励测算公式与跳过记录详情" width="550px">
|
||||
<div v-if="formulaDialog.row">
|
||||
<div class="mb-3">
|
||||
<span style="font-weight: 600;">奖项类型:</span>
|
||||
<el-tag size="small">{{ formulaDialog.row.reward_type_text || formulaDialog.row.reward_type }}</el-tag>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<span style="font-weight: 600;">结算金额/马豆:</span>
|
||||
<b>¥{{ formulaDialog.row.amount }}</b>
|
||||
<span v-if="formulaDialog.row.madow > 0" class="ml-2">({{ formulaDialog.row.madow }} 马豆)</span>
|
||||
</div>
|
||||
<div class="mb-3" v-if="formulaDialog.row.ext?.skip_reason">
|
||||
<span style="font-weight: 600; color: #e6a23c;">跳过/未发放原因:</span>
|
||||
<el-tag type="warning" size="small">{{ formulaDialog.row.ext.skip_reason }}</el-tag>
|
||||
</div>
|
||||
<div class="mb-2" style="font-weight: 600;">公式摘要与上下文(ext):</div>
|
||||
<div class="formula-box">
|
||||
{{ JSON.stringify(formulaDialog.row.ext || formulaDialog.row.formula || {}, null, 2) }}
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="formulaDialog.visible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -1,11 +1,60 @@
|
||||
{include file="/shopro/common/script" /}
|
||||
|
||||
<style>
|
||||
.user-index .stats-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.user-index .stat-card {
|
||||
background: #f8fafc;
|
||||
border: 1px solid var(--sa-border);
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.user-index .stat-title {
|
||||
font-size: 12px;
|
||||
color: var(--sa-subfont);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.user-index .stat-num {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--sa-title);
|
||||
}
|
||||
.user-index .active-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.user-index .active-dot.is-on {
|
||||
background: #52c41a;
|
||||
}
|
||||
.user-index .active-dot.is-off {
|
||||
background: #c0c4cc;
|
||||
}
|
||||
.user-index .asset-sub {
|
||||
font-size: 11px;
|
||||
color: var(--sa-subfont);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.user-index .badge-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="index" class="user-index panel panel-default panel-intro" v-cloak>
|
||||
<el-container class="panel-block">
|
||||
<el-header class="sa-header">
|
||||
<el-header class="sa-header" style="height: auto; padding-bottom: 12px;">
|
||||
<div class="sa-title sa-flex sa-row-between">
|
||||
<div class="sa-title-left">
|
||||
<div class="left-name">用户管理</div>
|
||||
<div class="left-name">会员管理</div>
|
||||
<sa-filter-condition v-model="state.filter" @filter-delete="onChangeFilter">
|
||||
</sa-filter-condition>
|
||||
</div>
|
||||
@@ -14,30 +63,125 @@
|
||||
<el-button class="sa-button-refresh" icon="Search" @click="onOpenFilter"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据看板概览 -->
|
||||
<el-row :gutter="12" class="stats-row">
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">全平台会员数</div>
|
||||
<div class="stat-num">{{ state.stats.total_users || 0 }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">创客团队骨干</div>
|
||||
<div class="stat-num" style="color: #409eff;">{{ state.stats.maker_count || 0 }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">线下酒道馆</div>
|
||||
<div class="stat-num" style="color: #67c23a;">{{ state.stats.venue_count || 0 }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">区县代理总数</div>
|
||||
<div class="stat-num" style="color: #e6a23c;">{{ state.stats.county_count || 0 }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">合伙股东总数</div>
|
||||
<div class="stat-num" style="color: #f56c6c;">{{ state.stats.partner_count || 0 }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">30日自购活跃</div>
|
||||
<div class="stat-num" style="color: #52c41a;">{{ state.stats.active_count || 0 }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-header>
|
||||
|
||||
<el-main class="sa-main">
|
||||
<el-table height="100%" class="sa-table" :data="state.data" stripe @sort-change="onChangeSort">
|
||||
<el-table-column prop="id" label="ID" min-width="90" sortable="custom"> </el-table-column>
|
||||
<el-table-column label="用户信息" min-width="150">
|
||||
<el-table-column prop="id" label="ID" min-width="80" sortable="custom"> </el-table-column>
|
||||
|
||||
<el-table-column label="用户信息" min-width="160">
|
||||
<template #default="scope">
|
||||
<sa-user-profile :user="scope.row" :id="scope.row.id"></sa-user-profile>
|
||||
<div class="asset-sub">{{ scope.row.mobile || scope.row.username || '' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="用户名" min-width="120"></el-table-column>
|
||||
<el-table-column prop="mobile" label="手机号" min-width="120"></el-table-column>
|
||||
<el-table-column sortable="custom" prop="commission" label="佣金" min-width="110"></el-table-column>
|
||||
<el-table-column sortable="custom" prop="total_consume" label="总消费" min-width="110"></el-table-column>
|
||||
<el-table-column sortable="custom" prop="score" label="积分" min-width="110"></el-table-column>
|
||||
<el-table-column sortable="custom" prop="money" label="余额" min-width="110"></el-table-column>
|
||||
<el-table-column sortable="custom" prop="createtime" label="注册时间" min-width="172"></el-table-column>
|
||||
<el-table-column sortable="custom" prop="logintime" label="上次登录" min-width="172"></el-table-column>
|
||||
<el-table-column fixed="right" label="操作" min-width="120">
|
||||
|
||||
<el-table-column label="推荐人" min-width="140">
|
||||
<template #default="scope">
|
||||
<sa-user-profile type="agent" :user="scope.row.parent_user" :id="scope.row.parent_user_id"></sa-user-profile>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="业务身份" min-width="190">
|
||||
<template #default="scope">
|
||||
<div style="font-weight: 500;">{{ scope.row.identity_text || '普通会员' }}</div>
|
||||
<div class="badge-group">
|
||||
<el-tag v-if="scope.row.level == 1" size="small" type="info">品酒师</el-tag>
|
||||
<el-tag v-if="scope.row.level >= 2" size="small" type="primary">创客 V{{ scope.row.level - 1 }}</el-tag>
|
||||
<el-tag v-if="scope.row.is_venue" size="small" type="success">酒道馆</el-tag>
|
||||
<el-tag v-if="scope.row.is_county" size="small" type="warning">区县代理</el-tag>
|
||||
<el-tag v-if="scope.row.is_partner" size="small" type="danger">合伙股东</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="30日自购活跃" min-width="110">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.is_advanced">
|
||||
<span class="sa-flex sa-col-center">
|
||||
<i class="active-dot" :class="scope.row.active_30d ? 'is-on' : 'is-off'"></i>
|
||||
{{ scope.row.active_30d ? '正常领奖' : '已暂停' }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag size="small" type="info" effect="plain">免考核</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="收益中心资产" min-width="190">
|
||||
<template #default="scope">
|
||||
<div><span class="asset-sub">可提现收益:</span> <b>¥{{ scope.row.commission || '0.00' }}</b></div>
|
||||
<div class="asset-sub">马豆: {{ scope.row.madow || 0 }} (约 ¥{{ scope.row.madow_value || '0.00' }})</div>
|
||||
<div class="asset-sub">可用券: ¥{{ scope.row.voucher_available || '0.00' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="商城钱包与消费" min-width="160">
|
||||
<template #default="scope">
|
||||
<div><span class="asset-sub">余额:</span> ¥{{ scope.row.money || '0.00' }}</div>
|
||||
<div class="asset-sub">积分: {{ scope.row.score || 0 }}</div>
|
||||
<div class="asset-sub">消费: ¥{{ scope.row.total_consume || '0.00' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column sortable="custom" prop="createtime" label="注册时间" min-width="165"></el-table-column>
|
||||
|
||||
<el-table-column fixed="right" label="操作" min-width="180">
|
||||
<template #default="scope">
|
||||
{if $auth->check('shopro/user/user/detail')}
|
||||
<el-button type="primary" link @click="onDetail(scope.row.id)">查看</el-button>
|
||||
<el-button type="primary" link @click="onDetail(scope.row.id, scope.row)">详情</el-button>
|
||||
{/if}
|
||||
<el-button v-if="scope.row.has_agent" type="success" link @click="onOpenAgentDetail(scope.row.id)">
|
||||
收益档案
|
||||
</el-button>
|
||||
{if $auth->check('shopro/user/user/recharge')}
|
||||
<el-button type="warning" link @click="onRecharge('money', scope.row.id)">
|
||||
充值
|
||||
</el-button>
|
||||
{/if}
|
||||
<el-popconfirm width="fit-content" confirm-button-text="确认" cancel-button-text="取消"
|
||||
title="确认删除这条记录?" @confirm="onDelete(scope.row.id)">
|
||||
title="确认删除该会员记录?" @confirm="onDelete(scope.row.id)">
|
||||
<template #reference>
|
||||
{if $auth->check('shopro/user/user/delete')}
|
||||
<el-button type="danger" link>删除</el-button>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
在新工单中引用
屏蔽一个用户