feat: Batch 3 - business pages (19 pages)

Agent Loop: 3 agents, all passed iteration 1
- 7 report/problem pages (flooded/inspection/equipment/supervise/project/construction)
- 6 supervisor+records pages
- 6 equipment+records pages
This commit is contained in:
2026-06-15 21:18:52 +08:00
parent 9c9bd40da6
commit 35810730e8
20 changed files with 3170 additions and 0 deletions

View File

@@ -210,6 +210,162 @@ const routes: RouteRecordRaw[] = [
title: '养护检查',
},
},
{
path: '/maintenanceCheckRecords',
name: 'MaintenanceCheckRecords',
component: () => import('@/views/maintenanceCheckRecords/index.vue'),
meta: {
title: '养护检查记录',
},
},
{
path: '/maintenanceCheckRecords/detail',
name: 'MaintenanceCheckRecordsDetail',
component: () => import('@/views/maintenanceCheckRecords/detail.vue'),
meta: {
title: '检查记录详情',
},
},
// ── 第三方监督模块 ──
{
path: '/supervisor',
name: 'Supervisor',
component: () => import('@/views/supervisor/index.vue'),
meta: {
title: '第三方监督人员',
},
},
{
path: '/supervisor/detail',
name: 'SupervisorDetail',
component: () => import('@/views/supervisor/detail.vue'),
meta: {
title: '人员详情',
},
},
{
path: '/superviseRecord',
name: 'SuperviseRecord',
component: () => import('@/views/superviseRecord/index.vue'),
meta: {
title: '监督记录',
},
},
{
path: '/superviseRecord/detail',
name: 'SuperviseRecordDetail',
component: () => import('@/views/superviseRecord/detail.vue'),
meta: {
title: '记录详情',
},
},
// ── 监测设备模块 ──
{
path: '/monitoringEquipment/:type?',
name: 'MonitoringEquipment',
component: () => import('@/views/monitoringEquipment/index.vue'),
meta: {
title: '监测设备',
},
},
{
path: '/equipmentInfo',
name: 'EquipmentInfo',
component: () => import('@/views/monitoringEquipment/equipmentInfo.vue'),
meta: {
title: '设备详情',
},
},
{
path: '/mapMonitoring',
name: 'MapMonitoring',
component: () => import('@/views/monitoringEquipment/mapMonitoring.vue'),
meta: {
title: '地图监控',
},
},
{
path: '/monitoringDetail',
name: 'MonitoringDetail',
component: () => import('@/views/monitoringEquipment/monitoringDetail.vue'),
meta: {
title: '监测详情',
},
},
// ── 有限空间作业记录模块 ──
{
path: '/yxkjzyRecords',
name: 'YxkjzyRecords',
component: () => import('@/views/yxkjzyRecords/index.vue'),
meta: {
title: '有限空间作业记录',
},
},
{
path: '/yxkjzyRecordsDetail/:detail?',
name: 'YxkjzyRecordsDetail',
component: () => import('@/views/yxkjzyRecords/detail.vue'),
meta: {
title: '作业记录详情',
},
},
// ── 问题上报模块 ──
{
path: '/problemReport',
name: 'ProblemReport',
component: () => import('@/views/problemReport/index.vue'),
meta: {
title: '问题上报',
},
},
{
path: '/reportFloodedPoints',
name: 'ReportFloodedPoints',
component: () => import('@/views/problemReport/reportFloodedPoints.vue'),
meta: {
title: '积淹点上报',
},
},
{
path: '/reportInspection',
name: 'ReportInspection',
component: () => import('@/views/problemReport/reportInspection.vue'),
meta: {
title: '巡检问题上报',
},
},
{
path: '/reportEquipmentRepair',
name: 'ReportEquipmentRepair',
component: () => import('@/views/problemReport/reportEquipmentRepair.vue'),
meta: {
title: '设备报修',
},
},
{
path: '/reportSupervise/:detail?',
name: 'ReportSupervise',
component: () => import('@/views/problemReport/reportSupervise.vue'),
meta: {
title: '督办问题上报',
},
},
{
path: '/reportProject/:detail?',
name: 'ReportProject',
component: () => import('@/views/problemReport/reportProject.vue'),
meta: {
title: '项目问题上报',
},
},
{
path: '/reportConstruction/:detail?',
name: 'ReportConstruction',
component: () => import('@/views/problemReport/reportConstruction.vue'),
meta: {
title: '施工问题上报',
},
},
// 404 兜底
{
path: '/:pathMatch(.*)*',

View File

@@ -0,0 +1,219 @@
<script setup lang="ts">
/**
* 养护检查记录详情页
*
* 展示检查记录的详细信息,包含基本信息、评分详情、检查内容、照片和结论。
*/
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { showImagePreview } from 'vant'
const router = useRouter()
const route = useRoute()
const detailId = (route.query.id as string) || '1'
/** 模拟检查记录详情 */
const record = ref({
id: detailId,
project: '城北供水管网养护',
inspector: '张检查员',
result: 'qualified',
score: 95,
date: '2025-06-15',
time: '14:00 - 16:30',
address: '城北工业大道1-50号',
content: '对城北片区供水管网养护工作进行验收检查,检查内容包括管道冲洗效果、阀门润滑情况、井盖完整性等。',
conclusion: '养护工作总体良好,各项指标达标,同意验收。',
remark: '建议加强井盖周围杂草清理。',
createTime: '2025-06-15 17:00:00',
scoreDetail: [
{ name: '管道冲洗', score: 18, maxScore: 20 },
{ name: '阀门润滑', score: 19, maxScore: 20 },
{ name: '井盖检查', score: 20, maxScore: 20 },
{ name: '防腐处理', score: 19, maxScore: 20 },
{ name: '安全措施', score: 19, maxScore: 20 },
],
photos: [
{ id: 1, url: 'https://via.placeholder.com/400x300/4CAF50/white?text=Check+1', desc: '管道冲洗效果' },
{ id: 2, url: 'https://via.placeholder.com/400x300/2196F3/white?text=Check+2', desc: '阀门润滑情况' },
{ id: 3, url: 'https://via.placeholder.com/400x300/FF9800/white?text=Check+3', desc: '井盖完整性' },
{ id: 4, url: 'https://via.placeholder.com/400x300/9C27B0/white?text=Check+4', desc: '现场安全措施' },
],
})
const resultMap: Record<string, string> = {
qualified: '合格',
unqualified: '不合格',
}
const resultColorMap: Record<string, 'success' | 'danger'> = {
qualified: 'success',
unqualified: 'danger',
}
/** 评分颜色 */
function scoreColor(score: number): string {
if (score >= 90) return '#4CAF50'
if (score >= 70) return '#FF9800'
return '#F44336'
}
/** 单项评分颜色 */
function itemScoreColor(score: number, maxScore: number): string {
const ratio = score / maxScore
if (ratio >= 0.9) return '#4CAF50'
if (ratio >= 0.7) return '#FF9800'
return '#F44336'
}
/** 预览照片 */
function previewPhoto(index: number) {
showImagePreview({
images: record.value.photos.map(p => p.url),
startPosition: index,
})
}
</script>
<template>
<div class="check-detail-page">
<van-nav-bar title="检查记录详情" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
<!-- 结果与评分 -->
<div class="result-bar">
<van-tag :type="resultColorMap[record.result]" size="large">
{{ resultMap[record.result] }}
</van-tag>
<div class="total-score" :style="{ color: scoreColor(record.score) }">
{{ record.score }}
</div>
</div>
<!-- 基本信息 -->
<van-cell-group title="基本信息" class="info-group">
<van-cell title="检查项目" :value="record.project" />
<van-cell title="检查员" :value="record.inspector" />
<van-cell title="检查日期" :value="record.date" />
<van-cell title="检查时间" :value="record.time" />
<van-cell title="检查地址" :value="record.address" />
</van-cell-group>
<!-- 评分详情 -->
<van-cell-group title="评分详情" class="info-group">
<van-cell v-for="item in record.scoreDetail" :key="item.name" :title="item.name">
<template #value>
<span :style="{ color: itemScoreColor(item.score, item.maxScore), fontWeight: 'bold' }">
{{ item.score }} / {{ item.maxScore }}
</span>
</template>
</van-cell>
</van-cell-group>
<!-- 检查内容 -->
<van-cell-group title="检查内容" class="info-group">
<van-cell>
<p class="desc-text">{{ record.content }}</p>
</van-cell>
</van-cell-group>
<!-- 现场照片 -->
<van-cell-group title="现场照片" class="info-group">
<van-cell>
<div class="photo-grid">
<div
v-for="(photo, index) in record.photos"
:key="photo.id"
class="photo-item"
@click="previewPhoto(index)"
>
<van-image
:src="photo.url"
width="100%"
height="100"
fit="cover"
radius="6"
/>
<p class="photo-desc">{{ photo.desc }}</p>
</div>
</div>
</van-cell>
</van-cell-group>
<!-- 结论与备注 -->
<van-cell-group title="检查结论" class="info-group">
<van-cell>
<p class="desc-text">{{ record.conclusion }}</p>
</van-cell>
</van-cell-group>
<van-cell-group title="备注" class="info-group">
<van-cell>
<p class="desc-text">{{ record.remark }}</p>
</van-cell>
</van-cell-group>
<!-- 时间信息 -->
<van-cell-group title="时间信息" class="info-group">
<van-cell title="创建时间" :value="record.createTime" />
</van-cell-group>
</div>
</template>
<style lang="scss" scoped>
.check-detail-page {
min-height: 100vh;
background: var(--color-bg-page);
padding-bottom: 24px;
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.result-bar {
padding: 12px 16px;
background: var(--color-bg-card);
text-align: center;
}
.total-score {
margin-top: 8px;
font-size: 28px;
font-weight: 700;
}
.info-group {
margin-top: 8px;
}
.desc-text {
font-size: 14px;
line-height: 1.6;
color: var(--color-text-regular);
padding: 4px 0;
}
.photo-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.photo-item {
cursor: pointer;
}
.photo-desc {
margin: 4px 0 0 0;
font-size: 11px;
color: var(--color-text-placeholder);
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,142 @@
<script setup lang="ts">
/**
* 养护检查记录列表页
*
* 展示养护检查记录,支持搜索和结果筛选(全部/合格/不合格),
* 卡片中显示评分,点击跳转检查记录详情。
*/
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
/** 搜索关键词 */
const searchText = ref('')
/** 当前激活的 Tab */
const activeTab = ref(0)
/** 模拟检查记录数据 */
const mockCheckRecords = [
{ id: 1, project: '城北供水管网养护', inspector: '张检查员', result: 'qualified', score: 95, date: '2025-06-15', content: '管道冲洗、阀门润滑、井盖检查' },
{ id: 2, project: '高新区阀门养护', inspector: '李检查员', result: 'unqualified', score: 58, date: '2025-06-14', content: '部分阀门操作卡涩,需重新养护' },
{ id: 3, project: '老城区水表更换', inspector: '王检查员', result: 'qualified', score: 88, date: '2025-06-13', content: '水表安装规范,计量准确' },
{ id: 4, project: '商业区消防栓检查', inspector: '赵检查员', result: 'qualified', score: 92, date: '2025-06-12', content: '消防栓出水正常,压力达标' },
{ id: 5, project: '东区泵站养护', inspector: '孙检查员', result: 'unqualified', score: 62, date: '2025-06-11', content: '泵站噪音超标,需维修后复查' },
]
/** 结果映射 */
const resultMap: Record<string, string> = {
qualified: '合格',
unqualified: '不合格',
}
const resultColorMap: Record<string, 'success' | 'danger'> = {
qualified: 'success',
unqualified: 'danger',
}
/** 评分颜色 */
function scoreColor(score: number): string {
if (score >= 90) return '#4CAF50'
if (score >= 70) return '#FF9800'
return '#F44336'
}
/** 筛选后的记录列表 */
const filteredRecords = computed(() => {
let list = mockCheckRecords
if (searchText.value) {
const kw = searchText.value.toLowerCase()
list = list.filter(r =>
r.project.toLowerCase().includes(kw) ||
r.inspector.toLowerCase().includes(kw) ||
r.content.toLowerCase().includes(kw)
)
}
if (activeTab.value === 1) list = list.filter(r => r.result === 'qualified')
else if (activeTab.value === 2) list = list.filter(r => r.result === 'unqualified')
return list
})
/** 跳转详情 */
function goDetail(id: number) {
router.push(`/maintenanceCheckRecords/detail?id=${id}`)
}
</script>
<template>
<div class="check-records-page">
<van-nav-bar title="养护检查记录" left-arrow fixed placeholder @click-left="router.back()" />
<van-search v-model="searchText" placeholder="搜索项目、检查员、内容" shape="round" />
<van-tabs v-model:active="activeTab" sticky>
<van-tab title="全部" />
<van-tab title="合格" />
<van-tab title="不合格" />
</van-tabs>
<div class="record-list">
<van-empty v-if="filteredRecords.length === 0" description="暂无检查记录" />
<van-card
v-for="record in filteredRecords"
:key="record.id"
:title="record.project"
:desc="`检查员: ${record.inspector}`"
@click="goDetail(record.id)"
>
<template #tags>
<van-tag :type="resultColorMap[record.result]" size="medium">
{{ resultMap[record.result] }}
</van-tag>
<van-tag size="medium" :color="scoreColor(record.score)" text-color="#fff">
{{ record.score }}
</van-tag>
</template>
<template #footer>
<div class="record-meta">
<span>{{ record.date }}</span>
<span>{{ record.content }}</span>
</div>
</template>
</van-card>
</div>
</div>
</template>
<style lang="scss" scoped>
.check-records-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.record-list {
padding: 0 8px;
:deep(.van-card) {
margin: 8px;
border-radius: 10px;
background: var(--color-bg-card);
}
:deep(.van-tag) {
margin-right: 4px;
}
}
.record-meta {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 12px;
color: var(--color-text-secondary);
}
</style>

View File

@@ -0,0 +1,133 @@
<script setup lang="ts">
/**
* 设备详情页
*
* 展示监测设备的详细信息,包含基本参数、运行状态、
* 最新数据和维护记录。
*/
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const detailId = (route.query.id as string) || '1'
/** 模拟设备详情 */
const equipment = ref({
id: detailId,
name: '流量监测仪 A-01',
type: 'flow_meter',
typeLabel: '流量计',
model: 'LDG-200S',
sn: 'SN2024LDG00201',
status: 'normal',
location: '城北供水管网1号节点',
installDate: '2025-01-15',
manufacturer: '南方测控科技有限公司',
range: '0-500 m³/h',
precision: '±0.5%',
protocol: 'Modbus RTU',
ipRating: 'IP68',
lastData: '125.6 m³/h',
lastUpdateTime: '2025-06-15 08:30:00',
batteryLevel: 85,
signalStrength: 92,
maintenanceRecords: [
{ date: '2025-05-20', type: '定期巡检', result: '正常', operator: '张维护员' },
{ date: '2025-04-15', type: '校准', result: '合格', operator: '李技术员' },
{ date: '2025-03-10', type: '电池更换', result: '已完成', operator: '王工程师' },
],
})
const statusMap: Record<string, string> = {
normal: '正常',
alarm: '报警',
offline: '离线',
}
const statusColorMap: Record<string, string> = {
normal: 'success',
alarm: 'danger',
offline: '#999',
}
</script>
<template>
<div class="equipment-detail-page">
<van-nav-bar title="设备详情" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
<!-- 状态标签 -->
<div class="status-bar">
<van-tag :type="statusColorMap[equipment.status] as never" size="large">
{{ statusMap[equipment.status] }}
</van-tag>
</div>
<!-- 基本信息 -->
<van-cell-group title="基本信息" class="info-group">
<van-cell title="设备名称" :value="equipment.name" />
<van-cell title="设备类型" :value="equipment.typeLabel" />
<van-cell title="设备型号" :value="equipment.model" />
<van-cell title="设备编号" :value="equipment.sn" />
<van-cell title="安装位置" :value="equipment.location" />
<van-cell title="安装日期" :value="equipment.installDate" />
<van-cell title="生产厂家" :value="equipment.manufacturer" />
</van-cell-group>
<!-- 技术参数 -->
<van-cell-group title="技术参数" class="info-group">
<van-cell title="量程范围" :value="equipment.range" />
<van-cell title="精度等级" :value="equipment.precision" />
<van-cell title="通信协议" :value="equipment.protocol" />
<van-cell title="防护等级" :value="equipment.ipRating" />
</van-cell-group>
<!-- 运行数据 -->
<van-cell-group title="运行数据" class="info-group">
<van-cell title="最新数据" :value="equipment.lastData" />
<van-cell title="更新时间" :value="equipment.lastUpdateTime" />
<van-cell title="电池电量" :value="`${equipment.batteryLevel}%`" />
<van-cell title="信号强度" :value="`${equipment.signalStrength}%`" />
</van-cell-group>
<!-- 维护记录 -->
<van-cell-group title="维护记录" class="info-group">
<van-cell v-for="(record, idx) in equipment.maintenanceRecords" :key="idx" :title="record.date">
<template #label>
<span>{{ record.type }} - {{ record.operator }}</span>
</template>
<template #value>
<van-tag :type="record.result === '正常' || record.result === '合格' || record.result === '已完成' ? 'success' : 'warning'" size="medium">
{{ record.result }}
</van-tag>
</template>
</van-cell>
</van-cell-group>
</div>
</template>
<style lang="scss" scoped>
.equipment-detail-page {
min-height: 100vh;
background: var(--color-bg-page);
padding-bottom: 24px;
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.status-bar {
padding: 12px 16px;
background: var(--color-bg-card);
text-align: center;
}
.info-group {
margin-top: 8px;
}
</style>

View File

@@ -0,0 +1,169 @@
<script setup lang="ts">
/**
* 监测设备列表页
*
* 展示监测设备列表,支持搜索和类型筛选,
* 点击卡片跳转设备详情,可进入地图监控和监测详情。
*/
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
/** 搜索关键词 */
const searchText = ref('')
/** 当前激活的 Tab */
const activeTab = ref(0)
/** 模拟监测设备数据 */
const mockEquipments = [
{ id: 1, name: '流量监测仪 A-01', type: 'flow_meter', typeLabel: '流量计', status: 'normal', location: '城北供水管网1号节点', lastData: '125.6 m³/h', updateTime: '2025-06-15 08:30' },
{ id: 2, name: '压力传感器 P-03', type: 'pressure_sensor', typeLabel: '压力传感器', status: 'alarm', location: '高新区主管网3号泵站', lastData: '0.85 MPa', updateTime: '2025-06-15 08:25' },
{ id: 3, name: '水质监测仪 W-07', type: 'water_quality', typeLabel: '水质监测仪', status: 'normal', location: '老城区饮用水源口', lastData: 'pH 7.2 / 浊度 0.5NTU', updateTime: '2025-06-15 08:28' },
{ id: 4, name: '液位计 L-02', type: 'liquid_level', typeLabel: '液位计', status: 'offline', location: '东区蓄水池2号', lastData: '3.2 m', updateTime: '2025-06-14 22:00' },
{ id: 5, name: '流量监测仪 A-05', type: 'flow_meter', typeLabel: '流量计', status: 'normal', location: '商业区供水管网12号', lastData: '98.3 m³/h', updateTime: '2025-06-15 08:32' },
{ id: 6, name: '水质监测仪 W-02', type: 'water_quality', typeLabel: '水质监测仪', status: 'alarm', location: '南城污水处理厂出水口', lastData: 'COD 85mg/L (超标)', updateTime: '2025-06-15 08:20' },
{ id: 7, name: '压力传感器 P-07', type: 'pressure_sensor', typeLabel: '压力传感器', status: 'normal', location: '城西加压站', lastData: '0.62 MPa', updateTime: '2025-06-15 08:31' },
{ id: 8, name: '液位计 L-05', type: 'liquid_level', typeLabel: '液位计', status: 'offline', location: '北区污水井5号', lastData: '1.8 m', updateTime: '2025-06-14 18:00' },
]
/** 状态映射 */
const statusMap: Record<string, string> = {
normal: '正常',
alarm: '报警',
offline: '离线',
}
const statusColorMap: Record<string, string> = {
normal: 'success',
alarm: 'danger',
offline: '#999',
}
/** 类型 Tab 映射 */
const typeTabs = ['全部', '流量计', '压力传感器', '水质监测仪', '液位计']
const typeKeys = ['', 'flow_meter', 'pressure_sensor', 'water_quality', 'liquid_level']
/** 筛选后的列表 */
const filteredEquipments = computed(() => {
let list = mockEquipments
if (searchText.value) {
const kw = searchText.value.toLowerCase()
list = list.filter(e =>
e.name.toLowerCase().includes(kw) ||
e.typeLabel.toLowerCase().includes(kw) ||
e.location.toLowerCase().includes(kw)
)
}
if (activeTab.value > 0 && typeKeys[activeTab.value]) {
list = list.filter(e => e.type === typeKeys[activeTab.value])
}
return list
})
/** 跳转设备详情 */
function goEquipmentInfo(id: number) {
router.push(`/equipmentInfo?id=${id}`)
}
/** 跳转地图监控 */
function goMapMonitoring() {
router.push('/mapMonitoring')
}
</script>
<template>
<div class="equipment-page">
<van-nav-bar
title="监测设备"
left-arrow fixed placeholder
@click-left="router.back()"
>
<template #right>
<van-icon name="map-marked" size="20" @click="goMapMonitoring" />
</template>
</van-nav-bar>
<van-search v-model="searchText" placeholder="搜索设备名称、类型、位置" shape="round" />
<van-tabs v-model:active="activeTab" sticky scrollspy>
<van-tab v-for="(tab, idx) in typeTabs" :key="idx" :title="tab" />
</van-tabs>
<div class="equipment-list">
<van-empty v-if="filteredEquipments.length === 0" description="暂无监测设备" />
<van-card
v-for="equip in filteredEquipments"
:key="equip.id"
:title="equip.name"
:desc="`类型: ${equip.typeLabel}`"
@click="goEquipmentInfo(equip.id)"
>
<template #tags>
<van-tag :color="statusColorMap[equip.status]" text-color="#fff" size="medium" v-if="equip.status !== 'normal'">
{{ statusMap[equip.status] }}
</van-tag>
<van-tag type="success" size="medium" v-else>
{{ statusMap[equip.status] }}
</van-tag>
</template>
<template #footer>
<div class="equipment-meta">
<span class="meta-location">{{ equip.location }}</span>
<span class="meta-data">最新数据: {{ equip.lastData }}</span>
<span class="meta-time">更新时间: {{ equip.updateTime }}</span>
</div>
</template>
</van-card>
</div>
</div>
</template>
<style lang="scss" scoped>
.equipment-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.equipment-list {
padding: 0 8px;
:deep(.van-card) {
margin: 8px;
border-radius: 10px;
background: var(--color-bg-card);
}
:deep(.van-tag) {
margin-right: 4px;
}
}
.equipment-meta {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 12px;
color: var(--color-text-secondary);
.meta-location {
color: var(--color-text-regular);
}
.meta-data {
font-weight: 500;
}
.meta-time {
color: var(--color-text-placeholder);
}
}
</style>

View File

@@ -0,0 +1,172 @@
<script setup lang="ts">
/**
* 地图监控页
*
* 在地图上展示监测设备位置和状态,
* 可点击设备查看实时数据。
*/
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
/** 地图加载状态 */
const mapLoaded = ref(false)
/** 模拟设备点位 */
const devicePoints = ref([
{ id: 1, name: '流量监测仪 A-01', status: 'normal', lat: 30.5, lng: 104.0 },
{ id: 2, name: '压力传感器 P-03', status: 'alarm', lat: 30.51, lng: 104.02 },
{ id: 3, name: '水质监测仪 W-07', status: 'normal', lat: 30.48, lng: 103.98 },
{ id: 4, name: '液位计 L-02', status: 'offline', lat: 30.52, lng: 104.05 },
])
const statusColorMap: Record<string, string> = {
normal: '#4CAF50',
alarm: '#F44336',
offline: '#999',
}
/** 跳转设备详情 */
function goEquipmentInfo(id: number) {
router.push(`/equipmentInfo?id=${id}`)
}
</script>
<template>
<div class="map-monitoring-page">
<van-nav-bar title="地图监控" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
<!-- 地图占位区 -->
<div class="map-placeholder">
<van-icon name="location-o" size="48" color="#ccc" />
<p class="map-title">地图监控</p>
<p class="map-hint">设备点位将在此地图上展示</p>
</div>
<!-- 设备点位列表面板底部浮动 -->
<div class="device-panel">
<div class="panel-header">
<span class="panel-title">设备列表</span>
<span class="panel-count"> {{ devicePoints.length }} </span>
</div>
<div
v-for="point in devicePoints"
:key="point.id"
class="device-item"
@click="goEquipmentInfo(point.id)"
>
<div class="device-dot" :style="{ backgroundColor: statusColorMap[point.status] }" />
<div class="device-info">
<span class="device-name">{{ point.name }}</span>
<span class="device-coord">{{ point.lat }}, {{ point.lng }}</span>
</div>
<van-icon name="arrow" color="#ccc" />
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.map-monitoring-page {
min-height: 100vh;
background: var(--color-bg-page);
display: flex;
flex-direction: column;
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.map-placeholder {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f5f7fa;
margin: 8px;
border-radius: 10px;
border: 1px dashed #ddd;
min-height: 300px;
.map-title {
margin: 12px 0 4px 0;
font-size: 18px;
font-weight: 600;
color: var(--color-text-regular);
}
.map-hint {
font-size: 13px;
color: var(--color-text-placeholder);
}
}
.device-panel {
background: var(--color-bg-card);
margin: 0 8px 8px;
border-radius: 10px;
overflow: hidden;
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--color-border);
.panel-title {
font-size: 15px;
font-weight: 600;
color: var(--color-text-regular);
}
.panel-count {
font-size: 12px;
color: var(--color-text-placeholder);
}
}
.device-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--color-border);
cursor: pointer;
&:last-child {
border-bottom: none;
}
.device-dot {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 12px;
flex-shrink: 0;
}
.device-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
.device-name {
font-size: 14px;
color: var(--color-text-regular);
}
.device-coord {
font-size: 11px;
color: var(--color-text-placeholder);
}
}
}
}
</style>

