feat 联调历史报告表
This commit is contained in:
2
src/auto-imports.d.ts
vendored
2
src/auto-imports.d.ts
vendored
@@ -261,6 +261,7 @@ declare global {
|
||||
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
||||
const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
|
||||
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
||||
const useProjectLifecycle: typeof import('./composables/useProjectLifecycle')['useProjectLifecycle']
|
||||
const useQuery: typeof import('./composables/useQuery')['useQuery']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
@@ -609,6 +610,7 @@ declare module 'vue' {
|
||||
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
||||
readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
|
||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||
readonly useProjectLifecycle: UnwrapRef<typeof import('./composables/useProjectLifecycle')['useProjectLifecycle']>
|
||||
readonly useQuery: UnwrapRef<typeof import('./composables/useQuery')['useQuery']>
|
||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { Project } from '@/service/project'
|
||||
import { useProjectLifecycle } from '@/composables/useProjectLifecycle'
|
||||
|
||||
const props = defineProps<{
|
||||
item: Project
|
||||
}>()
|
||||
|
||||
const { startProject, pauseProject, endProject } = useProjectLifecycle()
|
||||
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '已暂停',
|
||||
1: '已结束',
|
||||
@@ -30,6 +33,21 @@ function formatArea(value?: number) {
|
||||
const text = Number.isInteger(area) ? String(area) : String(area)
|
||||
return `${text}m²`
|
||||
}
|
||||
|
||||
function onStart() {
|
||||
if (props.item.id)
|
||||
startProject(props.item.id)
|
||||
}
|
||||
|
||||
function onPause() {
|
||||
if (props.item.id)
|
||||
pauseProject(props.item.id)
|
||||
}
|
||||
|
||||
function onEnd() {
|
||||
if (props.item.id)
|
||||
endProject(props.item.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -74,14 +92,14 @@ function formatArea(value?: number) {
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.status !== 1" class="project-actions">
|
||||
<wd-button size="large" round>
|
||||
<view v-if="item.status !== 1" class="project-actions" @tap.stop>
|
||||
<wd-button size="large" round @tap.stop="onStart">
|
||||
开始项目
|
||||
</wd-button>
|
||||
<wd-button type="warning" size="large" round>
|
||||
<wd-button type="warning" size="large" round @tap.stop="onPause">
|
||||
暂停项目
|
||||
</wd-button>
|
||||
<wd-button type="danger" size="large" round>
|
||||
<wd-button type="danger" size="large" round @tap.stop="onEnd">
|
||||
结束项目
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
import { useProjectLifecycle } from '@/composables/useProjectLifecycle'
|
||||
|
||||
const props = defineProps<{
|
||||
id: number
|
||||
projectName: string
|
||||
stakeRange: string
|
||||
status: number
|
||||
@@ -8,6 +11,8 @@ defineProps<{
|
||||
rollerCount: string
|
||||
}>()
|
||||
|
||||
const { startProject, pauseProject, endProject } = useProjectLifecycle()
|
||||
|
||||
function getStatusText(status: number) {
|
||||
const map: Record<number, string> = {
|
||||
0: '已暂停',
|
||||
@@ -25,6 +30,21 @@ function getStatusColor(status: number) {
|
||||
}
|
||||
return map[status] || '#999999'
|
||||
}
|
||||
|
||||
function onStart() {
|
||||
if (props.id)
|
||||
startProject(props.id)
|
||||
}
|
||||
|
||||
function onPause() {
|
||||
if (props.id)
|
||||
pauseProject(props.id)
|
||||
}
|
||||
|
||||
function onEnd() {
|
||||
if (props.id)
|
||||
endProject(props.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -80,13 +100,13 @@ function getStatusColor(status: number) {
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view v-if="status !== 1" class="btn-row">
|
||||
<wd-button size="large" type="primary" round>
|
||||
<wd-button size="large" type="primary" round @tap="onStart">
|
||||
开始项目
|
||||
</wd-button>
|
||||
<wd-button size="large" type="warning" round>
|
||||
<wd-button size="large" type="warning" round @tap="onPause">
|
||||
暂停项目
|
||||
</wd-button>
|
||||
<wd-button size="large" type="danger" round>
|
||||
<wd-button size="large" type="danger" round @tap="onEnd">
|
||||
结束项目
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
196
src/composables/useProjectLifecycle.ts
Normal file
196
src/composables/useProjectLifecycle.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import type { Project } from '@/service/project'
|
||||
import { useCurrentProject } from '@/composables/useCurrentProject'
|
||||
import { historyReportService, projectService, workRecordService } from '@/service'
|
||||
import { useDbStore } from '@/store/modules/db'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
/** 本地时间字符串,格式与 seed 一致:YYYY-MM-DD HH:mm:ss */
|
||||
function nowDateTime() {
|
||||
return new Date().toISOString().replace('T', ' ').slice(0, 19)
|
||||
}
|
||||
|
||||
/** 作业时长(小时),保留一位小数 */
|
||||
function calcDurationHours(start?: string, end?: string) {
|
||||
if (!start || !end)
|
||||
return 0
|
||||
const startMs = new Date(start.replace(/-/g, '/')).getTime()
|
||||
const endMs = new Date(end.replace(/-/g, '/')).getTime()
|
||||
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs <= startMs)
|
||||
return 0
|
||||
return Math.round((endMs - startMs) / (1000 * 60 * 60) * 10) / 10
|
||||
}
|
||||
|
||||
async function findOpenWorkRecord(projectId: number) {
|
||||
const records = await workRecordService.selectByProjectId(projectId)
|
||||
return records.find(item => item.id != null && !item.end_time) ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目开始 / 暂停 / 结束:
|
||||
* - 开始:status=2,新增工作记录(project_id + start_time)
|
||||
* - 暂停:status=0,补全当前工作记录 end_time 与 duration
|
||||
* - 结束:status=1,关闭未结束工作记录,写入历史报告
|
||||
*/
|
||||
export function useProjectLifecycle() {
|
||||
const db = useDbStore()
|
||||
const { currentProjectId, setCurrentProject } = useCurrentProject()
|
||||
let busy = false
|
||||
|
||||
async function refreshAfterChange(projectId: number) {
|
||||
await db.loadAll()
|
||||
if (currentProjectId.value === projectId) {
|
||||
const latest = await projectService.selectById(projectId)
|
||||
if (latest && latest.status !== 1)
|
||||
setCurrentProject(latest)
|
||||
else if (!latest || latest.status === 1)
|
||||
setCurrentProject(null)
|
||||
}
|
||||
}
|
||||
|
||||
async function withBusy(fn: () => Promise<void>) {
|
||||
if (busy)
|
||||
return
|
||||
busy = true
|
||||
try {
|
||||
await fn()
|
||||
}
|
||||
finally {
|
||||
busy = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 开始项目 → 作业中 */
|
||||
async function startProject(projectId: number) {
|
||||
await withBusy(async () => {
|
||||
const project = await projectService.selectById(projectId)
|
||||
if (!project) {
|
||||
uni.showToast({ title: '项目不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (project.status === 1) {
|
||||
uni.showToast({ title: '项目已结束', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (project.status === 2) {
|
||||
uni.showToast({ title: '项目已在作业中', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const open = await findOpenWorkRecord(projectId)
|
||||
if (open) {
|
||||
uni.showToast({ title: '存在未结束的作业记录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const startTime = nowDateTime()
|
||||
await workRecordService.insert({
|
||||
project_id: projectId,
|
||||
start_time: startTime,
|
||||
})
|
||||
|
||||
const payload: Partial<Project> = { status: 2 }
|
||||
if (!project.start_time)
|
||||
payload.start_time = startTime
|
||||
|
||||
await projectService.update(projectId, payload)
|
||||
setCurrentProject({ ...project, ...payload })
|
||||
await refreshAfterChange(projectId)
|
||||
uni.showToast({ title: '已开始项目', icon: 'success' })
|
||||
})
|
||||
}
|
||||
|
||||
/** 暂停项目 → 已暂停,并结束当前工作记录 */
|
||||
async function pauseProject(projectId: number) {
|
||||
await withBusy(async () => {
|
||||
const project = await projectService.selectById(projectId)
|
||||
if (!project) {
|
||||
uni.showToast({ title: '项目不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (project.status !== 2) {
|
||||
uni.showToast({ title: '仅作业中可暂停', icon: 'none' })
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
||||
await projectService.update(projectId, { status: 0 })
|
||||
await refreshAfterChange(projectId)
|
||||
uni.showToast({ title: '已暂停项目', icon: 'success' })
|
||||
})
|
||||
}
|
||||
|
||||
/** 结束项目 → 已结束,生成历史报告 */
|
||||
async function endProject(projectId: number) {
|
||||
await withBusy(async () => {
|
||||
const project = await projectService.selectById(projectId)
|
||||
if (!project) {
|
||||
uni.showToast({ title: '项目不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (project.status === 1) {
|
||||
uni.showToast({ title: '项目已结束', icon: 'none' })
|
||||
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)
|
||||
if (!latest) {
|
||||
uni.showToast({ title: '项目不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const workRecords = await workRecordService.selectByProjectId(projectId)
|
||||
const exceptionCount = workRecords.reduce((sum, item) => sum + (item.exception_count ?? 0), 0)
|
||||
|
||||
await historyReportService.insert({
|
||||
project_id: projectId,
|
||||
report_name: `${latest.project_name} 碾压报告`,
|
||||
device_id: storage.get<string>('device_unique_id') || undefined,
|
||||
construction_area: latest.stake_range,
|
||||
start_time: latest.start_time,
|
||||
end_time: endTime,
|
||||
duration: latest.total_duration ?? 0,
|
||||
compaction_area: latest.total_compaction_area ?? 0,
|
||||
pass_count: latest.total_pass_count ?? 0,
|
||||
qualified_rate: latest.qualified_rate ?? 0,
|
||||
avg_speed: latest.avg_speed ?? 0,
|
||||
avg_paving_temp: latest.avg_paving_temp ?? 0,
|
||||
avg_compaction: latest.avg_compaction ?? 0,
|
||||
exception_count: exceptionCount,
|
||||
})
|
||||
|
||||
await projectService.update(projectId, {
|
||||
status: 1,
|
||||
end_time: endTime,
|
||||
})
|
||||
|
||||
if (currentProjectId.value === projectId)
|
||||
setCurrentProject(null)
|
||||
|
||||
await refreshAfterChange(projectId)
|
||||
uni.showToast({ title: '已结束并生成报告', icon: 'success' })
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
startProject,
|
||||
pauseProject,
|
||||
endProject,
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
"navigationBarTitleText": "Road-Roller"
|
||||
},
|
||||
"pages": [
|
||||
// GENERATED BY UNI-PAGES, PLATFORM: APP
|
||||
// GENERATED BY UNI-PAGES, PLATFORM: H5 || APP
|
||||
{
|
||||
"path": "pages/Ahome/index",
|
||||
"type": "home",
|
||||
@@ -121,7 +121,7 @@
|
||||
"color": "@tabFontColor",
|
||||
"selectedColor": "@tabSelectedColor",
|
||||
"list": [
|
||||
// GENERATED BY UNI-PAGES, PLATFORM: APP
|
||||
// GENERATED BY UNI-PAGES, PLATFORM: H5 || APP
|
||||
{
|
||||
"pagePath": "pages/Ahome/index",
|
||||
"text": "首页",
|
||||
|
||||
@@ -109,6 +109,7 @@ onMounted(async () => {
|
||||
</view>
|
||||
<project-item
|
||||
v-for="item in projects"
|
||||
:id="item.id"
|
||||
:key="item.id"
|
||||
:project-name="item.projectName"
|
||||
:stake-range="item.stakeRange"
|
||||
|
||||
@@ -28,7 +28,7 @@ const tableSqls = [
|
||||
|
||||
/** 当前代码期望的各表字段(用于检测旧库结构) */
|
||||
const expectedColumns: Record<string, string[]> = {
|
||||
project: ['id', 'project_name', 'stake_range', 'total_area', 'start_time', 'end_time', 'total_duration', 'total_compaction_area', 'total_pass_count', 'qualified_rate', 'progress', 'uncompacted_area', 'status', 'created_at', 'updated_at'],
|
||||
project: ['id', 'project_name', 'stake_range', 'total_area', 'start_time', 'end_time', 'total_duration', 'total_compaction_area', 'total_pass_count', 'qualified_rate', 'progress', 'uncompacted_area', 'avg_speed', 'avg_paving_temp', 'avg_compaction', 'status', 'created_at', 'updated_at'],
|
||||
work_record: ['id', 'project_id', 'start_time', 'end_time', 'duration', 'compaction_area', 'pass_count', 'exception_count', 'created_at'],
|
||||
rtk_track: ['id', 'work_record_id', 'latitude', 'longitude', 'speed', 'heading', 'steel_wheel_temp', 'compaction_value', 'pass_count', 'qualified_rate', 'created_at'],
|
||||
warning_record: ['id', 'project_id', 'device_name', 'exception_type', 'exception_level', 'occurred_at', 'site_description', 'disposal_record', 'disposal_type', 'is_read', 'created_at'],
|
||||
|
||||
@@ -15,6 +15,9 @@ export const projectTableSql = `CREATE TABLE IF NOT EXISTS project (
|
||||
qualified_rate REAL DEFAULT 0,
|
||||
progress REAL DEFAULT 0,
|
||||
uncompacted_area REAL DEFAULT 0,
|
||||
avg_speed REAL DEFAULT 0,
|
||||
avg_paving_temp REAL DEFAULT 0,
|
||||
avg_compaction REAL DEFAULT 0,
|
||||
status INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
@@ -33,6 +36,9 @@ export interface Project {
|
||||
qualified_rate?: number // 压实合格率,默认 0
|
||||
progress?: number // 完成进度,默认 0
|
||||
uncompacted_area?: number // 未压实面积 = 总面积 - 压实面积,默认 0
|
||||
avg_speed?: number // 平均行驶速度,默认 0
|
||||
avg_paving_temp?: number // 平均温度,默认 0
|
||||
avg_compaction?: number // 平均压实度,默认 0
|
||||
status: 0 | 1 | 2 // 项目状态,0:已暂停,1:已结束,2:作业中,默认 0
|
||||
created_at?: string // 创建时间,默认当前时间
|
||||
updated_at?: string // 更新时间,默认当前时间
|
||||
|
||||
@@ -26,24 +26,36 @@ async function seedProject() {
|
||||
project_name: '白沟产业园场地',
|
||||
stake_range: '1号地块',
|
||||
total_area: 5200.0,
|
||||
avg_speed: 5.1,
|
||||
avg_paving_temp: 148.0,
|
||||
avg_compaction: 93.2,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
project_name: '大兴机场配套道路',
|
||||
stake_range: '北侧路段',
|
||||
total_area: 8800.0,
|
||||
avg_speed: 4.9,
|
||||
avg_paving_temp: 152.0,
|
||||
avg_compaction: 94.1,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
project_name: '保定城区改造工程',
|
||||
stake_range: '朝阳路 ~ 裕华路',
|
||||
total_area: 12000.0,
|
||||
avg_speed: 5.3,
|
||||
avg_paving_temp: 145.0,
|
||||
avg_compaction: 91.8,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
project_name: '沧州港区道路',
|
||||
stake_range: 'B区 ~ D区',
|
||||
total_area: 9500.0,
|
||||
avg_speed: 4.7,
|
||||
avg_paving_temp: 150.0,
|
||||
avg_compaction: 95.3,
|
||||
status: 1,
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user