feat 联调报告上传接口
This commit is contained in:
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -3240,8 +3240,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001737:
|
caniuse-lite@1.0.30001812:
|
||||||
resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==}
|
resolution: {integrity: sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==}
|
||||||
|
|
||||||
ccount@2.0.1:
|
ccount@2.0.1:
|
||||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||||
@@ -10260,7 +10260,7 @@ snapshots:
|
|||||||
autoprefixer@10.4.20(postcss@8.5.6):
|
autoprefixer@10.4.20(postcss@8.5.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.25.3
|
browserslist: 4.25.3
|
||||||
caniuse-lite: 1.0.30001737
|
caniuse-lite: 1.0.30001812
|
||||||
fraction.js: 4.3.7
|
fraction.js: 4.3.7
|
||||||
normalize-range: 0.1.2
|
normalize-range: 0.1.2
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
@@ -10410,7 +10410,7 @@ snapshots:
|
|||||||
|
|
||||||
browserslist@4.25.3:
|
browserslist@4.25.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001737
|
caniuse-lite: 1.0.30001812
|
||||||
electron-to-chromium: 1.5.209
|
electron-to-chromium: 1.5.209
|
||||||
node-releases: 2.0.19
|
node-releases: 2.0.19
|
||||||
update-browserslist-db: 1.1.3(browserslist@4.25.3)
|
update-browserslist-db: 1.1.3(browserslist@4.25.3)
|
||||||
@@ -10482,7 +10482,7 @@ snapshots:
|
|||||||
|
|
||||||
camelcase@6.3.0: {}
|
camelcase@6.3.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001737: {}
|
caniuse-lite@1.0.30001812: {}
|
||||||
|
|
||||||
ccount@2.0.1: {}
|
ccount@2.0.1: {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ApiResult, PageResult, ReportStatus } from '../types'
|
import type { ApiResult, ReportStatus } from '../types'
|
||||||
import { request } from '../request'
|
import { request } from '../request'
|
||||||
|
|
||||||
export interface ReportItem {
|
export interface ReportItem {
|
||||||
@@ -70,11 +70,10 @@ export interface UploadReportParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** GET /api/report/list 报告列表 */
|
/** GET /api/report/list 报告列表 */
|
||||||
export function getReportList(params?: Record<string, unknown>) {
|
export function getReportList() {
|
||||||
return request<PageResult<ReportItem>>({
|
return request({
|
||||||
url: '/api/report/list',
|
url: '/api/report/list',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +85,7 @@ export function getReportDetail(reportId: number | string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** POST /api/report/upload 上传报告(JSON) */
|
/** POST /api/report/upload 上传报告数据(JSON) */
|
||||||
export function uploadReport(data: UploadReportParams) {
|
export function uploadReport(data: UploadReportParams) {
|
||||||
return request<ApiResult>({
|
return request<ApiResult>({
|
||||||
url: '/api/report/upload',
|
url: '/api/report/upload',
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import storage from '@/utils/storage'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id?: number
|
reportId?: number
|
||||||
title: string
|
title: string
|
||||||
deviceCode: string
|
deviceCode: string
|
||||||
status: string
|
status: string
|
||||||
@@ -14,25 +12,13 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
function goToDetail() {
|
function goToDetail() {
|
||||||
const key = `report_${Date.now()}`
|
if (props.reportId == null) {
|
||||||
storage.set(key, {
|
uni.showToast({ title: '报告ID缺失', icon: 'none' })
|
||||||
id: props.id,
|
return
|
||||||
title: props.title,
|
}
|
||||||
deviceCode: props.deviceCode,
|
|
||||||
status: props.status,
|
|
||||||
time: props.time,
|
|
||||||
area: props.area,
|
|
||||||
duration: props.duration,
|
|
||||||
areaCount: props.areaCount,
|
|
||||||
passRate: props.passRate,
|
|
||||||
})
|
|
||||||
|
|
||||||
const query = [`key=${key}`]
|
|
||||||
if (props.id != null)
|
|
||||||
query.push(`id=${props.id}`)
|
|
||||||
|
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/report/detail/index?${query.join('&')}`,
|
url: `/pages/report/detail/index?reportId=${props.reportId}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import type { Project } from '@/service/project'
|
import type { Project } from '@/service/project'
|
||||||
|
import { uploadReport } from '@/api/report'
|
||||||
import { useCurrentProject } from '@/composables/useCurrentProject'
|
import { useCurrentProject } from '@/composables/useCurrentProject'
|
||||||
import { historyReportService, projectService, workRecordService } from '@/service'
|
import { historyReportService, projectService, workRecordService } from '@/service'
|
||||||
import { useDbStore } from '@/store/modules/db'
|
import { useDbStore } from '@/store/modules/db'
|
||||||
|
import { getDeviceId } from '@/utils/device'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
|
|
||||||
/** 本地时间字符串,格式与 seed 一致:YYYY-MM-DD HH:mm:ss */
|
/** 本地时间字符串,格式与 seed 一致:YYYY-MM-DD HH:mm:ss */
|
||||||
@@ -127,7 +129,7 @@ export function useProjectLifecycle() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 结束项目 → 已结束,生成历史报告 */
|
/** 结束项目 → 先上传报告,成功后再改本地状态并生成历史报告 */
|
||||||
async function endProject(projectId: number) {
|
async function endProject(projectId: number) {
|
||||||
await withBusy(async () => {
|
await withBusy(async () => {
|
||||||
const project = await projectService.selectById(projectId)
|
const project = await projectService.selectById(projectId)
|
||||||
@@ -136,34 +138,71 @@ export function useProjectLifecycle() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (project.status === 1) {
|
if (project.status === 1) {
|
||||||
uni.showToast({ title: '项目已结束', icon: 'none' })
|
uni.showToast({ title: '项目已经结束', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const endTime = nowDateTime()
|
|
||||||
const open = await findOpenWorkRecord(projectId)
|
|
||||||
if (open?.id) {
|
|
||||||
await workRecordService.update(open.id, {
|
|
||||||
end_time: endTime,
|
|
||||||
duration: calcDurationHours(open.start_time, endTime),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const latest = await projectService.selectById(projectId)
|
const latest = await projectService.selectById(projectId)
|
||||||
if (!latest) {
|
if (!latest) {
|
||||||
uni.showToast({ title: '项目不存在', icon: 'none' })
|
uni.showToast({ title: '项目不存在', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deviceId = getDeviceId() || storage.get<string>('device_unique_id') || ''
|
||||||
|
if (!deviceId || !latest.project_name) {
|
||||||
|
uni.showToast({ title: '上传失败,请重新点击结束项目', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = nowDateTime()
|
||||||
|
const workStartTime = latest.start_time || endTime
|
||||||
const workRecords = await workRecordService.selectByProjectId(projectId)
|
const workRecords = await workRecordService.selectByProjectId(projectId)
|
||||||
const exceptionCount = workRecords.reduce((sum, item) => sum + (item.exception_count ?? 0), 0)
|
const exceptionCount = workRecords.reduce((sum, item) => sum + (item.exception_count ?? 0), 0)
|
||||||
|
|
||||||
|
let uploaded = false
|
||||||
|
try {
|
||||||
|
const res = await uploadReport({
|
||||||
|
deviceId,
|
||||||
|
projectName: latest.project_name,
|
||||||
|
reportNo: `RTK-${projectId}`,
|
||||||
|
workStartTime,
|
||||||
|
workEndTime: endTime,
|
||||||
|
constructionSection: latest.stake_range,
|
||||||
|
compactArea: latest.total_compaction_area ?? 0,
|
||||||
|
qualificationRate: latest.qualified_rate ?? 0,
|
||||||
|
workDuration: latest.total_duration ?? 0,
|
||||||
|
passCount: latest.total_pass_count ?? 0,
|
||||||
|
avgSpeed: latest.avg_speed ?? 0,
|
||||||
|
avgTemperature: latest.avg_paving_temp ?? 0,
|
||||||
|
avgCompaction: latest.avg_compaction ?? 0,
|
||||||
|
exceptionCount,
|
||||||
|
})
|
||||||
|
uploaded = res.code === 200
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
uploaded = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uploaded) {
|
||||||
|
uni.showToast({ title: '上传失败,请重新点击结束项目', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传成功后再改本地状态
|
||||||
|
const open = await findOpenWorkRecord(projectId)
|
||||||
|
if (open?.id) {
|
||||||
|
await workRecordService.update(open.id, {
|
||||||
|
end_time: endTime,
|
||||||
|
duration: calcDurationHours(open.start_time, endTime),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
await historyReportService.insert({
|
await historyReportService.insert({
|
||||||
project_id: projectId,
|
project_id: projectId,
|
||||||
report_name: `${latest.project_name} 碾压报告`,
|
report_name: `${latest.project_name} 碾压报告`,
|
||||||
device_id: storage.get<string>('device_unique_id') || undefined,
|
device_id: deviceId,
|
||||||
construction_area: latest.stake_range,
|
construction_area: latest.stake_range,
|
||||||
start_time: latest.start_time,
|
start_time: workStartTime,
|
||||||
end_time: endTime,
|
end_time: endTime,
|
||||||
duration: latest.total_duration ?? 0,
|
duration: latest.total_duration ?? 0,
|
||||||
compaction_area: latest.total_compaction_area ?? 0,
|
compaction_area: latest.total_compaction_area ?? 0,
|
||||||
@@ -184,7 +223,7 @@ export function useProjectLifecycle() {
|
|||||||
setCurrentProject(null)
|
setCurrentProject(null)
|
||||||
|
|
||||||
await refreshAfterChange(projectId)
|
await refreshAfterChange(projectId)
|
||||||
uni.showToast({ title: '已结束并生成报告', icon: 'success' })
|
uni.showToast({ title: '项目已经结束', icon: 'success' })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"navigationBarTitleText": "Road-Roller"
|
"navigationBarTitleText": "Road-Roller"
|
||||||
},
|
},
|
||||||
"pages": [
|
"pages": [
|
||||||
// GENERATED BY UNI-PAGES, PLATFORM: H5 || APP
|
// GENERATED BY UNI-PAGES, PLATFORM: APP || H5
|
||||||
{
|
{
|
||||||
"path": "pages/Ahome/index",
|
"path": "pages/Ahome/index",
|
||||||
"type": "home",
|
"type": "home",
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
"color": "@tabFontColor",
|
"color": "@tabFontColor",
|
||||||
"selectedColor": "@tabSelectedColor",
|
"selectedColor": "@tabSelectedColor",
|
||||||
"list": [
|
"list": [
|
||||||
// GENERATED BY UNI-PAGES, PLATFORM: H5 || APP
|
// GENERATED BY UNI-PAGES, PLATFORM: APP || H5
|
||||||
{
|
{
|
||||||
"pagePath": "pages/Ahome/index",
|
"pagePath": "pages/Ahome/index",
|
||||||
"text": "首页",
|
"text": "首页",
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { ReportItem } from '@/api/report'
|
||||||
import type { HistoryReport } from '@/service/historyReport'
|
import type { HistoryReport } from '@/service/historyReport'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { uploadReport } from '@/api/report'
|
import { getReportDetail } from '@/api/report'
|
||||||
import { useDbStore } from '@/store/modules/db'
|
import { useDbStore } from '@/store/modules/db'
|
||||||
import { getDeviceId } from '@/utils/device'
|
|
||||||
import storage from '@/utils/storage'
|
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
layout: false,
|
layout: false,
|
||||||
@@ -31,11 +30,13 @@ interface ReportDetailView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const db = useDbStore()
|
const db = useDbStore()
|
||||||
|
const reportId = ref<number | null>(null)
|
||||||
const reportData = ref<ReportDetailView | null>(null)
|
const reportData = ref<ReportDetailView | null>(null)
|
||||||
|
const apiReport = ref<ReportItem | null>(null)
|
||||||
const rawReport = ref<HistoryReport | null>(null)
|
const rawReport = ref<HistoryReport | null>(null)
|
||||||
const uploading = ref(false)
|
const uploading = ref(false)
|
||||||
|
|
||||||
function parseDate(value?: string) {
|
function parseDate(value?: string | null) {
|
||||||
if (!value)
|
if (!value)
|
||||||
return null
|
return null
|
||||||
const date = new Date(value.replace(/-/g, '/'))
|
const date = new Date(value.replace(/-/g, '/'))
|
||||||
@@ -46,7 +47,7 @@ function pad(value: number, length = 2) {
|
|||||||
return String(value).padStart(length, '0')
|
return String(value).padStart(length, '0')
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDateTimeRange(start?: string, end?: string) {
|
function formatDateTimeRange(start?: string | null, end?: string | null) {
|
||||||
const startDate = parseDate(start)
|
const startDate = parseDate(start)
|
||||||
const endDate = parseDate(end)
|
const endDate = parseDate(end)
|
||||||
if (!startDate)
|
if (!startDate)
|
||||||
@@ -64,36 +65,45 @@ function formatDateTimeRange(start?: string, end?: string) {
|
|||||||
return `${datePart}${startHm}-${endDate.getFullYear()}年${pad(endDate.getMonth() + 1)}月${pad(endDate.getDate())}日${endHm}`
|
return `${datePart}${startHm}-${endDate.getFullYear()}年${pad(endDate.getMonth() + 1)}月${pad(endDate.getDate())}日${endHm}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 接口时间格式 yyyy-MM-dd HH:mm:ss */
|
function formatDuration(value?: number | null) {
|
||||||
function formatApiDateTime(value?: string) {
|
|
||||||
const date = parseDate(value)
|
|
||||||
if (!date)
|
|
||||||
return ''
|
|
||||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 报告编号:RTK-项目id */
|
|
||||||
function buildReportNo(item: HistoryReport) {
|
|
||||||
return `RTK-${item.project_id}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDuration(value?: number) {
|
|
||||||
const hours = value ?? 0
|
const hours = value ?? 0
|
||||||
const text = Number.isInteger(hours) ? String(hours) : hours.toFixed(1)
|
const text = Number.isInteger(hours) ? String(hours) : hours.toFixed(1)
|
||||||
return `${text}H`
|
return `${text}H`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatArea(value?: number) {
|
function formatArea(value?: number | null) {
|
||||||
return `${value ?? 0}m²`
|
return `${value ?? 0}m²`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRate(value?: number) {
|
function formatRate(value?: number | null) {
|
||||||
const rate = value ?? 0
|
const rate = value ?? 0
|
||||||
const text = Number.isInteger(rate) ? String(rate) : rate.toFixed(1)
|
const text = Number.isInteger(rate) ? String(rate) : rate.toFixed(1)
|
||||||
return `${text}%`
|
return `${text}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapReportDetail(item: HistoryReport): ReportDetailView {
|
function formatMetric(value: number | null | undefined, unit: string) {
|
||||||
|
return value != null ? `${value}${unit}` : '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapApiReportDetail(item: ReportItem): ReportDetailView {
|
||||||
|
return {
|
||||||
|
deviceCode: item.deviceId || '-',
|
||||||
|
time: formatDateTimeRange(item.workStartTime, item.workEndTime),
|
||||||
|
status: String(item.reportStatus) === '1' ? '有异常' : '作业已完成',
|
||||||
|
duration: formatDuration(item.workDuration),
|
||||||
|
areaCount: formatArea(item.compactArea),
|
||||||
|
passCount: `${item.passCount ?? 0}遍`,
|
||||||
|
passRate: formatRate(item.qualificationRate),
|
||||||
|
avgSpeed: formatMetric(item.avgSpeed, 'km/h'),
|
||||||
|
avgFrequency: formatMetric(item.avgFrequency, 'Hz'),
|
||||||
|
avgAmplitude: formatMetric(item.avgAmplitude, 'mm'),
|
||||||
|
avgTemperature: formatMetric(item.avgTemperature, '℃'),
|
||||||
|
avgCompaction: item.avgCompaction != null ? formatRate(item.avgCompaction) : '-',
|
||||||
|
exceptionCount: String(item.exceptionCount ?? 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapLocalReportDetail(item: HistoryReport): ReportDetailView {
|
||||||
return {
|
return {
|
||||||
deviceCode: item.device_id || '-',
|
deviceCode: item.device_id || '-',
|
||||||
time: formatDateTimeRange(item.start_time, item.end_time),
|
time: formatDateTimeRange(item.start_time, item.end_time),
|
||||||
@@ -102,120 +112,76 @@ function mapReportDetail(item: HistoryReport): ReportDetailView {
|
|||||||
areaCount: formatArea(item.compaction_area),
|
areaCount: formatArea(item.compaction_area),
|
||||||
passCount: `${item.pass_count ?? 0}遍`,
|
passCount: `${item.pass_count ?? 0}遍`,
|
||||||
passRate: formatRate(item.qualified_rate),
|
passRate: formatRate(item.qualified_rate),
|
||||||
avgSpeed: item.avg_speed != null ? `${item.avg_speed}km/h` : '-',
|
avgSpeed: formatMetric(item.avg_speed, 'km/h'),
|
||||||
avgFrequency: '-',
|
avgFrequency: '-',
|
||||||
avgAmplitude: '-',
|
avgAmplitude: '-',
|
||||||
avgTemperature: item.avg_paving_temp != null ? `${item.avg_paving_temp}℃` : '-',
|
avgTemperature: formatMetric(item.avg_paving_temp, '℃'),
|
||||||
avgCompaction: item.avg_compaction != null ? formatRate(item.avg_compaction) : '-',
|
avgCompaction: item.avg_compaction != null ? formatRate(item.avg_compaction) : '-',
|
||||||
exceptionCount: String(item.exception_count ?? 0),
|
exceptionCount: String(item.exception_count ?? 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyStoragePayload(data: Record<string, any>) {
|
/** 兼容 data 嵌套 / 扁平结构 */
|
||||||
reportData.value = {
|
function pickReportPayload(res: unknown): ReportItem | null {
|
||||||
deviceCode: data.deviceCode || '-',
|
if (!res || typeof res !== 'object')
|
||||||
time: data.time || '-',
|
return null
|
||||||
status: data.status || '作业已完成',
|
const raw = res as Record<string, unknown>
|
||||||
duration: data.duration || '-',
|
const nested = raw.data
|
||||||
areaCount: data.areaCount || '-',
|
if (nested && typeof nested === 'object' && 'reportId' in (nested as object))
|
||||||
passCount: data.passCount || '-',
|
return nested as ReportItem
|
||||||
passRate: data.passRate || '-',
|
if ('reportId' in raw)
|
||||||
avgSpeed: data.avgSpeed || '-',
|
return raw as unknown as ReportItem
|
||||||
avgFrequency: data.avgFrequency || '-',
|
return null
|
||||||
avgAmplitude: data.avgAmplitude || '-',
|
|
||||||
avgTemperature: data.avgTemperature || '-',
|
|
||||||
avgCompaction: data.avgCompaction || '-',
|
|
||||||
exceptionCount: data.exceptionCount || '-',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad(async (options) => {
|
async function loadLocalFallback(id: number) {
|
||||||
if (options?.key) {
|
|
||||||
const data = storage.get(options.key)
|
|
||||||
if (data)
|
|
||||||
applyStoragePayload(data)
|
|
||||||
storage.remove(options.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = Number(options?.id)
|
|
||||||
if (!id)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (!db.history_report.length || !db.project.length)
|
if (!db.history_report.length || !db.project.length)
|
||||||
await db.loadAll()
|
await db.loadAll()
|
||||||
|
|
||||||
const item = db.history_report.find(report => report.id === id)
|
const item = db.history_report.find(report => report.id === id)
|
||||||
if (item) {
|
if (item) {
|
||||||
rawReport.value = item
|
rawReport.value = item
|
||||||
reportData.value = mapReportDetail(item)
|
apiReport.value = null
|
||||||
|
reportData.value = mapLocalReportDetail(item)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 优先请求详情接口,失败回退本地库 */
|
||||||
|
async function loadReportDetail() {
|
||||||
|
const id = reportId.value
|
||||||
|
if (!id)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getReportDetail(id)
|
||||||
|
if (res.code === 200) {
|
||||||
|
const payload = pickReportPayload(res)
|
||||||
|
if (payload) {
|
||||||
|
apiReport.value = payload
|
||||||
|
rawReport.value = null
|
||||||
|
reportData.value = mapApiReportDetail(payload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 接口不通,走本地
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadLocalFallback(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad((options) => {
|
||||||
|
const id = Number(options?.reportId || options?.id)
|
||||||
|
reportId.value = id || null
|
||||||
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
loadReportDetail()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handleDownload() {
|
async function handleDownload() {
|
||||||
if (uploading.value)
|
console.log('下载报告')
|
||||||
return
|
|
||||||
|
|
||||||
const item = rawReport.value
|
|
||||||
if (!item) {
|
|
||||||
uni.showToast({ title: '暂无报告数据', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!db.project.length)
|
|
||||||
await db.loadAll()
|
|
||||||
|
|
||||||
const project = db.project.find(p => p.id === item.project_id)
|
|
||||||
const projectName = project?.project_name
|
|
||||||
if (!projectName) {
|
|
||||||
uni.showToast({ title: '项目信息不完整', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const workStartTime = formatApiDateTime(item.start_time)
|
|
||||||
const workEndTime = formatApiDateTime(item.end_time)
|
|
||||||
if (!workStartTime || !workEndTime) {
|
|
||||||
uni.showToast({ title: '作业时间不完整', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const deviceId = getDeviceId() || item.device_id || ''
|
|
||||||
if (!deviceId) {
|
|
||||||
uni.showToast({ title: '设备号缺失', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uploading.value = true
|
|
||||||
uni.showLoading({ title: '上传中...', mask: true })
|
|
||||||
try {
|
|
||||||
const res = await uploadReport({
|
|
||||||
deviceId,
|
|
||||||
projectName,
|
|
||||||
reportNo: buildReportNo(item),
|
|
||||||
workStartTime,
|
|
||||||
workEndTime,
|
|
||||||
constructionSection: item.construction_area,
|
|
||||||
compactArea: item.compaction_area,
|
|
||||||
qualificationRate: item.qualified_rate,
|
|
||||||
workDuration: item.duration,
|
|
||||||
passCount: item.pass_count,
|
|
||||||
avgSpeed: item.avg_speed,
|
|
||||||
avgTemperature: item.avg_paving_temp,
|
|
||||||
avgCompaction: item.avg_compaction,
|
|
||||||
exceptionCount: item.exception_count,
|
|
||||||
})
|
|
||||||
const ok = res.code === 200 || res.code === 0
|
|
||||||
if (!ok)
|
|
||||||
throw new Error(res.msg || '上传失败')
|
|
||||||
|
|
||||||
uni.showToast({ title: '上传成功', icon: 'success' })
|
|
||||||
}
|
|
||||||
catch (e: any) {
|
|
||||||
uni.showToast({ title: e?.message || '上传失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
uploading.value = false
|
|
||||||
uni.hideLoading()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { ReportItem } from '@/api/report'
|
||||||
import type { HistoryReport } from '@/service/historyReport'
|
import type { HistoryReport } from '@/service/historyReport'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { getReportList } from '@/api/report'
|
||||||
import { useDbStore } from '@/store/modules/db'
|
import { useDbStore } from '@/store/modules/db'
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
@@ -10,8 +13,27 @@ definePage({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 列表统一结构,便于接口 / 本地双数据源筛选展示 */
|
||||||
|
interface ReportListRow {
|
||||||
|
id?: number
|
||||||
|
title: string
|
||||||
|
deviceCode: string
|
||||||
|
status: string
|
||||||
|
time: string
|
||||||
|
area: string
|
||||||
|
duration: string
|
||||||
|
areaCount: string
|
||||||
|
passRate: string
|
||||||
|
projectId: string
|
||||||
|
startTime?: string
|
||||||
|
reportStatus: string
|
||||||
|
}
|
||||||
|
|
||||||
const db = useDbStore()
|
const db = useDbStore()
|
||||||
|
|
||||||
|
/** 非 null 表示接口可用,优先用接口数据 */
|
||||||
|
const apiReports = ref<ReportItem[] | null>(null)
|
||||||
|
|
||||||
const datetimerange = ref<string[]>([])
|
const datetimerange = ref<string[]>([])
|
||||||
const reportStatus = ref('')
|
const reportStatus = ref('')
|
||||||
const area = ref('')
|
const area = ref('')
|
||||||
@@ -30,24 +52,48 @@ const reportStatusOptions = [
|
|||||||
{ value: 'pending', text: '待审核' },
|
{ value: 'pending', text: '待审核' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const listRows = computed<ReportListRow[]>(() => {
|
||||||
|
if (apiReports.value) {
|
||||||
|
return apiReports.value
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => (b.reportId ?? 0) - (a.reportId ?? 0))
|
||||||
|
.map(mapApiReportItem)
|
||||||
|
}
|
||||||
|
return db.history_report
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => (b.id ?? 0) - (a.id ?? 0))
|
||||||
|
.map(mapLocalReportItem)
|
||||||
|
})
|
||||||
|
|
||||||
const areaOptions = computed(() => {
|
const areaOptions = computed(() => {
|
||||||
const areas = [...new Set(db.history_report.map(item => item.construction_area).filter(Boolean))] as string[]
|
const areas = [...new Set(listRows.value.map(item => item.area).filter(v => v && v !== '-'))]
|
||||||
return [
|
return [
|
||||||
{ value: 'all', text: '全部' },
|
{ value: 'all', text: '全部' },
|
||||||
...areas.map(text => ({ value: text, text })),
|
...areas.map(text => ({ value: text, text })),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const projectOptions = computed(() => [
|
const projectOptions = computed(() => {
|
||||||
{ value: 'all', text: '全部' },
|
if (apiReports.value) {
|
||||||
...db.project.map(item => ({ value: String(item.id), text: item.project_name })),
|
const map = new Map<string, string>()
|
||||||
])
|
for (const item of apiReports.value) {
|
||||||
|
if (item.projectId != null)
|
||||||
|
map.set(String(item.projectId), item.projectName || String(item.projectId))
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: 'all', text: '全部' },
|
||||||
|
...[...map.entries()].map(([value, text]) => ({ value, text })),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: 'all', text: '全部' },
|
||||||
|
...db.project.map(item => ({ value: String(item.id), text: item.project_name })),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const reports = computed(() => {
|
const reports = computed(() => {
|
||||||
const query = appliedQuery.value
|
const query = appliedQuery.value
|
||||||
return db.history_report
|
return listRows.value.filter(item => matchReport(item, query))
|
||||||
.filter(item => matchReport(item, query))
|
|
||||||
.map(mapReportItem)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function isAll(value: string) {
|
function isAll(value: string) {
|
||||||
@@ -55,16 +101,18 @@ function isAll(value: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function matchReport(
|
function matchReport(
|
||||||
item: HistoryReport,
|
item: ReportListRow,
|
||||||
query: { datetimerange: string[], reportStatus: string, area: string, project: string },
|
query: { datetimerange: string[], reportStatus: string, area: string, project: string },
|
||||||
) {
|
) {
|
||||||
if (!isAll(query.project) && String(item.project_id) !== query.project)
|
if (!isAll(query.project) && item.projectId !== query.project)
|
||||||
return false
|
return false
|
||||||
if (!isAll(query.area) && item.construction_area !== query.area)
|
if (!isAll(query.area) && item.area !== query.area)
|
||||||
return false
|
return false
|
||||||
if (query.reportStatus === 'pending')
|
if (query.reportStatus === 'pending')
|
||||||
return false
|
return false
|
||||||
if (!matchDateRange(item.start_time, query.datetimerange))
|
if (query.reportStatus === 'completed' && item.reportStatus === '1')
|
||||||
|
return false
|
||||||
|
if (!matchDateRange(item.startTime, query.datetimerange))
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -109,23 +157,46 @@ function formatDateTimeRange(start?: string, end?: string) {
|
|||||||
return `${datePart}${startHm}-${endDate.getFullYear()}年${pad(endDate.getMonth() + 1)}月${pad(endDate.getDate())}日${endHm}`
|
return `${datePart}${startHm}-${endDate.getFullYear()}年${pad(endDate.getMonth() + 1)}月${pad(endDate.getDate())}日${endHm}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDuration(value?: number) {
|
function formatDuration(value?: number | null) {
|
||||||
const hours = value ?? 0
|
const hours = value ?? 0
|
||||||
const text = Number.isInteger(hours) ? String(hours) : hours.toFixed(1)
|
const text = Number.isInteger(hours) ? String(hours) : hours.toFixed(1)
|
||||||
return `${text}H`
|
return `${text}H`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatArea(value?: number) {
|
function formatArea(value?: number | null) {
|
||||||
return `${value ?? 0}m²`
|
return `${value ?? 0}m²`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRate(value?: number) {
|
function formatRate(value?: number | null) {
|
||||||
const rate = value ?? 0
|
const rate = value ?? 0
|
||||||
const text = Number.isInteger(rate) ? String(rate) : rate.toFixed(1)
|
const text = Number.isInteger(rate) ? String(rate) : rate.toFixed(1)
|
||||||
return `${text}%`
|
return `${text}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapReportItem(item: HistoryReport) {
|
function mapReportStatus(status?: ReportItem['reportStatus']) {
|
||||||
|
if (String(status) === '1')
|
||||||
|
return '有异常'
|
||||||
|
return '已完成'
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapApiReportItem(item: ReportItem): ReportListRow {
|
||||||
|
return {
|
||||||
|
id: item.reportId,
|
||||||
|
title: item.reportNo || item.projectName || '碾压报告',
|
||||||
|
deviceCode: item.deviceId || '-',
|
||||||
|
status: mapReportStatus(item.reportStatus),
|
||||||
|
time: formatDateTimeRange(item.workStartTime, item.workEndTime),
|
||||||
|
area: item.constructionSection || '-',
|
||||||
|
duration: formatDuration(item.workDuration),
|
||||||
|
areaCount: formatArea(item.compactArea),
|
||||||
|
passRate: formatRate(item.qualificationRate),
|
||||||
|
projectId: item.projectId != null ? String(item.projectId) : '',
|
||||||
|
startTime: item.workStartTime,
|
||||||
|
reportStatus: String(item.reportStatus ?? '0'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapLocalReportItem(item: HistoryReport): ReportListRow {
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
title: item.report_name,
|
title: item.report_name,
|
||||||
@@ -136,6 +207,9 @@ function mapReportItem(item: HistoryReport) {
|
|||||||
duration: formatDuration(item.duration),
|
duration: formatDuration(item.duration),
|
||||||
areaCount: formatArea(item.compaction_area),
|
areaCount: formatArea(item.compaction_area),
|
||||||
passRate: formatRate(item.qualified_rate),
|
passRate: formatRate(item.qualified_rate),
|
||||||
|
projectId: item.project_id != null ? String(item.project_id) : '',
|
||||||
|
startTime: item.start_time,
|
||||||
|
reportStatus: '0',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +222,30 @@ function handleQuery() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
/** 优先拉接口;code 非 200 或请求失败则回退本地 SQLite */
|
||||||
if (!db.history_report.length)
|
async function loadReports() {
|
||||||
await db.loadAll()
|
// 本地库并行准备,不阻塞接口请求
|
||||||
|
const localTask = (!db.history_report.length || !db.project.length)
|
||||||
|
? db.loadAll().catch(() => undefined)
|
||||||
|
: Promise.resolve()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getReportList()
|
||||||
|
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||||
|
apiReports.value = res.rows
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 接口不通,走本地
|
||||||
|
}
|
||||||
|
|
||||||
|
apiReports.value = null
|
||||||
|
await localTask
|
||||||
|
}
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
loadReports()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -213,7 +308,7 @@ onMounted(async () => {
|
|||||||
<!-- 报告列表 -->
|
<!-- 报告列表 -->
|
||||||
<view class="report-header">
|
<view class="report-header">
|
||||||
<text class="report-title">
|
<text class="report-title">
|
||||||
报告列表
|
报告列表11111
|
||||||
</text>
|
</text>
|
||||||
<text class="report-count">
|
<text class="report-count">
|
||||||
共{{ reports.length }}条
|
共{{ reports.length }}条
|
||||||
@@ -226,8 +321,8 @@ onMounted(async () => {
|
|||||||
</view>
|
</view>
|
||||||
<report-item
|
<report-item
|
||||||
v-for="item in reports"
|
v-for="item in reports"
|
||||||
:id="item.id"
|
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
|
:report-id="item.id"
|
||||||
:title="item.title"
|
:title="item.title"
|
||||||
:device-code="item.deviceCode"
|
:device-code="item.deviceCode"
|
||||||
:status="item.status"
|
:status="item.status"
|
||||||
|
|||||||
Reference in New Issue
Block a user