View File

@@ -0,0 +1,177 @@
<script setup lang="ts">
/**
* 监测详情页
*
* 展示设备的监测数据详情,包含实时数据、历史趋势图表占位、
* 告警记录和数据分析。
*/
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const detailId = (route.query.id as string) || '1'
/** 模拟监测详情 */
const monitoring = ref({
id: detailId,
equipmentName: '流量监测仪 A-01',
equipmentType: '流量计',
currentValue: '125.6 m³/h',
status: 'normal',
updateTime: '2025-06-15 08:30:00',
statistics: {
today: { avg: '122.3 m³/h', max: '135.8 m³/h', min: '110.2 m³/h', total: '2935.2 m³' },
yesterday: { avg: '118.7 m³/h', max: '130.1 m³/h', min: '105.8 m³/h', total: '2848.8 m³' },
thisMonth: { avg: '120.5 m³/h', max: '145.2 m³/h', min: '95.6 m³/h', total: '86760 m³' },
},
alarmRecords: [
{ time: '2025-06-14 16:30:00', level: 'warning', content: '流量超过预警值 130 m³/h', value: '131.5 m³/h' },
{ time: '2025-06-10 09:15:00', level: 'alarm', content: '流量异常下降', value: '45.2 m³/h' },
{ time: '2025-06-05 22:00:00', level: 'warning', content: '信号短暂丢失', value: '-' },
],
})
const levelColorMap: Record<string, string> = {
warning: '#FF9800',
alarm: '#F44336',
}
</script>
<template>
<div class="monitoring-detail-page">
<van-nav-bar title="监测详情" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
<!-- 实时数据 -->
<div class="realtime-card">
<div class="realtime-value">{{ monitoring.currentValue }}</div>
<div class="realtime-label">当前流量</div>
<div class="realtime-time">更新时间: {{ monitoring.updateTime }}</div>
</div>
<!-- 图表占位区趋势图 -->
<van-cell-group title="流量趋势" class="info-group">
<van-cell>
<div class="chart-placeholder">
<van-icon name="chart-trending-o" size="36" color="#ccc" />
<span>流量趋势图表</span>
</div>
</van-cell>
</van-cell-group>
<!-- 今日统计 -->
<van-cell-group title="今日统计" class="info-group">
<van-cell title="平均流量" :value="monitoring.statistics.today.avg" />
<van-cell title="最大流量" :value="monitoring.statistics.today.max" />
<van-cell title="最小流量" :value="monitoring.statistics.today.min" />
<van-cell title="累计流量" :value="monitoring.statistics.today.total" />
</van-cell-group>
<!-- 昨日统计 -->
<van-cell-group title="昨日统计" class="info-group">
<van-cell title="平均流量" :value="monitoring.statistics.yesterday.avg" />
<van-cell title="最大流量" :value="monitoring.statistics.yesterday.max" />
<van-cell title="最小流量" :value="monitoring.statistics.yesterday.min" />
<van-cell title="累计流量" :value="monitoring.statistics.yesterday.total" />
</van-cell-group>
<!-- 本月统计 -->
<van-cell-group title="本月统计" class="info-group">
<van-cell title="平均流量" :value="monitoring.statistics.thisMonth.avg" />
<van-cell title="最大流量" :value="monitoring.statistics.thisMonth.max" />
<van-cell title="最小流量" :value="monitoring.statistics.thisMonth.min" />
<van-cell title="累计流量" :value="monitoring.statistics.thisMonth.total" />
</van-cell-group>
<!-- 告警记录 -->
<van-cell-group title="告警记录" class="info-group">
<van-empty v-if="monitoring.alarmRecords.length === 0" description="暂无告警" />
<van-cell v-for="(alarm, idx) in monitoring.alarmRecords" :key="idx" :title="alarm.content">
<template #label>
<span>{{ alarm.time }}</span>
</template>
<template #value>
<div class="alarm-info">
<span class="alarm-dot" :style="{ backgroundColor: levelColorMap[alarm.level] }" />
<span class="alarm-value">{{ alarm.value }}</span>
</div>
</template>
</van-cell>
</van-cell-group>
</div>
</template>
<style lang="scss" scoped>
.monitoring-detail-page {
min-height: 100vh;
background: var(--color-bg-page);
padding-bottom: 24px;
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.realtime-card {
margin: 8px;
padding: 20px 16px;
background: linear-gradient(135deg, var(--color-primary), #36a3f7);
border-radius: 12px;
text-align: center;
color: #fff;
.realtime-value {
font-size: 36px;
font-weight: 700;
}
.realtime-label {
margin-top: 8px;
font-size: 14px;
opacity: 0.9;
}
.realtime-time {
margin-top: 4px;
font-size: 12px;
opacity: 0.7;
}
}
.chart-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 180px;
background: #f5f7fa;
border-radius: 8px;
color: var(--color-text-placeholder);
font-size: 13px;
gap: 8px;
}
.info-group {
margin-top: 8px;
}
.alarm-info {
display: flex;
align-items: center;
gap: 6px;
}
.alarm-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.alarm-value {
font-size: 13px;
}
</style>

View File

@@ -0,0 +1,153 @@
<script setup lang="ts">
/**
* 问题上报入口页
*
* 通过 van-grid 展示可上报的问题类型,
* 点击卡片跳转到对应的上报表单页。
*/
import { useRouter } from 'vue-router'
const router = useRouter()
/** 上报类型列表 */
const reportTypes = [
{
text: '积淹点',
icon: 'warning-o',
route: '/reportFloodedPoints',
desc: '积水、淹没点位上报',
},
{
text: '巡检问题',
icon: 'search',
route: '/reportInspection',
desc: '巡检发现的问题',
},
{
text: '设备报修',
icon: 'setting-o',
route: '/reportEquipmentRepair',
desc: '设备故障报修',
},
{
text: '督办问题',
icon: 'records',
route: '/reportSupervise',
desc: '督办项目问题',
},
{
text: '项目问题',
icon: 'bulb-o',
route: '/reportProject',
desc: '项目相关问题',
},
{
text: '施工问题',
icon: 'cluster-o',
route: '/reportConstruction',
desc: '施工现场问题',
},
{
text: '积水调查',
icon: 'water-o' as string,
route: '/reportFloodedPoints',
desc: '积水调查记录',
},
{
text: '养护上报',
icon: 'logistics',
route: '/reportEquipmentRepair',
desc: '养护问题上报',
},
]
/** 跳转 */
function goReport(route: string) {
router.push(route)
}
</script>
<template>
<div class="report-index-page">
<van-nav-bar
title="问题上报"
left-arrow
fixed
placeholder
@click-left="router.back()"
/>
<div class="report-banner">
<div class="banner-title">选择上报类型</div>
<div class="banner-desc">请根据实际情况选择对应的问题类型进行上报</div>
</div>
<van-grid :column-num="3" :border="false" :gutter="12" class="report-grid">
<van-grid-item
v-for="item in reportTypes"
:key="item.text"
:icon="item.icon"
:text="item.text"
@click="goReport(item.route)"
>
<template #description>
<span class="grid-desc">{{ item.desc }}</span>
</template>
</van-grid-item>
</van-grid>
</div>
</template>
<style lang="scss" scoped>
.report-index-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.report-banner {
padding: 20px 16px 12px;
background: #fff;
margin-bottom: 12px;
.banner-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.banner-desc {
margin-top: 6px;
font-size: 13px;
color: #999;
}
}
.report-grid {
padding: 0 4px;
:deep(.van-grid-item__content) {
padding: 16px 8px;
background: var(--color-bg-card);
border-radius: 10px;
}
:deep(.van-grid-item__text) {
font-weight: 500;
font-size: 14px;
margin-top: 6px;
}
.grid-desc {
font-size: 11px;
color: #999;
margin-top: 2px;
}
}
</style>

View File

@@ -0,0 +1,144 @@
<script setup lang="ts">
/**
* 施工问题上报表单
*
* 用于上报施工现场发现的问题,
* 支持可选的 detail 路由参数预填充施工点信息。
*/
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { showSuccessToast } from 'vant'
const router = useRouter()
const route = useRoute()
/** 路由参数 — 预填充的施工点信息 */
const detailParam = (route.params.detail as string) || ''
/** 表单数据 */
const site = ref(detailParam)
const issue = ref('')
const description = ref('')
const photos = ref<Array<{ url: string }>>([])
/** 提交上报 */
function onSubmit() {
if (!site.value) {
showSuccessToast('请选择施工点')
return
}
if (!issue.value) {
showSuccessToast('请填写问题')
return
}
if (!description.value) {
showSuccessToast('请填写描述')
return
}
showSuccessToast('上报成功')
router.back()
}
</script>
<template>
<div class="report-form-page">
<van-nav-bar
title="施工问题上报"
left-arrow
fixed
placeholder
@click-left="router.back()"
/>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field
v-model="site"
name="site"
label="施工点"
placeholder="请选择施工点"
is-link
readonly
:rules="[{ required: true, message: '请选择施工点' }]"
/>
<van-field
v-model="issue"
name="issue"
label="问题"
placeholder="请输入问题标题"
:rules="[{ required: true, message: '请填写问题' }]"
/>
<van-field
v-model="description"
name="description"
label="描述"
placeholder="请描述施工问题"
type="textarea"
rows="3"
autosize
:rules="[{ required: true, message: '请填写描述' }]"
/>
</van-cell-group>
<div class="upload-section">
<div class="section-title">现场照片</div>
<van-uploader
v-model="photos"
:max-count="6"
:max-size="10 * 1024 * 1024"
accept="image/*"
multiple
:preview-full-image="true"
/>
</div>
<div class="submit-wrap">
<van-button
type="primary"
block
round
native-type="submit"
class="submit-btn"
>
提交上报
</van-button>
</div>
</van-form>
</div>
</template>
<style lang="scss" scoped>
.report-form-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.upload-section {
margin: 12px 16px;
padding: 12px;
background: var(--color-bg-card);
border-radius: 8px;
}
.section-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
color: #333;
}
.submit-wrap {
padding: 24px 16px;
}
.submit-btn {
height: 44px;
}
</style>

