feat(commission): 支持计奖商品参与设置、独立分成与多规格规则,完善团队中心与微信快捷登录
这个提交包含在:
@@ -6,6 +6,7 @@ use think\Db;
|
||||
use app\admin\model\shopro\user\User as UserModel;
|
||||
use app\admin\model\shopro\commission\Agent as AgentModel;
|
||||
use app\admin\model\shopro\goods\Goods as GoodsModel;
|
||||
use app\admin\model\shopro\partner\Reward as RewardModel;
|
||||
use addons\shopro\service\Wallet;
|
||||
|
||||
|
||||
@@ -268,11 +269,21 @@ class Agent extends Commission
|
||||
|
||||
$user->team_level = ($user->parent_user_id == $agentId) ? 1 : 2;
|
||||
$rawMobile = $user->mobile ? (string) $user->mobile : '';
|
||||
$maskedMobile = $rawMobile ? (strlen($rawMobile) == 11 ? substr_replace($rawMobile, '****', 3, 4) : $rawMobile) : '';
|
||||
$user->mobile = $maskedMobile;
|
||||
$user->mobile_hide = $maskedMobile;
|
||||
$user->mobile_raw = $rawMobile;
|
||||
$user->mobile_hide = $rawMobile ? (strlen($rawMobile) == 11 ? substr_replace($rawMobile, '****', 3, 4) : $rawMobile) : '';
|
||||
|
||||
// 间推所属推荐人手机号脱敏
|
||||
if ($user->parentUser && $user->parentUser->mobile) {
|
||||
$pMobile = (string) $user->parentUser->mobile;
|
||||
$pMasked = strlen($pMobile) == 11 ? substr_replace($pMobile, '****', 3, 4) : $pMobile;
|
||||
$user->parentUser->mobile = $pMasked;
|
||||
$user->parentUser->mobile_hide = $pMasked;
|
||||
}
|
||||
});
|
||||
|
||||
// 9. 计算整体团队健康度统计
|
||||
// 9. 计算整体团队健康度与综合收益业绩统计
|
||||
$summary = $this->getTeamHealthSummary($agentId, $level1Ids, $windowStart, $expiringStart);
|
||||
|
||||
$result = $data->toArray();
|
||||
@@ -282,7 +293,7 @@ class Agent extends Commission
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算团队保活健康度统计
|
||||
* 计算团队保活健康度与综合收益统计
|
||||
*/
|
||||
protected function getTeamHealthSummary($agentId, $level1Ids, $windowStart, $expiringStart)
|
||||
{
|
||||
@@ -298,6 +309,10 @@ class Agent extends Commission
|
||||
'expiring_count' => 0,
|
||||
'inactive_count' => 0,
|
||||
'partner_count' => 0,
|
||||
'team_total_consume' => '0.00',
|
||||
'team_total_reward' => '0.00',
|
||||
'team_month_reward' => '0.00',
|
||||
'team_order_count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -306,6 +321,7 @@ class Agent extends Commission
|
||||
$expiringCount = 0;
|
||||
$inactiveCount = 0;
|
||||
$partnerCount = 0;
|
||||
$teamOrderCount = 0;
|
||||
|
||||
foreach ($agents as $ag) {
|
||||
$level = intval($ag->level ?? 0);
|
||||
@@ -323,6 +339,8 @@ class Agent extends Commission
|
||||
} else {
|
||||
$inactiveCount++;
|
||||
}
|
||||
|
||||
$teamOrderCount += intval($ag->child_order_count_0 ?? 0);
|
||||
}
|
||||
|
||||
$missingAgentCount = count($teamUserIds) - count($agents);
|
||||
@@ -330,12 +348,31 @@ class Agent extends Commission
|
||||
$inactiveCount += $missingAgentCount;
|
||||
}
|
||||
|
||||
// 1. 团队累计自购总业绩
|
||||
$teamTotalConsume = UserModel::whereIn('id', $teamUserIds)->sum('total_consume');
|
||||
|
||||
// 2. 团队成员为当前用户贡献的累计收益与本月收益
|
||||
$monthStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$teamTotalReward = RewardModel::where('user_id', $agentId)
|
||||
->whereIn('buyer_id', $teamUserIds)
|
||||
->where('status', 1)
|
||||
->sum('amount');
|
||||
$teamMonthReward = RewardModel::where('user_id', $agentId)
|
||||
->whereIn('buyer_id', $teamUserIds)
|
||||
->where('status', 1)
|
||||
->where('createtime', '>=', $monthStart)
|
||||
->sum('amount');
|
||||
|
||||
return [
|
||||
'total_count' => count($teamUserIds),
|
||||
'active_count' => $activeCount,
|
||||
'expiring_count' => $expiringCount,
|
||||
'inactive_count' => $inactiveCount,
|
||||
'partner_count' => $partnerCount,
|
||||
'team_total_consume' => \addons\shopro\service\partner\IdentityService::formatMoney($teamTotalConsume),
|
||||
'team_total_reward' => \addons\shopro\service\partner\IdentityService::formatMoney($teamTotalReward),
|
||||
'team_month_reward' => \addons\shopro\service\partner\IdentityService::formatMoney($teamMonthReward),
|
||||
'team_order_count' => $teamOrderCount,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ class Index extends Commission
|
||||
{
|
||||
$userId = $this->service->user->id;
|
||||
$tab = $this->request->param('tab', 'all');
|
||||
$buyerId = intval($this->request->param('buyer_id', 0));
|
||||
$query = RewardModel::where('user_id', $userId)->with(['buyer', 'order']);
|
||||
|
||||
$tabMap = [
|
||||
@@ -52,6 +53,9 @@ class Index extends Commission
|
||||
if ($tab !== 'all' && isset($tabMap[$tab])) {
|
||||
$query->whereIn('reward_type', $tabMap[$tab]);
|
||||
}
|
||||
if ($buyerId > 0) {
|
||||
$query->where('buyer_id', $buyerId);
|
||||
}
|
||||
|
||||
$list = $query->order('id', 'desc')->paginate($this->request->param('list_rows', 10));
|
||||
$rows = [];
|
||||
@@ -74,11 +78,26 @@ class Index extends Commission
|
||||
];
|
||||
}
|
||||
|
||||
$buyerInfo = null;
|
||||
if ($buyerId > 0) {
|
||||
$buyerUser = \app\admin\model\shopro\user\User::get($buyerId);
|
||||
if ($buyerUser) {
|
||||
$rawMobile = (string) ($buyerUser->mobile ?? '');
|
||||
$buyerInfo = [
|
||||
'id' => $buyerUser->id,
|
||||
'nickname' => $buyerUser->nickname,
|
||||
'avatar' => $buyerUser->avatar,
|
||||
'mobile' => $rawMobile ? (strlen($rawMobile) == 11 ? substr_replace($rawMobile, '****', 3, 4) : $rawMobile) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('ok', [
|
||||
'total' => $list->total(),
|
||||
'per_page' => $list->listRows(),
|
||||
'current_page' => $list->currentPage(),
|
||||
'data' => $rows,
|
||||
'buyer_info' => $buyerInfo,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,10 @@ class Wechat extends Common
|
||||
});
|
||||
|
||||
if ($result) {
|
||||
$this->success('登陆成功');
|
||||
$auth = \app\common\library\Auth::instance();
|
||||
$this->success('登陆成功', [
|
||||
'token' => $auth->isLogin() ? $auth->getToken() : ''
|
||||
]);
|
||||
}
|
||||
$this->error('登陆失败');
|
||||
}
|
||||
|
||||
@@ -39,9 +39,60 @@ class AgentFilter extends BaseFilter
|
||||
if ($value === 'county') {
|
||||
return $query->where('is_county', 1);
|
||||
}
|
||||
if ($value === 'partner') {
|
||||
return $query->where('is_partner', 1);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 等级筛选 (如 1 为品酒师)
|
||||
*/
|
||||
public function level($query, $data)
|
||||
{
|
||||
$value = $this->plainValue($data);
|
||||
if ($value === '' || $value === null) {
|
||||
return $query;
|
||||
}
|
||||
return $query->where('level', (int)$value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 酒道馆筛选 (1 为酒道馆)
|
||||
*/
|
||||
public function isVenue($query, $data)
|
||||
{
|
||||
$value = $this->plainValue($data);
|
||||
if ($value === '' || $value === null) {
|
||||
return $query;
|
||||
}
|
||||
return $query->where('is_venue', (int)$value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 区县代理筛选 (1 为区县代理)
|
||||
*/
|
||||
public function isCounty($query, $data)
|
||||
{
|
||||
$value = $this->plainValue($data);
|
||||
if ($value === '' || $value === null) {
|
||||
return $query;
|
||||
}
|
||||
return $query->where('is_county', (int)$value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合伙股东筛选 (1 为合伙股东)
|
||||
*/
|
||||
public function isPartner($query, $data)
|
||||
{
|
||||
$value = $this->plainValue($data);
|
||||
if ($value === '' || $value === null) {
|
||||
return $query;
|
||||
}
|
||||
return $query->where('is_partner', (int)$value);
|
||||
}
|
||||
|
||||
public function active30d($query, $data)
|
||||
{
|
||||
$value = $this->plainValue($data);
|
||||
|
||||
@@ -66,7 +66,7 @@ class OrderCreate
|
||||
/**
|
||||
* 用户选择的兑换券抵扣金额
|
||||
*/
|
||||
protected $voucher_fee = 0;
|
||||
protected $voucher_fee = null;
|
||||
|
||||
/**
|
||||
* 用户备注
|
||||
@@ -158,7 +158,7 @@ class OrderCreate
|
||||
$this->invoice_id = $params['invoice_id'] ?? 0;
|
||||
|
||||
$this->coupon_id = $params['coupon_id'] ?? 0;
|
||||
$this->voucher_fee = isset($params['voucher_fee']) ? floatval($params['voucher_fee']) : 0;
|
||||
$this->voucher_fee = (isset($params['voucher_fee']) && $params['voucher_fee'] !== '') ? floatval($params['voucher_fee']) : null;
|
||||
$this->remark = $params['remark'] ?? '';
|
||||
|
||||
$this->money = (isset($params['money']) && $params['money'] > 0) ? $params['money'] : 0;
|
||||
@@ -611,7 +611,14 @@ class OrderCreate
|
||||
$available = (new \addons\shopro\service\partner\VoucherService())->available($this->user->id);
|
||||
}
|
||||
$this->orderData['voucher_available'] = $available;
|
||||
$want = max(0, round((float) $this->voucher_fee, 2));
|
||||
|
||||
// 兑换商城订单:若未显式指定抵扣额度,默认自动全额应用可用兑换券
|
||||
if ($this->voucher_fee === null) {
|
||||
$want = $this->order_type == 'score' ? (float) $available : 0;
|
||||
} else {
|
||||
$want = max(0, round((float) $this->voucher_fee, 2));
|
||||
}
|
||||
|
||||
$apply = min($want, (float) $available, (float) $this->orderData['pay_fee']);
|
||||
$apply = $apply > 0 ? $apply : 0;
|
||||
$this->orderData['voucher_fee'] = $apply;
|
||||
|
||||
@@ -7,9 +7,11 @@ namespace addons\shopro\service\partner;
|
||||
*/
|
||||
class MakerDiffReward
|
||||
{
|
||||
public function run($order, $buyerId, $chain, ActiveService $active)
|
||||
public function run($order, $buyerId, $chain, ActiveService $active, $rules = null)
|
||||
{
|
||||
$bonus = PartnerConfig::makerBonus();
|
||||
$bonus = (!empty($rules['maker_bonus']) && is_array($rules['maker_bonus']))
|
||||
? $rules['maker_bonus']
|
||||
: PartnerConfig::makerBonus();
|
||||
$seen = 0;
|
||||
$paid = [];
|
||||
|
||||
|
||||
@@ -7,11 +7,17 @@ namespace addons\shopro\service\partner;
|
||||
*/
|
||||
class MakerPeerReward
|
||||
{
|
||||
public function run($order, $buyerId, $chain, $diffResult, ActiveService $active)
|
||||
public function run($order, $buyerId, $chain, $diffResult, ActiveService $active, $rules = null)
|
||||
{
|
||||
$bonus = PartnerConfig::makerBonus();
|
||||
$peerRate = (float) PartnerConfig::get('partner_maker_peer_rate', 0.10);
|
||||
$overRate = (float) PartnerConfig::get('partner_maker_overtaken_rate', 0.05);
|
||||
$bonus = (!empty($rules['maker_bonus']) && is_array($rules['maker_bonus']))
|
||||
? $rules['maker_bonus']
|
||||
: PartnerConfig::makerBonus();
|
||||
$peerRate = (isset($rules['maker_peer_rate']) && is_numeric($rules['maker_peer_rate']))
|
||||
? (float) $rules['maker_peer_rate']
|
||||
: (float) PartnerConfig::get('partner_maker_peer_rate', 0.10);
|
||||
$overRate = (isset($rules['maker_overtaken_rate']) && is_numeric($rules['maker_overtaken_rate']))
|
||||
? (float) $rules['maker_overtaken_rate']
|
||||
: (float) PartnerConfig::get('partner_maker_overtaken_rate', 0.05);
|
||||
$minAmount = (float) PartnerConfig::get('partner_peer_min_amount', 0.10);
|
||||
if ($minAmount <= 0) {
|
||||
$minAmount = 0.10;
|
||||
|
||||
@@ -167,8 +167,143 @@ class PartnerConfig
|
||||
];
|
||||
}
|
||||
|
||||
public static function dailyTypes()
|
||||
public static function defaultRules()
|
||||
{
|
||||
return ['venue_share', 'county_share', 'madow_share', 'voucher_release'];
|
||||
return [
|
||||
'push3_amounts' => self::push3Amounts(),
|
||||
'maker_bonus' => self::makerBonus(),
|
||||
'maker_peer_rate' => (float) self::get('partner_maker_peer_rate', 0.10),
|
||||
'maker_overtaken_rate' => (float) self::get('partner_maker_overtaken_rate', 0.05),
|
||||
'venue_split' => (float) self::get('partner_venue_split', 50),
|
||||
'county_full' => (float) self::get('partner_county_full', 100),
|
||||
'venue_peer_rate' => (float) self::get('partner_venue_peer_rate', 0.10),
|
||||
'county_peer_rate' => (float) self::get('partner_county_peer_rate', 0.10),
|
||||
'voucher_times' => (float) self::get('partner_voucher_times', 1.5),
|
||||
'voucher_days' => (int) self::get('partner_voucher_days', 365),
|
||||
'venue_share' => (float) self::get('partner_venue_share_per_order', 100),
|
||||
'county_share' => (float) self::get('partner_county_share_per_order', 50),
|
||||
'is_custom' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定商品/规格的生效分成规则(优先独立规则,未设置项回退默认规则)
|
||||
*/
|
||||
public static function getEffectiveRules($goodsId, $skuPriceId = 0)
|
||||
{
|
||||
$rules = self::defaultRules();
|
||||
$goodsId = intval($goodsId);
|
||||
if ($goodsId <= 0) {
|
||||
return $rules;
|
||||
}
|
||||
|
||||
try {
|
||||
$cg = \think\Db::name('shopro_commission_goods')->where('goods_id', $goodsId)->find();
|
||||
if (!$cg || intval($cg['status'] ?? 0) !== 1) {
|
||||
// 未参与计奖
|
||||
$rules['status'] = 0;
|
||||
return $rules;
|
||||
}
|
||||
$rules['status'] = 1;
|
||||
$rules['commission_order_status'] = intval($cg['commission_order_status'] ?? 1);
|
||||
$selfRules = intval($cg['self_rules'] ?? 0);
|
||||
$rules['self_rules'] = $selfRules;
|
||||
|
||||
if ($selfRules !== 1 && $selfRules !== 2) {
|
||||
return $rules; // 默认规则
|
||||
}
|
||||
|
||||
$raw = $cg['commission_rules'] ?? '';
|
||||
$custom = is_array($raw) ? $raw : (json_decode($raw, true) ?: []);
|
||||
if (empty($custom)) {
|
||||
return $rules;
|
||||
}
|
||||
|
||||
// 多规格支持:优先匹配特定规格配置
|
||||
$skuKey = intval($skuPriceId);
|
||||
if ($skuKey > 0 && isset($custom[$skuKey]) && is_array($custom[$skuKey])) {
|
||||
$custom = $custom[$skuKey];
|
||||
} elseif ($skuKey > 0 && isset($custom['sku_' . $skuKey]) && is_array($custom['sku_' . $skuKey])) {
|
||||
$custom = $custom['sku_' . $skuKey];
|
||||
}
|
||||
|
||||
if (isset($custom['push3_amounts']) && is_array($custom['push3_amounts']) && count($custom['push3_amounts']) >= 3) {
|
||||
$rules['push3_amounts'] = array_map('floatval', array_values($custom['push3_amounts']));
|
||||
}
|
||||
if (isset($custom['maker_bonus']) && is_array($custom['maker_bonus'])) {
|
||||
$mb = [];
|
||||
foreach ($custom['maker_bonus'] as $lvl => $val) {
|
||||
$mb[intval($lvl)] = floatval($val);
|
||||
}
|
||||
$rules['maker_bonus'] = $mb;
|
||||
}
|
||||
if (isset($custom['maker_peer_rate']) && is_numeric($custom['maker_peer_rate'])) {
|
||||
$rules['maker_peer_rate'] = floatval($custom['maker_peer_rate']);
|
||||
}
|
||||
if (isset($custom['maker_overtaken_rate']) && is_numeric($custom['maker_overtaken_rate'])) {
|
||||
$rules['maker_overtaken_rate'] = floatval($custom['maker_overtaken_rate']);
|
||||
}
|
||||
if (isset($custom['venue_split']) && is_numeric($custom['venue_split'])) {
|
||||
$rules['venue_split'] = floatval($custom['venue_split']);
|
||||
}
|
||||
if (isset($custom['county_full']) && is_numeric($custom['county_full'])) {
|
||||
$rules['county_full'] = floatval($custom['county_full']);
|
||||
}
|
||||
if (isset($custom['venue_peer_rate']) && is_numeric($custom['venue_peer_rate'])) {
|
||||
$rules['venue_peer_rate'] = floatval($custom['venue_peer_rate']);
|
||||
}
|
||||
if (isset($custom['county_peer_rate']) && is_numeric($custom['county_peer_rate'])) {
|
||||
$rules['county_peer_rate'] = floatval($custom['county_peer_rate']);
|
||||
}
|
||||
if (isset($custom['voucher_times']) && is_numeric($custom['voucher_times'])) {
|
||||
$rules['voucher_times'] = floatval($custom['voucher_times']);
|
||||
}
|
||||
if (isset($custom['voucher_days']) && is_numeric($custom['voucher_days'])) {
|
||||
$rules['voucher_days'] = intval($custom['voucher_days']);
|
||||
}
|
||||
if (isset($custom['venue_share']) && is_numeric($custom['venue_share'])) {
|
||||
$rules['venue_share'] = floatval($custom['venue_share']);
|
||||
}
|
||||
if (isset($custom['county_share']) && is_numeric($custom['county_share'])) {
|
||||
$rules['county_share'] = floatval($custom['county_share']);
|
||||
}
|
||||
|
||||
$rules['is_custom'] = true;
|
||||
return $rules;
|
||||
} catch (\Exception $e) {
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析订单中主计奖商品的生效分成规则
|
||||
*/
|
||||
public static function getOrderEffectiveRules($order)
|
||||
{
|
||||
$default = self::defaultRules();
|
||||
$items = $order['items'] ?? [];
|
||||
if (empty($items) && !empty($order['id'])) {
|
||||
$fresh = \app\admin\model\shopro\order\Order::with(['items'])->where('id', $order['id'])->find();
|
||||
if ($fresh) {
|
||||
$items = $fresh['items'] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
// 优先寻找设置了独立分成规则的计奖商品
|
||||
foreach ($items as $item) {
|
||||
$ext = is_array($item['ext'] ?? null) ? $item['ext'] : (json_decode($item['ext'] ?? '', true) ?: []);
|
||||
if (isset($ext['is_commission']) && !$ext['is_commission']) {
|
||||
continue;
|
||||
}
|
||||
$goodsId = intval($item['goods_id'] ?? 0);
|
||||
$skuId = intval($item['goods_sku_price_id'] ?? 0);
|
||||
$rules = self::getEffectiveRules($goodsId, $skuId);
|
||||
if (!empty($rules['is_custom'])) {
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,16 +40,19 @@ class PartnerRewardService
|
||||
$parentChain = $chainService->collect($buyerId, false);
|
||||
$selfChain = $chainService->collect($buyerId, true);
|
||||
|
||||
// 动态解析商品独立分成规则与全局默认配置
|
||||
$orderRules = PartnerConfig::getOrderEffectiveRules($order);
|
||||
|
||||
$parent = $parentChain[0] ?? null;
|
||||
(new Push3Reward())->run($order, $buyerId, $parent, $active);
|
||||
(new Push3Reward())->run($order, $buyerId, $parent, $active, $orderRules);
|
||||
|
||||
$diff = (new MakerDiffReward())->run($order, $buyerId, $parentChain, $active);
|
||||
(new MakerPeerReward())->run($order, $buyerId, $parentChain, $diff, $active);
|
||||
$diff = (new MakerDiffReward())->run($order, $buyerId, $parentChain, $active, $orderRules);
|
||||
(new MakerPeerReward())->run($order, $buyerId, $parentChain, $diff, $active, $orderRules);
|
||||
|
||||
$team = (new TeamSplitReward())->run($order, $buyerId, $selfChain, $active);
|
||||
(new PeerShareReward())->run($order, $buyerId, $selfChain, $team, $active);
|
||||
$team = (new TeamSplitReward())->run($order, $buyerId, $selfChain, $active, $orderRules);
|
||||
(new PeerShareReward())->run($order, $buyerId, $selfChain, $team, $active, $orderRules);
|
||||
|
||||
(new VoucherService())->grant($order, $buyerId);
|
||||
(new VoucherService())->grant($order, $buyerId, $orderRules);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -7,10 +7,14 @@ namespace addons\shopro\service\partner;
|
||||
*/
|
||||
class PeerShareReward
|
||||
{
|
||||
public function run($order, $buyerId, $chain, $teamResult, ActiveService $active)
|
||||
public function run($order, $buyerId, $chain, $teamResult, ActiveService $active, $rules = null)
|
||||
{
|
||||
$venueRate = (float) PartnerConfig::get('partner_venue_peer_rate', 0.10);
|
||||
$countyRate = (float) PartnerConfig::get('partner_county_peer_rate', 0.10);
|
||||
$venueRate = (isset($rules['venue_peer_rate']) && is_numeric($rules['venue_peer_rate']))
|
||||
? (float) $rules['venue_peer_rate']
|
||||
: (float) PartnerConfig::get('partner_venue_peer_rate', 0.10);
|
||||
$countyRate = (isset($rules['county_peer_rate']) && is_numeric($rules['county_peer_rate']))
|
||||
? (float) $rules['county_peer_rate']
|
||||
: (float) PartnerConfig::get('partner_county_peer_rate', 0.10);
|
||||
|
||||
if (!empty($teamResult['venue'])) {
|
||||
$this->peer($order, $buyerId, $chain, $teamResult['venue'], 'is_venue', 'venue_peer', 'venue', $venueRate, $active, '酒道馆');
|
||||
|
||||
@@ -9,7 +9,7 @@ use app\admin\model\shopro\commission\Agent as AgentModel;
|
||||
*/
|
||||
class Push3Reward
|
||||
{
|
||||
public function run($order, $buyerId, $parent, ActiveService $active)
|
||||
public function run($order, $buyerId, $parent, ActiveService $active, $rules = null)
|
||||
{
|
||||
if (!$parent) {
|
||||
PartnerRewardService::writeSkip($order, $buyerId, 0, 'push3', 'taster', 'no_parent', '暂无推荐人,不计提推荐开拓奖');
|
||||
@@ -35,7 +35,9 @@ class Push3Reward
|
||||
$agent->direct_order_seq = intval($agent->direct_order_seq) + 1;
|
||||
$agent->save();
|
||||
|
||||
$amounts = PartnerConfig::push3Amounts();
|
||||
$amounts = (!empty($rules['push3_amounts']) && is_array($rules['push3_amounts']))
|
||||
? $rules['push3_amounts']
|
||||
: PartnerConfig::push3Amounts();
|
||||
$seq = intval($agent->direct_order_seq);
|
||||
$amount = $amounts[($seq - 1) % 3];
|
||||
|
||||
|
||||
@@ -7,10 +7,14 @@ namespace addons\shopro\service\partner;
|
||||
*/
|
||||
class TeamSplitReward
|
||||
{
|
||||
public function run($order, $buyerId, $chain, ActiveService $active)
|
||||
public function run($order, $buyerId, $chain, ActiveService $active, $rules = null)
|
||||
{
|
||||
$venueSplit = (float) PartnerConfig::get('partner_venue_split', 50);
|
||||
$countyFull = (float) PartnerConfig::get('partner_county_full', 100);
|
||||
$venueSplit = (isset($rules['venue_split']) && is_numeric($rules['venue_split']))
|
||||
? (float) $rules['venue_split']
|
||||
: (float) PartnerConfig::get('partner_venue_split', 50);
|
||||
$countyFull = (isset($rules['county_full']) && is_numeric($rules['county_full']))
|
||||
? (float) $rules['county_full']
|
||||
: (float) PartnerConfig::get('partner_county_full', 100);
|
||||
$result = ['venue' => null, 'county' => null];
|
||||
|
||||
$venue = $this->nearest($chain, 'is_venue', $active);
|
||||
|
||||
@@ -123,15 +123,19 @@ class VoucherService
|
||||
}
|
||||
}
|
||||
|
||||
public function grant($order, $buyerId)
|
||||
public function grant($order, $buyerId, $rules = null)
|
||||
{
|
||||
$exists = VoucherPlan::where('order_id', $order['id'])->find();
|
||||
if ($exists) {
|
||||
return $exists;
|
||||
}
|
||||
|
||||
$times = (float) PartnerConfig::get('partner_voucher_times', 1.5);
|
||||
$days = intval(PartnerConfig::get('partner_voucher_days', 365));
|
||||
$times = (isset($rules['voucher_times']) && is_numeric($rules['voucher_times']))
|
||||
? (float) $rules['voucher_times']
|
||||
: (float) PartnerConfig::get('partner_voucher_times', 1.5);
|
||||
$days = (isset($rules['voucher_days']) && is_numeric($rules['voucher_days']))
|
||||
? intval($rules['voucher_days'])
|
||||
: intval(PartnerConfig::get('partner_voucher_days', 365));
|
||||
$days = $days > 0 ? $days : 365;
|
||||
$rewardService = new PartnerRewardService();
|
||||
$qualifiedPayFee = $rewardService->getQualifiedPayFee($order);
|
||||
|
||||
@@ -249,6 +249,19 @@ class PayOper
|
||||
// 订单支付完成
|
||||
$user = User::where('id', $order->user_id)->find();
|
||||
if ($order_type == 'order') {
|
||||
// 兑换券同步核销(利用行锁即时扣减并标记,彻底消除异步队列延迟导致的并发双花漏洞)
|
||||
$ext = $order->ext;
|
||||
if (is_string($ext)) {
|
||||
$ext = json_decode($ext, true) ?: [];
|
||||
}
|
||||
if (!empty($ext['voucher_fee']) && empty($ext['voucher_consumed'])) {
|
||||
try {
|
||||
(new \addons\shopro\service\partner\VoucherService())->consumeByOrder($order);
|
||||
} catch (\Exception $e) {
|
||||
\think\Log::record('[PayOper.consumeByOrder] 兑换券同步核销异常: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
if ($pay_mode == 'offline') {
|
||||
Action::add($order, null, auth_admin(), 'admin', '管理员操作自动货到付款支付成功');
|
||||
// 在控制器执行后续内容,这里不再处理
|
||||
|
||||
@@ -34,10 +34,16 @@ class MiniProgram
|
||||
error_stop('登陆态已过期, 请重试', -1);
|
||||
}
|
||||
|
||||
// 如果前端传了手机号凭证(新版 code 或传统 iv+encryptedData),解析手机号
|
||||
$mobile = '';
|
||||
if (!empty($this->payload['code']) || (!empty($this->payload['iv']) && !empty($this->payload['encryptedData']))) {
|
||||
$mobile = $this->getUserPhoneNumber();
|
||||
}
|
||||
|
||||
$wechatUser = [
|
||||
'openid' => $sessionData['openid'],
|
||||
'unionid' => $sessionData['unionid'] ?? '',
|
||||
'mobile' => '',
|
||||
'mobile' => $mobile,
|
||||
'avatar' => '',
|
||||
'nickname' => '',
|
||||
];
|
||||
@@ -65,9 +71,32 @@ class MiniProgram
|
||||
return $wechatUser;
|
||||
}
|
||||
|
||||
// 解密微信小程序手机号
|
||||
// 解密微信小程序手机号(支持新版 code 与传统 iv + encryptedData)
|
||||
public function getUserPhoneNumber()
|
||||
{
|
||||
// 1. 优先使用新版微信小程序通过 code 获取手机号 API
|
||||
if (!empty($this->payload['code'])) {
|
||||
try {
|
||||
$response = $this->wechat->auth->httpPostJson('wxa/business/getuserphonenumber', [
|
||||
'code' => $this->payload['code']
|
||||
]);
|
||||
if (isset($response['errcode']) && (int)$response['errcode'] === 0 && !empty($response['phone_info']['purePhoneNumber'])) {
|
||||
if (!empty($response['phone_info']['countryCode']) && $response['phone_info']['countryCode'] !== '86') {
|
||||
error_stop('仅支持大陆地区手机号');
|
||||
}
|
||||
return $response['phone_info']['purePhoneNumber'];
|
||||
}
|
||||
if (!empty($response['errmsg']) && empty($this->payload['encryptedData'])) {
|
||||
error_stop('获取手机号失败: ' . $response['errmsg']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if (empty($this->payload['encryptedData'])) {
|
||||
error_stop('获取手机号异常: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 兼容传统 iv + encryptedData 模式
|
||||
if (empty($this->payload['sessionId'])) {
|
||||
error_stop('未获取到登陆态, 请重试', -1);
|
||||
}
|
||||
@@ -78,6 +107,10 @@ class MiniProgram
|
||||
error_stop('登陆态已过期, 请重试', -1);
|
||||
}
|
||||
|
||||
if (empty($this->payload['iv']) || empty($this->payload['encryptedData'])) {
|
||||
error_stop('缺少手机号授权凭证');
|
||||
}
|
||||
|
||||
$phoneInfo = $this->wechat->encryptor->decryptData($sessionData['session_key'], $this->payload['iv'], $this->payload['encryptedData']);
|
||||
|
||||
if (empty($phoneInfo['purePhoneNumber'])) {
|
||||
|
||||
@@ -146,6 +146,23 @@ class Wechat
|
||||
// 用户被删除,则重置第三方信息所绑定的用户id
|
||||
if (!$user) {
|
||||
$oauthInfo->user_id = 0;
|
||||
} else {
|
||||
// 如果本次快捷登录提供了手机号,且用户原本没有手机号,则为用户补充手机号
|
||||
$isChanged = false;
|
||||
if (!empty($extend['mobile']) && empty($user->mobile)) {
|
||||
$user->mobile = $extend['mobile'];
|
||||
$isChanged = true;
|
||||
}
|
||||
$verification = $user->verification;
|
||||
$verificationObj = is_object($verification) ? $verification : (object)$verification;
|
||||
if ((!empty($user->mobile) || !empty($extend['mobile'])) && empty($verificationObj->mobile)) {
|
||||
$verificationObj->mobile = 1;
|
||||
$user->verification = $verificationObj;
|
||||
$isChanged = true;
|
||||
}
|
||||
if ($isChanged) {
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +178,18 @@ class Wechat
|
||||
return false;
|
||||
}
|
||||
|
||||
// 注册新用户
|
||||
// 注册新用户或匹配已有手机号用户
|
||||
if ($oauthInfo->user_id === 0 && $user_id === 0) {
|
||||
// 如果获取到了手机号,检查系统是否已有该手机号用户
|
||||
if (!empty($extend['mobile'])) {
|
||||
$existingUser = UserModel::where('mobile', $extend['mobile'])->find();
|
||||
if ($existingUser) {
|
||||
// 直接将此第三方账号关联到该已有手机号用户
|
||||
$oauthInfo->user_id = $existingUser->id;
|
||||
return $oauthInfo->save();
|
||||
}
|
||||
}
|
||||
|
||||
$auth = (new UserAuth())->register([
|
||||
'nickname' => $oauthInfo->nickname ?? '',
|
||||
'avatar' => $oauthInfo->avatar ?? '',
|
||||
|
||||
@@ -317,6 +317,10 @@ class ScoreShop extends Common
|
||||
unset($skuPrice['createtime'], $skuPrice['updatetime'], $skuPrice['deletetime']); // 不手动更新时间
|
||||
$skuPrice['goods_id'] = $goods_id;
|
||||
|
||||
$skuPrice['score'] = isset($skuPrice['score']) && $skuPrice['score'] !== '' ? intval($skuPrice['score']) : 0;
|
||||
$skuPrice['price'] = isset($skuPrice['price']) && $skuPrice['price'] !== '' ? round(floatval($skuPrice['price']), 2) : 0.00;
|
||||
$skuPrice['stock'] = isset($skuPrice['stock']) && $skuPrice['stock'] !== '' ? intval($skuPrice['stock']) : 0;
|
||||
|
||||
$model = new ScoreSkuPrice;
|
||||
if (isset($skuPrice['id'])) {
|
||||
$model = $this->model->find($skuPrice['id']);
|
||||
|
||||
@@ -42,14 +42,21 @@ class Goods extends Common
|
||||
});
|
||||
|
||||
$config = sheep_config('shop.commission');
|
||||
$defaultPartnerRules = \addons\shopro\service\partner\PartnerConfig::defaultRules();
|
||||
$roleNames = \addons\shopro\service\partner\PartnerConfig::roleNames();
|
||||
$levels = \app\admin\model\shopro\commission\Level::order('level asc')->select();
|
||||
|
||||
$this->success('分销商品详情', null, [
|
||||
'goods' => $goodsList,
|
||||
'config' => $config
|
||||
'config' => $config,
|
||||
'default_partner_rules' => $defaultPartnerRules,
|
||||
'role_names' => $roleNames,
|
||||
'levels' => $levels,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置佣金(支持批量)
|
||||
* 设置计奖规则/佣金(支持批量)
|
||||
*
|
||||
* @param $id
|
||||
*/
|
||||
@@ -61,18 +68,50 @@ class Goods extends Common
|
||||
|
||||
// 接受全部参数
|
||||
$params = $this->request->only(['status', 'self_rules', 'commission_order_status', 'commission_config', 'commission_rules']);
|
||||
if (empty($id)) {
|
||||
$id = $this->request->param('id', '');
|
||||
}
|
||||
if (empty($id)) {
|
||||
$this->error('缺少必要参数: id');
|
||||
}
|
||||
|
||||
// 状态规范化
|
||||
if (isset($params['status'])) {
|
||||
$params['status'] = intval($params['status']) === 1 ? 1 : 0;
|
||||
}
|
||||
if (isset($params['commission_order_status'])) {
|
||||
$params['commission_order_status'] = intval($params['commission_order_status']) === 0 ? 0 : 1;
|
||||
}
|
||||
if (isset($params['self_rules'])) {
|
||||
$params['self_rules'] = intval($params['self_rules']);
|
||||
}
|
||||
|
||||
// 针对 commission_rules 防御性处理
|
||||
if (isset($params['commission_rules'])) {
|
||||
if (is_string($params['commission_rules'])) {
|
||||
$decoded = json_decode($params['commission_rules'], true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
||||
$params['commission_rules'] = $decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = Db::transaction(function () use ($id, $params) {
|
||||
$count = 0;
|
||||
$ids = explode(',', $id);
|
||||
$ids = explode(',', (string)$id);
|
||||
|
||||
foreach ($ids as $goods_id) {
|
||||
$goods_id = intval($goods_id);
|
||||
if ($goods_id <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($row = $this->model->get($goods_id)) {
|
||||
$row->save($params);
|
||||
} else {
|
||||
$model = new CommissionGoodsModel();
|
||||
$params['goods_id'] = $goods_id;
|
||||
$model->save($params);
|
||||
$saveData = $params;
|
||||
$saveData['goods_id'] = $goods_id;
|
||||
$model->save($saveData);
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class ScoreSkuPrice extends Common
|
||||
|
||||
|
||||
/**
|
||||
* 积分加现金
|
||||
* 兑换门槛(兑换券 + 积分)
|
||||
*
|
||||
* @param string $value
|
||||
* @param array $data
|
||||
@@ -50,10 +50,19 @@ class ScoreSkuPrice extends Common
|
||||
*/
|
||||
public function getScorePriceAttr($value, $data)
|
||||
{
|
||||
$score = $data['score'] ?? 0;
|
||||
$score = intval($data['score'] ?? 0);
|
||||
$price = $data['price'] ?? 0;
|
||||
$priceNum = floatval($price);
|
||||
|
||||
return $score . '积分' . ($price ? '+¥' . $price : '');
|
||||
if ($priceNum > 0 && $score > 0) {
|
||||
return '¥' . $price . '兑换券 + ' . $score . '积分';
|
||||
} elseif ($priceNum > 0) {
|
||||
return '¥' . $price . ' 兑换券';
|
||||
} elseif ($score > 0) {
|
||||
return $score . '积分';
|
||||
}
|
||||
|
||||
return '免费兑换';
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -113,9 +113,24 @@ class User extends Common
|
||||
{
|
||||
$value = array_filter((array)json_decode($value, true));
|
||||
$value = array_merge(['username' => 0, 'password' => 0, 'mobile' => 0], $value);
|
||||
// 若数据库中已有手机号,则自动判定为已绑定/已验证手机号
|
||||
if (!empty($data['mobile'])) {
|
||||
$value['mobile'] = 1;
|
||||
}
|
||||
return (object)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证字段
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
public function setVerificationAttr($value)
|
||||
{
|
||||
$value = is_object($value) || is_array($value) ? json_encode($value) : $value;
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function parentUser()
|
||||
{
|
||||
return $this->hasOne(User::class, 'id', 'parent_user_id')->field(['id', 'avatar', 'nickname', 'gender']);
|
||||
|
||||
@@ -56,17 +56,25 @@
|
||||
<el-container class="panel-block">
|
||||
<el-main>
|
||||
<el-scrollbar height="100%">
|
||||
<div class="title mb-4">商品名称:{{ state.title }}</div>
|
||||
<div class="title mb-3">商品名称:{{ state.title }}</div>
|
||||
<el-alert
|
||||
title="💡 兑换规则配置说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
class="mb-4"
|
||||
description="1. 纯兑换券兑换:【所需兑换券】填入所需面额(如 100),【所需积分】填 0。用户在小程序下单时将自动使用可用兑换券 1:1 全额抵扣(实付 0 元)。 2. 兑换券+积分组合:同时填写对应所需兑换券额度与所需积分。 3. 若用户可用兑换券不足,系统自动支持抵扣部分可用券额,剩余差额可使用微信现金支付补齐。"
|
||||
show-icon>
|
||||
</el-alert>
|
||||
<div class="sku-table-wrap">
|
||||
<table class="sku-table" rules="none">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sku-item sku-col">商品规格</th>
|
||||
<th class="sku-item">是否参与</th>
|
||||
<th class="sku-item">商品价格</th>
|
||||
<th class="sku-item">商品原价</th>
|
||||
<th class="sku-item">可兑换数量</th>
|
||||
<th class="sku-item">兑换积分</th>
|
||||
<th class="sku-item">兑换价格</th>
|
||||
<th class="sku-item">所需兑换券(元)</th>
|
||||
<th class="sku-item">所需积分</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -79,15 +87,18 @@
|
||||
<td class="sku-item">{{ sp.price }}</td>
|
||||
<th class="sku-item">
|
||||
<el-input v-if="goods.score_sku_prices[spindex].status == 'up'" type="number"
|
||||
placeholder="库存数量"
|
||||
v-model="goods.score_sku_prices[spindex].stock"></el-input>
|
||||
</th>
|
||||
<th class="sku-item">
|
||||
<el-input v-if="goods.score_sku_prices[spindex].status == 'up'" type="number"
|
||||
v-model="goods.score_sku_prices[spindex].score"></el-input>
|
||||
placeholder="如 100"
|
||||
v-model="goods.score_sku_prices[spindex].price"></el-input>
|
||||
</th>
|
||||
<th class="sku-item">
|
||||
<el-input v-if="goods.score_sku_prices[spindex].status == 'up'" type="number"
|
||||
v-model="goods.score_sku_prices[spindex].price"></el-input>
|
||||
placeholder="无积分填0"
|
||||
v-model="goods.score_sku_prices[spindex].score"></el-input>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<el-header class="sa-header">
|
||||
<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>
|
||||
@@ -121,12 +121,19 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="积分现金" min-width="200">
|
||||
<el-table-column label="兑换门槛 (兑换券/积分)" min-width="220">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.score_price" class="sa-flex">
|
||||
{{ scope.row.score_price.score }}积分
|
||||
<div v-if="Number(scope.row.score_price.price)">+¥{{ scope.row.score_price.price }}
|
||||
</div>
|
||||
<span v-if="Number(scope.row.score_price.price)" style="color: #e6a23c; font-weight: 600;">
|
||||
¥{{ scope.row.score_price.price }} 兑换券
|
||||
</span>
|
||||
<span v-if="Number(scope.row.score_price.price) && Number(scope.row.score_price.score)" class="mx-1">+</span>
|
||||
<span v-if="Number(scope.row.score_price.score)" style="color: #409eff; font-weight: 600;">
|
||||
{{ scope.row.score_price.score }}积分
|
||||
</span>
|
||||
<span v-if="!Number(scope.row.score_price.price) && !Number(scope.row.score_price.score)" style="color: #67c23a;">
|
||||
免费兑换
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</template>
|
||||
|
||||
@@ -3,94 +3,165 @@
|
||||
<style>
|
||||
.goods-form .sa-title.is-line {
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.goods-form .goods-item {
|
||||
margin-bottom: 16px;
|
||||
.goods-form .goods-card {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid var(--sa-border);
|
||||
}
|
||||
|
||||
.goods-form .goods-item .goods-title {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--sa-font);
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.goods-form .goods-item .goods-price {
|
||||
font-size: 14px;
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.goods-form .goods-item .goods-sku {
|
||||
width: fit-content;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
background: var(--el-color-primary);
|
||||
border-radius: 10px;
|
||||
padding: 0 8px;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
color: var(--sa-background-assist);
|
||||
}
|
||||
|
||||
.goods-form .commission-table {
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
.goods-form .section-box {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.goods-form .commission-table .commission-header {
|
||||
border-left: 1px solid var(--sa-border);
|
||||
}
|
||||
|
||||
.goods-form .commission-table .commission-header .col-item {
|
||||
padding: 0 12px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background: var(--sa-table-header-bg);
|
||||
border-top: 1px solid var(--sa-border);
|
||||
}
|
||||
|
||||
.goods-form .commission-table .commission-content {
|
||||
border-left: 1px solid var(--sa-border);
|
||||
}
|
||||
|
||||
.goods-form .commission-table .col-item {
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
padding: 0 12px;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
.goods-form .section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 1px solid var(--sa-border);
|
||||
border-bottom: 1px solid var(--sa-border);
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #ebeef5;
|
||||
}
|
||||
|
||||
.goods-form .commission-table .col-item .el-input {
|
||||
margin-right: 12px;
|
||||
.goods-form .section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.goods-form .commission-table .col-item .el-input:last-of-type {
|
||||
margin-right: 0;
|
||||
.goods-form .section-title::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
background: var(--el-color-primary);
|
||||
border-radius: 2px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.goods-form .commission-table .col-item.commission-item, .goods-form .commission-table .col-item.sku-col {
|
||||
width: 240px;
|
||||
min-width: 240px;
|
||||
.goods-form .section-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.goods-form .batch-edit {
|
||||
color: var(--el-color-primary);
|
||||
margin-left: 8px;
|
||||
.goods-form .rules-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.commission-popover .commission-item {
|
||||
margin-bottom: 12px;
|
||||
.goods-form .rules-grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.commission-popover .commission-item .el-input {
|
||||
margin-right: 12px;
|
||||
.goods-form .rules-grid-6 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.commission-popover .commission-item .el-input:last-of-type {
|
||||
margin-right: 0;
|
||||
.goods-form .field-item {
|
||||
background: #fafafa;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.goods-form .field-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.goods-form .preview-summary {
|
||||
background: #f0f9eb;
|
||||
border: 1px solid #e1f3d8;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.goods-form .preview-summary .summary-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #67c23a;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.goods-form .preview-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.goods-form .preview-item {
|
||||
background: #fff;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e1f3d8;
|
||||
}
|
||||
|
||||
.goods-form .sku-toolbar {
|
||||
background: #f4f6fa;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.goods-form .sku-tab-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -98,256 +169,311 @@
|
||||
<el-container class="panel-block">
|
||||
<el-main>
|
||||
<el-scrollbar height="100%">
|
||||
<div class="sa-title is-line">商品信息</div>
|
||||
<div v-for="goods in state.data?.goods" :key="goods">
|
||||
<div class="goods-item sa-flex sa-col-top">
|
||||
<sa-image class="mr-2" :url="goods.image" size="48"></sa-image>
|
||||
<!-- 商品基本信息卡片 -->
|
||||
<div class="goods-card">
|
||||
<div v-for="goods in state.data?.goods" :key="goods.id" class="goods-item sa-flex sa-col-top">
|
||||
<sa-image class="mr-3" :url="goods.image" size="64"></sa-image>
|
||||
<div>
|
||||
<div class="goods-title sa-table-line-1">
|
||||
{{ goods.title }}
|
||||
</div>
|
||||
<div v-if="goods.is_sku" class="goods-sku">
|
||||
{{ goods.is_sku ? '多规格' : '' }}
|
||||
<div class="goods-title sa-table-line-1">{{ goods.title }}</div>
|
||||
<div class="goods-price" v-if="goods.price">
|
||||
指导价:¥{{ Array.isArray(goods.price) ? goods.price.join(' ~ ') : goods.price }}
|
||||
</div>
|
||||
<div v-if="goods.is_sku" class="goods-sku">多规格商品(共 {{ goods.sku_prices?.length || 0 }} 种规格)</div>
|
||||
<div v-else class="goods-sku" style="background:#67c23a;">单规格基础商品</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sa-title is-line">佣金设置</div>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="是否参与">
|
||||
<el-radio-group v-model="state.commission_goods.status">
|
||||
<el-radio :label="0">不参与</el-radio>
|
||||
<el-radio :label="1">参与</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="分销商业绩">
|
||||
<el-radio-group v-model="state.commission_goods.commission_order_status">
|
||||
<el-radio :label="0">不计入</el-radio>
|
||||
<el-radio :label="1">
|
||||
<span class="sa-flex">
|
||||
计入
|
||||
<el-popover placement="top" :width="310" content="关闭则只分佣,不计入分销订单金额和订单数">
|
||||
<template #reference>
|
||||
<el-icon class="warning sa-m-l-8">
|
||||
<warning />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="佣金规则">
|
||||
<el-radio-group v-model="state.commission_goods.self_rules" @change="onChangeSelfRules">
|
||||
<el-radio :label="0">默认规则</el-radio>
|
||||
<el-radio :label="1" v-if="state.rulesType != 'batch'">独立规则</el-radio>
|
||||
<el-radio :label="2">批量规则</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="state.commission_goods.self_rules == 1 || state.commission_goods.self_rules == 2"
|
||||
label="分销设置">
|
||||
<el-switch v-model="state.commission_config_temp.status"
|
||||
@change="onChangeCommissionConfigStatus" :active-value="1" :inactive-value="0" />
|
||||
<span class="sa-m-l-8">
|
||||
{{ state.commission_config_temp.status ? '自定义' : '默认' }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="分销层级">
|
||||
<el-radio-group v-model="state.commission_config_temp.level"
|
||||
:disabled="state.commission_config_temp.status == 0">
|
||||
<el-radio :label="1">一级</el-radio>
|
||||
<el-radio :label="2">二级</el-radio>
|
||||
<el-radio :label="3">三级</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="分销自购">
|
||||
<div>
|
||||
<el-radio-group v-model="state.commission_config_temp.self_buy"
|
||||
:disabled="state.commission_config_temp.status == 0">
|
||||
<el-radio :label="0">关闭</el-radio>
|
||||
<el-radio :label="1">开启</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="tip"> 分销自购开启后,分销商自己购买时,下单可以给自己返佣 </div>
|
||||
|
||||
<!-- 计奖与分成核心设置 -->
|
||||
<el-form label-width="120px" label-position="left">
|
||||
<div class="section-box">
|
||||
<div class="section-header">
|
||||
<div class="section-title">计奖资格与业绩设置</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品结算方式">
|
||||
<div>
|
||||
<el-radio-group v-model="state.commission_config_temp.reward_type"
|
||||
:disabled="state.commission_config_temp.status == 0">
|
||||
<el-radio label="goods_price">商品价</el-radio>
|
||||
<el-radio label="pay_price">实际支付价</el-radio>
|
||||
<el-form-item label="是否参与计奖">
|
||||
<el-radio-group v-model="state.commission_goods.status">
|
||||
<el-radio :label="1"><el-tag type="success" size="small">参与计奖</el-tag></el-radio>
|
||||
<el-radio :label="0"><el-tag type="danger" size="small">不参与计奖</el-tag></el-radio>
|
||||
</el-radio-group>
|
||||
<div class="tip">
|
||||
商品价: 商品实际售价/规格价,实际支付价:实际支付的费用(不含运费)
|
||||
<div class="section-tip">
|
||||
开启后,购买该商品的新订单将参与收益中心推三返一、创客极差、团队拆分、兑换券及全网日结奖池;关闭后整单或单品排除计奖。
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="佣金结算方式">
|
||||
<el-radio-group v-model="state.commission_config_temp.reward_event"
|
||||
:disabled="state.commission_config_temp.status == 0">
|
||||
<el-radio label="paid">支付后结算</el-radio>
|
||||
<el-radio label="confirm">确认收货结算</el-radio>
|
||||
<el-radio label="finish">订单完成结算</el-radio>
|
||||
<el-radio label="admin">手动打款</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div class="commission-table">
|
||||
<template v-if="state.commission_goods.self_rules == 0">
|
||||
<div class="commission-header sa-flex">
|
||||
<div class="col-item">分销等级名称</div>
|
||||
<template v-for="commission in state.commission_config_temp.level" :key="commission">
|
||||
<div v-if="commission == 1" class="col-item commission-item">
|
||||
一级(自购)佣金比例
|
||||
</div>
|
||||
<div v-if="commission == 2" class="col-item commission-item"> 二级佣金比例 </div>
|
||||
<div v-if="commission == 3" class="col-item commission-item"> 三级佣金比例 </div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="commission-content sa-flex" v-for="level in state.levelData" :key="level">
|
||||
<div class="col-item">{{ level.name }}</div>
|
||||
<template v-for="commission in state.commission_config_temp.level" :key="commission">
|
||||
<div class="col-item commission-item">
|
||||
{{ level.commission_rules[`commission_${commission}`] }}%
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="state.commission_goods.self_rules == 1">
|
||||
<div class="commission-header sa-flex">
|
||||
<div class="col-item sku-col">商品规格</div>
|
||||
<div class="col-item">价格</div>
|
||||
<div class="col-item">分销等级名称</div>
|
||||
<template v-for="commission in state.commission_config_temp.level" :key="commission">
|
||||
<div class="col-item commission-item">
|
||||
<template v-if="commission == 1">一级(自购)佣金比例</template>
|
||||
<template v-if="commission == 2">二级佣金比例</template>
|
||||
<template v-if="commission == 3">三级佣金比例</template>
|
||||
<el-popover popper-class="commission-popover"
|
||||
v-model:visible="commissionPopover.flag[commission]" placement="top"
|
||||
:width="220" trigger="click">
|
||||
<div class="commission-item sa-flex">
|
||||
<el-input v-model="commissionPopover.form.rate" type="number"
|
||||
:disabled="commissionPopover.form.money != ''">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
<el-input v-model="commissionPopover.form.money" type="number"
|
||||
:disabled="commissionPopover.form.rate != ''">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="sa-flex sa-row-right">
|
||||
<el-button class="is-link" type="primary" size="small"
|
||||
@click="onCancelCommissionPopover(commission)">取消</el-button>
|
||||
<el-button type="primary" size="small"
|
||||
@click="onConfirmCommissionPopover(commission)">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon class="batch-edit">
|
||||
<edit />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="commission-content sa-flex">
|
||||
<div class="sa-flex sa-flex-col">
|
||||
<div class="sa-flex" v-for="sku in state.detailData.sku_prices" :key="sku">
|
||||
<div class="col-item sku-col" :style="{ height: 48 * state.levelData.length + 'px' }">
|
||||
{{ state.detailData?.is_sku ? sku.goods_sku_text.join(',') : '默认规格' }}
|
||||
</div>
|
||||
<div class="col-item" :style="{ height: 48 * state.levelData.length + 'px' }">
|
||||
{{ sku.price }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="col-item" v-for="level in state.levelData" :key="level">
|
||||
{{ level.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="state.commission_goods.status == 1">
|
||||
<el-form-item label="分销商业绩">
|
||||
<el-radio-group v-model="state.commission_goods.commission_order_status">
|
||||
<el-radio :label="1">计入业绩</el-radio>
|
||||
<el-radio :label="0">不计入业绩</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="section-tip">
|
||||
计入后,该订单将累加分销商团队订单金额和单数,用于创客升级考核。
|
||||
</div>
|
||||
<div>
|
||||
<div v-for="sku in state.commission_goods.commission_rules" :key="sku">
|
||||
<div class="sa-flex" v-for="level in sku" :key="level">
|
||||
<template v-for="commission in state.commission_config_temp.level"
|
||||
:key="commission">
|
||||
<div v-if="level[commission]" class="col-item commission-item">
|
||||
<el-input v-model="level[commission].rate" type="number"
|
||||
:disabled="level[commission].money != ''">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
<el-input v-model="level[commission].money" type="number"
|
||||
:disabled="level[commission].rate != ''">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="分成规则模式">
|
||||
<el-radio-group v-model="state.commission_goods.self_rules" @change="onChangeSelfRules">
|
||||
<el-radio :label="0">默认规则(跟随系统配置)</el-radio>
|
||||
<el-radio :label="1" v-if="state.rulesType != 'batch'">独立规则(单独设置分成)</el-radio>
|
||||
<el-radio :label="2">批量规则(统一设置分成)</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="section-tip">
|
||||
选择「默认规则」时直接继承系统收益中心全局配置;选择「独立规则」可为当前商品单独定制分成比例与金额。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="state.commission_goods.self_rules == 2">
|
||||
<div class="commission-header sa-flex">
|
||||
<div class="col-item">分销等级名称</div>
|
||||
<template v-for="commission in state.commission_config_temp.level" :key="commission">
|
||||
<div class="col-item commission-item">
|
||||
<template v-if="commission == 1">一级(自购)佣金比例</template>
|
||||
<template v-if="commission == 2">二级佣金比例</template>
|
||||
<template v-if="commission == 3">三级佣金比例</template>
|
||||
<el-popover popper-class="commission-popover"
|
||||
v-model:visible="commissionPopover.flag[commission]" placement="top"
|
||||
:width="220" trigger="click">
|
||||
<div class="commission-item sa-flex">
|
||||
<el-input v-model="commissionPopover.form.rate" type="number"
|
||||
:disabled="commissionPopover.form.money != ''">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="sa-flex sa-row-right">
|
||||
<el-button class="is-link" type="primary" size="small"
|
||||
@click="onCancelCommissionPopover(commission)">取消</el-button>
|
||||
<el-button size="small" type="primary"
|
||||
@click="onConfirmCommissionPopover(commission)">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon class="batch-edit">
|
||||
<edit />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="commission-content sa-flex">
|
||||
<div>
|
||||
<div class="col-item" v-for="level in state.levelData" :key="level">
|
||||
{{ level.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sa-flex" v-for="level in state.commission_goods.commission_rules"
|
||||
:key="level">
|
||||
<template v-for="commission in state.commission_config_temp.level"
|
||||
:key="commission">
|
||||
<div v-if="level[commission]" class="col-item commission-item">
|
||||
<el-input v-model="level[commission].rate" type="number"
|
||||
:disabled="level[commission].money != ''">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 状态为不参与时的警示提示 -->
|
||||
<el-alert v-if="state.commission_goods.status == 0" type="warning" show-icon :closable="false"
|
||||
title="商品未参与计奖"
|
||||
description="当前商品已关闭计奖,会员或下级购买该商品时将不产生推荐开拓奖、创客极差、酒道馆/区县团队奖及兑换券,亦不进入全网日结均分奖池。"
|
||||
style="margin-bottom: 20px;">
|
||||
</el-alert>
|
||||
|
||||
<!-- 模式 0:跟随系统默认规则预览卡片 -->
|
||||
<div v-if="state.commission_goods.status == 1 && state.commission_goods.self_rules == 0" class="preview-summary">
|
||||
<div class="summary-title sa-flex sa-row-between">
|
||||
<span>系统收益中心全局默认分成规则概览</span>
|
||||
<el-tag size="small" type="success">当前生效</el-tag>
|
||||
</div>
|
||||
<div class="preview-list">
|
||||
<div class="preview-item">
|
||||
<strong>推荐开拓奖(推三返一):</strong>
|
||||
循环返现 ¥{{ state.defaultPartnerRules.push3_amounts?.[0] || 398 }} / ¥{{ state.defaultPartnerRules.push3_amounts?.[1] || 400 }} / ¥{{ state.defaultPartnerRules.push3_amounts?.[2] || 600 }} 元
|
||||
</div>
|
||||
<div class="preview-item">
|
||||
<strong>创客极差基数(V1~V6):</strong>
|
||||
¥{{ state.defaultPartnerRules.maker_bonus?.['2'] || 20 }} ~ ¥{{ state.defaultPartnerRules.maker_bonus?.['7'] || 120 }} 元
|
||||
</div>
|
||||
<div class="preview-item">
|
||||
<strong>创客协同与超越:</strong>
|
||||
同级协同 {{ ((state.defaultPartnerRules.maker_peer_rate || 0.10) * 100).toFixed(0) }}%,超越关怀 {{ ((state.defaultPartnerRules.maker_overtaken_rate || 0.05) * 100).toFixed(0) }}%
|
||||
</div>
|
||||
<div class="preview-item">
|
||||
<strong>渠道运营统筹奖:</strong>
|
||||
酒道馆 ¥{{ state.defaultPartnerRules.venue_split || 50 }},区县统筹 ¥{{ state.defaultPartnerRules.county_full || 100 }}(有馆时各50)
|
||||
</div>
|
||||
<div class="preview-item">
|
||||
<strong>馆/县平级协同奖:</strong>
|
||||
酒道馆 {{ ((state.defaultPartnerRules.venue_peer_rate || 0.10) * 100).toFixed(0) }}%,区县代理 {{ ((state.defaultPartnerRules.county_peer_rate || 0.10) * 100).toFixed(0) }}%
|
||||
</div>
|
||||
<div class="preview-item">
|
||||
<strong>消费补贴兑换券:</strong>
|
||||
实付 {{ state.defaultPartnerRules.voucher_times || 1.5 }} 倍,分 {{ state.defaultPartnerRules.voucher_days || 365 }} 天线性释放
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模式 1 / 2:独立设置分成规则面板 -->
|
||||
<template v-if="state.commission_goods.status == 1 && (state.commission_goods.self_rules == 1 || state.commission_goods.self_rules == 2)">
|
||||
<!-- 多规格切换与快捷操作栏 -->
|
||||
<div class="sku-toolbar">
|
||||
<div class="sa-flex" v-if="state.detailData?.is_sku == 1 && state.rulesType != 'batch'">
|
||||
<span class="mr-2" style="font-size: 13px; font-weight: 500;">规格配置模式:</span>
|
||||
<el-radio-group v-model="state.skuMode" size="small">
|
||||
<el-radio-button label="unified">统一设置所有规格</el-radio-button>
|
||||
<el-radio-button label="sku">按规格分别设置</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="sa-flex" v-else>
|
||||
<el-tag size="small" type="info">商品独立分成模式</el-tag>
|
||||
</div>
|
||||
<div class="sa-flex">
|
||||
<el-button size="small" type="primary" plain @click="onFillDefaults(state.skuMode === 'sku' ? 'current_sku' : 'all_skus')">
|
||||
一键填入系统默认值
|
||||
</el-button>
|
||||
<el-button size="small" type="success" plain v-if="state.skuMode === 'sku' && state.detailData?.sku_prices?.length > 1" @click="onSyncToAllSkus">
|
||||
同步当前规则至全部规格
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当按规格分别设置时的规格切换 Tabs -->
|
||||
<div v-if="state.skuMode === 'sku' && state.detailData?.sku_prices?.length" style="margin-bottom: 16px;">
|
||||
<el-radio-group v-model="state.activeSkuId" size="default">
|
||||
<el-radio-button v-for="sku in state.detailData.sku_prices" :key="sku.id" :label="String(sku.id)">
|
||||
{{ sku.goods_sku_text?.join(' / ') || '默认规格' }}(¥{{ sku.price }})
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 当前激活的表单对象引用 -->
|
||||
<template v-for="curForm in [(state.skuMode === 'sku' && state.activeSkuId) ? state.skuRules[state.activeSkuId] : state.ruleForm]">
|
||||
<!-- 1. 推荐开拓奖(推三返一) -->
|
||||
<div class="section-box">
|
||||
<div class="section-header">
|
||||
<div class="section-title">1. 推荐开拓奖(推三返一循环返现)</div>
|
||||
<span class="section-tip">直推有效单奖励,每 3 笔订单形成一个循环返还货款</span>
|
||||
</div>
|
||||
<div class="rules-grid">
|
||||
<div class="field-item">
|
||||
<div class="field-label">第 1 单返现金额</div>
|
||||
<el-input v-model="curForm.push3_1" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">第 2 单返现金额</div>
|
||||
<el-input v-model="curForm.push3_2" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">第 3 单返现金额(波峰档)</div>
|
||||
<el-input v-model="curForm.push3_3" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 创客进阶奖(极差基数)与协同奖 -->
|
||||
<div class="section-box">
|
||||
<div class="section-header">
|
||||
<div class="section-title">2. 创客进阶奖(V1~V6极差基数)与协同奖</div>
|
||||
<span class="section-tip">向上链条创客按自身基数减去已截留最高基数计提差额</span>
|
||||
</div>
|
||||
<div class="rules-grid-6" style="margin-bottom: 14px;">
|
||||
<div class="field-item">
|
||||
<div class="field-label">创客 V1 进阶基数</div>
|
||||
<el-input v-model="curForm.maker_v1" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">创客 V2 进阶基数</div>
|
||||
<el-input v-model="curForm.maker_v2" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">创客 V3 进阶基数</div>
|
||||
<el-input v-model="curForm.maker_v3" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">创客 V4 进阶基数</div>
|
||||
<el-input v-model="curForm.maker_v4" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">创客 V5 进阶基数</div>
|
||||
<el-input v-model="curForm.maker_v5" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">创客 V6 进阶基数</div>
|
||||
<el-input v-model="curForm.maker_v6" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rules-grid-2">
|
||||
<div class="field-item">
|
||||
<div class="field-label">创客同级平级协同比例</div>
|
||||
<el-input v-model="curForm.maker_peer_rate" type="number" :min="0" :max="100">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">创客下级反超卓越比例</div>
|
||||
<el-input v-model="curForm.maker_overtaken_rate" type="number" :min="0" :max="100">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 实体渠道与区域运营奖(团队拆分) -->
|
||||
<div class="section-box">
|
||||
<div class="section-header">
|
||||
<div class="section-title">3. 实体渠道与区域运营奖(团队拆分)</div>
|
||||
<span class="section-tip">无馆有县独享,有馆有县各分其半</span>
|
||||
</div>
|
||||
<div class="rules-grid-2" style="margin-bottom: 14px;">
|
||||
<div class="field-item">
|
||||
<div class="field-label">酒道馆运营奖基准</div>
|
||||
<el-input v-model="curForm.venue_split" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">区县统筹全额(无馆时独享)</div>
|
||||
<el-input v-model="curForm.county_full" type="number" :min="0">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rules-grid-2">
|
||||
<div class="field-item">
|
||||
<div class="field-label">酒道馆平级协同比例</div>
|
||||
<el-input v-model="curForm.venue_peer_rate" type="number" :min="0" :max="100">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">区县代理平级协同比例</div>
|
||||
<el-input v-model="curForm.county_peer_rate" type="number" :min="0" :max="100">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 消费补贴兑换券计划 -->
|
||||
<div class="section-box">
|
||||
<div class="section-header">
|
||||
<div class="section-title">4. 消费补贴兑换券计划</div>
|
||||
<span class="section-tip">买家本人下单实付金额按倍数赠送并在商城按日释放抵扣</span>
|
||||
</div>
|
||||
<div class="rules-grid-2">
|
||||
<div class="field-item">
|
||||
<div class="field-label">赠送兑换券倍数(相对计奖实付)</div>
|
||||
<el-input v-model="curForm.voucher_times" type="number" :min="0" step="0.1">
|
||||
<template #append>倍</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">线性释放周期天数</div>
|
||||
<el-input v-model="curForm.voucher_days" type="number" :min="1">
|
||||
<template #append>天</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 全平台日结奖池注入(可选定制) -->
|
||||
<div class="section-box">
|
||||
<div class="section-header">
|
||||
<div class="section-title">5. 全平台日结均分奖池注入(可选定制)</div>
|
||||
<span class="section-tip">每日凌晨 00:30 全网有效订单向池中注入并由活跃身份均分</span>
|
||||
</div>
|
||||
<div class="rules-grid-2">
|
||||
<div class="field-item">
|
||||
<div class="field-label">酒道馆日结奖池单笔注入</div>
|
||||
<el-input v-model="curForm.venue_share" type="number" :min="0">
|
||||
<template #append>元/单</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="field-item">
|
||||
<div class="field-label">区县代理日结奖池单笔注入</div>
|
||||
<el-input v-model="curForm.county_share" type="number" :min="0">
|
||||
<template #append>元/单</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</el-form>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
<el-footer class="sa-footer--submit sa-flex sa-row-right">
|
||||
<el-button type="primary" @click="onConfirm">确定</el-button>
|
||||
<el-button type="primary" size="large" @click="onConfirm">保存分成设置</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
@@ -66,14 +66,15 @@
|
||||
<div>{{ scope.row.price?.join('~') || 0 }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分销规则" width="100">
|
||||
<el-table-column label="分成规则" width="120">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.commission_goods && scope.row.commission_goods.status == 1">
|
||||
<template v-if="scope.row.commission_goods.self_rules == 0">默认规则</template>
|
||||
<template v-if="scope.row.commission_goods.self_rules == 1">独立规则</template>
|
||||
<template v-if="scope.row.commission_goods.self_rules == 2">批量规则</template>
|
||||
<el-tag size="small" type="info" v-if="scope.row.commission_goods.self_rules == 0">默认规则</el-tag>
|
||||
<el-tag size="small" type="success" v-else-if="scope.row.commission_goods.self_rules == 1">独立规则</el-tag>
|
||||
<el-tag size="small" type="warning" v-else-if="scope.row.commission_goods.self_rules == 2">批量规则</el-tag>
|
||||
<el-tag size="small" type="info" v-else>默认规则</el-tag>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
<el-tag size="small" type="danger" v-else>不参与计奖</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品状态" width="100">
|
||||
@@ -83,15 +84,15 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="140" fixed="right">
|
||||
<el-table-column label="操作" min-width="160" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="sa-flex">
|
||||
<span class="mr-2"
|
||||
:class="`sa-color--${state.goodsStatusStyle[scope.row.commission_goods?.status] || 'info'}`">
|
||||
{{ scope.row.commission_goods?.status_text || '未参与' }}
|
||||
</span>
|
||||
<el-tag size="small" class="mr-2"
|
||||
:type="scope.row.commission_goods?.status == 1 ? 'success' : 'danger'">
|
||||
{{ scope.row.commission_goods?.status == 1 ? '参与中' : '未参与' }}
|
||||
</el-tag>
|
||||
{if $auth->check('shopro/commission/goods/edit')}
|
||||
<el-button type="primary" link @click="onEdit(scope.row.id)">设置计奖</el-button>
|
||||
<el-button type="primary" link @click="onEdit(scope.row.id)">设置分成</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</template>
|
||||
@@ -104,7 +105,7 @@
|
||||
已选择 <span>{{batchHandle.data.length}}</span> 项</div>
|
||||
<div class="sa-flex">
|
||||
{if $auth->check('shopro/commission/goods/edit')}
|
||||
<el-button type="primary" :disabled="!batchHandle.data.length" @click="onBatchHandle('edit')">设置佣金
|
||||
<el-button type="primary" :disabled="!batchHandle.data.length" @click="onBatchHandle('edit')">批量设置分成
|
||||
</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -189,8 +189,17 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
let cleanedPrices = (goods.score_sku_prices || []).map(item => {
|
||||
let clone = { ...item };
|
||||
if (clone.status === 'up') {
|
||||
clone.score = (clone.score === '' || clone.score === undefined || clone.score === null) ? 0 : Number(clone.score);
|
||||
clone.price = (clone.price === '' || clone.price === undefined || clone.price === null) ? 0 : Number(clone.price);
|
||||
clone.stock = (clone.stock === '' || clone.stock === undefined || clone.stock === null) ? 0 : Number(clone.stock);
|
||||
}
|
||||
return clone;
|
||||
});
|
||||
let submitForm = {
|
||||
sku_prices: goods.score_sku_prices,
|
||||
sku_prices: cleanedPrices,
|
||||
}
|
||||
if (state.type == 'add') {
|
||||
submitForm = {
|
||||
|
||||
@@ -29,6 +29,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
identity: '',
|
||||
is_venue: '',
|
||||
is_county: '',
|
||||
is_partner: '',
|
||||
active_30d: '',
|
||||
county_code: '',
|
||||
},
|
||||
@@ -110,6 +111,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
{ value: 'maker', label: '创客' },
|
||||
{ value: 'venue', label: '酒道馆' },
|
||||
{ value: 'county', label: '区县' },
|
||||
{ value: 'partner', label: '合伙股东' },
|
||||
]
|
||||
},
|
||||
},
|
||||
@@ -125,6 +127,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
value: '',
|
||||
options: { data: [{ value: '1', label: '是' }, { value: '0', label: '否' }] },
|
||||
},
|
||||
is_partner: {
|
||||
type: 'tselect',
|
||||
label: '合伙股东',
|
||||
value: '',
|
||||
options: { data: [{ value: '1', label: '是' }, { value: '0', label: '否' }] },
|
||||
},
|
||||
active_30d: {
|
||||
type: 'tselect',
|
||||
label: '30日活跃',
|
||||
@@ -207,6 +215,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
{ value: 'maker', label: roleNames.maker || '创客' },
|
||||
{ value: 'venue', label: roleNames.venue || '酒道馆' },
|
||||
{ value: 'county', label: roleNames.county || '区县' },
|
||||
{ value: 'partner', label: roleNames.partner || '合伙股东' },
|
||||
];
|
||||
}
|
||||
if (state.filter.tools.is_venue) {
|
||||
@@ -215,6 +224,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
if (state.filter.tools.is_county) {
|
||||
state.filter.tools.is_county.label = roleNames.county || '区县代理';
|
||||
}
|
||||
if (state.filter.tools.is_partner) {
|
||||
state.filter.tools.is_partner.label = roleNames.partner || '合伙股东';
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -405,8 +417,53 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLevelSelect()
|
||||
getData()
|
||||
// 解析 URL 查询参数并自动回显至筛选器(支持从仪表盘、其他模块跳转带参)
|
||||
const params = new URLSearchParams(location.search);
|
||||
const levelParam = params.get('level');
|
||||
if (levelParam !== null && levelParam !== '') {
|
||||
const parsedLevel = !isNaN(Number(levelParam)) ? Number(levelParam) : levelParam;
|
||||
state.filter.data.level = parsedLevel;
|
||||
if (state.filter.tools && state.filter.tools.level) {
|
||||
state.filter.tools.level.value = parsedLevel;
|
||||
}
|
||||
}
|
||||
const isVenueParam = params.get('is_venue');
|
||||
if (isVenueParam !== null && isVenueParam !== '') {
|
||||
state.filter.data.is_venue = isVenueParam;
|
||||
if (state.filter.tools && state.filter.tools.is_venue) {
|
||||
state.filter.tools.is_venue.value = isVenueParam;
|
||||
}
|
||||
}
|
||||
const isCountyParam = params.get('is_county');
|
||||
if (isCountyParam !== null && isCountyParam !== '') {
|
||||
state.filter.data.is_county = isCountyParam;
|
||||
if (state.filter.tools && state.filter.tools.is_county) {
|
||||
state.filter.tools.is_county.value = isCountyParam;
|
||||
}
|
||||
}
|
||||
const isPartnerParam = params.get('is_partner');
|
||||
if (isPartnerParam !== null && isPartnerParam !== '') {
|
||||
state.filter.data.is_partner = isPartnerParam;
|
||||
if (state.filter.tools && state.filter.tools.is_partner) {
|
||||
state.filter.tools.is_partner.value = isPartnerParam;
|
||||
}
|
||||
}
|
||||
const identityParam = params.get('identity');
|
||||
if (identityParam !== null && identityParam !== '') {
|
||||
state.filter.data.identity = identityParam;
|
||||
if (state.filter.tools && state.filter.tools.identity) {
|
||||
state.filter.tools.identity.value = identityParam;
|
||||
}
|
||||
}
|
||||
const userIdParam = params.get('user_id');
|
||||
if (userIdParam !== null && userIdParam !== '') {
|
||||
state.filter.data.user = { field: 'user_id', value: userIdParam };
|
||||
if (state.filter.tools && state.filter.tools.user) {
|
||||
state.filter.tools.user.value = { field: 'user_id', value: userIdParam };
|
||||
}
|
||||
}
|
||||
getLevelSelect();
|
||||
getData();
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
|
||||
var Controller = {
|
||||
index: () => {
|
||||
const { reactive, onMounted } = Vue
|
||||
const { reactive, onMounted } = Vue;
|
||||
const index = {
|
||||
setup() {
|
||||
const state = reactive({
|
||||
@@ -12,6 +12,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
data: {
|
||||
keyword: { field: 'id', value: '' },
|
||||
'commission_goods.status': '',
|
||||
'commission_goods.self_rules': '',
|
||||
},
|
||||
tools: {
|
||||
keyword: {
|
||||
@@ -39,11 +40,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
},
|
||||
'commission_goods.status': {
|
||||
type: 'tselect',
|
||||
label: '分销状态',
|
||||
label: '计奖状态',
|
||||
value: '',
|
||||
options: {
|
||||
data: [{
|
||||
label: '不参与',
|
||||
label: '未参与',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
@@ -52,6 +53,25 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
}],
|
||||
},
|
||||
},
|
||||
'commission_goods.self_rules': {
|
||||
type: 'tselect',
|
||||
label: '分成规则',
|
||||
value: '',
|
||||
options: {
|
||||
data: [{
|
||||
label: '默认规则',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: '独立规则',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '批量规则',
|
||||
value: '2',
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
condition: {},
|
||||
},
|
||||
@@ -64,7 +84,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
down: 'danger',
|
||||
hidden: 'info',
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
function getData() {
|
||||
let tempSearch = JSON.parse(JSON.stringify(state.filter.data));
|
||||
@@ -81,56 +101,56 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
...search,
|
||||
},
|
||||
}, function (ret, res) {
|
||||
state.data = res.data.data
|
||||
pagination.total = res.data.total
|
||||
return false
|
||||
}, function (ret, res) { })
|
||||
state.data = res.data.data;
|
||||
pagination.total = res.data.total;
|
||||
return false;
|
||||
}, function (ret, res) { });
|
||||
}
|
||||
|
||||
function onOpenFilter() {
|
||||
state.filter.drawer = true
|
||||
state.filter.drawer = true;
|
||||
}
|
||||
function onChangeFilter() {
|
||||
pagination.page = 1
|
||||
getData()
|
||||
state.filter.drawer && (state.filter.drawer = false)
|
||||
pagination.page = 1;
|
||||
getData();
|
||||
state.filter.drawer && (state.filter.drawer = false);
|
||||
}
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
list_rows: 10,
|
||||
total: 0,
|
||||
})
|
||||
});
|
||||
|
||||
const batchHandle = reactive({
|
||||
data: [],
|
||||
})
|
||||
});
|
||||
function onChangeSelection(val) {
|
||||
batchHandle.data = val
|
||||
batchHandle.data = val;
|
||||
}
|
||||
function onBatchHandle(type) {
|
||||
let ids = []
|
||||
let ids = [];
|
||||
batchHandle.data.forEach((item) => {
|
||||
ids.push(item.id)
|
||||
})
|
||||
ids.push(item.id);
|
||||
});
|
||||
switch (type) {
|
||||
case 'edit':
|
||||
onEdit(ids.join(','), 'batch')
|
||||
onEdit(ids.join(','), 'batch');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onEdit(ids, rulesType = '') {
|
||||
Fast.api.open(`shopro/commission/goods/edit?type=edit&id=${ids}&rulesType=${rulesType}`, "设置佣金", {
|
||||
Fast.api.open(`shopro/commission/goods/edit?type=edit&id=${ids}&rulesType=${rulesType}`, "设置分成规则", {
|
||||
callback() {
|
||||
getData()
|
||||
getData();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getData()
|
||||
})
|
||||
getData();
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
@@ -142,42 +162,158 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
onChangeSelection,
|
||||
onBatchHandle,
|
||||
onEdit,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
createApp('index', index);
|
||||
},
|
||||
edit: () => {
|
||||
Controller.form();
|
||||
},
|
||||
form: () => {
|
||||
const { reactive, onMounted } = Vue
|
||||
const { ElMessage } = ElementPlus
|
||||
const { reactive, onMounted } = Vue;
|
||||
const { ElMessage } = ElementPlus;
|
||||
const addEdit = {
|
||||
setup() {
|
||||
const state = reactive({
|
||||
type: new URLSearchParams(location.search).get('type'),
|
||||
id: new URLSearchParams(location.search).get('id'),
|
||||
rulesType: new URLSearchParams(location.search).get('rulesType'),
|
||||
data: {},
|
||||
detailData: {},
|
||||
commission_goods: {
|
||||
status: 1,
|
||||
self_rules: new URLSearchParams(location.search).get('rulesType') == 'batch' ? 2 : 0,
|
||||
commission_order_status: 1,
|
||||
commission_config: {},
|
||||
self_rules: new URLSearchParams(location.search).get('rulesType') == 'batch' ? 2 : 0,
|
||||
commission_rules: {},
|
||||
},
|
||||
defaultCommissionConfig: {
|
||||
status: 0,
|
||||
level: 2,
|
||||
self_buy: 0,
|
||||
reward_type: 'goods_price',
|
||||
reward_event: 'paid',
|
||||
defaultPartnerRules: {
|
||||
push3_amounts: [398, 400, 600],
|
||||
maker_bonus: { "2": 20, "3": 40, "4": 60, "5": 80, "6": 100, "7": 120 },
|
||||
maker_peer_rate: 0.10,
|
||||
maker_overtaken_rate: 0.05,
|
||||
venue_split: 50,
|
||||
county_full: 100,
|
||||
venue_peer_rate: 0.10,
|
||||
county_peer_rate: 0.10,
|
||||
voucher_times: 1.5,
|
||||
voucher_days: 365,
|
||||
venue_share: 100,
|
||||
county_share: 50,
|
||||
},
|
||||
commission_config_temp: {},
|
||||
commission_rules: {},
|
||||
levelData: [],
|
||||
})
|
||||
roleNames: {
|
||||
taster: '品酒师',
|
||||
maker: '创客',
|
||||
venue: '酒道馆',
|
||||
county: '区县代理',
|
||||
partner: '合伙股东'
|
||||
},
|
||||
levels: [],
|
||||
// 统一设置模式下的表单数据
|
||||
ruleForm: {
|
||||
push3_1: 398,
|
||||
push3_2: 400,
|
||||
push3_3: 600,
|
||||
maker_v1: 20,
|
||||
maker_v2: 40,
|
||||
maker_v3: 60,
|
||||
maker_v4: 80,
|
||||
maker_v5: 100,
|
||||
maker_v6: 120,
|
||||
maker_peer_rate: 10,
|
||||
maker_overtaken_rate: 5,
|
||||
venue_split: 50,
|
||||
county_full: 100,
|
||||
venue_peer_rate: 10,
|
||||
county_peer_rate: 10,
|
||||
voucher_times: 1.5,
|
||||
voucher_days: 365,
|
||||
venue_share: 100,
|
||||
county_share: 50,
|
||||
},
|
||||
skuMode: 'unified', // 'unified': 统一设置, 'sku': 按规格分别设置
|
||||
activeSkuId: '',
|
||||
skuRules: {},
|
||||
});
|
||||
|
||||
function makeFormFromDefaults(defaults) {
|
||||
const d = defaults || state.defaultPartnerRules;
|
||||
return {
|
||||
push3_1: d.push3_amounts?.[0] !== undefined ? d.push3_amounts[0] : 398,
|
||||
push3_2: d.push3_amounts?.[1] !== undefined ? d.push3_amounts[1] : 400,
|
||||
push3_3: d.push3_amounts?.[2] !== undefined ? d.push3_amounts[2] : 600,
|
||||
maker_v1: d.maker_bonus?.['2'] !== undefined ? d.maker_bonus['2'] : 20,
|
||||
maker_v2: d.maker_bonus?.['3'] !== undefined ? d.maker_bonus['3'] : 40,
|
||||
maker_v3: d.maker_bonus?.['4'] !== undefined ? d.maker_bonus['4'] : 60,
|
||||
maker_v4: d.maker_bonus?.['5'] !== undefined ? d.maker_bonus['5'] : 80,
|
||||
maker_v5: d.maker_bonus?.['6'] !== undefined ? d.maker_bonus['6'] : 100,
|
||||
maker_v6: d.maker_bonus?.['7'] !== undefined ? d.maker_bonus['7'] : 120,
|
||||
maker_peer_rate: Number(((d.maker_peer_rate !== undefined ? d.maker_peer_rate : 0.10) * 100).toFixed(2)),
|
||||
maker_overtaken_rate: Number(((d.maker_overtaken_rate !== undefined ? d.maker_overtaken_rate : 0.05) * 100).toFixed(2)),
|
||||
venue_split: d.venue_split !== undefined ? d.venue_split : 50,
|
||||
county_full: d.county_full !== undefined ? d.county_full : 100,
|
||||
venue_peer_rate: Number(((d.venue_peer_rate !== undefined ? d.venue_peer_rate : 0.10) * 100).toFixed(2)),
|
||||
county_peer_rate: Number(((d.county_peer_rate !== undefined ? d.county_peer_rate : 0.10) * 100).toFixed(2)),
|
||||
voucher_times: d.voucher_times !== undefined ? d.voucher_times : 1.5,
|
||||
voucher_days: d.voucher_days !== undefined ? d.voucher_days : 365,
|
||||
venue_share: d.venue_share !== undefined ? d.venue_share : 100,
|
||||
county_share: d.county_share !== undefined ? d.county_share : 50,
|
||||
};
|
||||
}
|
||||
|
||||
function rulesToForm(rules, defaults) {
|
||||
const d = defaults || state.defaultPartnerRules;
|
||||
const r = rules || {};
|
||||
return {
|
||||
push3_1: r.push3_amounts?.[0] !== undefined ? r.push3_amounts[0] : (d.push3_amounts?.[0] ?? 398),
|
||||
push3_2: r.push3_amounts?.[1] !== undefined ? r.push3_amounts[1] : (d.push3_amounts?.[1] ?? 400),
|
||||
push3_3: r.push3_amounts?.[2] !== undefined ? r.push3_amounts[2] : (d.push3_amounts?.[2] ?? 600),
|
||||
maker_v1: r.maker_bonus?.['2'] !== undefined ? r.maker_bonus['2'] : (d.maker_bonus?.['2'] ?? 20),
|
||||
maker_v2: r.maker_bonus?.['3'] !== undefined ? r.maker_bonus['3'] : (d.maker_bonus?.['3'] ?? 40),
|
||||
maker_v3: r.maker_bonus?.['4'] !== undefined ? r.maker_bonus['4'] : (d.maker_bonus?.['4'] ?? 60),
|
||||
maker_v4: r.maker_bonus?.['5'] !== undefined ? r.maker_bonus['5'] : (d.maker_bonus?.['5'] ?? 80),
|
||||
maker_v5: r.maker_bonus?.['6'] !== undefined ? r.maker_bonus['6'] : (d.maker_bonus?.['6'] ?? 100),
|
||||
maker_v6: r.maker_bonus?.['7'] !== undefined ? r.maker_bonus['7'] : (d.maker_bonus?.['7'] ?? 120),
|
||||
maker_peer_rate: r.maker_peer_rate !== undefined ? Number((r.maker_peer_rate * 100).toFixed(2)) : Number(((d.maker_peer_rate ?? 0.10) * 100).toFixed(2)),
|
||||
maker_overtaken_rate: r.maker_overtaken_rate !== undefined ? Number((r.maker_overtaken_rate * 100).toFixed(2)) : Number(((d.maker_overtaken_rate ?? 0.05) * 100).toFixed(2)),
|
||||
venue_split: r.venue_split !== undefined ? r.venue_split : (d.venue_split ?? 50),
|
||||
county_full: r.county_full !== undefined ? r.county_full : (d.county_full ?? 100),
|
||||
venue_peer_rate: r.venue_peer_rate !== undefined ? Number((r.venue_peer_rate * 100).toFixed(2)) : Number(((d.venue_peer_rate ?? 0.10) * 100).toFixed(2)),
|
||||
county_peer_rate: r.county_peer_rate !== undefined ? Number((r.county_peer_rate * 100).toFixed(2)) : Number(((d.county_peer_rate ?? 0.10) * 100).toFixed(2)),
|
||||
voucher_times: r.voucher_times !== undefined ? r.voucher_times : (d.voucher_times ?? 1.5),
|
||||
voucher_days: r.voucher_days !== undefined ? r.voucher_days : (d.voucher_days ?? 365),
|
||||
venue_share: r.venue_share !== undefined ? r.venue_share : (d.venue_share ?? 100),
|
||||
county_share: r.county_share !== undefined ? r.county_share : (d.county_share ?? 50),
|
||||
};
|
||||
}
|
||||
|
||||
function formToRules(form) {
|
||||
return {
|
||||
push3_amounts: [
|
||||
Number(form.push3_1 || 0),
|
||||
Number(form.push3_2 || 0),
|
||||
Number(form.push3_3 || 0)
|
||||
],
|
||||
maker_bonus: {
|
||||
"2": Number(form.maker_v1 || 0),
|
||||
"3": Number(form.maker_v2 || 0),
|
||||
"4": Number(form.maker_v3 || 0),
|
||||
"5": Number(form.maker_v4 || 0),
|
||||
"6": Number(form.maker_v5 || 0),
|
||||
"7": Number(form.maker_v6 || 0)
|
||||
},
|
||||
maker_peer_rate: Number((Number(form.maker_peer_rate || 0) / 100).toFixed(4)),
|
||||
maker_overtaken_rate: Number((Number(form.maker_overtaken_rate || 0) / 100).toFixed(4)),
|
||||
venue_split: Number(form.venue_split || 0),
|
||||
county_full: Number(form.county_full || 0),
|
||||
venue_peer_rate: Number((Number(form.venue_peer_rate || 0) / 100).toFixed(4)),
|
||||
county_peer_rate: Number((Number(form.county_peer_rate || 0) / 100).toFixed(4)),
|
||||
voucher_times: Number(form.voucher_times || 0),
|
||||
voucher_days: parseInt(form.voucher_days || 365),
|
||||
venue_share: Number(form.venue_share || 0),
|
||||
county_share: Number(form.county_share || 0)
|
||||
};
|
||||
}
|
||||
|
||||
function getDetail() {
|
||||
Fast.api.ajax({
|
||||
@@ -185,249 +321,170 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
type: 'GET',
|
||||
}, function (ret, res) {
|
||||
state.data = res.data;
|
||||
if (res.data.default_partner_rules) {
|
||||
state.defaultPartnerRules = res.data.default_partner_rules;
|
||||
}
|
||||
if (res.data.role_names) {
|
||||
state.roleNames = res.data.role_names;
|
||||
}
|
||||
if (res.data.levels) {
|
||||
state.levels = res.data.levels;
|
||||
}
|
||||
|
||||
if (state.rulesType != 'batch') {
|
||||
state.detailData = state.data.goods[0];
|
||||
state.detailData = (state.data.goods && state.data.goods[0]) ? state.data.goods[0] : {};
|
||||
}
|
||||
|
||||
Object.keys(state.defaultCommissionConfig).forEach((key) => {
|
||||
if (key != 'status') state.defaultCommissionConfig[key] = state.data.config[key];
|
||||
});
|
||||
const cg = state.detailData.commission_goods;
|
||||
if (cg) {
|
||||
state.commission_goods.status = cg.status !== undefined ? Number(cg.status) : 1;
|
||||
state.commission_goods.commission_order_status = cg.commission_order_status !== undefined ? Number(cg.commission_order_status) : 1;
|
||||
state.commission_goods.self_rules = cg.self_rules !== undefined ? Number(cg.self_rules) : 0;
|
||||
|
||||
state.commission_config_temp = JSON.parse(JSON.stringify(state.defaultCommissionConfig));
|
||||
|
||||
if (isEmpty(state.detailData.commission_goods)) {
|
||||
initCommission(state.commission_goods.self_rules);
|
||||
} else {
|
||||
state.commission_goods = state.detailData.commission_goods;
|
||||
|
||||
if (
|
||||
state.detailData.commission_goods.commission_config &&
|
||||
state.detailData.commission_goods.commission_config.status
|
||||
) {
|
||||
state.commission_config_temp = state.detailData.commission_goods.commission_config;
|
||||
let rawRules = cg.commission_rules;
|
||||
if (typeof rawRules === 'string') {
|
||||
try {
|
||||
rawRules = JSON.parse(rawRules);
|
||||
} catch (e) {
|
||||
rawRules = {};
|
||||
}
|
||||
}
|
||||
|
||||
initCommission(state.detailData.commission_goods.self_rules);
|
||||
}
|
||||
return false
|
||||
}, function (ret, res) { })
|
||||
}
|
||||
|
||||
function getLevelSelect() {
|
||||
Fast.api.ajax({
|
||||
url: `shopro/commission/level/select`,
|
||||
type: 'GET',
|
||||
data: {
|
||||
sort: 'level',
|
||||
order: 'asc',
|
||||
}
|
||||
}, function (ret, res) {
|
||||
state.levelData = res.data;
|
||||
state.type == 'edit' && getDetail()
|
||||
return false
|
||||
}, function (ret, res) { })
|
||||
}
|
||||
|
||||
function initCommission(type) {
|
||||
let rate_money = {
|
||||
1: {
|
||||
rate: '',
|
||||
money: '',
|
||||
},
|
||||
2: {
|
||||
rate: '',
|
||||
money: '',
|
||||
},
|
||||
3: {
|
||||
rate: '',
|
||||
money: '',
|
||||
},
|
||||
};
|
||||
if (!isObject(state.commission_goods.commission_rules)) {
|
||||
state.commission_goods.commission_rules = {};
|
||||
}
|
||||
if (type == 0) {
|
||||
state.commission_goods.commission_rules = null;
|
||||
} else if (type == 1) {
|
||||
state.detailData.sku_prices.forEach((i) => {
|
||||
if (state.commission_goods.commission_rules[i.id]) {
|
||||
state.levelData.forEach((j) => {
|
||||
if (state.commission_goods.commission_rules[i.id][j.level]) {
|
||||
for (let key in state.commission_goods.commission_rules[i.id][j.level]) {
|
||||
if (!state.commission_goods.commission_rules[i.id][j.level][key].rate) {
|
||||
state.commission_goods.commission_rules[i.id][j.level][key].rate = '';
|
||||
} else if (!state.commission_goods.commission_rules[i.id][j.level][key].money) {
|
||||
state.commission_goods.commission_rules[i.id][j.level][key].money = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.commission_goods.commission_rules[i.id][j.level] = JSON.parse(JSON.stringify(rate_money));
|
||||
}
|
||||
});
|
||||
|
||||
for (var level in state.commission_goods.commission_rules[i.id]) {
|
||||
let index = state.levelData.findIndex((l) => Number(l.level) == Number(level));
|
||||
if (index == -1) {
|
||||
delete state.commission_goods.commission_rules[i.id][level];
|
||||
// 检查是否包含规格级规则
|
||||
const skuPrices = state.detailData.sku_prices || [];
|
||||
let hasSkuSpecific = false;
|
||||
if (skuPrices.length > 0 && rawRules) {
|
||||
for (let sku of skuPrices) {
|
||||
if (rawRules[sku.id] || rawRules['sku_' + sku.id]) {
|
||||
hasSkuSpecific = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.commission_goods.commission_rules[i.id] = {};
|
||||
state.levelData.forEach((l) => {
|
||||
state.commission_goods.commission_rules[i.id][l.level] = JSON.parse(JSON.stringify(rate_money));
|
||||
}
|
||||
|
||||
if (hasSkuSpecific) {
|
||||
state.skuMode = 'sku';
|
||||
skuPrices.forEach((sku) => {
|
||||
const skuConfig = rawRules[sku.id] || rawRules['sku_' + sku.id] || rawRules;
|
||||
state.skuRules[sku.id] = rulesToForm(skuConfig, state.defaultPartnerRules);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (var sku in state.commission_goods.commission_rules) {
|
||||
let index = state.detailData.sku_prices.findIndex((s) => Number(s.id) == Number(sku));
|
||||
if (index == -1) {
|
||||
delete state.commission_goods.commission_rules[sku];
|
||||
}
|
||||
}
|
||||
} else if (type == 2) {
|
||||
state.levelData.forEach((l) => {
|
||||
if (state.commission_goods.commission_rules[l.level]) {
|
||||
if (skuPrices.length > 0) {
|
||||
state.activeSkuId = String(skuPrices[0].id);
|
||||
}
|
||||
} else {
|
||||
state.commission_goods.commission_rules[l.level] = JSON.parse(JSON.stringify(rate_money));
|
||||
state.skuMode = 'unified';
|
||||
state.ruleForm = rulesToForm(rawRules, state.defaultPartnerRules);
|
||||
// 预填充各 skuRules 备用
|
||||
skuPrices.forEach((sku) => {
|
||||
state.skuRules[sku.id] = JSON.parse(JSON.stringify(state.ruleForm));
|
||||
});
|
||||
if (skuPrices.length > 0) {
|
||||
state.activeSkuId = String(skuPrices[0].id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.ruleForm = makeFormFromDefaults(state.defaultPartnerRules);
|
||||
const skuPrices = state.detailData.sku_prices || [];
|
||||
skuPrices.forEach((sku) => {
|
||||
state.skuRules[sku.id] = makeFormFromDefaults(state.defaultPartnerRules);
|
||||
});
|
||||
if (skuPrices.length > 0) {
|
||||
state.activeSkuId = String(skuPrices[0].id);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, function (ret, res) { });
|
||||
}
|
||||
|
||||
function onFillDefaults(target) {
|
||||
const defaults = makeFormFromDefaults(state.defaultPartnerRules);
|
||||
if (target === 'current_sku' && state.activeSkuId) {
|
||||
state.skuRules[state.activeSkuId] = defaults;
|
||||
ElMessage({ message: '当前规格已填入系统默认规则', type: 'success' });
|
||||
} else if (target === 'all_skus') {
|
||||
const skuPrices = state.detailData.sku_prices || [];
|
||||
skuPrices.forEach((sku) => {
|
||||
state.skuRules[sku.id] = JSON.parse(JSON.stringify(defaults));
|
||||
});
|
||||
|
||||
for (var level in state.commission_goods.commission_rules) {
|
||||
let index = state.levelData.findIndex((l) => Number(l.level) == Number(level));
|
||||
if (index == -1) {
|
||||
delete state.commission_goods.commission_rules[level];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeSelfRules(self_rules) {
|
||||
if (self_rules == 1 || self_rules == 2) {
|
||||
state.commission_goods.commission_rules = {};
|
||||
if (state.commission_config_temp.status == 0) {
|
||||
state.commission_config_temp = JSON.parse(JSON.stringify(state.defaultCommissionConfig));
|
||||
}
|
||||
} else if (self_rules == 0) {
|
||||
state.commission_goods.commission_rules = null;
|
||||
state.commission_config_temp = JSON.parse(JSON.stringify(state.defaultCommissionConfig));
|
||||
}
|
||||
initCommission(self_rules);
|
||||
}
|
||||
|
||||
function onChangeCommissionConfigStatus(val) {
|
||||
if (val == 0) {
|
||||
// 默认
|
||||
state.commission_config_temp = JSON.parse(JSON.stringify(state.defaultCommissionConfig));
|
||||
state.ruleForm = defaults;
|
||||
ElMessage({ message: '所有规格已同步系统默认规则', type: 'success' });
|
||||
} else {
|
||||
// 自定义
|
||||
if (
|
||||
state.detailData.commission_goods &&
|
||||
state.detailData.commission_goods.commission_config
|
||||
) {
|
||||
state.commission_config_temp = state.detailData.commission_goods.commission_config;
|
||||
}
|
||||
|
||||
state.commission_config_temp.status = 1;
|
||||
state.ruleForm = defaults;
|
||||
ElMessage({ message: '已填入系统默认规则', type: 'success' });
|
||||
}
|
||||
}
|
||||
|
||||
const commissionPopover = reactive({
|
||||
flag: {},
|
||||
form: {
|
||||
money: '',
|
||||
rate: '',
|
||||
},
|
||||
});
|
||||
function onConfirmCommissionPopover(cl) {
|
||||
if (state.commission_goods.self_rules == 1) {
|
||||
for (let sku in state.commission_goods.commission_rules) {
|
||||
for (let level in state.commission_goods.commission_rules[sku]) {
|
||||
state.commission_goods.commission_rules[sku][level][cl] = JSON.parse(JSON.stringify(commissionPopover.form))
|
||||
}
|
||||
}
|
||||
} else if (state.commission_goods.self_rules == 2) {
|
||||
for (let level in state.commission_goods.commission_rules) {
|
||||
state.commission_goods.commission_rules[level][cl] = JSON.parse(JSON.stringify(commissionPopover.form))
|
||||
function onSyncToAllSkus() {
|
||||
const currentSource = (state.skuMode === 'sku' && state.activeSkuId)
|
||||
? state.skuRules[state.activeSkuId]
|
||||
: state.ruleForm;
|
||||
const skuPrices = state.detailData.sku_prices || [];
|
||||
skuPrices.forEach((sku) => {
|
||||
state.skuRules[sku.id] = JSON.parse(JSON.stringify(currentSource));
|
||||
});
|
||||
state.ruleForm = JSON.parse(JSON.stringify(currentSource));
|
||||
ElMessage({ message: '已将当前规则同步至全部规格', type: 'success' });
|
||||
}
|
||||
|
||||
function onChangeSelfRules(val) {
|
||||
if (val == 1 || val == 2) {
|
||||
if (!state.ruleForm.push3_1) {
|
||||
state.ruleForm = makeFormFromDefaults(state.defaultPartnerRules);
|
||||
}
|
||||
}
|
||||
onCancelCommissionPopover(cl);
|
||||
}
|
||||
function onCancelCommissionPopover(cl) {
|
||||
commissionPopover.flag[cl] = false;
|
||||
commissionPopover.form = {
|
||||
money: '',
|
||||
rate: '',
|
||||
};
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
const commission_goods = {
|
||||
...state.commission_goods,
|
||||
const payload = {
|
||||
status: Number(state.commission_goods.status),
|
||||
commission_order_status: Number(state.commission_goods.commission_order_status),
|
||||
self_rules: Number(state.commission_goods.self_rules),
|
||||
commission_rules: null,
|
||||
};
|
||||
commission_goods.commission_config = JSON.stringify({
|
||||
...state.commission_config_temp,
|
||||
});
|
||||
|
||||
let flag = true
|
||||
if (commission_goods.self_rules == 1) {
|
||||
for (var key1 in commission_goods.commission_rules) {
|
||||
for (var key2 in commission_goods.commission_rules[key1]) {
|
||||
for (var key3 in commission_goods.commission_rules[key1][key2]) {
|
||||
if (key3 <= state.commission_config_temp.level) {
|
||||
if ((commission_goods.commission_rules[key1][key2][key3].rate === '' || commission_goods.commission_rules[key1][key2][key3].rate < 0)
|
||||
&& (commission_goods.commission_rules[key1][key2][key3].money === '' || commission_goods.commission_rules[key1][key2][key3].money < 0)) {
|
||||
flag = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (payload.status === 1) {
|
||||
if (payload.self_rules === 0) {
|
||||
payload.commission_rules = null;
|
||||
} else {
|
||||
// 独立规则或批量规则
|
||||
const skuPrices = state.detailData?.sku_prices || [];
|
||||
if (state.skuMode === 'sku' && skuPrices.length > 0 && state.rulesType !== 'batch') {
|
||||
const rulesMap = {};
|
||||
skuPrices.forEach((sku) => {
|
||||
const form = state.skuRules[sku.id] || state.ruleForm;
|
||||
rulesMap[sku.id] = formToRules(form);
|
||||
});
|
||||
payload.commission_rules = rulesMap;
|
||||
} else {
|
||||
payload.commission_rules = formToRules(state.ruleForm);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (commission_goods.self_rules == 2) {
|
||||
for (var key1 in commission_goods.commission_rules) {
|
||||
for (var key2 in commission_goods.commission_rules[key1]) {
|
||||
if (key2 <= state.commission_config_temp.level) {
|
||||
if (commission_goods.commission_rules[key1][key2].rate === '' || commission_goods.commission_rules[key1][key2].rate < 0) {
|
||||
flag = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
ElMessage({
|
||||
message: '请将数据填写完整',
|
||||
type: 'warning',
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
Fast.api.ajax({
|
||||
url: `shopro/commission/goods/edit/id/${state.id}`,
|
||||
type: 'POST',
|
||||
data: JSON.parse(JSON.stringify(commission_goods))
|
||||
data: payload
|
||||
}, function (ret, res) {
|
||||
Fast.api.close()
|
||||
}, function (ret, res) { })
|
||||
ElMessage({ message: '设置保存成功', type: 'success' });
|
||||
Fast.api.close();
|
||||
return false;
|
||||
}, function (ret, res) { });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLevelSelect()
|
||||
})
|
||||
getDetail();
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
onFillDefaults,
|
||||
onSyncToAllSkus,
|
||||
onChangeSelfRules,
|
||||
onChangeCommissionConfigStatus,
|
||||
commissionPopover,
|
||||
onConfirmCommissionPopover,
|
||||
onCancelCommissionPopover,
|
||||
onConfirm,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
createApp('addEdit', addEdit);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -790,9 +790,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
|
||||
onOpenTab(url, "收益流水");
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开分销商/成员档案选项卡
|
||||
* @param {number|string} level 分销商等级(如 1 代表品酒师会员)
|
||||
*/
|
||||
function onOpenAgent(level) {
|
||||
let url = `shopro/commission/agent/index` + (level ? `&level=${level}` : '');
|
||||
onOpenTab(url, "成员档案");
|
||||
let url = `shopro/commission/agent/index` + (level !== undefined && level !== null && level !== '' ? `?level=${level}` : '');
|
||||
let title = (level == 1) ? "品酒师会员" : "成员档案";
|
||||
onOpenTab(url, title);
|
||||
}
|
||||
|
||||
function onOpenDaily() {
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
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(" 分销商成员档案 URL 路由 & 参数回显自动化规范测试");
|
||||
console.log("========================================================\n");
|
||||
|
||||
// 1. 验证 dashboard.js 中 onOpenAgent 修复
|
||||
const dashboardJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/dashboard.js');
|
||||
assert(fs.existsSync(dashboardJsPath), 'dashboard.js 文件存在');
|
||||
const dashboardContent = fs.readFileSync(dashboardJsPath, 'utf-8');
|
||||
|
||||
assert(!dashboardContent.includes('`shopro/commission/agent/index` + (level ? `&level=${level}` : \'\')'),
|
||||
'dashboard.js 中已彻底移除错误的 &level= 拼接语法');
|
||||
|
||||
assert(dashboardContent.includes('`shopro/commission/agent/index` + (level !== undefined && level !== null && level !== \'\' ? `?level=${level}` : \'\')'),
|
||||
'dashboard.js 中使用健壮的 ?level= 拼接参数');
|
||||
|
||||
// 模拟测试 onOpenAgent 各种参数行为
|
||||
function mockOnOpenAgent(level) {
|
||||
let captured = {};
|
||||
function onOpenTab(url, title) {
|
||||
captured = { url, title };
|
||||
}
|
||||
let url = `shopro/commission/agent/index` + (level !== undefined && level !== null && level !== '' ? `?level=${level}` : '');
|
||||
let title = (level == 1) ? "品酒师会员" : "成员档案";
|
||||
onOpenTab(url, title);
|
||||
return captured;
|
||||
}
|
||||
|
||||
const res1 = mockOnOpenAgent(1);
|
||||
assert(res1.url === 'shopro/commission/agent/index?level=1' && res1.title === '品酒师会员',
|
||||
'正常场景: level=1 生成正确的 query string ?level=1 且标题为品酒师会员');
|
||||
|
||||
const resEmpty = mockOnOpenAgent('');
|
||||
assert(resEmpty.url === 'shopro/commission/agent/index' && resEmpty.title === '成员档案',
|
||||
'边界场景: level="" 生成基础无参数 URL');
|
||||
|
||||
const resNull = mockOnOpenAgent(null);
|
||||
assert(resNull.url === 'shopro/commission/agent/index' && resNull.title === '成员档案',
|
||||
'边界场景: level=null 生成基础无参数 URL');
|
||||
|
||||
const resUndef = mockOnOpenAgent(undefined);
|
||||
assert(resUndef.url === 'shopro/commission/agent/index' && resUndef.title === '成员档案',
|
||||
'边界场景: level=undefined 生成基础无参数 URL');
|
||||
|
||||
// 2. 验证 agent.js URL 参数解析与初始化
|
||||
const agentJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/commission/agent.js');
|
||||
assert(fs.existsSync(agentJsPath), 'agent.js 文件存在');
|
||||
const agentContent = fs.readFileSync(agentJsPath, 'utf-8');
|
||||
|
||||
assert(agentContent.includes('is_partner: \'\''), 'agent.js filter.data 包含 is_partner');
|
||||
assert(agentContent.includes('is_partner: {') && agentContent.includes('label: \'合伙股东\''), 'agent.js filter.tools 包含 is_partner 筛选工具');
|
||||
assert(agentContent.includes('{ value: \'partner\', label: \'合伙股东\' }'), 'agent.js filter.tools.identity 选项中包含合伙股东');
|
||||
assert(agentContent.includes('new URLSearchParams(location.search)'), 'agent.js onMounted 中使用 URLSearchParams 解析参数');
|
||||
assert(agentContent.includes('params.get(\'level\')'), 'agent.js onMounted 读取 level 参数');
|
||||
assert(agentContent.includes('params.get(\'is_venue\')'), 'agent.js onMounted 读取 is_venue 参数');
|
||||
assert(agentContent.includes('params.get(\'is_county\')'), 'agent.js onMounted 读取 is_county 参数');
|
||||
assert(agentContent.includes('params.get(\'is_partner\')'), 'agent.js onMounted 读取 is_partner 参数');
|
||||
assert(agentContent.includes('params.get(\'identity\')'), 'agent.js onMounted 读取 identity 参数');
|
||||
assert(agentContent.includes('params.get(\'user_id\')'), 'agent.js onMounted 读取 user_id 参数');
|
||||
|
||||
// 模拟测试 agent.js onMounted 参数解析与赋值
|
||||
function mockAgentMount(searchString) {
|
||||
const params = new URLSearchParams(searchString);
|
||||
let state = {
|
||||
filter: {
|
||||
data: { level: '', is_venue: '', is_county: '', is_partner: '', identity: '', user: { field: 'user_id', value: '' } },
|
||||
tools: {
|
||||
level: { value: '' },
|
||||
is_venue: { value: '' },
|
||||
is_county: { value: '' },
|
||||
is_partner: { value: '' },
|
||||
identity: { value: '' },
|
||||
user: { value: { field: 'user_id', value: '' } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const levelParam = params.get('level');
|
||||
if (levelParam !== null && levelParam !== '') {
|
||||
const parsedLevel = !isNaN(Number(levelParam)) ? Number(levelParam) : levelParam;
|
||||
state.filter.data.level = parsedLevel;
|
||||
if (state.filter.tools && state.filter.tools.level) {
|
||||
state.filter.tools.level.value = parsedLevel;
|
||||
}
|
||||
}
|
||||
const isVenueParam = params.get('is_venue');
|
||||
if (isVenueParam !== null && isVenueParam !== '') {
|
||||
state.filter.data.is_venue = isVenueParam;
|
||||
if (state.filter.tools && state.filter.tools.is_venue) {
|
||||
state.filter.tools.is_venue.value = isVenueParam;
|
||||
}
|
||||
}
|
||||
const isCountyParam = params.get('is_county');
|
||||
if (isCountyParam !== null && isCountyParam !== '') {
|
||||
state.filter.data.is_county = isCountyParam;
|
||||
if (state.filter.tools && state.filter.tools.is_county) {
|
||||
state.filter.tools.is_county.value = isCountyParam;
|
||||
}
|
||||
}
|
||||
const isPartnerParam = params.get('is_partner');
|
||||
if (isPartnerParam !== null && isPartnerParam !== '') {
|
||||
state.filter.data.is_partner = isPartnerParam;
|
||||
if (state.filter.tools && state.filter.tools.is_partner) {
|
||||
state.filter.tools.is_partner.value = isPartnerParam;
|
||||
}
|
||||
}
|
||||
const identityParam = params.get('identity');
|
||||
if (identityParam !== null && identityParam !== '') {
|
||||
state.filter.data.identity = identityParam;
|
||||
if (state.filter.tools && state.filter.tools.identity) {
|
||||
state.filter.tools.identity.value = identityParam;
|
||||
}
|
||||
}
|
||||
const userIdParam = params.get('user_id');
|
||||
if (userIdParam !== null && userIdParam !== '') {
|
||||
state.filter.data.user = { field: 'user_id', value: userIdParam };
|
||||
if (state.filter.tools && state.filter.tools.user) {
|
||||
state.filter.tools.user.value = { field: 'user_id', value: userIdParam };
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
const mockState = mockAgentMount('?level=1&ref=addtabs');
|
||||
assert(mockState.filter.data.level === 1 && mockState.filter.tools.level.value === 1,
|
||||
'agent.js 正确从 ?level=1 解析数字等级 1 并同步至 filter.data 与 filter.tools');
|
||||
|
||||
const mockPartnerState = mockAgentMount('?is_partner=1');
|
||||
assert(mockPartnerState.filter.data.is_partner === '1' && mockPartnerState.filter.tools.is_partner.value === '1',
|
||||
'agent.js 正确从 ?is_partner=1 解析合伙股东标记');
|
||||
|
||||
// 3. 验证 AgentFilter.php 过滤能力
|
||||
const filterPhpPath = path.resolve(__dirname, '../addons/shopro/filter/commission/AgentFilter.php');
|
||||
assert(fs.existsSync(filterPhpPath), 'AgentFilter.php 文件存在');
|
||||
const filterPhpContent = fs.readFileSync(filterPhpPath, 'utf-8');
|
||||
|
||||
assert(filterPhpContent.includes('public function level($query, $data)'), 'AgentFilter 显式定义 level 过滤方法');
|
||||
assert(filterPhpContent.includes('public function isPartner($query, $data)'), 'AgentFilter 显式定义 isPartner 过滤方法');
|
||||
assert(filterPhpContent.includes('public function isVenue($query, $data)'), 'AgentFilter 显式定义 isVenue 过滤方法');
|
||||
assert(filterPhpContent.includes('public function isCounty($query, $data)'), 'AgentFilter 显式定义 isCounty 过滤方法');
|
||||
assert(filterPhpContent.includes('if ($value === \'partner\') {') && filterPhpContent.includes('where(\'is_partner\', 1)'),
|
||||
'AgentFilter identity 方法支持合伙股东 (partner)');
|
||||
|
||||
console.log("\n--------------------------------------------------------");
|
||||
console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`);
|
||||
console.log("========================================================");
|
||||
|
||||
process.exit(failed === 0 ? 0 : 1);
|
||||
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* 计奖商品独立分成与多规格规则专项验证测试
|
||||
* 覆盖范围:
|
||||
* 1. PartnerConfig (defaultRules, getEffectiveRules, getOrderEffectiveRules)
|
||||
* 2. PartnerRewardService (settleOrder 动态规则提取与分发)
|
||||
* 3. 奖项子服务 (Push3Reward, MakerDiffReward, MakerPeerReward, TeamSplitReward, PeerShareReward, VoucherService)
|
||||
* 4. 后台控制器 Goods.php (detail, edit)
|
||||
* 5. 后台模板 index.html & edit.html
|
||||
* 6. 后台前端脚本 goods.js (Vue 表单交互、多规格规则映射、默认值同步)
|
||||
* 7. 算法行为模拟:默认规则、单规格独立规则、多规格独立规则、混合订单提取、金额与倍数计算
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function assert(condition, message) {
|
||||
if (condition) {
|
||||
console.log(` \x1b[32m[PASS]\x1b[0m ${message}`);
|
||||
passCount++;
|
||||
} else {
|
||||
console.error(` \x1b[31m[FAIL]\x1b[0m ${message}`);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('========================================================');
|
||||
console.log(' 计奖商品独立分成与多规格规则专项测试');
|
||||
console.log('========================================================\n');
|
||||
|
||||
// ----------------------------------------------------
|
||||
// 1. PartnerConfig.php 验证
|
||||
// ----------------------------------------------------
|
||||
console.log('--- 1. PartnerConfig.php 规范验证 ---');
|
||||
const partnerConfigPath = path.resolve(__dirname, '../addons/shopro/service/partner/PartnerConfig.php');
|
||||
assert(fs.existsSync(partnerConfigPath), 'PartnerConfig.php 文件存在');
|
||||
if (fs.existsSync(partnerConfigPath)) {
|
||||
const content = fs.readFileSync(partnerConfigPath, 'utf-8');
|
||||
assert(content.includes('public static function defaultRules()'), '包含 defaultRules() 矩阵方法');
|
||||
assert(content.includes("'push3_amounts' => self::push3Amounts()"), 'defaultRules 包含 push3_amounts');
|
||||
assert(content.includes("'maker_bonus' => self::makerBonus()"), 'defaultRules 包含 maker_bonus');
|
||||
assert(content.includes("'maker_peer_rate' => (float) self::get('partner_maker_peer_rate'"), 'defaultRules 包含 maker_peer_rate');
|
||||
assert(content.includes("'maker_overtaken_rate' => (float) self::get('partner_maker_overtaken_rate'"), 'defaultRules 包含 maker_overtaken_rate');
|
||||
assert(content.includes("'venue_split' => (float) self::get('partner_venue_split'"), 'defaultRules 包含 venue_split');
|
||||
assert(content.includes("'county_full' => (float) self::get('partner_county_full'"), 'defaultRules 包含 county_full');
|
||||
assert(content.includes("'voucher_times' => (float) self::get('partner_voucher_times'"), 'defaultRules 包含 voucher_times');
|
||||
assert(content.includes('public static function getEffectiveRules($goodsId, $skuPriceId = 0)'), '包含 getEffectiveRules($goodsId, $skuPriceId) 方法');
|
||||
assert(content.includes('$selfRules !== 1 && $selfRules !== 2'), 'getEffectiveRules 识别 self_rules 规则类型 (0默认/1独立/2批量)');
|
||||
assert(content.includes("isset($custom[$skuKey])"), 'getEffectiveRules 支持根据 skuPriceId 匹配特定规格规则');
|
||||
assert(content.includes("isset($custom['sku_' . $skuKey])"), 'getEffectiveRules 兼容 sku_ 前缀格式');
|
||||
assert(content.includes('public static function getOrderEffectiveRules($order)'), '包含 getOrderEffectiveRules($order) 订单规则解析方法');
|
||||
assert(content.includes("!empty($rules['is_custom'])"), 'getOrderEffectiveRules 优先提取商品专属独立规则');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// 2. PartnerRewardService.php 验证
|
||||
// ----------------------------------------------------
|
||||
console.log('\n--- 2. PartnerRewardService.php 调度验证 ---');
|
||||
const partnerRewardServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/PartnerRewardService.php');
|
||||
assert(fs.existsSync(partnerRewardServicePath), 'PartnerRewardService.php 文件存在');
|
||||
if (fs.existsSync(partnerRewardServicePath)) {
|
||||
const content = fs.readFileSync(partnerRewardServicePath, 'utf-8');
|
||||
assert(content.includes('$orderRules = PartnerConfig::getOrderEffectiveRules($order)'), 'settleOrder 调用 getOrderEffectiveRules 提取订单专属规则');
|
||||
assert(content.includes('(new Push3Reward())->run($order, $buyerId, $parent, $active, $orderRules)'), 'Push3Reward 透传 $orderRules');
|
||||
assert(content.includes('(new MakerDiffReward())->run($order, $buyerId, $parentChain, $active, $orderRules)'), 'MakerDiffReward 透传 $orderRules');
|
||||
assert(content.includes('(new MakerPeerReward())->run($order, $buyerId, $parentChain, $diff, $active, $orderRules)'), 'MakerPeerReward 透传 $orderRules');
|
||||
assert(content.includes('(new TeamSplitReward())->run($order, $buyerId, $selfChain, $active, $orderRules)'), 'TeamSplitReward 透传 $orderRules');
|
||||
assert(content.includes('(new PeerShareReward())->run($order, $buyerId, $selfChain, $team, $active, $orderRules)'), 'PeerShareReward 透传 $orderRules');
|
||||
assert(content.includes('(new VoucherService())->grant($order, $buyerId, $orderRules)'), 'VoucherService grant 透传 $orderRules');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// 3. 各奖励子服务入参验证
|
||||
// ----------------------------------------------------
|
||||
console.log('\n--- 3. 奖项子服务参数消费验证 ---');
|
||||
const push3Path = path.resolve(__dirname, '../addons/shopro/service/partner/Push3Reward.php');
|
||||
if (fs.existsSync(push3Path)) {
|
||||
const c = fs.readFileSync(push3Path, 'utf-8');
|
||||
assert(c.includes('$rules = null') || c.includes('$rules = []'), 'Push3Reward::run 支持接收 $rules');
|
||||
assert(c.includes("$rules['push3_amounts']"), 'Push3Reward 优先使用 $rules 中的推三返一循环金额');
|
||||
}
|
||||
|
||||
const makerDiffPath = path.resolve(__dirname, '../addons/shopro/service/partner/MakerDiffReward.php');
|
||||
if (fs.existsSync(makerDiffPath)) {
|
||||
const c = fs.readFileSync(makerDiffPath, 'utf-8');
|
||||
assert(c.includes('$rules = null') || c.includes('$rules = []'), 'MakerDiffReward::run 支持接收 $rules');
|
||||
assert(c.includes("$rules['maker_bonus']"), 'MakerDiffReward 优先使用 $rules 中的各级极差基数');
|
||||
}
|
||||
|
||||
const makerPeerPath = path.resolve(__dirname, '../addons/shopro/service/partner/MakerPeerReward.php');
|
||||
if (fs.existsSync(makerPeerPath)) {
|
||||
const c = fs.readFileSync(makerPeerPath, 'utf-8');
|
||||
assert(c.includes('$rules = null') || c.includes('$rules = []'), 'MakerPeerReward::run 支持接收 $rules');
|
||||
assert(c.includes("$rules['maker_peer_rate']"), 'MakerPeerReward 优先使用 $rules 中的创客平级协同比例');
|
||||
assert(c.includes("$rules['maker_overtaken_rate']"), 'MakerPeerReward 优先使用 $rules 中的创客超越卓越比例');
|
||||
}
|
||||
|
||||
const teamSplitPath = path.resolve(__dirname, '../addons/shopro/service/partner/TeamSplitReward.php');
|
||||
if (fs.existsSync(teamSplitPath)) {
|
||||
const c = fs.readFileSync(teamSplitPath, 'utf-8');
|
||||
assert(c.includes('$rules = null') || c.includes('$rules = []'), 'TeamSplitReward::run 支持接收 $rules');
|
||||
assert(c.includes("$rules['venue_split']"), 'TeamSplitReward 优先使用 $rules 中的酒道馆运营分成金额');
|
||||
assert(c.includes("$rules['county_full']"), 'TeamSplitReward 优先使用 $rules 中的区县统筹分成金额');
|
||||
}
|
||||
|
||||
const peerSharePath = path.resolve(__dirname, '../addons/shopro/service/partner/PeerShareReward.php');
|
||||
if (fs.existsSync(peerSharePath)) {
|
||||
const c = fs.readFileSync(peerSharePath, 'utf-8');
|
||||
assert(c.includes('$rules = null') || c.includes('$rules = []'), 'PeerShareReward::run 支持接收 $rules');
|
||||
assert(c.includes("$rules['venue_peer_rate']"), 'PeerShareReward 优先使用 $rules 中的酒道馆协同比例');
|
||||
assert(c.includes("$rules['county_peer_rate']"), 'PeerShareReward 优先使用 $rules 中的区县协同比例');
|
||||
}
|
||||
|
||||
const voucherPath = path.resolve(__dirname, '../addons/shopro/service/partner/VoucherService.php');
|
||||
if (fs.existsSync(voucherPath)) {
|
||||
const c = fs.readFileSync(voucherPath, 'utf-8');
|
||||
assert(c.includes('$rules = null') || c.includes('$rules = []'), 'VoucherService::grant 支持接收 $rules');
|
||||
assert(c.includes("$rules['voucher_times']"), 'VoucherService 优先使用 $rules 中的消费券倍数');
|
||||
assert(c.includes("$rules['voucher_days']"), 'VoucherService 优先使用 $rules 中的消费券释放周期天数');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// 4. 后台控制器 Goods.php 验证
|
||||
// ----------------------------------------------------
|
||||
console.log('\n--- 4. 后台控制器 Goods.php 验证 ---');
|
||||
const goodsControllerPath = path.resolve(__dirname, '../application/admin/controller/shopro/commission/Goods.php');
|
||||
assert(fs.existsSync(goodsControllerPath), 'Goods.php 控制器存在');
|
||||
if (fs.existsSync(goodsControllerPath)) {
|
||||
const c = fs.readFileSync(goodsControllerPath, 'utf-8');
|
||||
assert(c.includes('PartnerConfig::defaultRules()'), 'detail() 返回系统全局 default_partner_rules');
|
||||
assert(c.includes('PartnerConfig::roleNames()'), 'detail() 返回定制化 role_names');
|
||||
assert(c.includes('Level::order'), 'detail() 返回创客等级 levels 列表');
|
||||
assert(c.includes("$params = $this->request->only(['status', 'self_rules', 'commission_order_status'"), 'edit() 严格获取指定参数');
|
||||
assert(c.includes("intval($params['status']) === 1 ? 1 : 0"), 'edit() 规范化 status 为 0/1');
|
||||
assert(c.includes("intval($params['commission_order_status']) === 0 ? 0 : 1"), 'edit() 规范化 commission_order_status 为 0/1');
|
||||
assert(c.includes("json_decode($params['commission_rules'], true)"), 'edit() 对 commission_rules 进行 JSON 防御校验');
|
||||
assert(c.includes('Db::transaction'), 'edit() 使用数据库事务保证批量或单条保存原子性');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// 5. 后台视图与前端脚本验证
|
||||
// ----------------------------------------------------
|
||||
console.log('\n--- 5. 后台视图与前端脚本验证 ---');
|
||||
const indexPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/goods/index.html');
|
||||
assert(fs.existsSync(indexPath), 'index.html 存在');
|
||||
if (fs.existsSync(indexPath)) {
|
||||
const c = fs.readFileSync(indexPath, 'utf-8');
|
||||
assert(c.includes('label="分成规则"'), '列表包含“分成规则”列');
|
||||
assert(c.includes('默认规则'), '列表包含“默认规则” Tag 展现');
|
||||
assert(c.includes('独立规则'), '列表包含“独立规则” Tag 展现');
|
||||
assert(c.includes('不参与计奖'), '列表包含“不参与计奖” Tag 展现');
|
||||
assert(c.includes('设置分成'), '操作区包含“设置分成”按钮');
|
||||
}
|
||||
|
||||
const editPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/goods/edit.html');
|
||||
assert(fs.existsSync(editPath), 'edit.html 存在');
|
||||
if (fs.existsSync(editPath)) {
|
||||
const c = fs.readFileSync(editPath, 'utf-8');
|
||||
assert(c.includes('v-model="state.commission_goods.status"'), '包含参与计奖开关状态');
|
||||
assert(c.includes('v-model="state.commission_goods.self_rules"'), '包含分成规则单选 (0默认/1独立/2批量)');
|
||||
assert(c.includes('推三返一循环返现'), '表单包含“推三返一循环返现”卡片');
|
||||
assert(c.includes('极差基数'), '表单包含“各级极差基数”卡片');
|
||||
assert(c.includes('创客下级反超卓越比例') || c.includes('协同奖'), '表单包含“协同与超越卓越奖”卡片');
|
||||
assert(c.includes('酒道馆运营奖基准') || c.includes('实体渠道与区域运营奖'), '表单包含“实体渠道与区域运营分成”卡片');
|
||||
assert(c.includes('消费补贴兑换券计划'), '表单包含“消费补贴兑换券计划”卡片');
|
||||
assert(c.includes('全平台日结均分奖池注入'), '表单包含“全平台日结均分奖池注入”卡片');
|
||||
assert(c.includes('@click="onFillDefaults'), '包含快速填入系统默认规则按钮');
|
||||
assert(c.includes('@click="onSyncToAllSkus"'), '包含同步至全部规格按钮');
|
||||
}
|
||||
|
||||
const goodsJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/commission/goods.js');
|
||||
assert(fs.existsSync(goodsJsPath), 'goods.js 存在');
|
||||
if (fs.existsSync(goodsJsPath)) {
|
||||
const c = fs.readFileSync(goodsJsPath, 'utf-8');
|
||||
assert(c.includes("'commission_goods.self_rules'"), '包含分成规则筛选器');
|
||||
assert(c.includes('function makeFormFromDefaults'), '包含 makeFormFromDefaults 工具函数');
|
||||
assert(c.includes('function rulesToForm'), '包含 rulesToForm 工具函数');
|
||||
assert(c.includes('function formToRules'), '包含 formToRules 工具函数');
|
||||
assert(c.includes('state.skuMode === \'sku\''), '支持按规格分别设置与统一设置判断');
|
||||
assert(c.includes('state.skuRules[sku.id]'), '保存时正确提取各规格独立规则映射');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// 6. 算法行为与业务逻辑模拟
|
||||
// ----------------------------------------------------
|
||||
console.log('\n--- 6. 算法行为与业务逻辑模拟断言 ---');
|
||||
|
||||
const mockSystemDefaults = {
|
||||
push3_amounts: [398, 400, 600],
|
||||
maker_bonus: { 2: 20, 3: 40, 4: 60, 5: 80, 6: 100, 7: 120 },
|
||||
maker_peer_rate: 0.10,
|
||||
maker_overtaken_rate: 0.05,
|
||||
venue_split: 50,
|
||||
county_full: 100,
|
||||
venue_peer_rate: 0.10,
|
||||
county_peer_rate: 0.10,
|
||||
voucher_times: 1.5,
|
||||
voucher_days: 365,
|
||||
venue_share: 100,
|
||||
county_share: 50,
|
||||
is_custom: false,
|
||||
};
|
||||
|
||||
function mockGetEffectiveRules(goodsRecord, skuPriceId = 0) {
|
||||
const rules = JSON.parse(JSON.stringify(mockSystemDefaults));
|
||||
if (!goodsRecord || Number(goodsRecord.status) !== 1) {
|
||||
rules.status = 0;
|
||||
return rules;
|
||||
}
|
||||
rules.status = 1;
|
||||
rules.commission_order_status = Number(goodsRecord.commission_order_status ?? 1);
|
||||
const selfRules = Number(goodsRecord.self_rules ?? 0);
|
||||
rules.self_rules = selfRules;
|
||||
|
||||
if (selfRules !== 1 && selfRules !== 2) {
|
||||
return rules; // 跟随默认规则
|
||||
}
|
||||
|
||||
let custom = goodsRecord.commission_rules;
|
||||
if (typeof custom === 'string') {
|
||||
try {
|
||||
custom = JSON.parse(custom);
|
||||
} catch (e) {
|
||||
custom = {};
|
||||
}
|
||||
}
|
||||
if (!custom || Object.keys(custom).length === 0) {
|
||||
return rules;
|
||||
}
|
||||
|
||||
const skuKey = Number(skuPriceId);
|
||||
if (skuKey > 0 && custom[skuKey] && typeof custom[skuKey] === 'object') {
|
||||
custom = custom[skuKey];
|
||||
} else if (skuKey > 0 && custom['sku_' + skuKey] && typeof custom['sku_' + skuKey] === 'object') {
|
||||
custom = custom['sku_' + skuKey];
|
||||
}
|
||||
|
||||
if (Array.isArray(custom.push3_amounts) && custom.push3_amounts.length >= 3) {
|
||||
rules.push3_amounts = custom.push3_amounts.map(Number);
|
||||
}
|
||||
if (custom.maker_bonus && typeof custom.maker_bonus === 'object') {
|
||||
const mb = {};
|
||||
for (const [k, v] of Object.entries(custom.maker_bonus)) {
|
||||
mb[parseInt(k)] = Number(v);
|
||||
}
|
||||
rules.maker_bonus = mb;
|
||||
}
|
||||
if (custom.maker_peer_rate !== undefined) rules.maker_peer_rate = Number(custom.maker_peer_rate);
|
||||
if (custom.maker_overtaken_rate !== undefined) rules.maker_overtaken_rate = Number(custom.maker_overtaken_rate);
|
||||
if (custom.venue_split !== undefined) rules.venue_split = Number(custom.venue_split);
|
||||
if (custom.county_full !== undefined) rules.county_full = Number(custom.county_full);
|
||||
if (custom.voucher_times !== undefined) rules.voucher_times = Number(custom.voucher_times);
|
||||
if (custom.voucher_days !== undefined) rules.voucher_days = parseInt(custom.voucher_days);
|
||||
if (custom.venue_share !== undefined) rules.venue_share = Number(custom.venue_share);
|
||||
if (custom.county_share !== undefined) rules.county_share = Number(custom.county_share);
|
||||
|
||||
rules.is_custom = true;
|
||||
return rules;
|
||||
}
|
||||
|
||||
// 模拟场景 1: 商品未开启计奖 (status = 0)
|
||||
const uncommissionedGoods = { goods_id: 201, status: 0, self_rules: 1 };
|
||||
const res1 = mockGetEffectiveRules(uncommissionedGoods);
|
||||
assert(res1.status === 0, '模拟场景 1: status=0 商品被标记为不参与计奖');
|
||||
assert(res1.is_custom === false, '模拟场景 1: status=0 商品不激活独立规则');
|
||||
|
||||
// 模拟场景 2: 开启计奖但使用默认规则 (self_rules = 0)
|
||||
const defaultGoods = { goods_id: 202, status: 1, self_rules: 0 };
|
||||
const res2 = mockGetEffectiveRules(defaultGoods);
|
||||
assert(res2.status === 1 && res2.self_rules === 0, '模拟场景 2: 开启计奖且 self_rules=0');
|
||||
assert(res2.push3_amounts[0] === 398 && res2.push3_amounts[1] === 400 && res2.push3_amounts[2] === 600, '模拟场景 2: 采用系统默认推三返一金额 (398/400/600)');
|
||||
assert(res2.voucher_times === 1.5, '模拟场景 2: 采用系统默认兑换券倍数 (1.5)');
|
||||
assert(res2.is_custom === false, '模拟场景 2: is_custom 为 false');
|
||||
|
||||
// 模拟场景 3: 独立规则设置单品分成 (self_rules = 1, 单规格)
|
||||
const customGoods = {
|
||||
goods_id: 203,
|
||||
status: 1,
|
||||
self_rules: 1,
|
||||
commission_rules: JSON.stringify({
|
||||
push3_amounts: [500, 500, 800],
|
||||
maker_bonus: { 2: 50, 3: 100, 4: 150, 5: 200, 6: 250, 7: 300 },
|
||||
venue_split: 80,
|
||||
county_full: 150,
|
||||
voucher_times: 2.0,
|
||||
voucher_days: 180
|
||||
})
|
||||
};
|
||||
const res3 = mockGetEffectiveRules(customGoods);
|
||||
assert(res3.status === 1 && res3.is_custom === true, '模拟场景 3: 成功识别单品独立规则 (is_custom = true)');
|
||||
assert(res3.push3_amounts[0] === 500 && res3.push3_amounts[2] === 800, '模拟场景 3: 推三返一正确覆盖为独立金额 [500, 500, 800]');
|
||||
assert(res3.maker_bonus[2] === 50 && res3.maker_bonus[3] === 100, '模拟场景 3: 创客极差正确覆盖为定制梯度');
|
||||
assert(res3.venue_split === 80 && res3.county_full === 150, '模拟场景 3: 渠道运营奖覆盖为 80 / 150');
|
||||
assert(res3.voucher_times === 2.0 && res3.voucher_days === 180, '模拟场景 3: 兑换券计划覆盖为 2.0 倍及 180 天');
|
||||
assert(res3.maker_peer_rate === 0.10, '模拟场景 3: 未设置项 (maker_peer_rate) 自动平滑回退至全局默认值 (0.10)');
|
||||
|
||||
// 模拟场景 4: 多规格分别设置独立分成 (多 SKU)
|
||||
const multiSkuGoods = {
|
||||
goods_id: 204,
|
||||
status: 1,
|
||||
self_rules: 1,
|
||||
commission_rules: JSON.stringify({
|
||||
1001: { // 500ml 规格
|
||||
push3_amounts: [200, 200, 300],
|
||||
venue_split: 30,
|
||||
voucher_times: 1.2
|
||||
},
|
||||
1002: { // 1000ml 礼盒装规格
|
||||
push3_amounts: [600, 600, 1000],
|
||||
venue_split: 120,
|
||||
voucher_times: 2.5
|
||||
}
|
||||
})
|
||||
};
|
||||
const resSku1001 = mockGetEffectiveRules(multiSkuGoods, 1001);
|
||||
const resSku1002 = mockGetEffectiveRules(multiSkuGoods, 1002);
|
||||
assert(resSku1001.push3_amounts[0] === 200 && resSku1001.venue_split === 30 && resSku1001.voucher_times === 1.2, '模拟场景 4: 规格 1001 准确读取小规格独立规则 (推三返一:200, 酒道馆:30, 券倍数:1.2)');
|
||||
assert(resSku1002.push3_amounts[0] === 600 && resSku1002.venue_split === 120 && resSku1002.voucher_times === 2.5, '模拟场景 4: 规格 1002 准确读取大礼盒独立规则 (推三返一:600, 酒道馆:120, 券倍数:2.5)');
|
||||
|
||||
// 模拟场景 5: 订单级规则提取优先级
|
||||
function mockGetOrderEffectiveRules(order, goodsMap) {
|
||||
for (const item of (order.items || [])) {
|
||||
if (item.ext && item.ext.is_commission === false) continue;
|
||||
const g = goodsMap[item.goods_id];
|
||||
const r = mockGetEffectiveRules(g, item.goods_sku_price_id);
|
||||
if (r.is_custom) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return JSON.parse(JSON.stringify(mockSystemDefaults));
|
||||
}
|
||||
|
||||
const mixedOrder = {
|
||||
id: 9901,
|
||||
items: [
|
||||
{ goods_id: 201, goods_sku_price_id: 0, pay_fee: 300, ext: { is_commission: false } }, // 不计奖
|
||||
{ goods_id: 202, goods_sku_price_id: 0, pay_fee: 500, ext: { is_commission: true } }, // 默认规则
|
||||
{ goods_id: 204, goods_sku_price_id: 1002, pay_fee: 1000, ext: { is_commission: true } } // 规格独立规则
|
||||
]
|
||||
};
|
||||
const orderEffective = mockGetOrderEffectiveRules(mixedOrder, {
|
||||
201: uncommissionedGoods,
|
||||
202: defaultGoods,
|
||||
204: multiSkuGoods
|
||||
});
|
||||
assert(orderEffective.is_custom === true, '模拟场景 5: 混合订单成功命中并优先提取独立规则商品配置');
|
||||
assert(orderEffective.push3_amounts[0] === 600 && orderEffective.voucher_times === 2.5, '模拟场景 5: 订单实际结算采用独立规则 (push3:600, 券倍数:2.5)');
|
||||
|
||||
// 模拟场景 6: 奖项结算金额计算断言
|
||||
// (1) 推三返一:第 1 单、第 2 单、第 3 单按商品定制发放
|
||||
const cycleRound = [1, 2, 3];
|
||||
const push3Pay = cycleRound.map((round) => {
|
||||
const idx = (round - 1) % 3;
|
||||
return orderEffective.push3_amounts[idx];
|
||||
});
|
||||
assert(push3Pay[0] === 600 && push3Pay[1] === 600 && push3Pay[2] === 1000, '模拟场景 6.1: 推三返一按定制金额循环 (600 -> 600 -> 1000)');
|
||||
|
||||
// (2) 消费补贴券计算
|
||||
const qualifiedPayFee = 1000; // 假设计奖实付 1000 元
|
||||
const voucherAmount = qualifiedPayFee * orderEffective.voucher_times;
|
||||
assert(voucherAmount === 2500, `模拟场景 6.2: 消费券根据定制 2.5 倍计算实发 2500 券(默认1.5倍为1500)`);
|
||||
|
||||
// ----------------------------------------------------
|
||||
// 测试汇总输出
|
||||
// ----------------------------------------------------
|
||||
console.log('\n--------------------------------------------------------');
|
||||
console.log(`测试执行结果: 通过 ${passCount} 项, 失败 ${failCount} 项`);
|
||||
console.log('========================================================\n');
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -56,6 +56,15 @@ if (fs.existsSync(voucherServicePath)) {
|
||||
assert(content.includes('$remainVoucher'), 'refundConsume 严格限制退回上限不超过剩余未退额');
|
||||
}
|
||||
|
||||
// 2.2 PayOper.php 同步核销与防双花验证
|
||||
const payOperPath = path.resolve(__dirname, '../addons/shopro/service/pay/PayOper.php');
|
||||
assert(fs.existsSync(payOperPath), 'PayOper.php 文件存在');
|
||||
if (fs.existsSync(payOperPath)) {
|
||||
const content = fs.readFileSync(payOperPath, 'utf-8');
|
||||
assert(content.includes('consumeByOrder($order)'), 'PayOper.php checkAndPaid 包含兑换券即时同步核销调用');
|
||||
assert(content.includes('voucher_consumed'), 'PayOper.php 具备 voucher_consumed 幂等检查');
|
||||
}
|
||||
|
||||
// 3. confirm.vue 验证
|
||||
const confirmVuePath = path.resolve(__dirname, '../uniapp/pages/order/confirm.vue');
|
||||
assert(fs.existsSync(confirmVuePath), 'confirm.vue 文件存在');
|
||||
@@ -83,7 +92,7 @@ if (fs.existsSync(scoreShopVuePath)) {
|
||||
assert(content.includes('userVoucherAvailable'), '积分商城首页引入可用兑换券计算属性');
|
||||
assert(content.includes('当前积分'), '展示当前积分余额看板');
|
||||
assert(content.includes('可用兑换券'), '展示可用兑换券余额看板');
|
||||
assert(content.includes('💡 兑换商品时支持使用兑换券 1:1 抵扣现金部分'), '包含兑换券抵扣说明提示');
|
||||
assert(content.includes('全额抵扣现金'), '包含兑换券全额抵扣说明提示');
|
||||
assert(content.includes('/pages/commission/voucher'), '提供快捷跳转兑换券明细入口');
|
||||
}
|
||||
|
||||
@@ -93,7 +102,30 @@ assert(fs.existsSync(scoreDetailVuePath), 'score.vue 文件存在');
|
||||
if (fs.existsSync(scoreDetailVuePath)) {
|
||||
const content = fs.readFileSync(scoreDetailVuePath, 'utf-8');
|
||||
assert(content.includes('voucher-tip-tag'), '积分商品详情包含兑换券抵扣标签组件');
|
||||
assert(content.includes('支持兑换券抵扣'), '积分商品详情标明支持兑换券抵扣现金');
|
||||
assert(content.includes('支持兑换券全额抵扣'), '积分商品详情标明支持兑换券全额抵扣');
|
||||
}
|
||||
|
||||
// 6.1 后台页面及模型验证
|
||||
const scoreSkuPricePath = path.resolve(__dirname, '../application/admin/model/shopro/app/ScoreSkuPrice.php');
|
||||
assert(fs.existsSync(scoreSkuPricePath), 'ScoreSkuPrice.php 文件存在');
|
||||
if (fs.existsSync(scoreSkuPricePath)) {
|
||||
const content = fs.readFileSync(scoreSkuPricePath, 'utf-8');
|
||||
assert(content.includes('兑换券'), 'ScoreSkuPrice 具备兑换券输出属性');
|
||||
}
|
||||
|
||||
const addHtmlPath = path.resolve(__dirname, '../application/admin/view/shopro/app/score_shop/add.html');
|
||||
assert(fs.existsSync(addHtmlPath), 'add.html 文件存在');
|
||||
if (fs.existsSync(addHtmlPath)) {
|
||||
const content = fs.readFileSync(addHtmlPath, 'utf-8');
|
||||
assert(content.includes('所需兑换券(元)'), 'add.html 包含所需兑换券表头');
|
||||
assert(content.includes('所需积分'), 'add.html 包含所需积分表头');
|
||||
}
|
||||
|
||||
const indexHtmlPath = path.resolve(__dirname, '../application/admin/view/shopro/app/score_shop/index.html');
|
||||
assert(fs.existsSync(indexHtmlPath), 'index.html 文件存在');
|
||||
if (fs.existsSync(indexHtmlPath)) {
|
||||
const content = fs.readFileSync(indexHtmlPath, 'utf-8');
|
||||
assert(content.includes('兑换门槛 (兑换券/积分)'), 'index.html 包含兑换门槛表头');
|
||||
}
|
||||
|
||||
// 7. 结算抵扣算法模拟
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 团队作战中心全功能补齐与手机号脱敏专项验证测试
|
||||
* 覆盖范围:
|
||||
* 1. 服务端:Agent.php 手机号脱敏与收益业绩统计
|
||||
* 2. 服务端:Index.php 收益明细 buyer_id 穿透过滤与 buyer_info
|
||||
* 3. 移动端:team.vue 专属邀请码、团队收益看板、卡片脱敏、一键拨打/复制/查收益
|
||||
* 4. 移动端:rewards.vue 穿透筛选提示、参数捕获与 API 传参
|
||||
* 5. 移动端:index.vue 收益中心首页团队快捷入口看板
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function assert(condition, testName, detail = '') {
|
||||
if (condition) {
|
||||
console.log(` \x1b[32m[PASS]\x1b[0m ${testName}`);
|
||||
passCount++;
|
||||
} else {
|
||||
console.error(` \x1b[31m[FAIL]\x1b[0m ${testName} - ${detail}`);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('========================================================');
|
||||
console.log(' 团队作战中心全功能闭环与手机号脱敏专项验证测试');
|
||||
console.log('========================================================\n');
|
||||
|
||||
// 1. 服务端 Agent.php 校验
|
||||
const agentPhpPath = path.resolve(__dirname, '../addons/shopro/controller/commission/Agent.php');
|
||||
assert(fs.existsSync(agentPhpPath), 'Agent.php 控制器存在');
|
||||
if (fs.existsSync(agentPhpPath)) {
|
||||
const content = fs.readFileSync(agentPhpPath, 'utf-8');
|
||||
assert(content.includes("use app\\admin\\model\\shopro\\partner\\Reward as RewardModel;"), 'Agent.php 引入了 RewardModel');
|
||||
assert(content.includes('$user->mobile = $maskedMobile;'), 'Agent.php 覆写了 $user->mobile 为脱敏号码');
|
||||
assert(content.includes('$user->mobile_hide = $maskedMobile;'), 'Agent.php 提供了 $user->mobile_hide 字段');
|
||||
assert(content.includes('$user->mobile_raw = $rawMobile;'), 'Agent.php 保留了 $user->mobile_raw 供客户端外呼');
|
||||
assert(content.includes('$user->parentUser->mobile = $pMasked;'), 'Agent.php 对所属间推推荐人手机号执行了脱敏');
|
||||
assert(content.includes("'team_total_consume'"), 'getTeamHealthSummary 返回团队自购总业绩');
|
||||
assert(content.includes("'team_total_reward'"), 'getTeamHealthSummary 返回团队贡献总收益');
|
||||
assert(content.includes("'team_month_reward'"), 'getTeamHealthSummary 返回本月团队贡献收益');
|
||||
assert(content.includes("'team_order_count'"), 'getTeamHealthSummary 返回团队总订单数');
|
||||
}
|
||||
|
||||
// 2. 服务端 partner/Index.php 校验
|
||||
const partnerIndexPhpPath = path.resolve(__dirname, '../addons/shopro/controller/partner/Index.php');
|
||||
assert(fs.existsSync(partnerIndexPhpPath), 'partner/Index.php 控制器存在');
|
||||
if (fs.existsSync(partnerIndexPhpPath)) {
|
||||
const content = fs.readFileSync(partnerIndexPhpPath, 'utf-8');
|
||||
assert(content.includes("intval($this->request->param('buyer_id', 0))"), 'rewards 接口接收 buyer_id 参数');
|
||||
assert(content.includes("$query->where('buyer_id', $buyerId);"), 'rewards 接口支持按 buyer_id 过滤明细');
|
||||
assert(content.includes("'buyer_info' => $buyerInfo"), 'rewards 接口返回穿透成员信息 buyer_info');
|
||||
}
|
||||
|
||||
// 3. 移动端 team.vue 校验
|
||||
const teamVuePath = path.resolve(__dirname, '../uniapp/pages/commission/team.vue');
|
||||
assert(fs.existsSync(teamVuePath), 'team.vue 页面文件存在');
|
||||
if (fs.existsSync(teamVuePath)) {
|
||||
const content = fs.readFileSync(teamVuePath, 'utf-8');
|
||||
// 专属邀请码
|
||||
assert(content.includes('专属邀请码:'), 'team.vue 包含专属邀请码标签');
|
||||
assert(content.includes('HL{{ userInfo.id ||'), 'team.vue 包含 HL + 用户ID 的邀请码渲染');
|
||||
assert(content.includes('onCopyInviteCode'), 'team.vue 包含 onCopyInviteCode 复制方法');
|
||||
assert(content.includes('onCopyShareLink'), 'team.vue 包含 onCopyShareLink 复制链接方法');
|
||||
// 团队收益看板
|
||||
assert(content.includes('team-wealth-box'), 'team.vue 包含 team-wealth-box 团队收益看板');
|
||||
assert(content.includes('state.summary.team_total_reward'), 'team.vue 绑定团队贡献总收益');
|
||||
assert(content.includes('state.summary.team_total_consume'), 'team.vue 绑定团队自购总业绩');
|
||||
assert(content.includes('state.summary.team_month_reward'), 'team.vue 绑定本月贡献收益');
|
||||
assert(content.includes('state.summary.team_order_count'), 'team.vue 绑定团队订单数');
|
||||
// 手机号脱敏展示与操作
|
||||
assert(content.includes('item.mobile_hide'), 'team.vue 列表卡片展示脱敏手机号');
|
||||
assert(content.includes('state.currentMember.mobile_hide'), 'team.vue 抽屉详情展示脱敏手机号');
|
||||
assert(content.includes('btn-reward'), 'team.vue 列表卡片提供直达收益流水按钮');
|
||||
assert(content.includes('onJumpRewards(item.id, item.nickname)'), 'team.vue 卡片支持一键穿透查收益流水');
|
||||
assert(content.includes('onJumpRewards(state.currentMember.id, state.currentMember.nickname)'), 'team.vue 抽屉支持一键穿透查收益流水');
|
||||
}
|
||||
|
||||
// 4. 移动端 rewards.vue 校验
|
||||
const rewardsVuePath = path.resolve(__dirname, '../uniapp/pages/commission/rewards.vue');
|
||||
assert(fs.existsSync(rewardsVuePath), 'rewards.vue 页面文件存在');
|
||||
if (fs.existsSync(rewardsVuePath)) {
|
||||
const content = fs.readFileSync(rewardsVuePath, 'utf-8');
|
||||
assert(content.includes('buyer-filter-bar'), 'rewards.vue 包含 buyer-filter-bar 穿透筛选提示条');
|
||||
assert(content.includes('onClearBuyerFilter'), 'rewards.vue 包含 onClearBuyerFilter 清除筛选方法');
|
||||
assert(content.includes('options?.buyer_id'), 'rewards.vue onLoad 中捕获 buyer_id 参数');
|
||||
assert(content.includes('params.buyer_id = state.buyerId;'), 'rewards.vue 请求 API 时传递 buyer_id');
|
||||
}
|
||||
|
||||
// 5. 移动端 index.vue 校验
|
||||
const indexVuePath = path.resolve(__dirname, '../uniapp/pages/commission/index.vue');
|
||||
assert(fs.existsSync(indexVuePath), 'index.vue 页面文件存在');
|
||||
if (fs.existsSync(indexVuePath)) {
|
||||
const content = fs.readFileSync(indexVuePath, 'utf-8');
|
||||
assert(content.includes('team-quick-card'), 'index.vue 包含 team-quick-card 快捷看板');
|
||||
assert(content.includes('团队作战中心'), 'index.vue 明确展示团队作战中心标题');
|
||||
assert(content.includes('/pages/commission/team'), 'index.vue 点击直达 /pages/commission/team');
|
||||
}
|
||||
|
||||
// 6. 算法测试:脱敏与邀请码逻辑
|
||||
const testMobile = "13912345678";
|
||||
const maskedMobile = testMobile.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2');
|
||||
assert(maskedMobile === "139****5678", `手机号脱敏算法验证正常: ${maskedMobile}`);
|
||||
|
||||
const testUserId = 2048;
|
||||
const inviteCode = `HL${testUserId}`;
|
||||
assert(inviteCode === "HL2048", `邀请码生成格式符合规范: ${inviteCode}`);
|
||||
|
||||
console.log('\n--------------------------------------------------------');
|
||||
console.log(`测试结果统计: 通过 ${passCount} 项, 失败 ${failCount} 项`);
|
||||
console.log('========================================================');
|
||||
|
||||
process.exit(failCount === 0 ? 0 : 1);
|
||||
@@ -0,0 +1,237 @@
|
||||
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. 后端服务层: MiniProgram.php
|
||||
const miniProgramPhpPath = path.resolve(__dirname, '../addons/shopro/service/third/wechat/MiniProgram.php');
|
||||
assert(fs.existsSync(miniProgramPhpPath), 'MiniProgram.php 文件存在');
|
||||
const miniProgramPhpContent = fs.readFileSync(miniProgramPhpPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
miniProgramPhpContent.includes('wxa/business/getuserphonenumber') && miniProgramPhpContent.includes("['code']"),
|
||||
'MiniProgram.php 支持微信新版 code 获取手机号接口',
|
||||
'未包含 wxa/business/getuserphonenumber 逻辑'
|
||||
);
|
||||
|
||||
assert(
|
||||
miniProgramPhpContent.includes('encryptor->decryptData') && miniProgramPhpContent.includes('purePhoneNumber'),
|
||||
'MiniProgram.php 保留传统 iv + encryptedData 解密降级兼容',
|
||||
'未包含 decryptData 解密兼容'
|
||||
);
|
||||
|
||||
assert(
|
||||
miniProgramPhpContent.includes('$mobile = $this->getUserPhoneNumber()') && miniProgramPhpContent.includes("'mobile' => $mobile"),
|
||||
'MiniProgram.php 在 login() 方法中当传入手机号凭证时自动提取 mobile',
|
||||
'login() 中未提取手机号'
|
||||
);
|
||||
|
||||
// 2. 后端服务层: Wechat.php
|
||||
const wechatServicePath = path.resolve(__dirname, '../addons/shopro/service/third/wechat/Wechat.php');
|
||||
assert(fs.existsSync(wechatServicePath), 'Wechat.php (Service) 文件存在');
|
||||
const wechatServiceContent = fs.readFileSync(wechatServicePath, 'utf-8');
|
||||
|
||||
assert(
|
||||
wechatServiceContent.includes("UserModel::where('mobile', $extend['mobile'])->find()"),
|
||||
'Wechat.php 在 registerOrBindUser 中检查已有手机号并直接绑定关联',
|
||||
'未检测到已有手机号用户直接关联逻辑'
|
||||
);
|
||||
|
||||
assert(
|
||||
wechatServiceContent.includes('$user->mobile = $extend['),
|
||||
'Wechat.php 为已绑定微信但尚无手机号的用户自动补充手机号',
|
||||
'未检测到已绑定账号补充手机号逻辑'
|
||||
);
|
||||
|
||||
// 3. 后端控制器层: Wechat.php
|
||||
const wechatControllerPath = path.resolve(__dirname, '../addons/shopro/controller/third/Wechat.php');
|
||||
assert(fs.existsSync(wechatControllerPath), 'Wechat.php (Controller) 文件存在');
|
||||
const wechatControllerContent = fs.readFileSync(wechatControllerPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
wechatControllerContent.includes("'token' => $auth->isLogin() ? $auth->getToken() : ''"),
|
||||
'Wechat.php (Controller) 在 login 成功响应数据中返回 token',
|
||||
'响应未返回 token'
|
||||
);
|
||||
|
||||
// 4. 前端平台驱动层: miniProgram.js
|
||||
const miniProgramJsPath = path.resolve(__dirname, '../uniapp/sheep/platform/provider/wechat/miniProgram.js');
|
||||
assert(fs.existsSync(miniProgramJsPath), 'miniProgram.js 文件存在');
|
||||
const miniProgramJsContent = fs.readFileSync(miniProgramJsPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
miniProgramJsContent.includes('const mobileLogin = async (e) => {') && miniProgramJsContent.includes('third.wechat.login'),
|
||||
'miniProgram.js 包含封装完善的 mobileLogin 方法',
|
||||
'未包含 mobileLogin 方法'
|
||||
);
|
||||
|
||||
assert(
|
||||
miniProgramJsContent.includes('mobileLogin,') && miniProgramJsContent.includes('export default {'),
|
||||
'miniProgram.js 在 export default 中导出了 mobileLogin',
|
||||
'未导出 mobileLogin'
|
||||
);
|
||||
|
||||
// 5. 前端弹窗状态控制: useModal.js
|
||||
const useModalPath = path.resolve(__dirname, '../uniapp/sheep/hooks/useModal.js');
|
||||
assert(fs.existsSync(useModalPath), 'useModal.js 文件存在');
|
||||
const useModalContent = fs.readFileSync(useModalPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
useModalContent.includes('MP-WEIXIN') && useModalContent.includes("targetType = 'mpMobileLogin'"),
|
||||
'useModal.js 微信小程序环境下默认弹窗类型为 mpMobileLogin',
|
||||
'未设置微信小程序默认弹窗类型为 mpMobileLogin'
|
||||
);
|
||||
|
||||
// 6. 新增组件: mp-mobile-login.vue
|
||||
const mpMobileLoginPath = path.resolve(__dirname, '../uniapp/sheep/components/s-auth-modal/components/mp-mobile-login.vue');
|
||||
assert(fs.existsSync(mpMobileLoginPath), 'mp-mobile-login.vue 文件存在');
|
||||
const mpMobileLoginContent = fs.readFileSync(mpMobileLoginPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
mpMobileLoginContent.includes('open-type="getPhoneNumber"') && mpMobileLoginContent.includes('@getphonenumber="onGetPhoneNumber"'),
|
||||
'mp-mobile-login.vue 包含 open-type="getPhoneNumber" 授权按钮',
|
||||
'缺少 getPhoneNumber 授权按钮'
|
||||
);
|
||||
|
||||
assert(
|
||||
mpMobileLoginContent.includes("showAuthModal('smsLogin')") && mpMobileLoginContent.includes("showAuthModal('accountLogin')"),
|
||||
'mp-mobile-login.vue 提供切换短信登录和账号登录的快速入口',
|
||||
'缺少其他登录方式入口'
|
||||
);
|
||||
|
||||
assert(
|
||||
mpMobileLoginContent.includes('agreeStatus') && mpMobileLoginContent.includes('mobileLogin'),
|
||||
'mp-mobile-login.vue 校验用户协议并调用 mobileLogin 登录',
|
||||
'缺少协议校验或登录调用'
|
||||
);
|
||||
|
||||
// 7. 弹窗容器: s-auth-modal.vue
|
||||
const sAuthModalPath = path.resolve(__dirname, '../uniapp/sheep/components/s-auth-modal/s-auth-modal.vue');
|
||||
assert(fs.existsSync(sAuthModalPath), 's-auth-modal.vue 文件存在');
|
||||
const sAuthModalContent = fs.readFileSync(sAuthModalPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
sAuthModalContent.includes("import mpMobileLogin from './components/mp-mobile-login.vue'") && sAuthModalContent.includes("authType === 'mpMobileLogin'"),
|
||||
's-auth-modal.vue 成功挂载 mp-mobile-login 组件',
|
||||
'未挂载 mp-mobile-login'
|
||||
);
|
||||
|
||||
assert(
|
||||
sAuthModalContent.includes("'mpMobileLogin'") && sAuthModalContent.includes('agreement-box'),
|
||||
's-auth-modal.vue 协议栏支持 mpMobileLogin 显示与勾选状态关联',
|
||||
'协议栏未支持 mpMobileLogin'
|
||||
);
|
||||
|
||||
// 8. 个人中心卡片组件: s-user-card.vue
|
||||
const sUserCardPath = path.resolve(__dirname, '../uniapp/sheep/components/s-user-card/s-user-card.vue');
|
||||
assert(fs.existsSync(sUserCardPath), 's-user-card.vue 文件存在');
|
||||
const sUserCardContent = fs.readFileSync(sUserCardPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
sUserCardContent.includes('#ifdef MP-WEIXIN') && sUserCardContent.includes('open-type="getPhoneNumber"') && sUserCardContent.includes('@getphonenumber="onQuickLogin"'),
|
||||
's-user-card.vue 在微信小程序端未登录时渲染 open-type="getPhoneNumber" 快捷登录按钮',
|
||||
'未检测到微信小程序快捷登录按钮'
|
||||
);
|
||||
|
||||
assert(
|
||||
sUserCardContent.includes('async function onQuickLogin(e)') && sUserCardContent.includes("mobileLogin(e.detail)"),
|
||||
's-user-card.vue 包含 onQuickLogin 处理函数调用 mobileLogin 完成快捷登录',
|
||||
'未检测到 onQuickLogin 处理逻辑'
|
||||
);
|
||||
|
||||
assert(
|
||||
sUserCardContent.includes('hasBoundMobile') && sUserCardContent.includes('userInfo.value?.mobile || userInfo.value?.verification?.mobile'),
|
||||
's-user-card.vue 使用 hasBoundMobile 判定:只要存在手机号则不展示绑定提示条',
|
||||
'缺少 hasBoundMobile 判定逻辑'
|
||||
);
|
||||
|
||||
assert(
|
||||
sUserCardContent.includes('onBindPhoneNumber') && sUserCardContent.includes('bindUserPhoneNumber(e.detail)'),
|
||||
's-user-card.vue 在微信小程序端去绑定支持 open-type="getPhoneNumber" 一键绑定',
|
||||
'缺少一键去绑定逻辑'
|
||||
);
|
||||
|
||||
assert(
|
||||
sUserCardContent.includes('#ifndef MP-WEIXIN') && sUserCardContent.includes('showAuthModal()'),
|
||||
's-user-card.vue 保留非微信小程序端正常点击弹窗唤醒逻辑',
|
||||
'未保留非微信小程序端唤醒逻辑'
|
||||
);
|
||||
|
||||
// 9. 用户模型与用户仓库:User.php & user.js
|
||||
const userModelPath = path.resolve(__dirname, '../application/admin/model/shopro/user/User.php');
|
||||
assert(fs.existsSync(userModelPath), 'User.php 模型存在');
|
||||
const userModelContent = fs.readFileSync(userModelPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
userModelContent.includes("if (!empty($data['mobile']))") && userModelContent.includes("$value['mobile'] = 1"),
|
||||
'User.php 模型 getVerificationAttr 当会员已有手机号时自动认定 mobile = 1',
|
||||
'缺少 mobile 存在时自动认定 mobile = 1 逻辑'
|
||||
);
|
||||
|
||||
assert(
|
||||
userModelContent.includes('function setVerificationAttr'),
|
||||
'User.php 模型定义了 setVerificationAttr 支持对象/数组自动 json 序列化',
|
||||
'缺少 setVerificationAttr 方法'
|
||||
);
|
||||
|
||||
const userStorePath = path.resolve(__dirname, '../uniapp/sheep/store/user.js');
|
||||
assert(fs.existsSync(userStorePath), 'user.js Store 存在');
|
||||
const userStoreContent = fs.readFileSync(userStorePath, 'utf-8');
|
||||
|
||||
assert(
|
||||
userStoreContent.includes('if (merged.mobile)') && userStoreContent.includes('mobile: 1'),
|
||||
'user.js getInfo() 当存在 mobile 时自动认定 verification.mobile = 1',
|
||||
'缺少 getInfo() 补充 verification.mobile 逻辑'
|
||||
);
|
||||
|
||||
// 10. 售后申请页编译修复: apply.vue
|
||||
const applyVuePath = path.resolve(__dirname, '../uniapp/pages/order/aftersale/apply.vue');
|
||||
assert(fs.existsSync(applyVuePath), 'apply.vue 存在');
|
||||
const applyVueContent = fs.readFileSync(applyVuePath, 'utf-8');
|
||||
|
||||
assert(
|
||||
applyVueContent.includes(':modelValue="formData"') && !applyVueContent.includes('v-model="formData"'),
|
||||
'apply.vue 使用 :modelValue="formData" 消除 const 重赋值编译错误',
|
||||
'仍包含 v-model="formData"'
|
||||
);
|
||||
|
||||
assert(
|
||||
applyVueContent.includes('let formData = reactive({'),
|
||||
'apply.vue 中 formData 使用 let 声明提供双重防护',
|
||||
'formData 不是 let 声明'
|
||||
);
|
||||
|
||||
// 11. bindUserPhoneNumber 弹性支持与自动更新
|
||||
assert(
|
||||
miniProgramJsContent.includes('bindUserPhoneNumber') && miniProgramJsContent.includes("sheep.$store('user').getInfo()"),
|
||||
'miniProgram.js bindUserPhoneNumber 成功后自动调用 getInfo() 刷新用户信息',
|
||||
'bindUserPhoneNumber 未自动刷新用户信息'
|
||||
);
|
||||
|
||||
// 12. 个人中心绑定横条受 showBindMobile 精确控制(微信小程序环境不冗余展示)
|
||||
assert(
|
||||
sUserCardContent.includes('v-if="showBindMobile"') && sUserCardContent.includes('#ifdef MP-WEIXIN'),
|
||||
's-user-card.vue 使用 showBindMobile 精确控制,微信小程序端不显示冗余绑定提示条',
|
||||
'未配置 showBindMobile 精确控制'
|
||||
);
|
||||
|
||||
console.log("\n--------------------------------------------------------");
|
||||
console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`);
|
||||
console.log("========================================================");
|
||||
|
||||
process.exit(failed === 0 ? 0 : 1);
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Shopro",
|
||||
"name": "恒链数通",
|
||||
"appid": "__UNI__082C0BA",
|
||||
"description": "Shopro是由SheepJS团队开发,使用Uniapp+Vue3技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。",
|
||||
"versionName": "3.2.0",
|
||||
|
||||
465
uniapp/package-lock.json
自动生成的
普通文件
465
uniapp/package-lock.json
自动生成的
普通文件
@@ -0,0 +1,465 @@
|
||||
{
|
||||
"name": "shopro",
|
||||
"version": "3.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "shopro",
|
||||
"version": "3.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hyoga/uni-socket.io": "^1.0.1",
|
||||
"dayjs": "^1.11.9",
|
||||
"lodash": "^4.17.21",
|
||||
"luch-request": "^3.1.0",
|
||||
"pinia": "2.1.4",
|
||||
"pinia-plugin-persist-uni": "1.2.0",
|
||||
"qs-canvas": "^1.0.11",
|
||||
"weixin-js-sdk": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.0.0",
|
||||
"vconsole": "^3.15.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dcloudio/types": {
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmmirror.com/@dcloudio/types/-/types-2.6.12.tgz",
|
||||
"integrity": "sha512-mrCMwcINy1IFjU9VUqLeWBkj404yWs5paLDttBcA+eqUjanuUQbBcTVPqlrGgkyzLXDcV2oDDZRSNxNpXi4kMQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@hyoga/uni-socket.io": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@hyoga/uni-socket.io/-/uni-socket.io-1.0.1.tgz",
|
||||
"integrity": "sha512-+a3PvweyFHx39IJfJWoO9wYsvv50XCIslJ23OiOs8/jMdCZud7c4uWoZF5gH2iskcX72JcDm1Gk5g5o8X7dxEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/after": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmmirror.com/after/-/after-0.8.2.tgz",
|
||||
"integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/arraybuffer.slice": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
|
||||
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/backo2": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/backo2/-/backo2-1.0.2.tgz",
|
||||
"integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
|
||||
"integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/blob": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/blob/-/blob-0.0.5.tgz",
|
||||
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/component-bind": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/component-bind/-/component-bind-1.0.0.tgz",
|
||||
"integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw=="
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz",
|
||||
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/component-inherit": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/component-inherit/-/component-inherit-0.0.3.tgz",
|
||||
"integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA=="
|
||||
},
|
||||
"node_modules/copy-text-to-clipboard": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz",
|
||||
"integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.50.0",
|
||||
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.50.0.tgz",
|
||||
"integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.23",
|
||||
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.23.tgz",
|
||||
"integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "3.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/engine.io-client/-/engine.io-client-3.5.6.tgz",
|
||||
"integrity": "sha512-2fDMKiXSU7bGRDCWEw9cHEdRNfoU8cpP6lt+nwJhv72tSJpO7YBsqMqYZ63eVvwX3l9prPl2k/mxhfVhY+SDWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"component-emitter": "~1.3.0",
|
||||
"component-inherit": "0.0.3",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"ws": "~7.5.10",
|
||||
"xmlhttprequest-ssl": "~1.6.2",
|
||||
"yeast": "0.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
|
||||
"integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"after": "0.8.2",
|
||||
"arraybuffer.slice": "~0.0.7",
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
"blob": "0.0.5",
|
||||
"has-binary2": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/has-binary2": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/has-binary2/-/has-binary2-1.0.3.tgz",
|
||||
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"isarray": "2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/has-cors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-cors/-/has-cors-1.1.0.tgz",
|
||||
"integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/indexof": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/indexof/-/indexof-0.0.1.tgz",
|
||||
"integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/luch-request": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/luch-request/-/luch-request-3.1.1.tgz",
|
||||
"integrity": "sha512-p7+mlcEtgRcd0OfXC4XZbyiwSr1XgCeqNT7LlVUjnk7InYl/8d5Rk7BUqAYNA2WRafI1wRIUQWRWZRpeUwWR0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dcloudio/types": "^2.0.16"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mutation-observer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz",
|
||||
"integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/parseqs": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/parseqs/-/parseqs-0.0.6.tgz",
|
||||
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parseuri": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/parseuri/-/parseuri-0.0.6.tgz",
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.4.tgz",
|
||||
"integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0",
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.4.0",
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.6.14 || ^3.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia-plugin-persist-uni": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/pinia-plugin-persist-uni/-/pinia-plugin-persist-uni-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZLdeB+nfeu0WD7WEv19E1u2cWbnK/J8fDR5w56JPGoQtbU4Uy8VLKSvfHlbuVzGxBaE1DhG4/ulWVm3oAArpg==",
|
||||
"dependencies": {
|
||||
"vue-demi": "^0.12.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0",
|
||||
"pinia": "^2.0.0",
|
||||
"vue": "^2.0.0 || >=3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia-plugin-persist-uni/node_modules/vue-demi": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz",
|
||||
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.9.7.tgz",
|
||||
"integrity": "sha512-T3mF5P7HFzZVLDQuCUNtJj6WK1pcpLMweMNUVwGb01bLNkH9AkKxtZvmInw8K6bnn2O3d6/5RHl6zSHiBAD0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/qs-canvas": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmmirror.com/qs-canvas/-/qs-canvas-1.0.11.tgz",
|
||||
"integrity": "sha512-WJwLwrAA1++ZqT6eR2F4Qa42yowybDUtQ2TDPtDOwhD1i4DzEOotug5N20cDkHat9l69ThOWoxV4qJbLKAjkNg==",
|
||||
"license": "BSD 3-Clause"
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/socket.io-client/-/socket.io-client-2.5.0.tgz",
|
||||
"integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"backo2": "1.0.2",
|
||||
"component-bind": "1.0.0",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-client": "~3.5.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "~3.3.0",
|
||||
"to-array": "0.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-3.3.6.tgz",
|
||||
"integrity": "sha512-+VwteZF0qtYVkcO1nhKf6ZUVz5wq6Ya+Lx10y3Y81Sp5+iyGGY2GTcd81W36C/r1tjP+GhXjiXaLKlCMyE+yHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"isarray": "2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/to-array": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/to-array/-/to-array-0.1.4.tgz",
|
||||
"integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A=="
|
||||
},
|
||||
"node_modules/vconsole": {
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz",
|
||||
"integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"copy-text-to-clipboard": "^3.0.1",
|
||||
"core-js": "^3.11.0",
|
||||
"mutation-observer": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "2.6.14",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-2.6.14.tgz",
|
||||
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==",
|
||||
"deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/weixin-js-sdk": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmmirror.com/weixin-js-sdk/-/weixin-js-sdk-1.6.5.tgz",
|
||||
"integrity": "sha512-Gph1WAWB2YN/lMOFB/ymb+hbU/wYazzJgu6PMMktCy9cSCeW5wA6Zwt0dpahJbJ+RJEwtTv2x9iIu0U4enuVSQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.13",
|
||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.13.tgz",
|
||||
"integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz",
|
||||
"integrity": "sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yeast": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/yeast/-/yeast-0.1.2.tgz",
|
||||
"integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<s-layout title="积分商城">
|
||||
<s-layout title="兑换商城">
|
||||
<!-- 资产看板:当前积分与可用兑换券 -->
|
||||
<view class="asset-banner ss-m-20 ss-p-20">
|
||||
<view class="ss-flex ss-row-between ss-col-center">
|
||||
@@ -17,7 +17,7 @@
|
||||
</button>
|
||||
</view>
|
||||
<view class="asset-tip ss-m-t-16">
|
||||
💡 兑换商品时支持使用兑换券 1:1 抵扣现金部分
|
||||
💡 兑换商品时支持使用可用兑换券 1:1 全额抵扣现金
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<s-empty
|
||||
v-if="state.pagination.total === 0"
|
||||
icon="/static/goods-empty.png"
|
||||
text="暂无积分商品"
|
||||
text="暂无兑换商品"
|
||||
></s-empty>
|
||||
<uni-load-more
|
||||
v-if="state.pagination.total > 0"
|
||||
|
||||
@@ -12,6 +12,28 @@
|
||||
</view>
|
||||
<!-- 账户信息 -->
|
||||
<account-info />
|
||||
<!-- 团队作战快捷看板 -->
|
||||
<view class="team-quick-card ss-flex ss-row-between ss-col-center" @tap="sheep.$router.go('/pages/commission/team')">
|
||||
<view class="team-quick-left ss-flex ss-col-center">
|
||||
<view class="icon-wrap ss-flex ss-row-center ss-col-center">
|
||||
<uni-icons type="staff" size="24" color="#ff6000"></uni-icons>
|
||||
</view>
|
||||
<view class="team-quick-info ss-m-l-16">
|
||||
<view class="team-title ss-flex ss-col-center">
|
||||
<text class="title-bold">团队作战中心</text>
|
||||
<text class="active-badge ss-m-l-10" v-if="userInfo?.active_30d !== false && agentInfo?.active_30d !== false">正常活跃</text>
|
||||
<text class="warn-badge ss-m-l-10" v-else>奖励暂停</text>
|
||||
</view>
|
||||
<view class="team-sub ss-m-t-6">
|
||||
直推 {{ agentInfo?.child_user_count_1 || 0 }} 人 · 团队共 {{ agentInfo?.child_user_count_all || 0 }} 人
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="team-quick-right ss-flex ss-col-center">
|
||||
<text class="manage-text">去管理</text>
|
||||
<uni-icons type="right" size="14" color="#ff6000"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 菜单栏 -->
|
||||
<commission-menu />
|
||||
<!-- 分销记录 -->
|
||||
@@ -103,4 +125,62 @@
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.team-quick-card {
|
||||
width: 694rpx;
|
||||
margin: 20rpx auto 0;
|
||||
padding: 22rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fffbf0 100%);
|
||||
border-radius: 16rpx;
|
||||
border: 1rpx solid #ffe8c2;
|
||||
box-shadow: 0 4rpx 16rpx rgba(247, 213, 152, 0.25);
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
|
||||
.icon-wrap {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
background: #fff0e6;
|
||||
}
|
||||
|
||||
.team-quick-info {
|
||||
.title-bold {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #692e04;
|
||||
}
|
||||
.active-badge {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
font-weight: 500;
|
||||
}
|
||||
.warn-badge {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
font-weight: 500;
|
||||
}
|
||||
.team-sub {
|
||||
font-size: 22rpx;
|
||||
color: #a17545;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
}
|
||||
|
||||
.team-quick-right {
|
||||
.manage-text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #ff6000;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,6 +12,19 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 穿透查看成员明细提示条 -->
|
||||
<view v-if="state.buyerId > 0" class="buyer-filter-bar ss-flex ss-row-between ss-col-center">
|
||||
<view class="ss-flex ss-col-center">
|
||||
<uni-icons type="person-filled" size="16" color="#ff6000"></uni-icons>
|
||||
<text class="filter-tip-text ss-m-l-10">
|
||||
正在筛选成员:<text class="buyer-highlight">【{{ state.buyerName || ('ID:' + state.buyerId) }}】</text>的贡献
|
||||
</text>
|
||||
</view>
|
||||
<button class="ss-reset-button clear-filter-btn" @tap="onClearBuyerFilter">
|
||||
查看全部
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<su-sticky>
|
||||
<su-tabs
|
||||
:list="tabMaps"
|
||||
@@ -139,6 +152,8 @@
|
||||
loadStatus: '',
|
||||
showDetail: false,
|
||||
current: null,
|
||||
buyerId: 0,
|
||||
buyerName: '',
|
||||
overview: {
|
||||
month_income: '0.00',
|
||||
skip_count: 0,
|
||||
@@ -179,11 +194,15 @@
|
||||
async function getLogList(page = 1, list_rows = 10) {
|
||||
state.loadStatus = 'loading';
|
||||
try {
|
||||
const res = await sheep.$api.partner.rewards({
|
||||
tab: tabMaps[state.currentTab]?.value || 'all',
|
||||
const params = {
|
||||
tab: tabMaps.value[state.currentTab]?.value || 'all',
|
||||
page,
|
||||
list_rows,
|
||||
});
|
||||
};
|
||||
if (state.buyerId > 0) {
|
||||
params.buyer_id = state.buyerId;
|
||||
}
|
||||
const res = await sheep.$api.partner.rewards(params);
|
||||
if (res?.code === 1) {
|
||||
const pageData = pickList(res.data);
|
||||
state.pagination = {
|
||||
@@ -220,7 +239,20 @@
|
||||
state.showDetail = true;
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
function onClearBuyerFilter() {
|
||||
state.buyerId = 0;
|
||||
state.buyerName = '';
|
||||
state.pagination = { ...emptyPagination };
|
||||
getLogList(1);
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options?.buyer_id) {
|
||||
state.buyerId = Number(options.buyer_id) || 0;
|
||||
}
|
||||
if (options?.buyer_name) {
|
||||
state.buyerName = decodeURIComponent(options.buyer_name);
|
||||
}
|
||||
getOverview();
|
||||
getLogList();
|
||||
});
|
||||
@@ -239,6 +271,29 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.buyer-filter-bar {
|
||||
margin: 16rpx 24rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: #fff4e5;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #ffd8a8;
|
||||
.filter-tip-text {
|
||||
font-size: 24rpx;
|
||||
color: #a17545;
|
||||
}
|
||||
.buyer-highlight {
|
||||
font-weight: bold;
|
||||
color: #ff6000;
|
||||
}
|
||||
.clear-filter-btn {
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 18rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #ff6000;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.header-box {
|
||||
background-color: $white;
|
||||
padding: 30rpx;
|
||||
|
||||
@@ -22,6 +22,25 @@
|
||||
<text class="referrer-name">{{ userInfo.parent_user.nickname }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 专属邀请码与拓展卡片 -->
|
||||
<view class="invite-action-card ss-flex ss-row-between ss-col-center">
|
||||
<view class="invite-code-wrap ss-flex ss-col-center">
|
||||
<text class="invite-label">专属邀请码:</text>
|
||||
<text class="invite-code">HL{{ userInfo.id || '---' }}</text>
|
||||
<button class="ss-reset-button copy-code-btn ss-m-l-10" @tap="onCopyInviteCode">复制</button>
|
||||
</view>
|
||||
<view class="invite-btn-group ss-flex ss-col-center">
|
||||
<button class="ss-reset-button invite-btn-poster ss-flex ss-col-center" @tap="onOpenShareModal">
|
||||
<uni-icons type="image" size="14" color="#ffffff"></uni-icons>
|
||||
<text class="ss-m-l-6">邀请海报</text>
|
||||
</button>
|
||||
<button class="ss-reset-button invite-btn-link ss-m-l-12 ss-flex ss-col-center" @tap="onCopyShareLink">
|
||||
<uni-icons type="link" size="14" color="#ff6000"></uni-icons>
|
||||
<text class="ss-m-l-6">复制链接</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 团队核心数据总览卡片 -->
|
||||
<view class="team-data-box ss-flex ss-col-center ss-row-between">
|
||||
<!-- 团队总人数卡片 -->
|
||||
@@ -85,6 +104,20 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 团队收益与业绩统计大盘 -->
|
||||
<view class="team-wealth-box ss-flex ss-col-center ss-row-between">
|
||||
<view class="wealth-card ss-flex-1 ss-m-r-16">
|
||||
<view class="wealth-title">团队贡献总收益(元)</view>
|
||||
<view class="wealth-num text-orange">¥{{ state.summary.team_total_reward || '0.00' }}</view>
|
||||
<view class="wealth-sub">本月贡献: ¥{{ state.summary.team_month_reward || '0.00' }}</view>
|
||||
</view>
|
||||
<view class="wealth-card ss-flex-1">
|
||||
<view class="wealth-title">团队自购总业绩(元)</view>
|
||||
<view class="wealth-num text-gold">¥{{ state.summary.team_total_consume || '0.00' }}</view>
|
||||
<view class="wealth-sub">团队累计订单: {{ state.summary.team_order_count || 0 }} 笔</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 30天自购保活健康度胶囊条 -->
|
||||
<view class="health-bar ss-flex ss-row-between ss-col-center">
|
||||
<view class="health-title">保活健康度:</view>
|
||||
@@ -206,6 +239,10 @@
|
||||
{{ item.team_level === 1 ? '一级直推' : '二级间推' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="member-sub-info ss-flex ss-col-center ss-m-t-4" v-if="item.mobile_hide || item.id">
|
||||
<text class="member-phone" v-if="item.mobile_hide">{{ item.mobile_hide }}</text>
|
||||
<text class="member-id-tag ss-m-l-8">ID: {{ item.id }}</text>
|
||||
</view>
|
||||
<view class="member-meta-row ss-flex ss-col-center ss-m-t-8">
|
||||
<!-- 身份徽章 -->
|
||||
<view v-if="memberLevelName(item)" class="tag-box ss-flex ss-col-center">
|
||||
@@ -310,13 +347,20 @@
|
||||
<text class="ss-m-l-6">拨打</text>
|
||||
</button>
|
||||
<button
|
||||
v-if="item.mobile_raw"
|
||||
v-if="item.mobile_raw || item.mobile_hide"
|
||||
class="ss-reset-button action-btn btn-copy ss-m-l-12"
|
||||
@tap.stop="onCopyText(item.mobile_raw, '手机号已复制')"
|
||||
@tap.stop="onCopyText(item.mobile_raw || item.mobile_hide, '手机号已复制')"
|
||||
>
|
||||
<uni-icons type="paperclip" size="14" color="#666666"></uni-icons>
|
||||
<text class="ss-m-l-6">复制手机</text>
|
||||
</button>
|
||||
<button
|
||||
class="ss-reset-button action-btn btn-reward ss-m-l-12"
|
||||
@tap.stop="onJumpRewards(item.id, item.nickname)"
|
||||
>
|
||||
<uni-icons type="wallet" size="14" color="#ff6000"></uni-icons>
|
||||
<text class="ss-m-l-6">收益</text>
|
||||
</button>
|
||||
<button
|
||||
class="ss-reset-button action-btn btn-detail ss-m-l-12"
|
||||
@tap.stop="onOpenMemberDetail(item)"
|
||||
@@ -387,6 +431,8 @@
|
||||
<view class="drawer-sub ss-flex ss-col-center ss-m-t-8">
|
||||
<text class="user-id">ID: {{ state.currentMember.id }}</text>
|
||||
<text class="dot-split">·</text>
|
||||
<text class="user-phone">手机: {{ state.currentMember.mobile_hide || '未绑定' }}</text>
|
||||
<text class="dot-split">·</text>
|
||||
<text class="join-time">{{ formatTime(state.currentMember.createtime) }} 加入</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -496,16 +542,16 @@
|
||||
<text class="ss-m-l-8">呼叫成员</text>
|
||||
</button>
|
||||
<button
|
||||
v-if="state.currentMember.mobile_raw"
|
||||
v-if="state.currentMember.mobile_raw || state.currentMember.mobile_hide"
|
||||
class="ss-reset-button drawer-btn drawer-btn-sub ss-flex-1 ss-m-r-16"
|
||||
@tap="onCopyText(state.currentMember.mobile_raw, '手机号已复制')"
|
||||
@tap="onCopyText(state.currentMember.mobile_raw || state.currentMember.mobile_hide, '手机号已复制')"
|
||||
>
|
||||
<uni-icons type="paperclip" size="16" color="#ff6000"></uni-icons>
|
||||
<text class="ss-m-l-8">复制电话</text>
|
||||
</button>
|
||||
<button
|
||||
class="ss-reset-button drawer-btn drawer-btn-outline ss-flex-1"
|
||||
@tap="onJumpRewards(state.currentMember.id)"
|
||||
@tap="onJumpRewards(state.currentMember.id, state.currentMember.nickname)"
|
||||
>
|
||||
<uni-icons type="list" size="16" color="#666666"></uni-icons>
|
||||
<text class="ss-m-l-8">查收益流水</text>
|
||||
@@ -577,6 +623,10 @@
|
||||
expiring_count: 0,
|
||||
inactive_count: 0,
|
||||
partner_count: 0,
|
||||
team_total_consume: '0.00',
|
||||
team_total_reward: '0.00',
|
||||
team_month_reward: '0.00',
|
||||
team_order_count: 0,
|
||||
},
|
||||
pagination: {
|
||||
data: [],
|
||||
@@ -728,9 +778,32 @@
|
||||
state.showDetail = true;
|
||||
}
|
||||
|
||||
function onJumpRewards(buyerId) {
|
||||
function onJumpRewards(buyerId, buyerName = '') {
|
||||
state.showDetail = false;
|
||||
sheep.$router.go(`/pages/commission/rewards?buyer_id=${buyerId}`);
|
||||
const nameParam = buyerName ? `&buyer_name=${encodeURIComponent(buyerName)}` : '';
|
||||
sheep.$router.go(`/pages/commission/rewards?buyer_id=${buyerId}${nameParam}`);
|
||||
}
|
||||
|
||||
function onCopyInviteCode() {
|
||||
if (!userInfo.value?.id) {
|
||||
sheep.$helper.toast('请先登录');
|
||||
return;
|
||||
}
|
||||
const code = `HL${userInfo.value.id}`;
|
||||
sheep.$helper.copyText(code, '专属邀请码已复制');
|
||||
}
|
||||
|
||||
function onCopyShareLink() {
|
||||
if (!userInfo.value?.id) {
|
||||
sheep.$helper.toast('请先登录');
|
||||
return;
|
||||
}
|
||||
const shareQuery = sheep.$platform.share.buildSpmQuery({
|
||||
page: '1',
|
||||
query: '0',
|
||||
});
|
||||
const shareUrl = sheep.$platform.share.buildSpmPath(shareQuery);
|
||||
sheep.$helper.copyText(shareUrl, '专属推广链接已复制');
|
||||
}
|
||||
|
||||
function onOpenShareModal() {
|
||||
@@ -825,6 +898,59 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 专属邀请码卡片
|
||||
.invite-action-card {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 16rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.invite-code-wrap {
|
||||
.invite-label {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
.invite-code {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
font-family: OPPOSANS;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
.copy-code-btn {
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
color: #ff6000;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.invite-btn-group {
|
||||
.invite-btn-poster {
|
||||
height: 48rpx;
|
||||
padding: 0 16rpx;
|
||||
border-radius: 24rpx;
|
||||
background: #ff6000;
|
||||
color: #ffffff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.invite-btn-link {
|
||||
height: 48rpx;
|
||||
padding: 0 16rpx;
|
||||
border-radius: 24rpx;
|
||||
background: #ffffff;
|
||||
color: #ff6000;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 团队信息总览
|
||||
.team-data-box {
|
||||
.data-card {
|
||||
@@ -893,6 +1019,41 @@
|
||||
color: #333333;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
// 团队收益与业绩看板
|
||||
.team-wealth-box {
|
||||
margin-top: 20rpx;
|
||||
|
||||
.wealth-card {
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 18rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.wealth-title {
|
||||
font-size: 22rpx;
|
||||
color: #999999;
|
||||
line-height: 28rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
.wealth-num {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
font-family: OPPOSANS;
|
||||
line-height: 38rpx;
|
||||
margin-bottom: 6rpx;
|
||||
|
||||
&.text-orange {
|
||||
color: #ff6000;
|
||||
}
|
||||
&.text-gold {
|
||||
color: #c48a3a;
|
||||
}
|
||||
}
|
||||
.wealth-sub {
|
||||
font-size: 20rpx;
|
||||
color: #888888;
|
||||
line-height: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,6 +1288,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
.member-sub-info {
|
||||
font-size: 20rpx;
|
||||
color: #999999;
|
||||
font-family: OPPOSANS;
|
||||
|
||||
.member-phone {
|
||||
color: #666666;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.member-id-tag {
|
||||
background: #f3f4f6;
|
||||
color: #9ca3af;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 18rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.member-meta-row {
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1240,6 +1419,10 @@
|
||||
background: #f3f4f6;
|
||||
color: #4b5563;
|
||||
}
|
||||
&.btn-reward {
|
||||
background: #fff7ed;
|
||||
color: #ea580c;
|
||||
}
|
||||
&.btn-detail {
|
||||
background: #ffffff;
|
||||
color: #666666;
|
||||
|
||||
@@ -33,13 +33,14 @@
|
||||
<view v-if="goodsPrice.price > 0" class="price-text"> ¥{{ goodsPrice.price }} </view>
|
||||
<text v-if="goodsPrice.price > 0 && goodsPrice.score > 0">+</text>
|
||||
<image
|
||||
v-if="goodsPrice.score > 0"
|
||||
:src="sheep.$url.static('/assets/addons/shopro/uniapp/goods/score1.svg')"
|
||||
class="score-img"
|
||||
></image>
|
||||
<view class="score-text ss-m-r-16">
|
||||
<view v-if="goodsPrice.score > 0" class="score-text ss-m-r-16">
|
||||
{{ goodsPrice.score }}
|
||||
</view>
|
||||
<view v-if="goodsPrice.price > 0" class="voucher-tip-tag">支持兑换券抵扣</view>
|
||||
<view v-if="goodsPrice.price > 0" class="voucher-tip-tag">支持兑换券全额抵扣</view>
|
||||
</view>
|
||||
<view class="sales-text">
|
||||
{{ formatExchange(state.goodsInfo.sales_show_type, state.goodsInfo.sales) }}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
navbar="custom"
|
||||
tabbar="/pages/index/index"
|
||||
:bgStyle="template.style?.background"
|
||||
:navbarStyle="template.style?.navbar"
|
||||
:navbarStyle="navbarStyle"
|
||||
onShareAppMessage
|
||||
:showFloatButton="true"
|
||||
>
|
||||
@@ -30,6 +30,43 @@
|
||||
});
|
||||
const template = computed(() => sheep.$store('app').template?.home);
|
||||
|
||||
// 代码直接接管首页顶部导航栏 Logo,不依赖后台装修配置
|
||||
const navbarStyle = computed(() => {
|
||||
const base = template.value?.style?.navbar || {};
|
||||
return {
|
||||
...base,
|
||||
mode: 'normal',
|
||||
type: 'color',
|
||||
color: '#FFFFFF',
|
||||
list: {
|
||||
mp: [
|
||||
{
|
||||
width: 3,
|
||||
height: 1,
|
||||
top: 0,
|
||||
left: 0,
|
||||
type: 'image',
|
||||
src: '/static/logo.png',
|
||||
url: '/pages/index/index',
|
||||
borderRadius: 0,
|
||||
},
|
||||
],
|
||||
app: [
|
||||
{
|
||||
width: 4,
|
||||
height: 1,
|
||||
top: 0,
|
||||
left: 2,
|
||||
type: 'image',
|
||||
src: '/static/logo.png',
|
||||
url: '/pages/index/index',
|
||||
borderRadius: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
onLoad((options) => {
|
||||
// #ifdef MP
|
||||
// 小程序识别二维码
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:num="state.goodsItem.goods_num"></s-goods-item>
|
||||
</view>
|
||||
|
||||
<uni-forms ref="form" v-model="formData" :rules="rules" label-position="top">
|
||||
<uni-forms ref="form" :modelValue="formData" :rules="rules" label-position="top">
|
||||
<!-- 售后类型 -->
|
||||
<view class="refund-item">
|
||||
<view class="item-title ss-m-b-20">售后类型</view>
|
||||
@@ -135,7 +135,7 @@ const state = reactive({
|
||||
},
|
||||
],
|
||||
});
|
||||
const formData = reactive({
|
||||
let formData = reactive({
|
||||
type: '',
|
||||
reason: '',
|
||||
mobile: '',
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
>
|
||||
<view class="item-title">兑换券</view>
|
||||
<view class="ss-flex ss-col-center" @tap="openVoucher">
|
||||
<text class="item-value text-red" v-if="state.orderPayload.voucher_fee > 0"
|
||||
<text class="item-value text-red" v-if="Number(state.orderInfo.voucher_fee || state.orderPayload.voucher_fee) > 0"
|
||||
>-¥{{ state.orderInfo.voucher_fee || state.orderPayload.voucher_fee }}</text
|
||||
>
|
||||
<text
|
||||
@@ -197,7 +197,7 @@
|
||||
<view class="footer-box border-top ss-flex ss-row-between ss-p-x-20 ss-col-center">
|
||||
<view class="total-box-footer ss-flex ss-col-center">
|
||||
<view class="total-num ss-font-30 text-red"> ¥{{ state.orderInfo.pay_fee }} </view>
|
||||
<view v-if="state.orderPayload.order_type === 'score'" class="ss-flex">
|
||||
<view v-if="state.orderPayload.order_type === 'score' && Number(state.orderInfo.score_amount) > 0" class="ss-flex">
|
||||
<view class="total-num ss-font-30 text-red ss-m-l-4">+</view>
|
||||
<image
|
||||
:src="sheep.$url.static('/assets/addons/shopro/uniapp/goods/score1.svg')"
|
||||
@@ -313,10 +313,22 @@
|
||||
}
|
||||
|
||||
if (exchangeNow.value) {
|
||||
const hasScore = Number(state.orderInfo.score_amount) > 0;
|
||||
const voucherFee = Number(state.orderInfo.voucher_fee || state.orderPayload.voucher_fee || 0);
|
||||
let confirmContent = '确定立即兑换此商品?';
|
||||
if (hasScore && voucherFee > 0) {
|
||||
confirmContent = `确定使用 ${voucherFee} 元兑换券与 ${state.orderInfo.score_amount} 积分立即兑换?`;
|
||||
} else if (voucherFee > 0) {
|
||||
confirmContent = `确定使用 ${voucherFee} 元兑换券立即兑换(现金实付 0 元)?`;
|
||||
} else if (hasScore) {
|
||||
confirmContent = `确定使用 ${state.orderInfo.score_amount} 积分立即兑换?`;
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定使用积分立即兑换?',
|
||||
title: '兑换确认',
|
||||
content: confirmContent,
|
||||
cancelText: '再想想',
|
||||
confirmText: '确定兑换',
|
||||
success: async function (res) {
|
||||
if (res.confirm) {
|
||||
submitOrder();
|
||||
@@ -359,6 +371,10 @@
|
||||
state.orderInfo.goods_list.forEach((item) => {
|
||||
state.totalNumber += item.goods_num;
|
||||
});
|
||||
// 积分/兑换商城订单:若后端自动计算了兑换券抵扣,同步至 orderPayload
|
||||
if (state.orderPayload.order_type === 'score' && state.orderPayload.voucher_fee === undefined && data.voucher_fee !== undefined) {
|
||||
state.orderPayload.voucher_fee = data.voucher_fee;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取可用优惠券
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<!-- 微信小程序手机号一键快捷登录 - mpMobileLogin -->
|
||||
<template>
|
||||
<view class="mp-mobile-login-wrap">
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-50">
|
||||
<view class="ss-flex ss-m-b-16">
|
||||
<view class="head-title head-title-animation">手机号快捷登录</view>
|
||||
</view>
|
||||
<view class="head-subtitle">授权微信手机号,享有一键快捷登录体验</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信手机号一键登录主按钮 -->
|
||||
<view class="btn-box ss-m-b-40">
|
||||
<button
|
||||
class="ss-reset-button wechat-quick-btn ss-flex ss-row-center ss-col-center"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="onGetPhoneNumber"
|
||||
>
|
||||
<text class="cicon-wechat ss-m-r-16 wechat-icon"></text>
|
||||
<text class="btn-text">手机号一键快捷登录</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 备选切换登录方式 -->
|
||||
<view class="switch-box ss-flex ss-row-center ss-col-center">
|
||||
<view class="switch-item" @tap="showAuthModal('smsLogin')">验证码登录</view>
|
||||
<view class="divider"></view>
|
||||
<view class="switch-item" @tap="showAuthModal('accountLogin')">账号密码登录</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
import { showAuthModal, closeAuthModal } from '@/sheep/hooks/useModal';
|
||||
|
||||
const props = defineProps({
|
||||
agreeStatus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['onConfirm']);
|
||||
|
||||
async function onGetPhoneNumber(e) {
|
||||
if (!props.agreeStatus) {
|
||||
emits('onConfirm', true);
|
||||
sheep.$helper.toast('请阅读并勾选协议');
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.detail?.errMsg !== 'getPhoneNumber:ok') {
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: '登录中...',
|
||||
mask: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await sheep.$platform.useProvider('wechat').mobileLogin(e.detail);
|
||||
if (result) {
|
||||
closeAuthModal();
|
||||
await sheep.$store('user').loginAfter();
|
||||
sheep.$helper.toast('登录成功');
|
||||
} else {
|
||||
sheep.$helper.toast('登录失败,请重试');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('mobileLogin error', err);
|
||||
sheep.$helper.toast('登录异常,请稍后重试');
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../index.scss';
|
||||
|
||||
.mp-mobile-login-wrap {
|
||||
padding-top: 10rpx;
|
||||
}
|
||||
|
||||
.btn-box {
|
||||
width: 100%;
|
||||
margin-top: 50rpx;
|
||||
}
|
||||
|
||||
.wechat-quick-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #07c160;
|
||||
border-radius: 44rpx;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 8rpx 16rpx rgba(7, 193, 96, 0.25);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.wechat-icon {
|
||||
font-size: 40rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-box {
|
||||
margin-top: 36rpx;
|
||||
font-size: 26rpx;
|
||||
color: #888888;
|
||||
|
||||
.switch-item {
|
||||
padding: 10rpx 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
&:active {
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 2rpx;
|
||||
height: 24rpx;
|
||||
background-color: #dddddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -8,6 +8,13 @@
|
||||
@click="closeAuthModal"
|
||||
/> -->
|
||||
<view class="login-wrap">
|
||||
<!-- 0. 微信小程序手机号一键快捷登录 mpMobileLogin -->
|
||||
<mp-mobile-login
|
||||
v-if="authType === 'mpMobileLogin'"
|
||||
:agreeStatus="state.protocol"
|
||||
@onConfirm="onConfirm"
|
||||
/>
|
||||
|
||||
<!-- 1. 账号密码登录 accountLogin -->
|
||||
<account-login
|
||||
v-if="authType === 'accountLogin'"
|
||||
@@ -56,7 +63,7 @@
|
||||
>立即注册</button
|
||||
>
|
||||
<view class="or-title">或</view>
|
||||
<button class="ss-reset-button login-btn" @tap="thirdLogin('wechat')">快捷登录</button>
|
||||
<button class="ss-reset-button login-btn" @tap="showAuthModal('mpMobileLogin')">快捷登录</button>
|
||||
<view class="circle"></view>
|
||||
</view>
|
||||
|
||||
@@ -99,7 +106,7 @@
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="['accountLogin', 'smsLogin', 'smsRegister'].includes(authType)"
|
||||
v-if="['accountLogin', 'smsLogin', 'smsRegister', 'mpMobileLogin'].includes(authType)"
|
||||
class="agreement-box ss-flex ss-row-center"
|
||||
:class="{ shake: currentProtocol }"
|
||||
>
|
||||
@@ -136,6 +143,7 @@
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import mpMobileLogin from './components/mp-mobile-login.vue';
|
||||
import accountLogin from './components/account-login.vue';
|
||||
import smsLogin from './components/sms-login.vue';
|
||||
import smsRegister from './components/sms-register.vue';
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
class="menu-icon-wrap ss-flex ss-row-center ss-col-center"
|
||||
@tap="sheep.$router.go(data.url)"
|
||||
>
|
||||
<image class="nav-image" :src="sheep.$url.cdn(data.src)" mode="aspectFit"></image>
|
||||
<image
|
||||
class="nav-image"
|
||||
:src="data.src && data.src.startsWith('/static/') ? data.src : sheep.$url.cdn(data.src)"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<view class="ss-flex-1" v-if="data.type == 'search'" :style="[{ width: width }]">
|
||||
<s-search-block
|
||||
@@ -55,8 +59,11 @@
|
||||
}
|
||||
|
||||
.menu-icon-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.nav-image {
|
||||
height: 24px;
|
||||
height: 30px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,15 +29,22 @@
|
||||
<view class="ss-col-bottom">
|
||||
<view
|
||||
v-if="goodsFields.score_price?.show"
|
||||
class="md-goods-price ss-m-t-16 font-OPPOSANS ss-m-r-10 ss-flex"
|
||||
class="md-goods-price ss-m-t-16 font-OPPOSANS ss-m-r-10 ss-flex ss-col-center"
|
||||
:style="[{ color: goodsFields.score_price.color }]"
|
||||
>
|
||||
<view>{{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}</view>
|
||||
<view v-if="Number(data.price?.[0] || data.price) > 0">
|
||||
¥{{ data.price?.[0] || data.price }}
|
||||
<text v-if="Number(data.score) > 0">+</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="Number(data.score) > 0"
|
||||
:src="sheep.$url.static('/assets/addons/shopro/uniapp/goods/score1.svg')"
|
||||
class="score-img"
|
||||
></image>
|
||||
{{ data.score }}
|
||||
<text v-if="Number(data.score) > 0">{{ data.score }}</text>
|
||||
<view v-if="Number(data.score) <= 0 && Number(data.price?.[0] || data.price) > 0" class="voucher-sub-tag ss-m-l-8">
|
||||
兑换券
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
@@ -88,15 +95,22 @@
|
||||
<view class="ss-m-t-10">
|
||||
<view
|
||||
v-if="goodsFields.score_price?.show"
|
||||
class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
|
||||
class="lg-goods-price ss-m-r-12 ss-flex ss-col-center font-OPPOSANS"
|
||||
:style="[{ color: goodsFields.score_price.color }]"
|
||||
>
|
||||
<view>{{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}</view>
|
||||
<view v-if="Number(data.price?.[0] || data.price) > 0">
|
||||
¥{{ data.price?.[0] || data.price }}
|
||||
<text v-if="Number(data.score) > 0">+</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="Number(data.score) > 0"
|
||||
:src="sheep.$url.static('/assets/addons/shopro/uniapp/goods/score1.svg')"
|
||||
class="score-img"
|
||||
></image>
|
||||
{{ data.score }}
|
||||
<text v-if="Number(data.score) > 0">{{ data.score }}</text>
|
||||
<view v-if="Number(data.score) <= 0 && Number(data.price?.[0] || data.price) > 0" class="voucher-sub-tag ss-m-l-8">
|
||||
兑换券
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="goodsFields.price?.show && data.original_price > 0"
|
||||
@@ -146,13 +160,20 @@
|
||||
</view>
|
||||
</slot>
|
||||
<view class="ss-flex ss-col-bottom ss-p-l-16 ss-p-r-16 font-OPPOSANS">
|
||||
<view class="sl-goods-price ss-m-r-12 ss-flex">
|
||||
<view>{{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}</view>
|
||||
<view class="sl-goods-price ss-m-r-12 ss-flex ss-col-center">
|
||||
<view v-if="Number(data.price?.[0] || data.price) > 0">
|
||||
¥{{ data.price?.[0] || data.price }}
|
||||
<text v-if="Number(data.score) > 0">+</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="Number(data.score) > 0"
|
||||
:src="sheep.$url.static('/assets/addons/shopro/uniapp/goods/score1.svg')"
|
||||
class="score-img"
|
||||
></image>
|
||||
<view>{{ data.score ? data.score : '' }}</view>
|
||||
<view v-if="Number(data.score) > 0">{{ data.score }}</view>
|
||||
<view v-if="Number(data.score) <= 0 && Number(data.price?.[0] || data.price) > 0" class="voucher-sub-tag ss-m-l-8">
|
||||
兑换券
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="data.original_price > 0"
|
||||
@@ -454,4 +475,13 @@
|
||||
height: 36rpx;
|
||||
margin: 0 4rpx;
|
||||
}
|
||||
.voucher-sub-tag {
|
||||
font-size: 20rpx;
|
||||
color: #ff3000;
|
||||
background: rgba(255, 48, 0, 0.1);
|
||||
border: 1rpx solid rgba(255, 48, 0, 0.25);
|
||||
border-radius: 6rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
line-height: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
<template>
|
||||
<view class="ss-user-info-wrap ss-p-t-50">
|
||||
<view class="ss-flex ss-col-center ss-row-between ss-m-b-20">
|
||||
<view class="left-box ss-flex ss-col-center ss-m-l-36">
|
||||
<!-- 已登录状态 -->
|
||||
<view v-if="isLogin" class="left-box ss-flex ss-col-center ss-m-l-36">
|
||||
<view class="avatar-box ss-m-r-24">
|
||||
<image
|
||||
class="avatar-img"
|
||||
:src="
|
||||
isLogin
|
||||
? sheep.$url.cdn(userInfo.avatar)
|
||||
: sheep.$url.static('/assets/addons/shopro/uniapp/default_avatar.png')
|
||||
"
|
||||
:src="sheep.$url.cdn(userInfo.avatar)"
|
||||
mode="aspectFill"
|
||||
@tap="sheep.$router.go('/pages/user/info')"
|
||||
></image>
|
||||
@@ -20,7 +17,7 @@
|
||||
<view class="nick-name ss-m-r-20">{{ userInfo?.nickname || nickname }}</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="isLogin && showIdentityBadges"
|
||||
v-if="showIdentityBadges"
|
||||
class="identity-row ss-flex ss-col-center ss-flex-wrap"
|
||||
@tap="onBadgeTap"
|
||||
>
|
||||
@@ -32,6 +29,55 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 未登录状态:微信小程序原生手机号快捷登录按钮 -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
v-else
|
||||
class="ss-reset-button left-box ss-flex ss-col-center ss-m-l-36 unlogin-btn"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="onQuickLogin"
|
||||
>
|
||||
<view class="avatar-box ss-m-r-24">
|
||||
<image
|
||||
class="avatar-img"
|
||||
:src="sheep.$url.static('/assets/addons/shopro/uniapp/default_avatar.png')"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view class="user-meta">
|
||||
<view class="nickname-box ss-flex ss-col-center">
|
||||
<view class="nick-name ss-m-r-20">点击登录</view>
|
||||
</view>
|
||||
<view class="quick-login-badge ss-flex ss-col-center">
|
||||
<text class="cicon-wechat ss-m-r-8"></text>
|
||||
<text class="quick-badge-text">手机号快捷登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 未登录状态:其他平台普通点击呼出弹窗 -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view
|
||||
v-else
|
||||
class="left-box ss-flex ss-col-center ss-m-l-36"
|
||||
@tap="showAuthModal()"
|
||||
>
|
||||
<view class="avatar-box ss-m-r-24">
|
||||
<image
|
||||
class="avatar-img"
|
||||
:src="sheep.$url.static('/assets/addons/shopro/uniapp/default_avatar.png')"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view class="user-meta">
|
||||
<view class="nickname-box ss-flex ss-col-center">
|
||||
<view class="nick-name ss-m-r-20">{{ nickname }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view class="right-box ss-m-r-52">
|
||||
<button class="ss-reset-button" @tap="showShareModal">
|
||||
<text class="sicon-qrcode"></text>
|
||||
@@ -41,13 +87,24 @@
|
||||
|
||||
<view
|
||||
class="bind-mobile-box ss-flex ss-row-between ss-col-center"
|
||||
v-if="isLogin && !userInfo.verification?.mobile"
|
||||
v-if="showBindMobile"
|
||||
>
|
||||
<view class="ss-flex">
|
||||
<text class="cicon-mobile-o"></text>
|
||||
<view class="mobile-title ss-m-l-20"> 点击绑定手机号确保账户安全 </view>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
class="ss-reset-button bind-btn"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="onBindPhoneNumber"
|
||||
>
|
||||
去绑定
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button class="ss-reset-button bind-btn" @tap="onBind">去绑定</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -158,10 +215,50 @@
|
||||
hasPartner.value,
|
||||
);
|
||||
|
||||
const hasBoundMobile = computed(() => {
|
||||
return Boolean(userInfo.value?.mobile || userInfo.value?.verification?.mobile);
|
||||
});
|
||||
|
||||
const showBindMobile = computed(() => {
|
||||
// 1. 未登录不展示
|
||||
if (!isLogin.value) return false;
|
||||
// 2. 存在已绑定手机号不展示
|
||||
if (hasBoundMobile.value) return false;
|
||||
// 3. 微信小程序环境:默认手机号快捷登录体系,不展示此冗余横幅
|
||||
// #ifdef MP-WEIXIN
|
||||
return false;
|
||||
// #endif
|
||||
// 4. 其他环境:受平台设置“绑定手机号”开关控制
|
||||
return Boolean(sheep.$store('app').platform?.bind_mobile);
|
||||
});
|
||||
|
||||
function onBind() {
|
||||
showAuthModal('changeMobile');
|
||||
}
|
||||
|
||||
// 微信小程序一键快捷绑定手机号
|
||||
async function onBindPhoneNumber(e) {
|
||||
if (e.detail?.errMsg !== 'getPhoneNumber:ok') {
|
||||
showAuthModal('changeMobile');
|
||||
return;
|
||||
}
|
||||
uni.showLoading({ title: '绑定中...', mask: true });
|
||||
try {
|
||||
const result = await sheep.$platform.useProvider('wechat').bindUserPhoneNumber(e.detail);
|
||||
if (result) {
|
||||
await sheep.$store('user').getInfo();
|
||||
sheep.$helper.toast('绑定成功');
|
||||
} else {
|
||||
showAuthModal('changeMobile');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('onBindPhoneNumber error', err);
|
||||
showAuthModal('changeMobile');
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
function onBadgeTap() {
|
||||
if (!isLogin.value) {
|
||||
showAuthModal();
|
||||
@@ -169,12 +266,71 @@
|
||||
}
|
||||
sheep.$router.go('/pages/commission/index');
|
||||
}
|
||||
|
||||
// 微信小程序手机号一键快捷登录
|
||||
async function onQuickLogin(e) {
|
||||
if (e.detail?.errMsg !== 'getPhoneNumber:ok') {
|
||||
showAuthModal();
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: '登录中...',
|
||||
mask: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await sheep.$platform.useProvider('wechat').mobileLogin(e.detail);
|
||||
if (result) {
|
||||
await sheep.$store('user').loginAfter();
|
||||
sheep.$helper.toast('登录成功');
|
||||
} else {
|
||||
sheep.$helper.toast('登录失败,请重试');
|
||||
showAuthModal();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('onQuickLogin error', err);
|
||||
sheep.$helper.toast('登录异常,请稍后重试');
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ss-user-info-wrap {
|
||||
box-sizing: border-box;
|
||||
|
||||
.unlogin-btn {
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.quick-login-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-top: 8rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 28rpx;
|
||||
color: #07c160;
|
||||
background: rgba(7, 193, 96, 0.1);
|
||||
border-radius: 20rpx;
|
||||
border: 1rpx solid rgba(7, 193, 96, 0.25);
|
||||
|
||||
.cicon-wechat {
|
||||
font-size: 24rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.quick-badge-text {
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-box {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
|
||||
@@ -6,13 +6,22 @@ import test from '@/sheep/helper/test.js';
|
||||
import $api from '@/sheep/api';
|
||||
|
||||
// 打开授权弹框
|
||||
export function showAuthModal(type = 'accountLogin') {
|
||||
export function showAuthModal(type = '') {
|
||||
const modal = $store('modal');
|
||||
let targetType = type;
|
||||
if (!targetType) {
|
||||
// #ifdef MP-WEIXIN
|
||||
targetType = 'mpMobileLogin';
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
targetType = 'accountLogin';
|
||||
// #endif
|
||||
}
|
||||
|
||||
// closeAuthModal();
|
||||
setTimeout(() => {
|
||||
modal.$patch((state) => {
|
||||
state.auth = type;
|
||||
state.auth = targetType;
|
||||
});
|
||||
}, 200);
|
||||
|
||||
|
||||
@@ -50,20 +50,27 @@ const login = async () => {
|
||||
// 微信小程序手机号授权登陆
|
||||
const mobileLogin = async (e) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (e.errMsg !== 'getPhoneNumber:ok') {
|
||||
const detail = e?.detail || e || {};
|
||||
if (detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { code } = await third.wechat.login(
|
||||
let currentSessionId = uni.getStorageSync('sessionId');
|
||||
if (!currentSessionId) {
|
||||
await getSessionId(false);
|
||||
currentSessionId = uni.getStorageSync('sessionId');
|
||||
}
|
||||
|
||||
const res = await third.wechat.login(
|
||||
{
|
||||
platform: 'miniProgram',
|
||||
payload: encodeURIComponent(
|
||||
JSON.stringify({
|
||||
sessionId: uni.getStorageSync('sessionId'),
|
||||
code: e.code,
|
||||
iv: e.iv,
|
||||
encryptedData: e.encryptedData,
|
||||
sessionId: currentSessionId,
|
||||
code: detail.code,
|
||||
iv: detail.iv,
|
||||
encryptedData: detail.encryptedData,
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -72,12 +79,35 @@ const mobileLogin = async (e) => {
|
||||
},
|
||||
);
|
||||
|
||||
if (code === 1) {
|
||||
if (res?.code === 1) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (code === -1) {
|
||||
getSessionId(false);
|
||||
if (res?.code === -1) {
|
||||
const got = await getSessionId(false);
|
||||
if (got) {
|
||||
const retryRes = await third.wechat.login(
|
||||
{
|
||||
platform: 'miniProgram',
|
||||
payload: encodeURIComponent(
|
||||
JSON.stringify({
|
||||
sessionId: uni.getStorageSync('sessionId'),
|
||||
code: e.code,
|
||||
iv: e.iv,
|
||||
encryptedData: e.encryptedData,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
shareInfo: uni.getStorageSync('shareLog') || {},
|
||||
},
|
||||
);
|
||||
if (retryRes?.code === 1) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(false);
|
||||
});
|
||||
@@ -229,20 +259,57 @@ const checkUpdate = async (silence = true) => {
|
||||
// 绑定用户手机号
|
||||
const bindUserPhoneNumber = (e) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { code } = await third.wechat.bindUserPhoneNumber({
|
||||
const detail = e?.detail || e || {};
|
||||
if (detail.errMsg && !detail.errMsg.includes('ok')) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let sessionId = uni.getStorageSync('sessionId');
|
||||
if (!sessionId) {
|
||||
await getSessionId(false);
|
||||
sessionId = uni.getStorageSync('sessionId');
|
||||
}
|
||||
|
||||
const res = await third.wechat.bindUserPhoneNumber({
|
||||
platform: 'miniProgram',
|
||||
payload: encodeURIComponent(
|
||||
JSON.stringify({
|
||||
sessionId: uni.getStorageSync('sessionId'),
|
||||
iv: e.iv,
|
||||
encryptedData: e.encryptedData,
|
||||
code: e.code,
|
||||
sessionId: sessionId,
|
||||
iv: detail.iv,
|
||||
encryptedData: detail.encryptedData,
|
||||
code: detail.code,
|
||||
}),
|
||||
),
|
||||
});
|
||||
if (code === 1) {
|
||||
|
||||
if (res?.code === 1) {
|
||||
await sheep.$store('user').getInfo();
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// sessionId 过期重试
|
||||
if (res?.code === -1) {
|
||||
await getSessionId(false);
|
||||
const retryRes = await third.wechat.bindUserPhoneNumber({
|
||||
platform: 'miniProgram',
|
||||
payload: encodeURIComponent(
|
||||
JSON.stringify({
|
||||
sessionId: uni.getStorageSync('sessionId'),
|
||||
iv: detail.iv,
|
||||
encryptedData: detail.encryptedData,
|
||||
code: detail.code,
|
||||
}),
|
||||
),
|
||||
});
|
||||
if (retryRes?.code === 1) {
|
||||
await sheep.$store('user').getInfo();
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
resolve(false);
|
||||
});
|
||||
};
|
||||
@@ -279,6 +346,7 @@ function subscribeMessage(event) {
|
||||
export default {
|
||||
load,
|
||||
login,
|
||||
mobileLogin,
|
||||
bind,
|
||||
unbind,
|
||||
transfer,
|
||||
|
||||
@@ -80,6 +80,9 @@ const user = defineStore({
|
||||
if (!Array.isArray(merged.identities)) {
|
||||
merged.identities = [];
|
||||
}
|
||||
if (merged.mobile) {
|
||||
merged.verification = { ...(merged.verification || {}), mobile: 1 };
|
||||
}
|
||||
this.userInfo = merged;
|
||||
|
||||
return Promise.resolve(merged);
|
||||
@@ -151,7 +154,7 @@ const user = defineStore({
|
||||
// 登录后设置全局分享参数
|
||||
$share.getShareInfo();
|
||||
// 提醒绑定手机号
|
||||
if (app().platform.bind_mobile && !this.userInfo.verification?.mobile) {
|
||||
if (app().platform.bind_mobile && !this.userInfo?.mobile && !this.userInfo?.verification?.mobile) {
|
||||
showAuthModal('changeMobile');
|
||||
}
|
||||
|
||||
|
||||
二进制
uniapp/static/logo.jpg
普通文件
二进制
uniapp/static/logo.jpg
普通文件
二进制文件未显示。
|
之后 宽度: | 高度: | 大小: 77 KiB |
二进制
uniapp/static/logo.png
普通文件
二进制
uniapp/static/logo.png
普通文件
二进制文件未显示。
|
之后 宽度: | 高度: | 大小: 160 KiB |
二进制
uniapp/static/logo_trans.png
普通文件
二进制
uniapp/static/logo_trans.png
普通文件
二进制文件未显示。
|
之后 宽度: | 高度: | 大小: 184 KiB |
在新工单中引用
屏蔽一个用户