feat(commission): 收益中心新增30天保活状态与秒级动态预警倒计时看板
这个提交包含在:
@@ -111,6 +111,35 @@ class IdentityService
|
||||
}
|
||||
$available = max(0, (float) $released - (float) $used);
|
||||
|
||||
$windowDays = PartnerConfig::activeDays();
|
||||
$isActive = (new ActiveService())->isActive($agent);
|
||||
$now = time();
|
||||
$lastTime = $last ? intval($last) : 0;
|
||||
|
||||
$expireTime = 0;
|
||||
$remainingSeconds = 0;
|
||||
$daysRemaining = 0;
|
||||
$activeStatus = 'inactive';
|
||||
|
||||
if ($isActive) {
|
||||
if ($lastTime > 0) {
|
||||
$orderDay = strtotime(date('Y-m-d 00:00:00', $lastTime));
|
||||
$expireTime = $orderDay + $windowDays * 86400;
|
||||
$remainingSeconds = max(0, $expireTime - $now);
|
||||
$daysRemaining = intval(ceil($remainingSeconds / 86400));
|
||||
$activeStatus = ($daysRemaining <= 5) ? 'expiring' : 'active';
|
||||
} else {
|
||||
$activeStatus = 'active';
|
||||
$daysRemaining = $windowDays;
|
||||
$expireTime = $now + $windowDays * 86400;
|
||||
$remainingSeconds = $windowDays * 86400;
|
||||
}
|
||||
} else {
|
||||
$activeStatus = 'inactive';
|
||||
$daysRemaining = 0;
|
||||
$remainingSeconds = 0;
|
||||
}
|
||||
|
||||
$data = is_object($agent) ? $agent->toArray() : $agent;
|
||||
return array_merge($data, $identity, [
|
||||
'is_venue' => intval($data['is_venue'] ?? 0),
|
||||
@@ -124,8 +153,12 @@ class IdentityService
|
||||
'voucher_released' => self::formatMoney($released),
|
||||
'voucher_used' => self::formatMoney($used),
|
||||
'voucher_available' => self::formatMoney($available),
|
||||
'active_30d' => (new ActiveService())->isActive($agent),
|
||||
'last_self_order_time' => $last ? intval($last) : 0,
|
||||
'active_30d' => $isActive,
|
||||
'active_status' => $activeStatus,
|
||||
'days_remaining' => $daysRemaining,
|
||||
'expire_time' => $expireTime,
|
||||
'expire_seconds' => $remainingSeconds,
|
||||
'last_self_order_time' => $lastTime,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -147,6 +180,11 @@ class IdentityService
|
||||
'madow_value' => '0.00',
|
||||
'voucher_available' => '0.00',
|
||||
'active_30d' => true,
|
||||
'active_status' => 'inactive',
|
||||
'days_remaining' => 0,
|
||||
'expire_time' => 0,
|
||||
'expire_seconds' => 0,
|
||||
'last_self_order_time' => 0,
|
||||
'agent_form' => self::agentFormEnabled(),
|
||||
'partner_mode' => PartnerConfig::isPartnerMode(),
|
||||
];
|
||||
@@ -166,6 +204,11 @@ class IdentityService
|
||||
'madow_value' => $full['madow_value'],
|
||||
'voucher_available' => $full['voucher_available'],
|
||||
'active_30d' => $full['active_30d'],
|
||||
'active_status' => $full['active_status'] ?? 'inactive',
|
||||
'days_remaining' => $full['days_remaining'] ?? 0,
|
||||
'expire_time' => $full['expire_time'] ?? 0,
|
||||
'expire_seconds' => $full['expire_seconds'] ?? 0,
|
||||
'last_self_order_time' => $full['last_self_order_time'] ?? 0,
|
||||
'agent_form' => self::agentFormEnabled(),
|
||||
'partner_mode' => PartnerConfig::isPartnerMode(),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* 30天收益保活倒计时看板与状态机综合测试用例
|
||||
* 覆盖:
|
||||
* 1. 后端 IdentityService 保活到期时间与状态判定(正常活跃、临期预警、奖励暂停)
|
||||
* 2. 前端 30 天秒级倒计时时分秒换算逻辑
|
||||
* 3. 前端 index.vue 模板节点及样式定义完整性
|
||||
* 4. mp-weixin 编译产物 index.wxml / index.wxss / index.js 完整性
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function it(desc, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` [PASS] ${desc}`);
|
||||
passCount++;
|
||||
} catch (err) {
|
||||
console.error(` [FAIL] ${desc}`);
|
||||
console.error(` Error: ${err.message}`);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('========================================================');
|
||||
console.log(' 30天收益保活倒计时看板与状态机覆盖性测试');
|
||||
console.log('========================================================\n');
|
||||
|
||||
// 1. 测试 IdentityService.php
|
||||
const identityServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/IdentityService.php');
|
||||
const identityContent = fs.readFileSync(identityServicePath, 'utf8');
|
||||
|
||||
it('IdentityService.php 存在并包含 active_status 字段逻辑', () => {
|
||||
assert(fs.existsSync(identityServicePath), 'IdentityService.php 不存在');
|
||||
assert(identityContent.includes("'active_status'"), "未在 attachAgent 中计算 'active_status'");
|
||||
});
|
||||
|
||||
it('IdentityService.php 正确区分 active / expiring / inactive 三种状态', () => {
|
||||
assert(identityContent.includes("'inactive'"), "缺少 inactive 状态判定");
|
||||
assert(identityContent.includes("'expiring'"), "缺少 expiring 状态判定");
|
||||
assert(identityContent.includes("'active'"), "缺少 active 状态判定");
|
||||
});
|
||||
|
||||
it('IdentityService.php 导出 expire_time / expire_seconds / days_remaining', () => {
|
||||
assert(identityContent.includes("'expire_time'"), "缺少 expire_time");
|
||||
assert(identityContent.includes("'expire_seconds'"), "缺少 expire_seconds");
|
||||
assert(identityContent.includes("'days_remaining'"), "缺少 days_remaining");
|
||||
});
|
||||
|
||||
// 2. 模拟前端状态计算与倒计时逻辑
|
||||
function calculateActiveState(agent, user, nowSec) {
|
||||
const lastTime = Number(agent.last_self_order_time || user.last_self_order_time || 0);
|
||||
const isExplicitInactive = agent.active_30d === false || user.active_30d === false || agent.is_active_30d === false || user.is_active_30d === false;
|
||||
|
||||
if (isExplicitInactive || (lastTime <= 0 && (agent.active_30d === false || user.active_30d === false))) {
|
||||
return {
|
||||
status: 'inactive',
|
||||
title: '收益奖励已暂停',
|
||||
badge: '暂缓计奖',
|
||||
btnText: '去选购恢复'
|
||||
};
|
||||
}
|
||||
|
||||
let expireTime = Number(agent.expire_time || 0);
|
||||
if (!expireTime) {
|
||||
expireTime = lastTime > 0 ? (lastTime + 30 * 86400) : (nowSec + 30 * 86400);
|
||||
}
|
||||
|
||||
const remainingSeconds = Math.max(0, expireTime - nowSec);
|
||||
const remainingDays = Math.ceil(remainingSeconds / 86400);
|
||||
|
||||
if (remainingSeconds <= 0) {
|
||||
return {
|
||||
status: 'inactive',
|
||||
title: '收益奖励已暂停',
|
||||
badge: '暂缓计奖',
|
||||
btnText: '去选购恢复'
|
||||
};
|
||||
}
|
||||
|
||||
if (remainingDays <= 5) {
|
||||
return {
|
||||
status: 'expiring',
|
||||
title: '30天保活临期预警',
|
||||
badge: `仅剩 ${remainingDays} 天`,
|
||||
btnText: '立即自购保活'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'active',
|
||||
title: '30天收益资格生效中',
|
||||
badge: '正常活跃',
|
||||
btnText: '去自购续期'
|
||||
};
|
||||
}
|
||||
|
||||
function calculateCountdown(expireTime, nowSec) {
|
||||
const remaining = Math.max(0, expireTime - nowSec);
|
||||
const d = Math.floor(remaining / 86400);
|
||||
const h = Math.floor((remaining % 86400) / 3600);
|
||||
const m = Math.floor((remaining % 3600) / 60);
|
||||
const s = remaining % 60;
|
||||
return {
|
||||
days: d < 10 ? '0' + d : String(d),
|
||||
hours: h < 10 ? '0' + h : String(h),
|
||||
minutes: m < 10 ? '0' + m : String(m),
|
||||
seconds: s < 10 ? '0' + s : String(s),
|
||||
totalSeconds: remaining
|
||||
};
|
||||
}
|
||||
|
||||
// 3. 正常场景:自购在第 2 天(剩余 28 天) -> 正常活跃
|
||||
it('正常场景:剩余28天 -> 状态为 active 且倒计时天数格式为 28', () => {
|
||||
const now = 1700000000;
|
||||
const expire = now + 28 * 86400 + 3600 * 5 + 60 * 12 + 30; // 28天5小时12分30秒
|
||||
const state = calculateActiveState({ expire_time: expire, last_self_order_time: now - 2 * 86400 }, {}, now);
|
||||
const cd = calculateCountdown(expire, now);
|
||||
|
||||
assert.strictEqual(state.status, 'active');
|
||||
assert.strictEqual(state.badge, '正常活跃');
|
||||
assert.strictEqual(cd.days, '28');
|
||||
assert.strictEqual(cd.hours, '05');
|
||||
assert.strictEqual(cd.minutes, '12');
|
||||
assert.strictEqual(cd.seconds, '30');
|
||||
});
|
||||
|
||||
// 4. 临期场景:剩余 3 天(<= 5 天) -> 临期预警
|
||||
it('临期预警场景:剩余3天 -> 状态为 expiring,提示立即自购保活', () => {
|
||||
const now = 1700000000;
|
||||
const expire = now + 3 * 86400 + 3600 * 2 + 60 * 1 + 5;
|
||||
const state = calculateActiveState({ expire_time: expire, last_self_order_time: now - 27 * 86400 }, {}, now);
|
||||
const cd = calculateCountdown(expire, now);
|
||||
|
||||
assert.strictEqual(state.status, 'expiring');
|
||||
assert.strictEqual(state.badge, '仅剩 4 天'); // ceil
|
||||
assert.strictEqual(state.btnText, '立即自购保活');
|
||||
assert.strictEqual(cd.days, '03');
|
||||
assert.strictEqual(cd.hours, '02');
|
||||
assert.strictEqual(cd.minutes, '01');
|
||||
assert.strictEqual(cd.seconds, '05');
|
||||
});
|
||||
|
||||
// 5. 业务失败/过期场景:超过30天未自购 -> 奖励暂停
|
||||
it('过期场景:剩余时间为0 -> 状态为 inactive,按钮为去选购恢复', () => {
|
||||
const now = 1700000000;
|
||||
const expire = now - 100;
|
||||
const state = calculateActiveState({ expire_time: expire, last_self_order_time: now - 31 * 86400 }, {}, now);
|
||||
const cd = calculateCountdown(expire, now);
|
||||
|
||||
assert.strictEqual(state.status, 'inactive');
|
||||
assert.strictEqual(state.title, '收益奖励已暂停');
|
||||
assert.strictEqual(state.btnText, '去选购恢复');
|
||||
assert.strictEqual(cd.totalSeconds, 0);
|
||||
assert.strictEqual(cd.days, '00');
|
||||
});
|
||||
|
||||
// 6. 前端源码 index.vue 结构检查
|
||||
const indexVuePath = path.resolve(__dirname, '../uniapp/pages/commission/index.vue');
|
||||
const indexVueContent = fs.readFileSync(indexVuePath, 'utf8');
|
||||
|
||||
it('uniapp/pages/commission/index.vue 包含 active-countdown-card 卡片', () => {
|
||||
assert(indexVueContent.includes('active-countdown-card'), '未找到 active-countdown-card 类');
|
||||
assert(indexVueContent.includes('countdown.days'), '未找到倒计时天数绑定');
|
||||
assert(indexVueContent.includes('countdown.hours'), '未找到倒计时小时绑定');
|
||||
assert(indexVueContent.includes('countdown.minutes'), '未找到倒计时分钟绑定');
|
||||
assert(indexVueContent.includes('countdown.seconds'), '未找到倒计时秒数绑定');
|
||||
});
|
||||
|
||||
it('uniapp/pages/commission/index.vue 包含定时器生命周期管理', () => {
|
||||
assert(indexVueContent.includes('setInterval(updateCountdown, 1000)'), '缺少 1 秒定时器');
|
||||
assert(indexVueContent.includes('clearInterval(timer)'), '缺少定时器清理');
|
||||
assert(indexVueContent.includes('onShow'), '缺少 onShow 挂载');
|
||||
assert(indexVueContent.includes('onHide'), '缺少 onHide 清理');
|
||||
assert(indexVueContent.includes('onUnload'), '缺少 onUnload 清理');
|
||||
});
|
||||
|
||||
// 7. 微信小程序已编译产物检查
|
||||
const wxmlPath = path.resolve(__dirname, '../uniapp/unpackage/dist/dev/mp-weixin/pages/commission/index.wxml');
|
||||
const wxssPath = path.resolve(__dirname, '../uniapp/unpackage/dist/dev/mp-weixin/pages/commission/index.wxss');
|
||||
const jsPath = path.resolve(__dirname, '../uniapp/unpackage/dist/dev/mp-weixin/pages/commission/index.js');
|
||||
|
||||
it('mp-weixin 产物完整且包含 active-countdown-card 节点与样式', () => {
|
||||
const wxml = fs.readFileSync(wxmlPath, 'utf8');
|
||||
const wxss = fs.readFileSync(wxssPath, 'utf8');
|
||||
const js = fs.readFileSync(jsPath, 'utf8');
|
||||
|
||||
assert(wxml.includes('active-countdown-card'), 'index.wxml 缺少 active-countdown-card');
|
||||
assert(wxml.includes('timer-digits'), 'index.wxml 缺少 timer-digits 倒计时节点');
|
||||
assert(wxss.includes('.active-countdown-card'), 'index.wxss 缺少 .active-countdown-card');
|
||||
assert(wxss.includes('.status-active'), 'index.wxss 缺少 .status-active');
|
||||
assert(wxss.includes('.status-expiring'), 'index.wxss 缺少 .status-expiring');
|
||||
assert(wxss.includes('.status-inactive'), 'index.wxss 缺少 .status-inactive');
|
||||
assert(js.includes('countdown'), 'index.js 缺少 countdown 响应式对象');
|
||||
assert(js.includes('activeState'), 'index.js 缺少 activeState 响应式计算属性');
|
||||
});
|
||||
|
||||
console.log(`\n--------------------------------------------------------`);
|
||||
console.log(`测试结果统计: 通过 ${passCount} 项, 失败 ${failCount} 项`);
|
||||
console.log(`========================================================\n`);
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -3,13 +3,60 @@
|
||||
<s-layout navbar="inner" class="index-wrap" title="收益中心" :bgStyle="bgStyle" onShareAppMessage>
|
||||
<!-- 分销商信息 -->
|
||||
<commission-info />
|
||||
<!-- 30 日活跃警示 -->
|
||||
<view v-if="userInfo?.active_30d === false || agentInfo?.active_30d === false" class="active-warn">
|
||||
<view class="warn-text">近 30 日暂无本人订单,业务奖励已暂停。</view>
|
||||
<button class="ss-reset-button warn-btn" @tap="sheep.$router.go('/pages/index/index')">
|
||||
去首页
|
||||
</button>
|
||||
<!-- 30 天收益保活与预警倒计时看板 -->
|
||||
<view class="active-countdown-card" :class="'status-' + activeState.status">
|
||||
<!-- 头部状态栏 -->
|
||||
<view class="card-header ss-flex ss-row-between ss-col-center">
|
||||
<view class="header-left ss-flex ss-col-center">
|
||||
<view class="status-icon-box ss-flex ss-row-center ss-col-center">
|
||||
<text v-if="activeState.status === 'active'" class="cicon-check icon-symbol text-green"></text>
|
||||
<text v-else-if="activeState.status === 'expiring'" class="cicon-notice icon-symbol text-amber"></text>
|
||||
<text v-else class="cicon-close icon-symbol text-red"></text>
|
||||
</view>
|
||||
<view class="header-title-box ss-m-l-12 ss-flex ss-col-center">
|
||||
<text class="status-title-text">{{ activeState.title }}</text>
|
||||
<text class="status-pill ss-m-l-10" :class="'pill-' + activeState.status">{{ activeState.badge }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<button class="ss-reset-button action-btn" :class="'btn-' + activeState.status" @tap="onActionTap">
|
||||
{{ activeState.btnText }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 倒计时与状态内容 -->
|
||||
<view class="card-body ss-m-t-14">
|
||||
<!-- 正常活跃 / 临期预警:展示秒级动态跳动倒计时 -->
|
||||
<view v-if="activeState.status !== 'inactive'" class="countdown-row ss-flex ss-col-center ss-row-between">
|
||||
<view class="countdown-label ss-flex ss-col-center">
|
||||
<text class="label-prefix">保活剩余:</text>
|
||||
<text class="label-days-highlight">{{ countdown.days }}</text>
|
||||
<text class="label-days-text">天</text>
|
||||
</view>
|
||||
<view class="timer-digits ss-flex ss-col-center">
|
||||
<view class="digit-box"><text class="num">{{ countdown.hours }}</text><text class="unit">时</text></view>
|
||||
<text class="colon">:</text>
|
||||
<view class="digit-box"><text class="num">{{ countdown.minutes }}</text><text class="unit">分</text></view>
|
||||
<text class="colon">:</text>
|
||||
<view class="digit-box sec-box"><text class="num">{{ countdown.seconds }}</text><text class="unit">秒</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 已暂停状态说明 -->
|
||||
<view v-else class="inactive-row ss-flex ss-col-center">
|
||||
<text class="inactive-desc">近 30 日内暂无本人自购订单,所有收益分成已暂缓沉淀。</text>
|
||||
</view>
|
||||
|
||||
<!-- 规则小贴士与上次自购时间 -->
|
||||
<view class="rule-footer ss-flex ss-row-between ss-col-center ss-m-t-10">
|
||||
<view class="footer-left ss-flex ss-col-center ss-line-1 ss-flex-1">
|
||||
<text class="footer-dot">●</text>
|
||||
<text class="footer-tip-text ss-line-1">{{ activeState.tip }}</text>
|
||||
</view>
|
||||
<text v-if="activeState.lastOrderText" class="footer-right-time ss-m-l-10">{{ activeState.lastOrderText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账户信息 -->
|
||||
<account-info />
|
||||
<!-- 团队作战快捷看板 -->
|
||||
@@ -21,7 +68,8 @@
|
||||
<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="active-badge ss-m-l-10" v-if="activeState.status === 'active'">正常活跃 (剩{{ countdown.days }}天)</text>
|
||||
<text class="amber-badge ss-m-l-10" v-else-if="activeState.status === 'expiring'">临期预警 (仅剩{{ countdown.days }}天)</text>
|
||||
<text class="warn-badge ss-m-l-10" v-else>奖励暂停</text>
|
||||
</view>
|
||||
<view class="team-sub ss-m-t-6">
|
||||
@@ -48,8 +96,9 @@
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { onShow, onHide, onUnload } from '@dcloudio/uni-app';
|
||||
import { computed, reactive } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import commissionInfo from './components/commission-info.vue';
|
||||
import accountInfo from './components/account-info.vue';
|
||||
import commissionLog from './components/commission-log.vue';
|
||||
@@ -68,6 +117,130 @@
|
||||
const agentInfo = computed(() => sheep.$store('user').agentInfo);
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
|
||||
// 实时倒计时数据
|
||||
const countdown = reactive({
|
||||
days: '00',
|
||||
hours: '00',
|
||||
minutes: '00',
|
||||
seconds: '00',
|
||||
totalSeconds: 0,
|
||||
});
|
||||
|
||||
let timer = null;
|
||||
|
||||
// 活跃状态与文案综合计算
|
||||
const activeState = computed(() => {
|
||||
const user = userInfo.value || {};
|
||||
const agent = agentInfo.value || {};
|
||||
|
||||
const lastTime = Number(agent.last_self_order_time || user.last_self_order_time || 0);
|
||||
const isExplicitInactive = user.active_30d === false || agent.active_30d === false;
|
||||
|
||||
// 1. 明确暂停或无自购
|
||||
if (isExplicitInactive || (lastTime <= 0 && agent.active_30d === false)) {
|
||||
return {
|
||||
status: 'inactive',
|
||||
title: '收益奖励已暂停',
|
||||
badge: '暂缓计奖',
|
||||
btnText: '去选购恢复',
|
||||
tip: '次日完成任意一笔本人自购订单即可自动恢复保活',
|
||||
lastOrderText: lastTime > 0 ? `上次自购: ${dayjs(lastTime * 1000).format('MM-DD HH:mm')}` : '暂无自购记录',
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 计算到期时间戳
|
||||
const nowSec = Math.floor(Date.now() / 1000);
|
||||
let expireTime = Number(agent.expire_time || 0);
|
||||
if (!expireTime) {
|
||||
expireTime = lastTime > 0 ? (lastTime + 30 * 86400) : (nowSec + 30 * 86400);
|
||||
}
|
||||
|
||||
const remainingSeconds = Math.max(0, expireTime - nowSec);
|
||||
const remainingDays = Math.ceil(remainingSeconds / 86400);
|
||||
|
||||
if (remainingSeconds <= 0) {
|
||||
return {
|
||||
status: 'inactive',
|
||||
title: '收益奖励已暂停',
|
||||
badge: '暂缓计奖',
|
||||
btnText: '去选购恢复',
|
||||
tip: '近30日暂无本人自购,次日下单后即可自动激活',
|
||||
lastOrderText: lastTime > 0 ? `上次自购: ${dayjs(lastTime * 1000).format('MM-DD HH:mm')}` : '',
|
||||
};
|
||||
}
|
||||
|
||||
if (remainingDays <= 5) {
|
||||
return {
|
||||
status: 'expiring',
|
||||
title: '30天保活临期预警',
|
||||
badge: `仅剩 ${remainingDays} 天`,
|
||||
btnText: '立即自购保活',
|
||||
tip: '即将暂停分润!请在倒计时结束前完成自购续期',
|
||||
lastOrderText: lastTime > 0 ? `上次自购: ${dayjs(lastTime * 1000).format('MM-DD HH:mm')}` : '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'active',
|
||||
title: '30天收益资格生效中',
|
||||
badge: '正常活跃',
|
||||
btnText: '去自购续期',
|
||||
tip: '已满足30日保活,享有全部团队分红与奖项',
|
||||
lastOrderText: lastTime > 0 ? `上次自购: ${dayjs(lastTime * 1000).format('MM-DD HH:mm')}` : '',
|
||||
};
|
||||
});
|
||||
|
||||
function updateCountdown() {
|
||||
const user = userInfo.value || {};
|
||||
const agent = agentInfo.value || {};
|
||||
const lastTime = Number(agent.last_self_order_time || user.last_self_order_time || 0);
|
||||
|
||||
if (user.active_30d === false || agent.active_30d === false) {
|
||||
countdown.days = '00';
|
||||
countdown.hours = '00';
|
||||
countdown.minutes = '00';
|
||||
countdown.seconds = '00';
|
||||
countdown.totalSeconds = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const nowSec = Math.floor(Date.now() / 1000);
|
||||
let expireTime = Number(agent.expire_time || 0);
|
||||
if (!expireTime) {
|
||||
expireTime = lastTime > 0 ? (lastTime + 30 * 86400) : (nowSec + 30 * 86400);
|
||||
}
|
||||
|
||||
const remaining = Math.max(0, expireTime - nowSec);
|
||||
countdown.totalSeconds = remaining;
|
||||
|
||||
const d = Math.floor(remaining / 86400);
|
||||
const h = Math.floor((remaining % 86400) / 3600);
|
||||
const m = Math.floor((remaining % 3600) / 60);
|
||||
const s = remaining % 60;
|
||||
|
||||
countdown.days = d < 10 ? '0' + d : String(d);
|
||||
countdown.hours = h < 10 ? '0' + h : String(h);
|
||||
countdown.minutes = m < 10 ? '0' + m : String(m);
|
||||
countdown.seconds = s < 10 ? '0' + s : String(s);
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
if (timer) clearInterval(timer);
|
||||
updateCountdown();
|
||||
timer = setInterval(updateCountdown, 1000);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function onActionTap() {
|
||||
sheep.$router.go('/pages/index/index');
|
||||
}
|
||||
|
||||
async function getUserInfo() {
|
||||
await sheep.$store('user').getInfo();
|
||||
}
|
||||
@@ -82,10 +255,21 @@
|
||||
state.error = res?.code || 0;
|
||||
state.errorData = res?.data || {};
|
||||
}
|
||||
updateCountdown();
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
getUserInfo();
|
||||
getAgentInfo();
|
||||
startTimer();
|
||||
});
|
||||
|
||||
onHide(() => {
|
||||
stopTimer();
|
||||
});
|
||||
|
||||
onUnload(() => {
|
||||
stopTimer();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -94,35 +278,231 @@
|
||||
background-size: 100% 100% !important;
|
||||
}
|
||||
|
||||
.active-warn {
|
||||
.active-countdown-card {
|
||||
width: 694rpx;
|
||||
margin: 0 auto 16rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
margin: 16rpx auto 0;
|
||||
padding: 24rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
background: #fff4e5;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: 16rpx;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.warn-text {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx;
|
||||
color: #a17545;
|
||||
padding-right: 16rpx;
|
||||
// 正常活跃状态
|
||||
&.status-active {
|
||||
background: linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%);
|
||||
border: 1rpx solid #bbf7d0;
|
||||
box-shadow: 0 4rpx 16rpx rgba(187, 247, 208, 0.35);
|
||||
|
||||
.status-icon-box {
|
||||
background: #dcfce7;
|
||||
}
|
||||
.status-title-text {
|
||||
color: #166534;
|
||||
}
|
||||
.pill-active {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
.btn-active {
|
||||
background: #059669;
|
||||
color: #ffffff;
|
||||
}
|
||||
.digit-box {
|
||||
background: #ecfdf5;
|
||||
border: 1rpx solid #a7f3d0;
|
||||
.num {
|
||||
color: #047857;
|
||||
}
|
||||
.unit {
|
||||
color: #065f46;
|
||||
}
|
||||
}
|
||||
.colon {
|
||||
color: #059669;
|
||||
}
|
||||
}
|
||||
|
||||
.warn-btn {
|
||||
flex-shrink: 0;
|
||||
height: 52rpx;
|
||||
padding: 0 22rpx;
|
||||
background: #ff6000;
|
||||
border-radius: 26rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ffffff;
|
||||
// 临期预警状态
|
||||
&.status-expiring {
|
||||
background: linear-gradient(135deg, #fff7ed 0%, #fffbeb 100%);
|
||||
border: 1rpx solid #fed7aa;
|
||||
box-shadow: 0 4rpx 18rpx rgba(253, 186, 116, 0.45);
|
||||
|
||||
.status-icon-box {
|
||||
background: #ffedd5;
|
||||
}
|
||||
.status-title-text {
|
||||
color: #9a3412;
|
||||
}
|
||||
.pill-expiring {
|
||||
background: #ffedd5;
|
||||
color: #c2410c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.btn-expiring {
|
||||
background: linear-gradient(90deg, #ea580c 0%, #f97316 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(234, 88, 12, 0.3);
|
||||
}
|
||||
.digit-box {
|
||||
background: #ffedd5;
|
||||
border: 1rpx solid #fdba74;
|
||||
.num {
|
||||
color: #c2410c;
|
||||
}
|
||||
.unit {
|
||||
color: #9a3412;
|
||||
}
|
||||
}
|
||||
.colon {
|
||||
color: #ea580c;
|
||||
}
|
||||
}
|
||||
|
||||
// 奖励暂停状态
|
||||
&.status-inactive {
|
||||
background: linear-gradient(135deg, #fef2f2 0%, #fff1f2 100%);
|
||||
border: 1rpx solid #fecaca;
|
||||
box-shadow: 0 4rpx 16rpx rgba(254, 202, 202, 0.35);
|
||||
|
||||
.status-icon-box {
|
||||
background: #fee2e2;
|
||||
}
|
||||
.status-title-text {
|
||||
color: #991b1b;
|
||||
}
|
||||
.pill-inactive {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
.btn-inactive {
|
||||
background: #dc2626;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
.status-icon-box {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 24rpx;
|
||||
.icon-symbol {
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text-green { color: #059669; }
|
||||
.text-amber { color: #d97706; }
|
||||
.text-red { color: #dc2626; }
|
||||
}
|
||||
|
||||
.status-title-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
height: 52rpx;
|
||||
padding: 0 24rpx;
|
||||
border-radius: 26rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
.countdown-row {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
padding: 12rpx 16rpx;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.countdown-label {
|
||||
.label-prefix {
|
||||
font-size: 24rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
.label-days-highlight {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
margin: 0 2rpx;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
.label-days-text {
|
||||
font-size: 22rpx;
|
||||
color: #475569;
|
||||
}
|
||||
}
|
||||
|
||||
.timer-digits {
|
||||
.digit-box {
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 6rpx;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
min-width: 52rpx;
|
||||
justify-content: center;
|
||||
|
||||
.num {
|
||||
font-family: OPPOSANS, DIN, monospace;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.unit {
|
||||
font-size: 18rpx;
|
||||
margin-left: 2rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.colon {
|
||||
font-weight: bold;
|
||||
font-size: 24rpx;
|
||||
margin: 0 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inactive-row {
|
||||
background: rgba(254, 226, 226, 0.4);
|
||||
padding: 14rpx 18rpx;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.inactive-desc {
|
||||
font-size: 24rpx;
|
||||
color: #991b1b;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.rule-footer {
|
||||
font-size: 20rpx;
|
||||
color: #94a3b8;
|
||||
|
||||
.footer-dot {
|
||||
font-size: 14rpx;
|
||||
color: #cbd5e1;
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.footer-tip-text {
|
||||
font-size: 22rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.footer-right-time {
|
||||
font-size: 20rpx;
|
||||
color: #94a3b8;
|
||||
font-family: OPPOSANS;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +539,14 @@
|
||||
color: #059669;
|
||||
font-weight: 500;
|
||||
}
|
||||
.amber-badge {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #ffedd5;
|
||||
color: #c2410c;
|
||||
font-weight: 500;
|
||||
}
|
||||
.warn-badge {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
|
||||
在新工单中引用
屏蔽一个用户