View File

@@ -0,0 +1,181 @@
<script setup lang="ts">
/**
* 设备报修上报表单
*
* 用于上报设备故障报修,
* 包含设备选择、故障类型、描述和紧急程度。
*/
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { showSuccessToast } from 'vant'
const router = useRouter()
/** 表单数据 */
const equipment = ref('')
const faultType = ref('')
const description = ref('')
const urgency = ref('')
const photos = ref<Array<{ url: string }>>([])
/** 设备选项 */
const equipmentOptions = [
{ text: '水泵', value: 'pump' },
{ text: '阀门', value: 'valve' },
{ text: '电机', value: 'motor' },
{ text: '仪表', value: 'meter' },
{ text: '管道', value: 'pipe' },
]
/** 故障类型 */
const faultTypeOptions = [
{ text: '无法启动', value: 'noStart' },
{ text: '异常噪音', value: 'noise' },
{ text: '漏水', value: 'leak' },
{ text: '运行不稳定', value: 'unstable' },
{ text: '其他故障', value: 'other' },
]
/** 紧急程度 */
const urgencyColumns = [
{ text: '紧急', value: 'urgent' },
{ text: '一般', value: 'normal' },
{ text: '低', value: 'low' },
]
/** 提交上报 */
function onSubmit() {
if (!equipment.value) {
showSuccessToast('请选择设备')
return
}
if (!faultType.value) {
showSuccessToast('请选择故障类型')
return
}
if (!description.value) {
showSuccessToast('请填写描述')
return
}
if (!urgency.value) {
showSuccessToast('请选择紧急程度')
return
}
showSuccessToast('上报成功')
router.back()
}
</script>
<template>
<div class="report-form-page">
<van-nav-bar
title="设备报修"
left-arrow
fixed
placeholder
@click-left="router.back()"
/>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field
v-model="equipment"
name="equipment"
label="设备"
placeholder="请选择设备"
is-link
readonly
:rules="[{ required: true, message: '请选择设备' }]"
/>
<van-field
v-model="faultType"
name="faultType"
label="故障类型"
placeholder="请选择故障类型"
is-link
readonly
:rules="[{ required: true, message: '请选择故障类型' }]"
/>
<van-field
v-model="urgency"
name="urgency"
label="紧急程度"
placeholder="请选择紧急程度"
is-link
readonly
:rules="[{ required: true, message: '请选择紧急程度' }]"
/>
<van-field
v-model="description"
name="description"
label="描述"
placeholder="请描述设备故障情况"
type="textarea"
rows="3"
autosize
:rules="[{ required: true, message: '请填写描述' }]"
/>
</van-cell-group>
<div class="upload-section">
<div class="section-title">现场照片</div>
<van-uploader
v-model="photos"
:max-count="6"
:max-size="10 * 1024 * 1024"
accept="image/*"
multiple
:preview-full-image="true"
/>
</div>
<div class="submit-wrap">
<van-button
type="primary"
block
round
native-type="submit"
class="submit-btn"
>
提交上报
</van-button>
</div>
</van-form>
</div>
</template>
<style lang="scss" scoped>
.report-form-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.upload-section {
margin: 12px 16px;
padding: 12px;
background: var(--color-bg-card);
border-radius: 8px;
}
.section-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
color: #333;
}
.submit-wrap {
padding: 24px 16px;
}
.submit-btn {
height: 44px;
}
</style>

