Compare commits
2
Commits
f4f06e8079
...
143df07f51
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
143df07f51 | ||
|
|
ff92ab75ef |
@@ -3,7 +3,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {}
|
import { getToken } from './utils/storage'
|
||||||
|
import { initVConsole } from './utils/vconsole'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* App 启动时按登录态决定首屏入口,避免用户手动判断跳转。
|
||||||
|
*/
|
||||||
|
onLaunch() {
|
||||||
|
initVConsole()
|
||||||
|
|
||||||
|
if (getToken()) {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/home/index'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
+1
-2
@@ -19,8 +19,7 @@ export function login(data) {
|
|||||||
url: '/login',
|
url: '/login',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data,
|
data,
|
||||||
needToken: false,
|
needToken: false
|
||||||
contentType: 'application/x-www-form-urlencoded;charset=utf-8'
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-2
@@ -12,7 +12,18 @@ export function pullTask(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报任务步骤状态。
|
* 确认任务已被当前设备接收。
|
||||||
|
*/
|
||||||
|
export function receiveTask(data) {
|
||||||
|
return request({
|
||||||
|
url: '/af/mobile/task/receive',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上报任务步骤状态(二期预留)。
|
||||||
*/
|
*/
|
||||||
export function reportTaskStep(data) {
|
export function reportTaskStep(data) {
|
||||||
return request({
|
return request({
|
||||||
@@ -23,7 +34,7 @@ export function reportTaskStep(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报任务最终结果。
|
* 上报任务最终结果(二期预留)。
|
||||||
*/
|
*/
|
||||||
export function reportTaskResult(data) {
|
export function reportTaskResult(data) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
+2
-2
@@ -5,8 +5,8 @@
|
|||||||
export const appConfig = {
|
export const appConfig = {
|
||||||
appName: '充值自动化',
|
appName: '充值自动化',
|
||||||
appVersion: '0.1.0',
|
appVersion: '0.1.0',
|
||||||
baseUrl: 'http://your-backend-domain',
|
baseUrl: 'http://10.0.2.2:8111',
|
||||||
wsBaseUrl: 'ws://your-backend-domain',
|
wsBaseUrl: 'ws://10.0.2.2:8111',
|
||||||
rechargeUrl: 'https://your-recharge-page',
|
rechargeUrl: 'https://your-recharge-page',
|
||||||
primaryColor: '#007aff'
|
primaryColor: '#007aff'
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -45,10 +45,19 @@
|
|||||||
"backgroundColor": "#f2f2f7"
|
"backgroundColor": "#f2f2f7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/task/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "任务列表",
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"backgroundColor": "#f2f2f7"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/log/index",
|
"path": "pages/log/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "执行日志",
|
"navigationBarTitleText": "接收日志",
|
||||||
"navigationBarTextStyle": "black",
|
"navigationBarTextStyle": "black",
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
"backgroundColor": "#f2f2f7"
|
"backgroundColor": "#f2f2f7"
|
||||||
|
|||||||
+299
-44
@@ -2,7 +2,7 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="page-header">
|
<view class="page-header">
|
||||||
<view class="page-title">工作台</view>
|
<view class="page-title">工作台</view>
|
||||||
<view class="page-subtitle">展示账号、设备和连接状态,后续任务和 WebSocket 都从这里进入。</view>
|
<view class="page-subtitle">登录后自动绑定账号设备,持续心跳并在空闲时拉取分发任务。</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="section">
|
<view class="section">
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<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 class="row-desc">依据绑定和心跳刷新</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag">{{ onlineStatusText }}</view>
|
<view class="tag">{{ onlineStatusText }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -41,17 +41,17 @@
|
|||||||
<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">WebSocket 连接状态</view>
|
<view class="row-title">账号设备绑定状态</view>
|
||||||
<view class="row-desc">任务下发和服务端通知入口</view>
|
<view class="row-desc">登录后自动调用 register</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag">{{ wsStatusText }}</view>
|
<view class="tag">{{ registerStatus }}</view>
|
||||||
</view>
|
</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 class="row-desc">空闲时轮询 pull,收到后自动 receive</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag">{{ taskStatusText }}</view>
|
<view class="tag">{{ taskStatusText }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -59,26 +59,44 @@
|
|||||||
<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 class="row-desc">本期只做到接收确认,不做执行回调</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag">{{ douyinStatusText }}</view>
|
<view class="field-value">{{ currentTaskNo }}</view>
|
||||||
</view>
|
</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 class="row-desc">分发任务里携带的订单信息</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag">{{ alipayStatusText }}</view>
|
<view class="field-value">{{ currentOrderNo }}</view>
|
||||||
|
</view>
|
||||||
|
</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="field-value">{{ currentAmount }}</view>
|
||||||
|
</view>
|
||||||
|
</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="field-value">{{ currentRechargeAccount }}</view>
|
||||||
</view>
|
</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 class="row-desc">心跳上报成功后刷新</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="field-value">{{ lastHeartbeatTime }}</view>
|
<view class="field-value">{{ lastHeartbeatTime }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -87,22 +105,50 @@
|
|||||||
|
|
||||||
<view class="section">
|
<view class="section">
|
||||||
<view class="section-title">功能入口</view>
|
<view class="section-title">功能入口</view>
|
||||||
<button class="button-primary" @tap="goRecharge">开始充值</button>
|
<button class="button-primary" @tap="manualPullTask">立即拉取任务</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="goRecharge">开始充值</button>
|
||||||
<button class="button-secondary button-space" @tap="goDouyin">切换抖音账号</button>
|
<button class="button-secondary button-space" @tap="goDouyin">切换抖音账号</button>
|
||||||
<button class="button-secondary button-space" @tap="goAlipay">切换支付宝账号</button>
|
<button class="button-secondary button-space" @tap="goAlipay">切换支付宝账号</button>
|
||||||
<button class="button-secondary button-space" @tap="reconnectWebSocket">重新连接 WebSocket</button>
|
|
||||||
<button class="button-danger button-space" @tap="logout">退出登录</button>
|
<button class="button-danger button-space" @tap="logout">退出登录</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import appConfig from '../../config/index'
|
import { heartbeatDevice, registerDevice } from '../../api/device'
|
||||||
|
import { pullTask, receiveTask } from '../../api/task'
|
||||||
import { getDeviceProfile } from '../../services/device'
|
import { getDeviceProfile } from '../../services/device'
|
||||||
import { clearAuth, getUserInfo } from '../../utils/storage'
|
import { clearAuth, getUserInfo, setDeviceInfo } from '../../utils/storage'
|
||||||
|
|
||||||
|
const HEARTBEAT_INTERVAL = 15000
|
||||||
|
const PULL_INTERVAL = 8000
|
||||||
|
|
||||||
|
function formatTime(value) {
|
||||||
|
if (!value) {
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pad = (num) => (num < 10 ? `0${num}` : `${num}`)
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTaskId(task) {
|
||||||
|
if (!task) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return task.taskId || task.id || task.task_no || task.taskNo || ''
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首页负责展示设备、账号和连接状态,并提供后续功能入口。
|
* 首页负责设备自动绑定、心跳上报和任务接收一期闭环。
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -110,9 +156,15 @@ export default {
|
|||||||
deviceInfo: {
|
deviceInfo: {
|
||||||
deviceCode: '',
|
deviceCode: '',
|
||||||
appVersion: '',
|
appVersion: '',
|
||||||
systemVersion: ''
|
systemVersion: '',
|
||||||
|
platform: ''
|
||||||
},
|
},
|
||||||
lastHeartbeatTime: '--'
|
registerStatus: '未绑定',
|
||||||
|
deviceOnlineStatus: 'OFFLINE',
|
||||||
|
lastHeartbeatTime: '--',
|
||||||
|
heartbeatTimer: null,
|
||||||
|
pullTimer: null,
|
||||||
|
pulling: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -120,20 +172,32 @@ export default {
|
|||||||
const userInfo = this.$store.state.userInfo || getUserInfo() || {}
|
const userInfo = this.$store.state.userInfo || getUserInfo() || {}
|
||||||
return userInfo.userName || userInfo.nickName || userInfo.username || '未登录'
|
return userInfo.userName || userInfo.nickName || userInfo.username || '未登录'
|
||||||
},
|
},
|
||||||
onlineStatusText() {
|
currentTask() {
|
||||||
return this.deviceInfo.deviceCode ? '在线' : '未注册'
|
return this.$store.state.currentTask || null
|
||||||
},
|
},
|
||||||
wsStatusText() {
|
onlineStatusText() {
|
||||||
return this.$store.state.wsStatus === 'connected' ? '已连接' : '未连接'
|
return this.deviceOnlineStatus === 'ONLINE' ? '在线' : '离线'
|
||||||
},
|
},
|
||||||
taskStatusText() {
|
taskStatusText() {
|
||||||
return this.$store.state.currentTask ? '执行中' : '空闲'
|
const status = this.currentTask && this.currentTask.status
|
||||||
|
return status || 'IDLE'
|
||||||
},
|
},
|
||||||
douyinStatusText() {
|
currentTaskNo() {
|
||||||
return this.$store.state.douyinLoginStatus === 'success' ? '已登录' : '未知'
|
const task = this.currentTask || {}
|
||||||
|
return task.taskNo || task.task_no || '--'
|
||||||
},
|
},
|
||||||
alipayStatusText() {
|
currentOrderNo() {
|
||||||
return this.$store.state.alipayLoginStatus === 'success' ? '已登录' : '未知'
|
const task = this.currentTask || {}
|
||||||
|
return task.orderNo || task.order_no || '--'
|
||||||
|
},
|
||||||
|
currentAmount() {
|
||||||
|
const task = this.currentTask || {}
|
||||||
|
const amount = task.amount
|
||||||
|
return amount === undefined || amount === null || amount === '' ? '--' : String(amount)
|
||||||
|
},
|
||||||
|
currentRechargeAccount() {
|
||||||
|
const task = this.currentTask || {}
|
||||||
|
return task.rechargeAccount || task.recharge_account || '--'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
@@ -146,16 +210,220 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.syncDeviceInfo()
|
this.syncDeviceInfo()
|
||||||
|
this.startDispatchFlow()
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
this.stopDispatchFlow()
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
this.stopDispatchFlow()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* 刷新当前设备信息,供首页和后续注册接口复用。
|
* 刷新当前设备信息,供绑定、心跳和任务拉取统一复用。
|
||||||
*/
|
*/
|
||||||
syncDeviceInfo() {
|
syncDeviceInfo() {
|
||||||
const profile = getDeviceProfile()
|
const profile = getDeviceProfile()
|
||||||
this.deviceInfo = profile
|
this.deviceInfo = profile
|
||||||
|
setDeviceInfo(profile)
|
||||||
this.$store.commit('setDeviceInfo', profile)
|
this.$store.commit('setDeviceInfo', profile)
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 启动一期分发流程:先绑定,再心跳和轮询任务。
|
||||||
|
*/
|
||||||
|
async startDispatchFlow() {
|
||||||
|
this.stopDispatchFlow()
|
||||||
|
await this.registerCurrentDevice()
|
||||||
|
this.startHeartbeatTimer()
|
||||||
|
this.startPullTimer()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 停止心跳和拉取定时器,避免页面切换后重复上报。
|
||||||
|
*/
|
||||||
|
stopDispatchFlow() {
|
||||||
|
if (this.heartbeatTimer) {
|
||||||
|
clearInterval(this.heartbeatTimer)
|
||||||
|
this.heartbeatTimer = null
|
||||||
|
}
|
||||||
|
if (this.pullTimer) {
|
||||||
|
clearInterval(this.pullTimer)
|
||||||
|
this.pullTimer = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 登录后自动绑定账号与设备,满足一期账号设备登记要求。
|
||||||
|
*/
|
||||||
|
async registerCurrentDevice() {
|
||||||
|
const userName = this.userName
|
||||||
|
const deviceCode = this.deviceInfo.deviceCode
|
||||||
|
if (!userName || userName === '未登录' || !deviceCode) {
|
||||||
|
this.registerStatus = '绑定失败'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await registerDevice({
|
||||||
|
userName,
|
||||||
|
deviceCode,
|
||||||
|
appVersion: this.deviceInfo.appVersion,
|
||||||
|
systemVersion: this.deviceInfo.systemVersion,
|
||||||
|
platform: this.deviceInfo.platform
|
||||||
|
})
|
||||||
|
this.registerStatus = '已绑定'
|
||||||
|
this.pushTraceLog('DEVICE_REGISTER', `账号 ${userName} 绑定设备 ${deviceCode} 成功`)
|
||||||
|
} catch (error) {
|
||||||
|
this.registerStatus = '绑定失败'
|
||||||
|
this.pushTraceLog('DEVICE_REGISTER_FAIL', error.msg || error.message || '账号设备绑定失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 开启心跳定时上报,并在首次进入页面时立即上报一次。
|
||||||
|
*/
|
||||||
|
startHeartbeatTimer() {
|
||||||
|
this.reportHeartbeat()
|
||||||
|
this.heartbeatTimer = setInterval(() => {
|
||||||
|
this.reportHeartbeat()
|
||||||
|
}, HEARTBEAT_INTERVAL)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 上报当前账号设备心跳,用于后端判断在线与空闲状态。
|
||||||
|
*/
|
||||||
|
async reportHeartbeat() {
|
||||||
|
const userName = this.userName
|
||||||
|
const deviceCode = this.deviceInfo.deviceCode
|
||||||
|
if (!userName || userName === '未登录' || !deviceCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await heartbeatDevice({
|
||||||
|
userName,
|
||||||
|
deviceCode,
|
||||||
|
taskStatus: this.currentTask ? 'HAS_TASK' : 'IDLE',
|
||||||
|
currentTaskId: resolveTaskId(this.currentTask)
|
||||||
|
})
|
||||||
|
|
||||||
|
const heartbeatTime = (result.data && result.data.lastHeartbeatTime) || result.lastHeartbeatTime || Date.now()
|
||||||
|
this.lastHeartbeatTime = formatTime(heartbeatTime)
|
||||||
|
|
||||||
|
if (this.deviceOnlineStatus !== 'ONLINE') {
|
||||||
|
this.pushTraceLog('HEARTBEAT_ONLINE', '心跳恢复成功,设备在线')
|
||||||
|
}
|
||||||
|
this.deviceOnlineStatus = 'ONLINE'
|
||||||
|
} catch (error) {
|
||||||
|
if (this.deviceOnlineStatus !== 'OFFLINE') {
|
||||||
|
this.pushTraceLog('HEARTBEAT_FAIL', error.msg || error.message || '心跳上报失败')
|
||||||
|
}
|
||||||
|
this.deviceOnlineStatus = 'OFFLINE'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 启动空闲轮询,按一期要求通过 pull 接收任务。
|
||||||
|
*/
|
||||||
|
startPullTimer() {
|
||||||
|
this.tryPullTask()
|
||||||
|
this.pullTimer = setInterval(() => {
|
||||||
|
this.tryPullTask()
|
||||||
|
}, PULL_INTERVAL)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 空闲时拉取任务,拿到任务后立即确认接收。
|
||||||
|
*/
|
||||||
|
async tryPullTask() {
|
||||||
|
if (this.currentTask || this.pulling) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const userName = this.userName
|
||||||
|
const deviceCode = this.deviceInfo.deviceCode
|
||||||
|
if (!userName || userName === '未登录' || !deviceCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pulling = true
|
||||||
|
try {
|
||||||
|
const result = await pullTask({ userName, deviceCode })
|
||||||
|
const task = result.data || result
|
||||||
|
const taskId = resolveTaskId(task)
|
||||||
|
|
||||||
|
// 无任务返回时直接结束本次轮询。
|
||||||
|
if (!taskId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextTask = {
|
||||||
|
...task,
|
||||||
|
taskId,
|
||||||
|
status: task.status || 'PUSHED'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.commit('setCurrentTask', nextTask)
|
||||||
|
this.$store.commit('upsertTaskHistory', nextTask)
|
||||||
|
this.pushTraceLog('TASK_PULL', `收到任务 ${nextTask.taskNo || nextTask.taskId}`)
|
||||||
|
await this.confirmTaskReceive(nextTask)
|
||||||
|
} catch (error) {
|
||||||
|
this.pushTraceLog('TASK_PULL_FAIL', error.msg || error.message || '任务拉取失败')
|
||||||
|
} finally {
|
||||||
|
this.pulling = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 对拉取到的任务调用 receive,明确任务已送达当前账号设备。
|
||||||
|
*/
|
||||||
|
async confirmTaskReceive(task) {
|
||||||
|
const userName = this.userName
|
||||||
|
const deviceCode = this.deviceInfo.deviceCode
|
||||||
|
const taskId = resolveTaskId(task)
|
||||||
|
if (!taskId || !userName || userName === '未登录' || !deviceCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await receiveTask({
|
||||||
|
taskId,
|
||||||
|
userName,
|
||||||
|
deviceCode
|
||||||
|
})
|
||||||
|
|
||||||
|
const receivedTask = {
|
||||||
|
...task,
|
||||||
|
status: 'RECEIVED',
|
||||||
|
receiveTime: Date.now()
|
||||||
|
}
|
||||||
|
this.$store.commit('setCurrentTask', receivedTask)
|
||||||
|
this.$store.commit('upsertTaskHistory', receivedTask)
|
||||||
|
this.pushTraceLog('TASK_RECEIVE', `任务 ${task.taskNo || taskId} 已确认接收`)
|
||||||
|
} catch (error) {
|
||||||
|
this.pushTraceLog('TASK_RECEIVE_FAIL', error.msg || error.message || '任务接收确认失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 手动触发一次任务拉取,方便联调时快速验证拉取接口。
|
||||||
|
*/
|
||||||
|
manualPullTask() {
|
||||||
|
this.tryPullTask()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 写入接收轨迹日志,供日志页和排查联调问题使用。
|
||||||
|
*/
|
||||||
|
pushTraceLog(event, text) {
|
||||||
|
this.$store.commit('pushLog', {
|
||||||
|
event,
|
||||||
|
time: formatTime(Date.now()),
|
||||||
|
taskNo: this.currentTaskNo,
|
||||||
|
text
|
||||||
|
})
|
||||||
|
},
|
||||||
|
goTask() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/task/index'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
goLog() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/log/index'
|
||||||
|
})
|
||||||
|
},
|
||||||
goRecharge() {
|
goRecharge() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/recharge/index'
|
url: '/pages/recharge/index'
|
||||||
@@ -171,21 +439,8 @@ export default {
|
|||||||
url: '/pages/account/alipay'
|
url: '/pages/account/alipay'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
reconnectWebSocket() {
|
|
||||||
if (!appConfig.wsBaseUrl || appConfig.wsBaseUrl.indexOf('your-backend-domain') > -1) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请先配置 WebSocket 地址',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showToast({
|
|
||||||
title: 'WebSocket 待接入',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
logout() {
|
logout() {
|
||||||
|
this.stopDispatchFlow()
|
||||||
clearAuth()
|
clearAuth()
|
||||||
this.$store.commit('resetState')
|
this.$store.commit('resetState')
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
|
|||||||
+12
-6
@@ -1,26 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="page-header">
|
<view class="page-header">
|
||||||
<view class="page-title">执行日志</view>
|
<view class="page-title">接收日志</view>
|
||||||
<view class="page-subtitle">后续任务、步骤、失败原因都会在这里按时间顺序展示。</view>
|
<view class="page-subtitle">记录设备绑定、心跳、任务拉取和接收确认轨迹。</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="section">
|
<view class="section">
|
||||||
<view class="section-title">日志列表</view>
|
<view class="section-title">日志列表</view>
|
||||||
<view v-if="logs.length" class="log-list">
|
<view v-if="logs.length" class="log-list">
|
||||||
<view v-for="(item, index) in logs" :key="index" class="list-item">
|
<view v-for="(item, index) in logs" :key="index" class="list-item">
|
||||||
<view class="log-time">{{ item.time }}</view>
|
<view class="row">
|
||||||
<view class="log-text">{{ item.text }}</view>
|
<view class="log-time">{{ item.time || '--' }}</view>
|
||||||
|
<view class="tag">{{ item.event || 'TRACE' }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="log-text">{{ item.text || '--' }}</view>
|
||||||
|
<view class="row" style="margin-top: 8rpx;">
|
||||||
|
<view class="row-desc">任务号:{{ item.taskNo || '--' }}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="empty-state">当前还没有日志记录</view>
|
</view>
|
||||||
|
<view v-else class="empty-state">当前还没有接收轨迹</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/**
|
/**
|
||||||
* 日志页负责承载后续任务步骤和失败现场的本地展示。
|
* 日志页负责展示一期任务接收轨迹,便于联调排查。
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
+12
-1
@@ -58,6 +58,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getCaptcha, getInfo, login } from '../../api/auth'
|
import { getCaptcha, getInfo, login } from '../../api/auth'
|
||||||
import {
|
import {
|
||||||
|
getToken,
|
||||||
getRememberUsername,
|
getRememberUsername,
|
||||||
removeRememberUsername,
|
removeRememberUsername,
|
||||||
setRememberUsername,
|
setRememberUsername,
|
||||||
@@ -82,6 +83,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
|
/**
|
||||||
|
* 已有登录态时直接进入首页,避免重复展示登录页。
|
||||||
|
*/
|
||||||
|
if (getToken()) {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/home/index'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.username = getRememberUsername()
|
this.username = getRememberUsername()
|
||||||
this.rememberAccount = !!this.username
|
this.rememberAccount = !!this.username
|
||||||
this.refreshCaptcha()
|
this.refreshCaptcha()
|
||||||
@@ -97,7 +108,7 @@ export default {
|
|||||||
this.uuid = captcha.uuid || ''
|
this.uuid = captcha.uuid || ''
|
||||||
this.captchaEnabled = captcha.captchaEnabled !== false
|
this.captchaEnabled = captcha.captchaEnabled !== false
|
||||||
const img = captcha.img || ''
|
const img = captcha.img || ''
|
||||||
this.captchaImg = img.indexOf('data:image') === 0 ? img : `data:image/gif;base64,${img}`
|
this.captchaImg = img.indexOf('data:image') === 0 ? img : `data:image/jpeg;base64,${img}`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.captchaImg = ''
|
this.captchaImg = ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<view class="page-header">
|
||||||
|
<view class="page-title">任务列表</view>
|
||||||
|
<view class="page-subtitle">展示当前任务和最近接收任务,便于一期联调核对任务状态。</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">当前任务</view>
|
||||||
|
<view v-if="currentTask" class="summary-grid">
|
||||||
|
<view class="summary-item">
|
||||||
|
<view class="summary-label">任务号</view>
|
||||||
|
<view class="summary-value">{{ formatTaskNo(currentTask) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="summary-item">
|
||||||
|
<view class="summary-label">任务状态</view>
|
||||||
|
<view class="summary-value">{{ formatStatus(currentTask) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="summary-item">
|
||||||
|
<view class="summary-label">订单号</view>
|
||||||
|
<view class="summary-value">{{ formatOrderNo(currentTask) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="summary-item">
|
||||||
|
<view class="summary-label">金额</view>
|
||||||
|
<view class="summary-value">{{ formatAmount(currentTask) }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-state">当前没有执行中的任务</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">历史任务</view>
|
||||||
|
<view v-if="taskHistory.length">
|
||||||
|
<view v-for="item in taskHistory" :key="item.taskId" class="list-item">
|
||||||
|
<view class="row">
|
||||||
|
<view class="row-left">
|
||||||
|
<view class="row-title">{{ formatTaskNo(item) }}</view>
|
||||||
|
<view class="row-desc">订单号:{{ formatOrderNo(item) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="tag">{{ formatStatus(item) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="row" style="margin-top: 10rpx;">
|
||||||
|
<view class="row-desc">金额:{{ formatAmount(item) }}</view>
|
||||||
|
<view class="row-desc">充值账号:{{ formatRechargeAccount(item) }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-state">暂未接收到任务</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 任务页负责展示当前任务和最近任务接收结果。
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
currentTask() {
|
||||||
|
return this.$store.state.currentTask || null
|
||||||
|
},
|
||||||
|
taskHistory() {
|
||||||
|
return this.$store.state.taskHistory || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 统一读取任务号字段,兼容不同接口字段命名。
|
||||||
|
*/
|
||||||
|
formatTaskNo(task) {
|
||||||
|
return task.taskNo || task.task_no || task.taskId || '--'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 统一读取订单号字段,便于列表展示。
|
||||||
|
*/
|
||||||
|
formatOrderNo(task) {
|
||||||
|
return task.orderNo || task.order_no || '--'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 统一读取状态字段,保证接收状态可见。
|
||||||
|
*/
|
||||||
|
formatStatus(task) {
|
||||||
|
return task.status || '--'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 统一读取金额字段,展示任务关键金额信息。
|
||||||
|
*/
|
||||||
|
formatAmount(task) {
|
||||||
|
const amount = task.amount
|
||||||
|
return amount === undefined || amount === null || amount === '' ? '--' : String(amount)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 统一读取充值账号字段,便于核对任务目标账号。
|
||||||
|
*/
|
||||||
|
formatRechargeAccount(task) {
|
||||||
|
return task.rechargeAccount || task.recharge_account || '--'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -11,6 +11,7 @@ export default new Vuex.Store({
|
|||||||
deviceInfo: null,
|
deviceInfo: null,
|
||||||
wsStatus: 'disconnected',
|
wsStatus: 'disconnected',
|
||||||
currentTask: null,
|
currentTask: null,
|
||||||
|
taskHistory: [],
|
||||||
currentStep: '',
|
currentStep: '',
|
||||||
douyinLoginStatus: 'unknown',
|
douyinLoginStatus: 'unknown',
|
||||||
alipayLoginStatus: 'unknown',
|
alipayLoginStatus: 'unknown',
|
||||||
@@ -32,6 +33,34 @@ export default new Vuex.Store({
|
|||||||
setCurrentTask(state, task) {
|
setCurrentTask(state, task) {
|
||||||
state.currentTask = task
|
state.currentTask = task
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 记录最近接收到的任务,供任务页展示当前任务和历史任务。
|
||||||
|
*/
|
||||||
|
upsertTaskHistory(state, task) {
|
||||||
|
if (!task) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskId = task.taskId || task.id || task.task_no || task.taskNo
|
||||||
|
if (!taskId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextTask = {
|
||||||
|
...task,
|
||||||
|
taskId
|
||||||
|
}
|
||||||
|
const index = state.taskHistory.findIndex((item) => (item.taskId || item.id || item.task_no || item.taskNo) === taskId)
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
state.taskHistory.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.taskHistory.unshift(nextTask)
|
||||||
|
if (state.taskHistory.length > 50) {
|
||||||
|
state.taskHistory.length = 50
|
||||||
|
}
|
||||||
|
},
|
||||||
setCurrentStep(state, step) {
|
setCurrentStep(state, step) {
|
||||||
state.currentStep = step
|
state.currentStep = step
|
||||||
},
|
},
|
||||||
@@ -53,6 +82,7 @@ export default new Vuex.Store({
|
|||||||
state.deviceInfo = null
|
state.deviceInfo = null
|
||||||
state.wsStatus = 'disconnected'
|
state.wsStatus = 'disconnected'
|
||||||
state.currentTask = null
|
state.currentTask = null
|
||||||
|
state.taskHistory = []
|
||||||
state.currentStep = ''
|
state.currentStep = ''
|
||||||
state.douyinLoginStatus = 'unknown'
|
state.douyinLoginStatus = 'unknown'
|
||||||
state.alipayLoginStatus = 'unknown'
|
state.alipayLoginStatus = 'unknown'
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export function request(options) {
|
|||||||
resolve(result)
|
resolve(result)
|
||||||
},
|
},
|
||||||
fail: (error) => {
|
fail: (error) => {
|
||||||
|
console.error('[request fail]', buildUrl(url), error)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '后端接口连接异常',
|
title: '后端接口连接异常',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
let vConsoleLoaded = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅在 H5 开发环境注入 vConsole,方便排查接口、路由和运行时错误。
|
||||||
|
*/
|
||||||
|
export function initVConsole() {
|
||||||
|
if (process.env.UNI_PLATFORM !== 'h5' || process.env.NODE_ENV !== 'development' || vConsoleLoaded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = 'https://unpkg.com/vconsole@3.15.1/dist/vconsole.min.js'
|
||||||
|
script.onload = () => {
|
||||||
|
// 只在开发环境挂载,不影响正式包。
|
||||||
|
// eslint-disable-next-line no-new
|
||||||
|
new window.VConsole()
|
||||||
|
vConsoleLoaded = true
|
||||||
|
}
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user