Compare commits

..
2 Commits
Author SHA1 Message Date
fenghp f7ffe317ac 添加冷却时间等概念 2026-05-15 09:25:36 +08:00
fenghp 0b327121fe 登录页面优化 2026-05-12 20:37:44 +08:00
7 changed files with 249 additions and 72 deletions
+4 -2
View File
@@ -13,8 +13,10 @@
export const appConfig = { export const appConfig = {
appName: '充值自动化', appName: '充值自动化',
appVersion: '0.1.0', appVersion: '0.1.0',
baseUrl: 'http://10.0.2.2:8111', // baseUrl: 'http://10.0.2.2:8111',
wsBaseUrl: 'ws://10.0.2.2:8111', // wsBaseUrl: 'ws://10.0.2.2:8111',
baseUrl: 'http://192.168.10.2:8111',
wsBaseUrl: 'ws://192.168.10.2:8111',
rechargeUrl: 'https://www.douyin.com/pay', rechargeUrl: 'https://www.douyin.com/pay',
primaryColor: '#007aff', primaryColor: '#007aff',
dryRun: false dryRun: false
+2 -1
View File
@@ -3,6 +3,7 @@
{ {
"path": "pages/login/index", "path": "pages/login/index",
"style": { "style": {
"navigationStyle": "custom",
"navigationBarTitleText": "登录", "navigationBarTitleText": "登录",
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
@@ -12,7 +13,7 @@
{ {
"path": "pages/home/index", "path": "pages/home/index",
"style": { "style": {
"navigationBarTitleText": "首页", "navigationBarTitleText": "工作台",
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f2f2f7" "backgroundColor": "#f2f2f7"
+72 -55
View File
@@ -1,57 +1,28 @@
<template> <template>
<view class="page"> <view class="page home-page">
<view class="page-header"> <view class="section profile-card">
<view class="page-title">工作台</view> <view class="profile-row">
<view class="page-subtitle">登录后自动绑定账号设备,持续心跳并在空闲时拉取分发任务。</view> <view class="profile-label">当前账号</view>
</view> <view class="profile-value">{{ userName }}</view>
</view>
<view class="section"> <view class="profile-row">
<view class="section-title">基础状态</view> <view class="profile-label">设备编号</view>
<view class="summary-grid"> <view class="profile-device">{{ deviceInfo.deviceCode || '--' }}</view>
<view class="summary-item"> </view>
<view class="summary-label">当前账号</view> <view class="profile-row">
<view class="summary-value">{{ userName }}</view> <view class="profile-label">设备在线状态</view>
</view> <view class="profile-status">
<view class="summary-item"> <view class="tag">{{ onlineStatusText }}</view>
<view class="summary-label">设备编号</view>
<view class="summary-value">{{ deviceInfo.deviceCode || '--' }}</view>
</view>
<view class="summary-item">
<view class="summary-label">App 版本</view>
<view class="summary-value">{{ deviceInfo.appVersion || '--' }}</view>
</view>
<view class="summary-item">
<view class="summary-label">系统版本</view>
<view class="summary-value">{{ deviceInfo.systemVersion || '--' }}</view>
</view> </view>
</view> </view>
</view> </view>
<view class="section"> <view class="section">
<view class="section-title">运行状态</view> <view class="section-title">运行状态</view>
<view class="list-item">
<view class="row">
<view class="row-left">
<view class="row-title">设备在线状态</view>
<view class="row-desc">依据绑定和心跳刷新</view>
</view>
<view class="tag">{{ onlineStatusText }}</view>
</view>
</view>
<view class="list-item">
<view class="row">
<view class="row-left">
<view class="row-title">账号设备绑定状态</view>
<view class="row-desc">登录后自动调用 register</view>
</view>
<view class="tag">{{ registerStatus }}</view>
</view>
</view>
<view class="list-item"> <view class="list-item">
<view class="row"> <view class="row">
<view class="row-left"> <view class="row-left">
<view class="row-title">工作状态</view> <view class="row-title">工作状态</view>
<view class="row-desc">设备主动控制是否允许开始接单</view>
</view> </view>
<view class="tag">{{ workStatusText }}</view> <view class="tag">{{ workStatusText }}</view>
</view> </view>
@@ -60,7 +31,6 @@
<view class="row"> <view class="row">
<view class="row-left"> <view class="row-left">
<view class="row-title">当前任务状态</view> <view class="row-title">当前任务状态</view>
<view class="row-desc">工作状态为可工作时轮询 pull,收到后自动接收并开始执行</view>
</view> </view>
<view class="tag">{{ taskStatusText }}</view> <view class="tag">{{ taskStatusText }}</view>
</view> </view>
@@ -69,7 +39,6 @@
<view class="row"> <view class="row">
<view class="row-left"> <view class="row-left">
<view class="row-title">当前任务号</view> <view class="row-title">当前任务号</view>
<view class="row-desc">收到任务后将自动进入充值执行页</view>
</view> </view>
<view class="field-value">{{ currentTaskNo }}</view> <view class="field-value">{{ currentTaskNo }}</view>
</view> </view>
@@ -78,7 +47,6 @@
<view class="row"> <view class="row">
<view class="row-left"> <view class="row-left">
<view class="row-title">订单号</view> <view class="row-title">订单号</view>
<view class="row-desc">分发任务里携带的订单信息</view>
</view> </view>
<view class="field-value">{{ currentOrderNo }}</view> <view class="field-value">{{ currentOrderNo }}</view>
</view> </view>
@@ -87,7 +55,6 @@
<view class="row"> <view class="row">
<view class="row-left"> <view class="row-left">
<view class="row-title">充值金额</view> <view class="row-title">充值金额</view>
<view class="row-desc">用于首页快速确认任务内容</view>
</view> </view>
<view class="field-value">{{ currentAmount }}</view> <view class="field-value">{{ currentAmount }}</view>
</view> </view>
@@ -96,7 +63,6 @@
<view class="row"> <view class="row">
<view class="row-left"> <view class="row-left">
<view class="row-title">充值账号</view> <view class="row-title">充值账号</view>
<view class="row-desc">任务指定充值目标账号</view>
</view> </view>
<view class="field-value">{{ currentRechargeAccount }}</view> <view class="field-value">{{ currentRechargeAccount }}</view>
</view> </view>
@@ -105,7 +71,6 @@
<view class="row"> <view class="row">
<view class="row-left"> <view class="row-left">
<view class="row-title">最近心跳时间</view> <view class="row-title">最近心跳时间</view>
<view class="row-desc">心跳上报成功后刷新</view>
</view> </view>
<view class="field-value">{{ lastHeartbeatTime }}</view> <view class="field-value">{{ lastHeartbeatTime }}</view>
</view> </view>
@@ -119,12 +84,6 @@
:disabled="workStatusUpdating" :disabled="workStatusUpdating"
@tap="toggleWorkStatus" @tap="toggleWorkStatus"
>{{ workStatusButtonText }}</button> >{{ workStatusButtonText }}</button>
<button
class="button-primary button-space"
:disabled="!canStartRecharge"
@tap="startRecharge"
>开始充值</button>
<button class="button-secondary button-space" :disabled="!canPullTask" @tap="manualPullTask">立即拉取任务</button>
<button class="button-secondary button-space" @tap="goTask">任务列表</button> <button class="button-secondary button-space" @tap="goTask">任务列表</button>
<button class="button-secondary button-space" @tap="goLog">接收日志</button> <button class="button-secondary button-space" @tap="goLog">接收日志</button>
<button class="button-secondary button-space" @tap="goRecharge">配置账号</button> <button class="button-secondary button-space" @tap="goRecharge">配置账号</button>
@@ -615,3 +574,61 @@ export default {
} }
} }
</script> </script>
<style lang="scss" scoped>
.home-page {
padding-top: 20rpx;
}
.profile-card {
padding: 20rpx 24rpx;
border: 1rpx solid rgba(60, 60, 67, 0.06);
border-radius: 24rpx;
background: #ffffff;
box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.05);
}
.profile-row + .profile-row {
margin-top: 16rpx;
padding-top: 16rpx;
border-top: 1rpx solid rgba(60, 60, 67, 0.08);
}
.profile-row {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 24rpx;
}
.profile-label {
font-size: 24rpx;
color: #5b6472;
flex-shrink: 0;
}
.profile-value {
font-size: 32rpx;
font-weight: 600;
color: #111827;
line-height: 1.4;
text-align: right;
}
.profile-device {
font-size: 24rpx;
font-weight: 500;
color: #111827;
line-height: 1.4;
word-break: break-all;
text-align: right;
flex: 1;
}
.profile-status {
display: flex;
justify-content: flex-end;
flex: 1;
}
</style>
+148 -12
View File
@@ -1,12 +1,17 @@
<template> <template>
<view class="page"> <view class="page login-page">
<view class="page-header"> <view class="login-bg">
<view class="page-title">充值自动化</view> <view class="bg-orb bg-orb-left"></view>
<view class="page-subtitle">使用 RuoYi 账号登录后进入首页,页面样式保持 iOS 风格。</view> <view class="bg-orb bg-orb-right"></view>
</view> </view>
<view class="section"> <view class="page-header login-header">
<view class="section-title">登录</view> <view class="page-title">欢迎登录</view>
<view class="page-subtitle">输入账号信息后继续进入工作台</view>
</view>
<view class="section login-card">
<view class="card-caption">Account</view>
<view class="field"> <view class="field">
<view class="field-label">用户名</view> <view class="field-label">用户名</view>
<input <input
@@ -38,7 +43,7 @@
placeholder="请输入验证码" placeholder="请输入验证码"
/> />
<view class="captcha-box" @tap="refreshCaptcha"> <view class="captcha-box" @tap="refreshCaptcha">
<image v-if="captchaImg" class="captcha-image" :src="captchaImg" mode="aspectFill" /> <image v-if="captchaImg" class="captcha-image" :src="captchaImg" mode="aspectFit" />
<view v-else class="captcha-placeholder">点击刷新</view> <view v-else class="captcha-placeholder">点击刷新</view>
</view> </view>
</view> </view>
@@ -46,7 +51,7 @@
<view class="row remember-row"> <view class="row remember-row">
<view class="row-left"> <view class="row-left">
<view class="row-title">记住账号</view> <view class="row-title">记住账号</view>
<view class="row-desc">下次打开时自动预填用户名</view> <view class="row-desc">仅保留用户名</view>
</view> </view>
<switch :checked="rememberAccount" color="#007aff" @change="onRememberChange" /> <switch :checked="rememberAccount" color="#007aff" @change="onRememberChange" />
</view> </view>
@@ -177,6 +182,88 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.login-page {
position: relative;
overflow: hidden;
padding: 32rpx 32rpx 48rpx;
background:
radial-gradient(circle at top left, rgba(138, 180, 248, 0.32), transparent 34%),
radial-gradient(circle at right 20%, rgba(255, 255, 255, 0.9), transparent 30%),
linear-gradient(180deg, #eef4ff 0%, #f6f8fc 48%, #f4f5f7 100%);
}
.login-bg {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.bg-orb {
position: absolute;
border-radius: 50%;
filter: blur(16rpx);
}
.bg-orb-left {
top: 20rpx;
left: -80rpx;
width: 280rpx;
height: 280rpx;
background: rgba(138, 180, 248, 0.28);
}
.bg-orb-right {
top: 240rpx;
right: -100rpx;
width: 360rpx;
height: 360rpx;
background: rgba(255, 255, 255, 0.72);
}
.login-header {
position: relative;
z-index: 1;
margin-bottom: 36rpx;
padding: 8rpx 8rpx 0;
}
.login-card {
position: relative;
z-index: 1;
padding: 28rpx;
border: 1rpx solid rgba(255, 255, 255, 0.6);
border-radius: 32rpx;
background: rgba(255, 255, 255, 0.68);
box-shadow: 0 24rpx 60rpx rgba(83, 104, 141, 0.12);
backdrop-filter: blur(24rpx);
}
.card-caption {
margin-bottom: 24rpx;
font-size: 22rpx;
color: rgba(17, 24, 39, 0.45);
letter-spacing: 6rpx;
text-transform: uppercase;
}
.field {
margin-bottom: 22rpx;
}
.field-label {
margin-bottom: 12rpx;
color: #5f6b7a;
}
.input {
height: 92rpx;
border: 1rpx solid rgba(255, 255, 255, 0.7);
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.72);
box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, 0.6);
}
.captcha-input { .captcha-input {
flex: 1; flex: 1;
margin-right: 16rpx; margin-right: 16rpx;
@@ -184,10 +271,15 @@ export default {
.captcha-box { .captcha-box {
width: 220rpx; width: 220rpx;
height: 88rpx; height: 92rpx;
border-radius: 20rpx; border: 1rpx solid rgba(255, 255, 255, 0.72);
border-radius: 24rpx;
overflow: hidden; overflow: hidden;
background: #f7f7fa; display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 12rpx 32rpx rgba(83, 104, 141, 0.08);
} }
.captcha-image, .captcha-image,
@@ -196,6 +288,10 @@ export default {
height: 100%; height: 100%;
} }
.captcha-image {
display: block;
}
.captcha-placeholder { .captcha-placeholder {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -205,6 +301,46 @@ export default {
} }
.remember-row { .remember-row {
margin-top: 12rpx; margin-top: 8rpx;
padding: 10rpx 4rpx 4rpx;
}
.row-title {
font-weight: 600;
}
.row-desc {
margin-top: 6rpx;
color: #7b8794;
}
.button-primary {
height: 92rpx;
line-height: 92rpx;
border-radius: 24rpx;
font-weight: 600;
background: linear-gradient(135deg, #4f8cff 0%, #2f6bff 100%);
box-shadow: 0 18rpx 36rpx rgba(47, 107, 255, 0.22);
}
.button-space {
margin-top: 22rpx;
}
.page-title {
font-size: 52rpx;
font-weight: 700;
letter-spacing: 1rpx;
}
.page-subtitle {
margin-top: 14rpx;
font-size: 26rpx;
color: #6c7a89;
}
.section-title::before,
.section-title::after {
display: none;
} }
</style> </style>
+9 -1
View File
@@ -4,6 +4,14 @@ function isAppPlus() {
return typeof plus !== 'undefined' && !!plus.webview return typeof plus !== 'undefined' && !!plus.webview
} }
/**
* 计算页面原生导航栏占位,避免子 WebView 从顶端铺开后遮住标题栏。
*/
function getNavigationOffset() {
const info = uni.getSystemInfoSync ? uni.getSystemInfoSync() : {}
return `${(info.statusBarHeight || 0) + 44}px`
}
/** /**
* 打开充值 WebView,后续自动化流程复用同一个容器。 * 打开充值 WebView,后续自动化流程复用同一个容器。
*/ */
@@ -26,7 +34,7 @@ export function openRechargeWebview(url, parentWebview) {
} }
rechargeWebview = plus.webview.create(url, 'recharge-webview', { rechargeWebview = plus.webview.create(url, 'recharge-webview', {
top: '0px', top: getNavigationOffset(),
bottom: '0px' bottom: '0px'
}) })
currentWebview.append(rechargeWebview) currentWebview.append(rechargeWebview)
+9 -1
View File
@@ -12,6 +12,14 @@ function isAppPlus() {
return typeof plus !== 'undefined' && !!plus.webview return typeof plus !== 'undefined' && !!plus.webview
} }
/**
* 计算页面原生导航栏占位,避免自动执行页的子 WebView 被顶部标题栏遮挡。
*/
function getNavigationOffset() {
const info = uni.getSystemInfoSync ? uni.getSystemInfoSync() : {}
return `${(info.statusBarHeight || 0) + 44}px`
}
function resolveWebviewLoadWaiters(loaded) { function resolveWebviewLoadWaiters(loaded) {
const waiters = webviewLoadWaiters.slice() const waiters = webviewLoadWaiters.slice()
webviewLoadWaiters = [] webviewLoadWaiters = []
@@ -89,7 +97,7 @@ export function openWebview(url, parentWebview) {
} }
webviewInstance = plus.webview.create(url, 'recharge-webview', { webviewInstance = plus.webview.create(url, 'recharge-webview', {
top: '0px', top: getNavigationOffset(),
bottom: '0px' bottom: '0px'
}) })
webviewLoaded = false webviewLoaded = false
+5
View File
@@ -30,6 +30,7 @@ export default new Vuex.Store({
logs: [], logs: [],
executionState: getExecutionStateStorage(), executionState: getExecutionStateStorage(),
workStatus: getWorkStatusStorage(), workStatus: getWorkStatusStorage(),
cooldownUntil: '',
currentStepIndex: -1, currentStepIndex: -1,
currentStepCode: '', currentStepCode: '',
stepLogs: [], stepLogs: [],
@@ -108,6 +109,9 @@ export default new Vuex.Store({
state.workStatus = value || 'NOT_READY' state.workStatus = value || 'NOT_READY'
setWorkStatusStorage(state.workStatus) setWorkStatusStorage(state.workStatus)
}, },
setCooldownUntil(state, value) {
state.cooldownUntil = value || ''
},
setCurrentStepIndex(state, index) { setCurrentStepIndex(state, index) {
state.currentStepIndex = index state.currentStepIndex = index
}, },
@@ -139,6 +143,7 @@ export default new Vuex.Store({
state.logs = [] state.logs = []
state.executionState = 'IDLE' state.executionState = 'IDLE'
state.workStatus = 'NOT_READY' state.workStatus = 'NOT_READY'
state.cooldownUntil = ''
state.currentStepIndex = -1 state.currentStepIndex = -1
state.currentStepCode = '' state.currentStepCode = ''
state.stepLogs = [] state.stepLogs = []