View File

@@ -0,0 +1,136 @@
<script setup lang="ts">
/**
* 积淹点上报表单
*
* 用于上报积水、淹没点位信息,
* 包含位置选择、水位、描述和照片上传。
*/
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { showSuccessToast } from 'vant'
const router = useRouter()
/** 表单数据 */
const location = ref('')
const waterLevel = ref('')
const description = ref('')
const photos = ref<Array<{ url: string }>>([])
/** 提交上报 */
function onSubmit() {
if (!location.value) {
showSuccessToast('请选择位置')
return
}
if (!description.value) {
showSuccessToast('请填写描述')
return
}
showSuccessToast('上报成功')
router.back()
}
</script>
<template>
<div class="report-form-page">
<van-nav-bar
title="积淹点上报"
left-arrow
fixed
placeholder
@click-left="router.back()"
/>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field
v-model="location"
name="location"
label="位置"
placeholder="点击选择位置"
is-link
readonly
:rules="[{ required: true, message: '请选择位置' }]"
/>
<van-field
v-model="waterLevel"
name="waterLevel"
label="水位(cm)"
placeholder="请输入水位高度"
type="number"
/>
<van-field
v-model="description"
name="description"
label="描述"
placeholder="请描述积水情况"
type="textarea"
rows="3"
autosize
:rules="[{ required: true, message: '请填写描述' }]"
/>
</van-cell-group>
<div class="upload-section">
<div class="section-title">现场照片</div>
<van-uploader
v-model="photos"
:max-count="6"
:max-size="10 * 1024 * 1024"
accept="image/*"
multiple
:preview-full-image="true"
/>
</div>
<div class="submit-wrap">
<van-button
type="primary"
block
round
native-type="submit"
class="submit-btn"
>
提交上报
</van-button>
</div>
</van-form>
</div>
</template>
<style lang="scss" scoped>
.report-form-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.upload-section {
margin: 12px 16px;
padding: 12px;
background: var(--color-bg-card);
border-radius: 8px;
}
.section-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
color: #333;
}
.submit-wrap {
padding: 24px 16px;
}
.submit-btn {
height: 44px;
}
</style>

