feat(pda): connect online picking workflow
This commit is contained in:
+1
-1
@@ -47,7 +47,7 @@
|
||||
return true
|
||||
},
|
||||
methods: {
|
||||
openOperation(operation) { if (operation.available) uni.navigateTo({ url: '/pages/picking/picking' }) }
|
||||
openOperation(operation) { if (operation.available) uni.navigateTo({ url: operation.name === '集装箱拣货' ? '/pages/tasks/tasks' : '/pages/operation/operation' }) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+20
-15
@@ -7,32 +7,37 @@
|
||||
<view class="login-card">
|
||||
<view class="login-field"><text class="login-field-label">工号 / 用户名</text><input v-model="form.userId" class="login-field-input" aria-label="工号 / 用户名" placeholder="请输入工号" confirm-type="next" @focus="error = ''" /></view>
|
||||
<view class="login-field"><text class="login-field-label">密码</text><input v-model="form.password" class="login-field-input" aria-label="密码" type="password" placeholder="请输入密码" confirm-type="done" @focus="error = ''" @confirm="login" /></view>
|
||||
<view class="login-field"><text class="login-field-label">作业仓库</text><picker aria-label="作业仓库" :range="warehouses" :value="warehouseIndex" @change="changeWarehouse"><view class="login-field-select"><text>{{ form.warehouse }}</text><text class="select-chevron">⌄</text></view></picker></view>
|
||||
<text v-if="error" class="login-error" role="alert">{{ error }}</text>
|
||||
<button class="login-btn" @tap="login">登录</button>
|
||||
<view class="login-accounts"><text class="login-accounts-title">快捷登录(演示账号)</text><view class="login-quick-btns"><button v-for="account in accounts" :key="account.id" class="login-quick-btn" @tap="enter(account)"><text class="login-quick-btn-name">{{ account.id }} {{ account.name }}</text><text class="login-quick-btn-role"> · {{ account.role }}</text></button></view></view>
|
||||
<button class="login-btn" :disabled="submitting" @tap="login">{{ submitting ? '登录中...' : '登录' }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DEMO_ACCOUNTS, WAREHOUSES } from '@/data/demo-data.js'
|
||||
import { session, signIn } from '@/stores/session.js'
|
||||
import { login as loginRequest } from '@/api/auth.js'
|
||||
import { session, restoreSession, signInWithTokens } from '@/stores/session.js'
|
||||
import { requireOnline } from '@/utils/network.js'
|
||||
export default {
|
||||
data() { return { accounts: DEMO_ACCOUNTS, warehouses: WAREHOUSES, form: { userId: '', password: '', warehouse: WAREHOUSES[0] }, error: '' } },
|
||||
computed: { warehouseIndex() { return Math.max(0, this.warehouses.indexOf(this.form.warehouse)) } },
|
||||
onLoad() { if (session.currentUser) uni.reLaunch({ url: '/pages/home/home' }) },
|
||||
data() { return { form: { userId: '', password: '' }, error: '', submitting: false } },
|
||||
onLoad() { if (session.currentUser || restoreSession()) uni.reLaunch({ url: '/pages/home/home' }) },
|
||||
methods: {
|
||||
changeWarehouse(event) { this.form.warehouse = this.warehouses[Number(event.detail.value)] || this.warehouses[0] },
|
||||
login() {
|
||||
async login() {
|
||||
const userId = this.form.userId.trim()
|
||||
if (!userId) return void (this.error = '请输入工号')
|
||||
if (!this.form.password) return void (this.error = '请输入密码')
|
||||
const account = this.accounts.find((entry) => entry.id === userId)
|
||||
if (!account || account.password !== this.form.password) return void (this.error = '工号或密码错误')
|
||||
this.enter(account)
|
||||
},
|
||||
enter(account) { signIn(account, this.form.warehouse); uni.reLaunch({ url: '/pages/home/home' }) }
|
||||
this.submitting = true
|
||||
this.error = ''
|
||||
try {
|
||||
requireOnline('当前网络不可用,无法登录')
|
||||
const tokens = await loginRequest(userId, this.form.password)
|
||||
signInWithTokens(tokens)
|
||||
uni.reLaunch({ url: '/pages/home/home' })
|
||||
} catch (error) {
|
||||
this.error = error.network ? '当前网络不可用,请联网后重试' : (error.message || '登录失败')
|
||||
} finally {
|
||||
this.submitting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
onLoad(options) { this.moduleKey = options.key || 'outbound' },
|
||||
onShow() { if (!requireSession()) return; this.user = session.currentUser; this.overview = getPickingOverview() },
|
||||
methods: {
|
||||
openOperation(operation) { if (operation.available) uni.navigateTo({ url: '/pages/picking/picking' }) }
|
||||
openOperation(operation) { if (operation.available) uni.navigateTo({ url: operation.name === '集装箱拣货' ? '/pages/tasks/tasks' : '/pages/operation/operation' }) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+42
-22
@@ -140,7 +140,7 @@
|
||||
<input v-model="sealNo" class="seal-input" placeholder="输入封签号(必填)" maxlength="64" />
|
||||
<button class="evidence-btn seal-btn" @tap="saveSealNo">保存封签</button>
|
||||
</view>
|
||||
<text v-if="remoteError" class="remote-error" role="alert">{{ remoteError }}</text>
|
||||
<view v-if="remoteError" class="remote-error-row"><text class="remote-error" role="alert">{{ remoteError }}</text><button class="evidence-retry-btn" :disabled="remoteLoading" @tap="refreshTask">刷新任务</button></view>
|
||||
<button class="loading-complete-btn" :disabled="submitting || loadingVersion === null || !completedBoxes[currentBoxIndex] || !evidenceComplete" @tap="completeLoadingTask">确认装柜完成</button>
|
||||
<text v-if="loadingVersion === null" class="version-hint">等待统一订单详情提供装柜版本号后可提交完成</text>
|
||||
</view>
|
||||
@@ -326,6 +326,8 @@
|
||||
import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js'
|
||||
import { claimContainerPickingTask, completeLoading, completePicking, EVIDENCE_SLOTS, getContainerPickingTask, getEvidence, getPickingTask, isRemoteReady, normalizePickingTask, submitContainerPickingBatch, uploadEvidenceFile, upsertEvidence, voidEvidence } from '@/api/pda-loading.js'
|
||||
import { createEvidenceUploadQueue } from '@/utils/evidence-upload-queue.js'
|
||||
import { isOnline } from '@/utils/network.js'
|
||||
import { stableOperationId } from '@/utils/idempotency.js'
|
||||
import ScanTargetInput from '@/components/ScanTargetInput.vue'
|
||||
import PopupHeader from '@/components/PopupHeader.vue'
|
||||
|
||||
@@ -433,8 +435,7 @@
|
||||
: !this.completedBoxes[entry.index])
|
||||
},
|
||||
remoteSyncState() {
|
||||
if (!this.currentBox.inboundId) return { label: '演示任务', tone: 'local' }
|
||||
if (!isRemoteReady(this.currentBox.inboundId)) return { label: '未连接', tone: 'error' }
|
||||
if (!isRemoteReady(this.currentBox.inboundId || this.currentBox.boxNo)) return { label: '未连接', tone: 'error' }
|
||||
if (this.remoteLoading) return { label: '同步中', tone: 'loading' }
|
||||
if (this.remoteError) return { label: '同步失败', tone: 'error' }
|
||||
return { label: this.remotePick ? '已同步' : '待同步', tone: this.remotePick ? 'ready' : 'local' }
|
||||
@@ -445,11 +446,24 @@
|
||||
},
|
||||
created() {
|
||||
this.evidenceQueue = createEvidenceUploadQueue({
|
||||
upload: (item, onProgress) => uploadEvidenceFile(item.inboundId, item.evidenceType, item.localPath, null, onProgress),
|
||||
upload: (item, onProgress) => uploadEvidenceFile(item.inboundId, item.evidenceType, item.localPath, item.tenantId, onProgress),
|
||||
onChange: (items) => { this.evidenceQueueItems = items }
|
||||
})
|
||||
},
|
||||
onLoad() { requireSession() },
|
||||
onLoad(options = {}) {
|
||||
if (!requireSession()) return
|
||||
if (options.containerNo) {
|
||||
this.boxes = [{
|
||||
boxNo: String(options.containerNo), orderNo: String(options.orderNo || options.inboundCode || ''),
|
||||
inboundId: options.inboundId ? String(options.inboundId) : null,
|
||||
inboundVersion: options.inboundVersion == null ? null : Number(options.inboundVersion),
|
||||
warehouseId: options.warehouseId ? String(options.warehouseId) : null,
|
||||
tenantId: options.tenantId ? String(options.tenantId) : null, items: []
|
||||
}]
|
||||
this.currentBoxIndex = 0
|
||||
this.remoteTaskId = options.taskId ? String(options.taskId) : null
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
if (!requireSession()) return
|
||||
this.startHardwareScanner()
|
||||
@@ -472,10 +486,9 @@
|
||||
restoreRemoteDraft() {
|
||||
const restored = restorePickingSession(this.draftIdentity())
|
||||
if (!restored) return false
|
||||
if (Array.isArray(restored.boxes) && restored.boxes.length) this.boxes = restored.boxes
|
||||
this.currentBoxIndex = Math.min(Number(restored.currentBoxIndex || 0), Math.max(0, this.boxes.length - 1))
|
||||
this.itemStates = restored.itemStates || this.itemStates
|
||||
this.partialQuantities = restored.partialQuantities || {}
|
||||
const itemIds = new Set(this.currentBox.items.map((item) => String(item.id)))
|
||||
this.itemStates = Object.fromEntries(Object.entries(restored.itemStates || {}).filter(([id]) => itemIds.has(String(id))))
|
||||
this.partialQuantities = Object.fromEntries(Object.entries(restored.partialQuantities || {}).filter(([id]) => itemIds.has(String(id))))
|
||||
this.boxDrafts = restored.boxDrafts || {}
|
||||
this.completedBoxes = restored.completedBoxes || {}
|
||||
this.scanHistory = restored.scanHistory || []
|
||||
@@ -680,7 +693,7 @@
|
||||
this.assignmentVersion = this.remotePick.assignmentVersion ?? this.assignmentVersion
|
||||
this.restoreRemoteDraft()
|
||||
if (this.currentBox.inboundId) {
|
||||
const evidence = await getEvidence(this.currentBox.inboundId)
|
||||
const evidence = await getEvidence(this.currentBox.inboundId, this.currentBox.tenantId)
|
||||
this.evidenceFiles = (evidence && evidence.evidences ? evidence.evidences : []).reduce((all, item) => { (all[item.evidenceType] ||= []).push(item); return all }, {})
|
||||
const seal = this.evidenceEntry('SEAL')
|
||||
if (seal) this.sealNo = seal.sealNo || ''
|
||||
@@ -689,12 +702,19 @@
|
||||
this.remoteError = error.network ? '网络不可用,无法开始拣货,恢复网络后请重试' : (error.message || '任务同步失败')
|
||||
} finally { this.remoteLoading = false }
|
||||
},
|
||||
async refreshTask() {
|
||||
if (!this.currentBox.boxNo) return
|
||||
this.persistSession()
|
||||
await this.syncRemoteTask()
|
||||
if (!this.remoteError) this.showToast('任务已刷新,本地拣货标记已保留', 'success')
|
||||
},
|
||||
evidenceUploadLabel(type) {
|
||||
const queued = this.evidenceQueueEntry(type)
|
||||
if (queued) return queued.status === 'UPLOADING' ? `上传中 ${queued.progress || 0}%` : '等待上传'
|
||||
return this.evidenceEntry(type) ? '重新选择' : '拍照/选择'
|
||||
return this.evidenceEntry(type) ? '继续拍照' : '拍照'
|
||||
},
|
||||
chooseEvidence(slot) {
|
||||
if (!isOnline()) { this.showToast('当前网络不可用,请联网后再拍照上传', 'warning'); return }
|
||||
if (typeof uni.chooseImage !== 'function') { this.showToast('当前环境不支持选择照片', 'warning'); return }
|
||||
uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['camera'], success: (result) => {
|
||||
const path = result.tempFilePaths && result.tempFilePaths[0]
|
||||
@@ -706,7 +726,7 @@
|
||||
},
|
||||
async queueEvidenceUpload(type, path) {
|
||||
if (!this.currentBox.inboundId || !isRemoteReady(this.currentBox.inboundId)) { this.showToast('当前会话未连接服务端,证据保留本地', 'warning'); return }
|
||||
const entry = this.evidenceQueue.add({ inboundId: this.currentBox.inboundId, evidenceType: type, localPath: path })
|
||||
const entry = this.evidenceQueue.add({ inboundId: this.currentBox.inboundId, tenantId: this.currentBox.tenantId, evidenceType: type, localPath: path })
|
||||
await this.evidenceQueue.flush()
|
||||
const latest = this.evidenceQueue.snapshot().find((item) => item.id === entry.id)
|
||||
if (latest?.status !== 'SUCCEEDED') { this.remoteError = latest?.error || '文件上传失败'; return }
|
||||
@@ -732,7 +752,7 @@
|
||||
if (!current.fileId) { this.showToast('照片尚未上传完成', 'warning'); return }
|
||||
const payload = { expectedVersion: current.version || 0, evidenceType: type, fileId: current.fileId || null, fileName: current.fileName || null, sealNo: type === 'SEAL' ? this.sealNo : null, remark: null, operationId: operationId || `evidence-${this.remoteTaskId || this.currentBox.inboundId}-${type}-${current.fileId || 'seal'}` }
|
||||
try {
|
||||
const result = await upsertEvidence(this.currentBox.inboundId, payload)
|
||||
const result = await upsertEvidence(this.currentBox.inboundId, payload, this.currentBox.tenantId)
|
||||
this.evidenceFiles = (result && result.evidences ? result.evidences : []).reduce((all, item) => { const list = (all[item.evidenceType] || []).filter((entry) => entry.id || entry.fileId !== item.fileId); const index = list.findIndex((entry) => entry.id === item.id); all[item.evidenceType] = index >= 0 ? list.map((entry, i) => i === index ? item : entry) : [...list, item]; return all }, this.evidenceFiles)
|
||||
this.showToast(`${type === 'SEAL' ? '封签' : '照片'}已保存`, 'success')
|
||||
} catch (error) { this.remoteError = error.network ? '网络失败,证据未提交,请重试' : (error.message || '证据保存失败'); this.showToast('证据保存失败', 'error') }
|
||||
@@ -745,7 +765,7 @@
|
||||
if (!photo?.id || !this.currentBox.inboundId) return
|
||||
const reason = 'PDA现场标记错图'
|
||||
try {
|
||||
await voidEvidence(this.currentBox.inboundId, photo.id, { expectedVersion: photo.version || 0, reason, operationId: `void-${this.currentBox.inboundId}-${photo.id}-${photo.version || 0}` })
|
||||
await voidEvidence(this.currentBox.inboundId, photo.id, { expectedVersion: photo.version || 0, reason, operationId: `void-${this.currentBox.inboundId}-${photo.id}-${photo.version || 0}` }, this.currentBox.tenantId)
|
||||
this.evidenceFiles = { ...this.evidenceFiles, [photo.evidenceType]: (this.evidenceFiles[photo.evidenceType] || []).map((item) => item.id === photo.id ? { ...item, status: 'VOID', voidReason: reason } : item) }
|
||||
this.showToast('照片已作废,可重新拍摄', 'success')
|
||||
} catch (error) {
|
||||
@@ -758,7 +778,7 @@
|
||||
if (!this.completedBoxes[this.currentBoxIndex]) { this.showToast('请先提交拣货完成', 'warning'); return }
|
||||
if (!this.evidenceComplete) { this.showToast('五类有效照片和封签号均为必填', 'warning'); return }
|
||||
this.submitting = true
|
||||
try { await completeLoading(this.currentBox.inboundId, { expectedVersion: this.loadingVersion, remark: 'PDA确认装柜完成' }); this.showToast('已确认装柜完成', 'success') }
|
||||
try { await completeLoading(this.currentBox.inboundId, { expectedVersion: this.loadingVersion, remark: 'PDA确认装柜完成' }, this.currentBox.tenantId); this.showToast('已确认装柜完成', 'success') }
|
||||
catch (error) { this.remoteError = error.network ? '网络失败,装柜完成未提交' : (error.message || '装柜完成失败'); this.showToast('装柜完成失败', 'error') }
|
||||
finally { this.submitting = false }
|
||||
},
|
||||
@@ -876,9 +896,9 @@
|
||||
},
|
||||
async performSubmit(incompleteReason = '') {
|
||||
const beforeSubmit = this.summary
|
||||
const remoteRequired = Boolean(this.currentBox.inboundId || (session.token && session.tenantId))
|
||||
const remoteRequired = Boolean(session.token)
|
||||
if (remoteRequired && !isRemoteReady(this.currentBox.inboundId || this.currentBox.boxNo)) {
|
||||
this.remoteError = '缺少服务端会话或 tenantId,未提交拣货完成'
|
||||
this.remoteError = '缺少服务端任务信息,未提交拣货完成'
|
||||
this.showToast('拣货完成未提交', 'error')
|
||||
return
|
||||
}
|
||||
@@ -887,15 +907,15 @@
|
||||
try {
|
||||
if (!this.remoteTaskId) throw new Error('尚未进入服务端拣货任务,请联网刷新后重试')
|
||||
if (this.pickingVersion === null) throw new Error('缺少拣货版本号 pickingVersion,无法提交')
|
||||
const submitId = this.submitId || `submit-${session.currentUser?.id || 'operator'}-${this.remoteTaskId}-${this.currentBox.boxNo}-${JSON.stringify(this.itemStates)}-${JSON.stringify(this.partialQuantities)}-${incompleteReason}`
|
||||
const submitId = this.submitId || stableOperationId('submit', `${session.currentUser?.id || 'operator'}-${this.remoteTaskId}-${this.currentBox.boxNo}`, { itemStates: this.itemStates, partialQuantities: this.partialQuantities, incompleteReason })
|
||||
this.submitId = submitId
|
||||
const actions = this.currentBox.items.map((item) => {
|
||||
const status = this.itemState(item)
|
||||
const itemId = item.taskItemId ?? item.remoteItemId ?? item.id
|
||||
if (status === ITEM_STATUS.PICKED) return { type: 'PICK', itemId, pickedPackageCount: Number(item.qty || 0), locationIds: item.locationIds || [] }
|
||||
if (status === ITEM_STATUS.PARTIAL) return { type: 'PARTIAL', itemId, finalPickedPackageCount: Number(this.partialQuantities[item.id] || 0), locationIds: item.locationIds || [] }
|
||||
if (status === ITEM_STATUS.SKIPPED) return { type: 'SKIP', itemId, reason: item.skipReason || incompleteReason || '现场跳拣' }
|
||||
if (incompleteReason) return { type: 'SKIP', itemId, reason: `${incompleteReason}(提交时确认未拣)` }
|
||||
if (status === ITEM_STATUS.PICKED) return { type: 'PICK', itemId, pickedPackageCount: Number(item.qty || 0), locationIds: item.locationIds || [], version: Number(item.version || 0) }
|
||||
if (status === ITEM_STATUS.PARTIAL) return { type: 'PARTIAL', itemId, finalPickedPackageCount: Number(this.partialQuantities[item.id] || 0), locationIds: item.locationIds || [], version: Number(item.version || 0) }
|
||||
if (status === ITEM_STATUS.SKIPPED) return { type: 'SKIP', itemId, reason: item.skipReason || incompleteReason || '现场跳拣', version: Number(item.version || 0) }
|
||||
if (incompleteReason) return { type: 'SKIP', itemId, reason: `${incompleteReason}(提交时确认未拣)`, version: Number(item.version || 0) }
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
await submitContainerPickingBatch({ taskId: this.remoteTaskId, containerNo: this.currentBox.boxNo, version: Number(this.pickingVersion) }, actions, { warehouseId: this.currentBox.warehouseId || session.currentUser?.warehouseId || session.currentUser?.warehouse, operationId: submitId })
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<view class="page-shell task-page">
|
||||
<PortalHeader title="拣货任务" :user="user" :fallback-url="'/pages/home/home'" />
|
||||
<view class="task-content">
|
||||
<view class="task-toolbar">
|
||||
<text class="task-title">待处理任务</text>
|
||||
<button class="task-refresh" :disabled="loading" @tap="loadTasks">刷新</button>
|
||||
</view>
|
||||
<view v-if="error" class="task-error" role="alert">{{ error }}</view>
|
||||
<view v-if="loading" class="task-state">正在加载真实任务...</view>
|
||||
<view v-else-if="!tasks.length && !error" class="task-state">暂无待处理拣货任务</view>
|
||||
<view v-for="task in tasks" :key="task.id || task.inboundId || task.displayNo" class="task-card">
|
||||
<view class="task-card-head"><text class="task-code">{{ task.containerNo || '待分配柜次' }}</text><text class="task-status">{{ task.mainStatus || '待拣货' }}</text></view>
|
||||
<text class="task-order">{{ task.displayNo || '未命名订单' }}</text>
|
||||
<text class="task-meta">{{ task.warehouseName || (task.warehouseId ? `仓库 ${task.warehouseId}` : '缺少仓库信息') }}</text>
|
||||
<button class="task-enter" :disabled="!task.containerNo || !task.warehouseId" @tap="enterTask(task)">{{ task.containerNo && task.warehouseId ? '进入并开始拣货' : '缺少任务信息' }}</button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="toast.visible" class="app-toast" :class="toast.type" role="status">{{ toast.message }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PortalHeader from '@/components/PortalHeader.vue'
|
||||
import { listLoadingOrders } from '@/api/pda-loading.js'
|
||||
import { session, requireSession } from '@/stores/session.js'
|
||||
import { isOnline, watchNetwork } from '@/utils/network.js'
|
||||
|
||||
export default {
|
||||
components: { PortalHeader },
|
||||
data() { return { user: session.currentUser, tasks: [], loading: false, error: '', online: true, toast: { visible: false, message: '', type: '' } } },
|
||||
onLoad() { if (!requireSession()) return; this._stopNetwork = watchNetwork((value) => { this.online = value }) },
|
||||
onShow() { if (!requireSession()) return; this.user = session.currentUser; this.loadTasks() },
|
||||
onUnload() { if (this._stopNetwork) this._stopNetwork(); clearTimeout(this._toastTimer) },
|
||||
methods: {
|
||||
async loadTasks() {
|
||||
if (!isOnline()) { this.error = '当前网络不可用,请联网后刷新'; return }
|
||||
this.loading = true; this.error = ''
|
||||
try {
|
||||
const result = await listLoadingOrders({ page: 1, size: 50 })
|
||||
this.tasks = (result.records || []).filter((task) => task.containerNo || task.taskId || task.inboundId)
|
||||
} catch (error) {
|
||||
this.error = error.network ? '当前网络不可用,请联网后刷新' : (error.message || '任务加载失败')
|
||||
} finally { this.loading = false }
|
||||
},
|
||||
enterTask(task) {
|
||||
if (!task.containerNo || !task.warehouseId) { this.showToast('任务缺少柜号或仓库标识,无法开始', 'error'); return }
|
||||
uni.navigateTo({ url: `/pages/picking/picking?containerNo=${encodeURIComponent(task.containerNo)}&warehouseId=${encodeURIComponent(task.warehouseId)}&inboundId=${encodeURIComponent(task.inboundId || '')}&inboundVersion=${encodeURIComponent(task.inboundVersion ?? '')}&tenantId=${encodeURIComponent(task.tenantId || '')}&taskId=${encodeURIComponent(task.taskId || '')}&orderNo=${encodeURIComponent(task.displayNo || '')}` })
|
||||
},
|
||||
showToast(message, type) { clearTimeout(this._toastTimer); this.toast = { visible: true, message, type }; this._toastTimer = setTimeout(() => { this.toast.visible = false }, 2200) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-content { padding: 24px 16px 40px; }
|
||||
.task-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
||||
.task-title { color: #12233f; font-size: 22px; font-weight: 700; }
|
||||
.task-refresh { margin: 0; min-width: 92px; height: 44px; border: 0; border-radius: 10px; background: #1a73e8; color: #fff; font-size: 15px; }
|
||||
.task-refresh[disabled] { opacity: .6; }
|
||||
.task-error, .task-state { padding: 24px 16px; border-radius: 12px; background: #fff; color: #667085; text-align: center; }
|
||||
.task-error { color: #b42318; background: #fff1f0; }
|
||||
.task-card { padding: 16px; margin-bottom: 12px; border: 1px solid #e3eaf3; border-radius: 12px; background: #fff; box-shadow: 0 4px 16px rgba(23, 55, 93, .06); }
|
||||
.task-card-head { display: flex; align-items: center; justify-content: space-between; }
|
||||
.task-code { color: #12233f; font-weight: 700; }
|
||||
.task-status { color: #16794a; font-size: 13px; }
|
||||
.task-order, .task-meta { display: block; margin-top: 8px; color: #667085; font-size: 14px; }
|
||||
.task-enter { width: 100%; margin: 16px 0 0; height: 48px; border: 0; border-radius: 10px; background: #1a73e8; color: #fff; font-size: 16px; }
|
||||
.task-enter[disabled] { background: #d0d5dd; color: #667085; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user