View File

@@ -0,0 +1,160 @@
<script setup lang="ts">
/**
* 巡检问题上报表单
*
* 用于上报巡检过程中发现的问题,
* 包含设施选择、问题类型、描述和照片上传。
*/
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { showSuccessToast } from 'vant'
const router = useRouter()
/** 表单数据 */
const facility = ref('')
const problemType = ref('')
const description = ref('')
const photos = ref<Array<{ url: string }>>([])
/** 设施选项 */
const facilityOptions = [
{ text: '供水管网', value: 'pipe' },
{ text: '阀门井', value: 'valve' },
{ text: '消防栓', value: 'hydrant' },
{ text: '水表', value: 'meter' },
{ text: '泵站', value: 'pump' },
]
/** 问题类型选项 */
const problemTypeOptions = [
{ text: '管道渗漏', value: 'leak' },
{ text: '阀门故障', value: 'valveFault' },
{ text: '水表异常', value: 'meterFault' },
{ text: '设施损坏', value: 'damage' },
{ text: '其他问题', value: 'other' },
]
/** 提交上报 */
function onSubmit() {
if (!facility.value) {
showSuccessToast('请选择设施')
return
}
if (!problemType.value) {
showSuccessToast('请选择问题类型')
return
}
if (!description.value) {
showSuccessToast('请填写描述')
return
}
showSuccessToast('上报成功')
router.back()
}
</script>
<template>
<div class="report-form-page">
<van-nav-bar
title="巡检问题上报"
left-arrow
fixed
placeholder
@click-left="router.back()"
/>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field
v-model="facility"
name="facility"
label="设施"
placeholder="请选择设施类型"
is-link
readonly
:rules="[{ required: true, message: '请选择设施' }]"
/>
<van-field
v-model="problemType"
name="problemType"
label="问题类型"
placeholder="请选择问题类型"
is-link
readonly
:rules="[{ required: true, message: '请选择问题类型' }]"
/>
<van-field
v-model="description"
name="description"
label="描述"
placeholder="请描述巡检发现的问题"
type="textarea"
rows="3"
autosize
:rules="[{ required: true, message: '请填写描述' }]"
/>
</van-cell-group>
<div class="upload-section">
<div class="section-title">现场照片</div>
<van-uploader
v-model="photos"
:max-count="6"
:max-size="10 * 1024 * 1024"
accept="image/*"
multiple
:preview-full-image="true"
/>
</div>
<div class="submit-wrap">
<van-button
type="primary"
block
round
native-type="submit"
class="submit-btn"
>
提交上报
</van-button>
</div>
</van-form>
</div>
</template>
<style lang="scss" scoped>
.report-form-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.upload-section {
margin: 12px 16px;
padding: 12px;
background: var(--color-bg-card);
border-radius: 8px;
}
.section-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
color: #333;
}
.submit-wrap {
padding: 24px 16px;
}
.submit-btn {
height: 44px;
}
</style>

View File

@@ -0,0 +1,155 @@
<script setup lang="ts">
/**
* 项目问题上报表单
*
* 用于上报项目相关问题,
* 支持可选的 detail 路由参数预填充项目信息。
*/
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { showSuccessToast } from 'vant'
const router = useRouter()
const route = useRoute()
/** 路由参数 — 预填充的项目信息 */
const detailParam = (route.params.detail as string) || ''
/** 表单数据 */
const project = ref(detailParam)
const issueType = ref('')
const description = ref('')
const photos = ref<Array<{ url: string }>>([])
/** 问题类型选项 */
const issueTypeOptions = [
{ text: '进度延迟', value: 'delay' },
{ text: '质量问题', value: 'quality' },
{ text: '安全隐患', value: 'safety' },
{ text: '材料问题', value: 'material' },
{ text: '其他', value: 'other' },
]
/** 提交上报 */
function onSubmit() {
if (!project.value) {
showSuccessToast('请选择项目')
return
}
if (!issueType.value) {
showSuccessToast('请选择问题类型')
return
}
if (!description.value) {
showSuccessToast('请填写描述')
return
}
showSuccessToast('上报成功')
router.back()
}
</script>
<template>
<div class="report-form-page">
<van-nav-bar
title="项目问题上报"
left-arrow
fixed
placeholder
@click-left="router.back()"
/>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field
v-model="project"
name="project"
label="项目"
placeholder="请选择项目"
is-link
readonly
:rules="[{ required: true, message: '请选择项目' }]"
/>
<van-field
v-model="issueType"
name="issueType"
label="问题类型"
placeholder="请选择问题类型"
is-link
readonly
:rules="[{ required: true, message: '请选择问题类型' }]"
/>
<van-field
v-model="description"
name="description"
label="描述"
placeholder="请描述项目问题"
type="textarea"
rows="3"
autosize
:rules="[{ required: true, message: '请填写描述' }]"
/>
</van-cell-group>
<div class="upload-section">
<div class="section-title">现场照片</div>
<van-uploader
v-model="photos"
:max-count="6"
:max-size="10 * 1024 * 1024"
accept="image/*"
multiple
:preview-full-image="true"
/>
</div>
<div class="submit-wrap">
<van-button
type="primary"
block
round
native-type="submit"
class="submit-btn"
>
提交上报
</van-button>
</div>
</van-form>
</div>
</template>
<style lang="scss" scoped>
.report-form-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.upload-section {
margin: 12px 16px;
padding: 12px;
background: var(--color-bg-card);
border-radius: 8px;
}
.section-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
color: #333;
}
.submit-wrap {
padding: 24px 16px;
}
.submit-btn {
height: 44px;
}
</style>

View File

@@ -0,0 +1,144 @@
<script setup lang="ts">
/**
* 督办问题上报表单
*
* 用于上报督办项目中发现的问题,
* 支持可选的 detail 路由参数。
*/
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { showSuccessToast } from 'vant'
const router = useRouter()
const route = useRoute()
/** 路由参数 — 预填充的项目信息 */
const detailParam = (route.params.detail as string) || ''
/** 表单数据 */
const project = ref(detailParam)
const problem = ref('')
const description = ref('')
const photos = ref<Array<{ url: string }>>([])
/** 提交上报 */
function onSubmit() {
if (!project.value) {
showSuccessToast('请选择督办项目')
return
}
if (!problem.value) {
showSuccessToast('请选择问题')
return
}
if (!description.value) {
showSuccessToast('请填写描述')
return
}
showSuccessToast('上报成功')
router.back()
}
</script>
<template>
<div class="report-form-page">
<van-nav-bar
title="督办问题上报"
left-arrow
fixed
placeholder
@click-left="router.back()"
/>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field
v-model="project"
name="project"
label="督办项目"
placeholder="请选择督办项目"
is-link
readonly
:rules="[{ required: true, message: '请选择督办项目' }]"
/>
<van-field
v-model="problem"
name="problem"
label="问题"
placeholder="请输入问题标题"
:rules="[{ required: true, message: '请填写问题' }]"
/>
<van-field
v-model="description"
name="description"
label="描述"
placeholder="请描述督办发现的问题"
type="textarea"
rows="3"
autosize
:rules="[{ required: true, message: '请填写描述' }]"
/>
</van-cell-group>
<div class="upload-section">
<div class="section-title">现场照片</div>
<van-uploader
v-model="photos"
:max-count="6"
:max-size="10 * 1024 * 1024"
accept="image/*"
multiple
:preview-full-image="true"
/>
</div>
<div class="submit-wrap">
<van-button
type="primary"
block
round
native-type="submit"
class="submit-btn"
>
提交上报
</van-button>
</div>
</van-form>
</div>
</template>
<style lang="scss" scoped>
.report-form-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.upload-section {
margin: 12px 16px;
padding: 12px;
background: var(--color-bg-card);
border-radius: 8px;
}
.section-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
color: #333;
}
.submit-wrap {
padding: 24px 16px;
}
.submit-btn {
height: 44px;
}
</style>

View File

@@ -0,0 +1,183 @@
<script setup lang="ts">
/**
* 监督记录详情页
*
* 展示监督记录的详细信息,包含基本信息、监督内容、现场照片和结论。
*/
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { showToast, showImagePreview } from 'vant'
const router = useRouter()
const route = useRoute()
const detailId = (route.query.id as string) || '1'
/** 模拟监督记录详情 */
const record = ref({
id: detailId,
supervisorName: '张监督',
project: '城北供水改造工程',
type: 'daily',
result: 'qualified',
date: '2025-06-15',
time: '09:30 - 11:00',
address: '城北工业大道12号施工段',
content: '对管道焊接质量进行日常监督,检查焊缝外观、焊接参数记录、焊工资质等。焊接质量良好,符合规范要求。',
conclusion: '合格。焊接作业规范,焊接参数符合设计要求,建议继续保持。',
remark: '现场焊接环境良好,防护措施到位。',
createTime: '2025-06-15 11:15:00',
photos: [
{ id: 1, url: 'https://via.placeholder.com/400x300/4CAF50/white?text=Photo+1', desc: '焊接现场全景' },
{ id: 2, url: 'https://via.placeholder.com/400x300/2196F3/white?text=Photo+2', desc: '焊缝细节' },
{ id: 3, url: 'https://via.placeholder.com/400x300/FF9800/white?text=Photo+3', desc: '焊工资质核查' },
],
})
const typeMap: Record<string, string> = {
daily: '日常监督',
special: '专项监督',
rectify: '整改复查',
}
const resultMap: Record<string, string> = {
qualified: '合格',
unqualified: '不合格',
}
const resultColorMap: Record<string, 'success' | 'danger'> = {
qualified: 'success',
unqualified: 'danger',
}
/** 预览照片 */
function previewPhoto(index: number) {
showImagePreview({
images: record.value.photos.map(p => p.url),
startPosition: index,
})
}
</script>
<template>
<div class="record-detail-page">
<van-nav-bar title="记录详情" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
<!-- 结果标签 -->
<div class="result-bar">
<van-tag :type="resultColorMap[record.result]" size="large">
{{ resultMap[record.result] }}
</van-tag>
</div>
<!-- 基本信息 -->
<van-cell-group title="基本信息" class="info-group">
<van-cell title="监督人" :value="record.supervisorName" />
<van-cell title="所属项目" :value="record.project" />
<van-cell title="监督类型" :value="typeMap[record.type]" />
<van-cell title="监督日期" :value="record.date" />
<van-cell title="监督时间" :value="record.time" />
<van-cell title="监督地址" :value="record.address" />
</van-cell-group>
<!-- 监督内容 -->
<van-cell-group title="监督内容" class="info-group">
<van-cell>
<p class="desc-text">{{ record.content }}</p>
</van-cell>
</van-cell-group>
<!-- 现场照片 -->
<van-cell-group title="现场照片" class="info-group">
<van-cell>
<div class="photo-grid">
<div
v-for="(photo, index) in record.photos"
:key="photo.id"
class="photo-item"
@click="previewPhoto(index)"
>
<van-image
:src="photo.url"
width="100%"
height="100"
fit="cover"
radius="6"
/>
<p class="photo-desc">{{ photo.desc }}</p>
</div>
</div>
</van-cell>
</van-cell-group>
<!-- 结论与备注 -->
<van-cell-group title="监督结论" class="info-group">
<van-cell>
<p class="desc-text">{{ record.conclusion }}</p>
</van-cell>
</van-cell-group>
<van-cell-group title="备注" class="info-group">
<van-cell>
<p class="desc-text">{{ record.remark }}</p>
</van-cell>
</van-cell-group>
<!-- 时间信息 -->
<van-cell-group title="时间信息" class="info-group">
<van-cell title="创建时间" :value="record.createTime" />
</van-cell-group>
</div>
</template>
<style lang="scss" scoped>
.record-detail-page {
min-height: 100vh;
background: var(--color-bg-page);
padding-bottom: 24px;
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.result-bar {
padding: 12px 16px;
background: var(--color-bg-card);
text-align: center;
}
.info-group {
margin-top: 8px;
}
.desc-text {
font-size: 14px;
line-height: 1.6;
color: var(--color-text-regular);
padding: 4px 0;
}
.photo-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.photo-item {
cursor: pointer;
}
.photo-desc {
margin: 4px 0 0 0;
font-size: 11px;
color: var(--color-text-placeholder);
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,150 @@
<script setup lang="ts">
/**
* 监督记录列表页
*
* 展示第三方监督记录,支持搜索和类型筛选(全部/日常监督/专项监督/整改复查),
* 点击卡片跳转记录详情。
*/
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
/** 搜索关键词 */
const searchText = ref('')
/** 当前激活的 Tab */
const activeTab = ref(0)
/** 模拟监督记录数据 */
const mockRecords = [
{ id: 1, supervisorName: '张监督', project: '城北供水改造工程', type: 'daily', result: 'qualified', date: '2025-06-15', content: '对管道焊接质量进行日常监督' },
{ id: 2, supervisorName: '李监督', project: '高新区管网新建', type: 'special', result: 'unqualified', date: '2025-06-14', content: '专项检查管材进场质量' },
{ id: 3, supervisorName: '王监督', project: '老城区雨污分流', type: 'rectify', result: 'qualified', date: '2025-06-13', content: '复查上次整改问题落实情况' },
{ id: 4, supervisorName: '赵监督', project: '东区泵站升级', type: 'daily', result: 'qualified', date: '2025-06-12', content: '泵站基础施工日常监督' },
{ id: 5, supervisorName: '孙监督', project: '商业区管道维护', type: 'special', result: 'unqualified', date: '2025-06-11', content: '专项检查管道防腐处理' },
]
/** 类型映射 */
const typeMap: Record<string, string> = {
daily: '日常监督',
special: '专项监督',
rectify: '整改复查',
}
const typeColorMap: Record<string, string> = {
daily: 'primary',
special: 'warning',
rectify: 'success',
}
/** 结果映射 */
const resultMap: Record<string, string> = {
qualified: '合格',
unqualified: '不合格',
}
const resultColorMap: Record<string, 'success' | 'danger'> = {
qualified: 'success',
unqualified: 'danger',
}
/** 筛选后的记录列表 */
const filteredRecords = computed(() => {
let list = mockRecords
if (searchText.value) {
const kw = searchText.value.toLowerCase()
list = list.filter(r =>
r.supervisorName.toLowerCase().includes(kw) ||
r.project.toLowerCase().includes(kw) ||
r.content.toLowerCase().includes(kw)
)
}
if (activeTab.value === 1) list = list.filter(r => r.type === 'daily')
else if (activeTab.value === 2) list = list.filter(r => r.type === 'special')
else if (activeTab.value === 3) list = list.filter(r => r.type === 'rectify')
return list
})
/** 跳转详情 */
function goDetail(id: number) {
router.push(`/superviseRecord/detail?id=${id}`)
}
</script>
<template>
<div class="record-page">
<van-nav-bar title="监督记录" left-arrow fixed placeholder @click-left="router.back()" />
<van-search v-model="searchText" placeholder="搜索监督人、项目、内容" shape="round" />
<van-tabs v-model:active="activeTab" sticky>
<van-tab title="全部" />
<van-tab title="日常监督" />
<van-tab title="专项监督" />
<van-tab title="整改复查" />
</van-tabs>
<div class="record-list">
<van-empty v-if="filteredRecords.length === 0" description="暂无监督记录" />
<van-card
v-for="record in filteredRecords"
:key="record.id"
:title="record.project"
:desc="`监督人: ${record.supervisorName}`"
@click="goDetail(record.id)"
>
<template #tags>
<van-tag :type="typeColorMap[record.type] as 'primary' | 'success' | 'warning' | 'danger'" size="medium">
{{ typeMap[record.type] }}
</van-tag>
<van-tag :type="resultColorMap[record.result]" size="medium" plain>
{{ resultMap[record.result] }}
</van-tag>
</template>
<template #footer>
<div class="record-meta">
<span>{{ record.date }}</span>
<span>{{ record.content }}</span>
</div>
</template>
</van-card>
</div>
</div>
</template>
<style lang="scss" scoped>
.record-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.record-list {
padding: 0 8px;
:deep(.van-card) {
margin: 8px;
border-radius: 10px;
background: var(--color-bg-card);
}
:deep(.van-tag) {
margin-right: 4px;
}
}
.record-meta {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 12px;
color: var(--color-text-secondary);
}
</style>

View File

@@ -0,0 +1,108 @@
<script setup lang="ts">
/**
* 监督人员详情页
*
* 展示第三方监督人员详细信息,包含基本信息、在岗状态和履职记录。
*/
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const detailId = (route.query.id as string) || '1'
/** 模拟监督人员详情 */
const supervisor = ref({
id: detailId,
name: '张监督',
company: '水务监理公司A',
status: 'on_duty',
phone: '13800001001',
project: '城北供水改造工程',
rating: 4.8,
certNo: 'JL2023001',
certType: '注册监理工程师',
entryDate: '2025-01-15',
education: '本科',
major: '水利工程',
superviseYears: 8,
superviseCount: 126,
problemCount: 34,
lastOnDuty: '2025-06-15 08:30',
})
const statusMap: Record<string, string> = {
on_duty: '在岗',
off_duty: '离岗',
}
const statusColorMap: Record<string, 'success' | 'danger'> = {
on_duty: 'success',
off_duty: 'danger',
}
</script>
<template>
<div class="supervisor-detail-page">
<van-nav-bar title="人员详情" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
<!-- 状态标签 -->
<div class="status-bar">
<van-tag :type="statusColorMap[supervisor.status]" size="large">
{{ statusMap[supervisor.status] }}
</van-tag>
</div>
<!-- 基本信息 -->
<van-cell-group title="基本信息" class="info-group">
<van-cell title="姓名" :value="supervisor.name" />
<van-cell title="所属公司" :value="supervisor.company" />
<van-cell title="联系电话" :value="supervisor.phone" />
<van-cell title="所属项目" :value="supervisor.project" />
<van-cell title="评分" :value="`${supervisor.rating} 分`" />
</van-cell-group>
<!-- 资质信息 -->
<van-cell-group title="资质信息" class="info-group">
<van-cell title="证书编号" :value="supervisor.certNo" />
<van-cell title="证书类型" :value="supervisor.certType" />
<van-cell title="学历" :value="supervisor.education" />
<van-cell title="专业" :value="supervisor.major" />
</van-cell-group>
<!-- 履职统计 -->
<van-cell-group title="履职统计" class="info-group">
<van-cell title="从业年限" :value="`${supervisor.superviseYears} 年`" />
<van-cell title="监督次数" :value="`${supervisor.superviseCount} 次`" />
<van-cell title="发现问题" :value="`${supervisor.problemCount} 个`" />
<van-cell title="最近到岗" :value="supervisor.lastOnDuty" />
<van-cell title="入职日期" :value="supervisor.entryDate" />
</van-cell-group>
</div>
</template>
<style lang="scss" scoped>
.supervisor-detail-page {
min-height: 100vh;
background: var(--color-bg-page);
padding-bottom: 24px;
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.status-bar {
padding: 12px 16px;
background: var(--color-bg-card);
text-align: center;
}
.info-group {
margin-top: 8px;
}
</style>

View File

@@ -0,0 +1,135 @@
<script setup lang="ts">
/**
* 第三方监督人员列表页
*
* 展示监督人员列表,支持搜索和状态筛选(全部/在岗/离岗),
* 点击卡片跳转人员详情。
*/
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
/** 搜索关键词 */
const searchText = ref('')
/** 当前激活的 Tab */
const activeTab = ref(0)
/** 模拟监督人员数据 */
const mockSupervisors = [
{ id: 1, name: '张监督', company: '水务监理公司A', status: 'on_duty', phone: '13800001001', project: '城北供水改造工程', rating: 4.8 },
{ id: 2, name: '李监督', company: '市政监理公司B', status: 'off_duty', phone: '13800001002', project: '高新区管网新建', rating: 4.5 },
{ id: 3, name: '王监督', company: '环境监理公司C', status: 'on_duty', phone: '13800001003', project: '老城区雨污分流', rating: 4.9 },
{ id: 4, name: '赵监督', company: '水务监理公司A', status: 'on_duty', phone: '13800001004', project: '东区泵站升级', rating: 4.6 },
{ id: 5, name: '孙监督', company: '市政监理公司B', status: 'off_duty', phone: '13800001005', project: '商业区管道维护', rating: 4.3 },
]
/** 状态映射 */
const statusMap: Record<string, string> = {
on_duty: '在岗',
off_duty: '离岗',
}
const statusColorMap: Record<string, 'success' | 'danger'> = {
on_duty: 'success',
off_duty: 'danger',
}
/** 筛选后的列表 */
const filteredSupervisors = computed(() => {
let list = mockSupervisors
if (searchText.value) {
const kw = searchText.value.toLowerCase()
list = list.filter(s =>
s.name.toLowerCase().includes(kw) ||
s.company.toLowerCase().includes(kw) ||
s.project.toLowerCase().includes(kw)
)
}
if (activeTab.value === 1) list = list.filter(s => s.status === 'on_duty')
else if (activeTab.value === 2) list = list.filter(s => s.status === 'off_duty')
return list
})
/** 跳转详情 */
function goDetail(id: number) {
router.push(`/supervisor/detail?id=${id}`)
}
</script>
<template>
<div class="supervisor-page">
<van-nav-bar title="第三方监督人员" left-arrow fixed placeholder @click-left="router.back()" />
<van-search v-model="searchText" placeholder="搜索姓名、公司、项目" shape="round" />
<van-tabs v-model:active="activeTab" sticky>
<van-tab title="全部" />
<van-tab title="在岗" />
<van-tab title="离岗" />
</van-tabs>
<div class="supervisor-list">
<van-empty v-if="filteredSupervisors.length === 0" description="暂无监督人员" />
<van-card
v-for="supervisor in filteredSupervisors"
:key="supervisor.id"
:title="supervisor.name"
:desc="supervisor.company"
@click="goDetail(supervisor.id)"
>
<template #tags>
<van-tag :type="statusColorMap[supervisor.status]" size="medium">
{{ statusMap[supervisor.status] }}
</van-tag>
<van-tag type="primary" size="medium" plain>
{{ supervisor.rating }}
</van-tag>
</template>
<template #footer>
<div class="supervisor-meta">
<span>{{ supervisor.phone }}</span>
<span>{{ supervisor.project }}</span>
</div>
</template>
</van-card>
</div>
</div>
</template>
<style lang="scss" scoped>
.supervisor-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.supervisor-list {
padding: 0 8px;
:deep(.van-card) {
margin: 8px;
border-radius: 10px;
background: var(--color-bg-card);
}
:deep(.van-tag) {
margin-right: 4px;
}
}
.supervisor-meta {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 12px;
color: var(--color-text-secondary);
}
</style>

View File

@@ -0,0 +1,200 @@
<script setup lang="ts">
/**
* 有限空间作业记录详情页
*
* 展示有限空间作业的详细信息,包含基本信息、
* 安全条件确认清单、作业审批和参与人员。
*/
import { ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const detailId = (route.params.detail as string) || '1'
/** 模拟作业记录详情 */
const record = ref({
id: detailId,
title: '污水井清淤作业 2025-0615-01',
location: '城北工业大道12号污水井',
spaceType: '污水井',
status: 'in_progress',
operator: '张作业员',
supervisor: '王监督员',
guardian: '李监护人',
startTime: '2025-06-15 08:00',
endTime: '',
duration: '进行中',
depth: '4.5 m',
diameter: '1.2 m',
oxygenContent: '20.8%',
hazardousGas: '未检出',
temperature: '28°C',
humidity: '65%',
ventilation: '正常',
description: '对城北工业大道12号污水井进行清淤作业清理井内沉积物检查井壁结构完整性。',
/** 安全条件确认清单 */
safetyChecklist: [
{ id: 1, item: '作业审批手续是否完备', checked: true, required: true },
{ id: 2, item: '通风设备是否正常运行', checked: true, required: true },
{ id: 3, item: '气体检测是否合格', checked: true, required: true },
{ id: 4, item: '安全绳/安全带是否完好', checked: true, required: true },
{ id: 5, item: '通讯设备是否畅通', checked: true, required: true },
{ id: 6, item: '应急救援设备是否就位', checked: true, required: true },
{ id: 7, item: '人员防护装备是否齐全', checked: true, required: true },
{ id: 8, item: '现场警示标识是否设置', checked: false, required: true },
{ id: 9, item: '现场监护人是否到位', checked: true, required: true },
{ id: 10, item: '作业环境照明是否充足', checked: true, required: false },
],
})
const statusMap: Record<string, string> = {
pending: '待开始',
in_progress: '进行中',
completed: '已完成',
}
const statusColorMap: Record<string, string> = {
pending: 'warning',
in_progress: 'primary',
completed: 'success',
}
/** 安全清单统计 */
const checklistStats = computed(() => {
const total = record.value.safetyChecklist.length
const checked = record.value.safetyChecklist.filter(c => c.checked).length
const required = record.value.safetyChecklist.filter(c => c.required).length
const requiredChecked = record.value.safetyChecklist.filter(c => c.required && c.checked).length
return { total, checked, required, requiredChecked }
})
</script>
<template>
<div class="yxkjzy-detail-page">
<van-nav-bar title="作业记录详情" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
<!-- 状态标签 -->
<div class="status-bar">
<van-tag :type="statusColorMap[record.status] as never" size="large">
{{ statusMap[record.status] }}
</van-tag>
</div>
<!-- 基本信息 -->
<van-cell-group title="基本信息" class="info-group">
<van-cell title="作业编号" :value="record.title" />
<van-cell title="作业地点" :value="record.location" />
<van-cell title="空间类型" :value="record.spaceType" />
<van-cell title="作业员" :value="record.operator" />
<van-cell title="监督员" :value="record.supervisor" />
<van-cell title="监护人" :value="record.guardian" />
<van-cell title="开始时间" :value="record.startTime" />
<van-cell title="结束时间" :value="record.endTime || '-'" />
<van-cell title="作业时长" :value="record.duration" />
</van-cell-group>
<!-- 空间参数 -->
<van-cell-group title="空间参数" class="info-group">
<van-cell title="深度" :value="record.depth" />
<van-cell title="直径" :value="record.diameter" />
</van-cell-group>
<!-- 环境监测 -->
<van-cell-group title="环境监测" class="info-group">
<van-cell title="氧气含量" :value="record.oxygenContent" />
<van-cell title="有害气体" :value="record.hazardousGas" />
<van-cell title="温度" :value="record.temperature" />
<van-cell title="湿度" :value="record.humidity" />
<van-cell title="通风状态" :value="record.ventilation" />
</van-cell-group>
<!-- 安全条件确认清单 -->
<van-cell-group :title="`安全条件确认清单 (${checklistStats.requiredChecked}/${checklistStats.required} 必检项)`" class="info-group">
<van-cell v-for="item in record.safetyChecklist" :key="item.id">
<template #title>
<div class="checklist-item">
<van-icon
:name="item.checked ? 'success' : 'close'"
:color="item.checked ? '#4CAF50' : '#F44336'"
size="18"
/>
<span :class="{ 'required-tag': item.required }">{{ item.required ? '【必检】' : '' }}{{ item.item }}</span>
</div>
</template>
<template #value>
<van-tag :type="item.checked ? 'success' : 'danger'" size="medium">
{{ item.checked ? '已确认' : '未确认' }}
</van-tag>
</template>
</van-cell>
</van-cell-group>
<!-- 作业描述 -->
<van-cell-group title="作业描述" class="info-group">
<van-cell>
<p class="desc-text">{{ record.description }}</p>
</van-cell>
</van-cell-group>
<!-- 安全提示 -->
<div class="safety-tips">
<van-notice-bar
left-icon="warning-o"
color="#FF9800"
background="#FFF3E0"
:scrollable="false"
>
有限空间作业必须严格遵守"先通风、再检测、后作业"原则未确认安全条件不得进入
</van-notice-bar>
</div>
</div>
</template>
<style lang="scss" scoped>
.yxkjzy-detail-page {
min-height: 100vh;
background: var(--color-bg-page);
padding-bottom: 24px;
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.status-bar {
padding: 12px 16px;
background: var(--color-bg-card);
text-align: center;
}
.info-group {
margin-top: 8px;
}
.checklist-item {
display: flex;
align-items: center;
gap: 8px;
.required-tag {
color: var(--color-text-regular);
}
}
.desc-text {
font-size: 14px;
line-height: 1.8;
color: var(--color-text-regular);
padding: 4px 0;
text-indent: 2em;
}
.safety-tips {
margin: 12px 0;
}
</style>

View File

@@ -0,0 +1,153 @@
<script setup lang="ts">
/**
* 有限空间作业记录列表页
*
* 展示有限空间作业记录,支持搜索和状态筛选(全部/进行中/已完成),
* 点击卡片跳转作业记录详情。
*/
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
/** 搜索关键词 */
const searchText = ref('')
/** 当前激活的 Tab */
const activeTab = ref(0)
/** 模拟有限空间作业记录数据 */
const mockRecords = [
{ id: 1, title: '污水井清淤作业 2025-0615-01', location: '城北工业大道12号污水井', status: 'in_progress', operator: '张作业员', startTime: '2025-06-15 08:00', spaceType: '污水井' },
{ id: 2, title: '阀门井维修作业 2025-0614-03', location: '高新区3号阀门井', status: 'completed', operator: '李作业员', startTime: '2025-06-14 14:00', spaceType: '阀门井' },
{ id: 3, title: '蓄水池清洗作业 2025-0615-02', location: '东区蓄水池2号', status: 'in_progress', operator: '王作业员', startTime: '2025-06-15 09:00', spaceType: '蓄水池' },
{ id: 4, title: '管道检修作业 2025-0613-01', location: '商业区地下管廊B段', status: 'completed', operator: '赵作业员', startTime: '2025-06-13 10:00', spaceType: '管廊' },
{ id: 5, title: '检查井勘察作业 2025-0615-04', location: '老城区中山路检查井', status: 'pending', operator: '孙作业员', startTime: '2025-06-15 13:00', spaceType: '检查井' },
{ id: 6, title: '污水井疏通作业 2025-0612-02', location: '南城污水处理厂入口井', status: 'completed', operator: '钱作业员', startTime: '2025-06-12 08:30', spaceType: '污水井' },
]
/** 状态映射 */
const statusMap: Record<string, string> = {
pending: '待开始',
in_progress: '进行中',
completed: '已完成',
}
const statusColorMap: Record<string, string> = {
pending: 'warning',
in_progress: 'primary',
completed: 'success',
}
/** 空间类型图标映射 */
const spaceIconMap: Record<string, string> = {
'污水井': 'orders-o',
'阀门井': 'setting-o',
'蓄水池': 'aim-o',
'管廊': 'guide-o',
'检查井': 'search',
}
/** 筛选后的列表 */
const filteredRecords = computed(() => {
let list = mockRecords
if (searchText.value) {
const kw = searchText.value.toLowerCase()
list = list.filter(r =>
r.title.toLowerCase().includes(kw) ||
r.location.toLowerCase().includes(kw) ||
r.operator.toLowerCase().includes(kw) ||
r.spaceType.toLowerCase().includes(kw)
)
}
if (activeTab.value === 1) list = list.filter(r => r.status === 'in_progress')
else if (activeTab.value === 2) list = list.filter(r => r.status === 'completed')
return list
})
/** 跳转详情 */
function goDetail(id: number) {
router.push(`/yxkjzyRecordsDetail/${id}`)
}
</script>
<template>
<div class="yxkjzy-records-page">
<van-nav-bar title="有限空间作业记录" left-arrow fixed placeholder @click-left="router.back()" />
<van-search v-model="searchText" placeholder="搜索作业标题、位置、作业员" shape="round" />
<van-tabs v-model:active="activeTab" sticky>
<van-tab title="全部" />
<van-tab title="进行中" />
<van-tab title="已完成" />
</van-tabs>
<div class="record-list">
<van-empty v-if="filteredRecords.length === 0" description="暂无作业记录" />
<van-card
v-for="record in filteredRecords"
:key="record.id"
:title="record.title"
:desc="`位置: ${record.location}`"
@click="goDetail(record.id)"
>
<template #tags>
<van-tag :type="statusColorMap[record.status] as never" size="medium">
{{ statusMap[record.status] }}
</van-tag>
</template>
<template #thumb>
<van-icon :name="spaceIconMap[record.spaceType] || 'label-o'" size="32" color="#999" />
</template>
<template #footer>
<div class="record-meta">
<span class="meta-type">空间类型: {{ record.spaceType }}</span>
<span>作业员: {{ record.operator }}</span>
<span>开始时间: {{ record.startTime }}</span>
</div>
</template>
</van-card>
</div>
</div>
</template>
<style lang="scss" scoped>
.yxkjzy-records-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
--van-nav-bar-text-color: #fff;
--van-nav-bar-icon-color: #fff;
}
}
.record-list {
padding: 0 8px;
:deep(.van-card) {
margin: 8px;
border-radius: 10px;
background: var(--color-bg-card);
}
:deep(.van-tag) {
margin-right: 4px;
}
}
.record-meta {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 12px;
color: var(--color-text-secondary);
.meta-type {
color: var(--color-text-regular);
}
}
</style>