This commit is contained in:
parent
aca88d1214
commit
14f130f2c7
|
|
@ -0,0 +1,546 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { apiFetch } from '../../lib/api'
|
||||
|
||||
const points = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const searchKeyword = ref('')
|
||||
const statusFilter = ref('')
|
||||
const typeFilter = ref('')
|
||||
const showAddModal = ref(false)
|
||||
const showEditModal = ref(false)
|
||||
const editingPoint = ref(null)
|
||||
const formData = ref({
|
||||
name: '',
|
||||
code: '',
|
||||
type: '',
|
||||
projectId: '',
|
||||
deviceId: '',
|
||||
status: 'active'
|
||||
})
|
||||
|
||||
const pointTypes = [
|
||||
{ label: '结构监测', value: 'structure' },
|
||||
{ label: '环境监测', value: 'environment' },
|
||||
{ label: '视频监控', value: 'video' },
|
||||
{ label: '位移监测', value: 'displacement' },
|
||||
{ label: '温湿度监测', value: 'temperature' }
|
||||
]
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '在线', value: 'active' },
|
||||
{ label: '离线', value: 'inactive' },
|
||||
{ label: '维护中', value: 'maintenance' }
|
||||
]
|
||||
|
||||
async function loadPoints() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const res = await apiFetch('/api/points')
|
||||
if (res?.items) {
|
||||
points.value = res.items
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message || '加载监测点位失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filteredPoints = computed(() => {
|
||||
return points.value.filter(point => {
|
||||
const matchKeyword = !searchKeyword.value ||
|
||||
point.name.includes(searchKeyword.value) ||
|
||||
point.code.includes(searchKeyword.value)
|
||||
const matchStatus = !statusFilter.value || point.status === statusFilter.value
|
||||
const matchType = !typeFilter.value || point.type === typeFilter.value
|
||||
return matchKeyword && matchStatus && matchType
|
||||
})
|
||||
})
|
||||
|
||||
function getStatusLabel(status) {
|
||||
const statusMap = {
|
||||
active: '在线',
|
||||
inactive: '离线',
|
||||
maintenance: '维护中'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
function getStatusClass(status) {
|
||||
const classMap = {
|
||||
active: 'status-active',
|
||||
inactive: 'status-inactive',
|
||||
maintenance: 'status-maintenance'
|
||||
}
|
||||
return classMap[status] || ''
|
||||
}
|
||||
|
||||
function getTypeLabel(type) {
|
||||
const typeItem = pointTypes.find(t => t.value === type)
|
||||
return typeItem ? typeItem.label : type
|
||||
}
|
||||
|
||||
function openAddModal() {
|
||||
formData.value = {
|
||||
name: '',
|
||||
code: '',
|
||||
type: '',
|
||||
projectId: '',
|
||||
deviceId: '',
|
||||
status: 'active'
|
||||
}
|
||||
showAddModal.value = true
|
||||
}
|
||||
|
||||
function openEditModal(point) {
|
||||
editingPoint.value = point
|
||||
formData.value = {
|
||||
name: point.name,
|
||||
code: point.code,
|
||||
type: point.type,
|
||||
projectId: point.projectId,
|
||||
deviceId: point.deviceId,
|
||||
status: point.status
|
||||
}
|
||||
showEditModal.value = true
|
||||
}
|
||||
|
||||
async function handleAdd() {
|
||||
try {
|
||||
await apiFetch('/api/points', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(formData.value)
|
||||
})
|
||||
showAddModal.value = false
|
||||
loadPoints()
|
||||
} catch (err) {
|
||||
error.value = err.message || '添加失败'
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
await apiFetch(`/api/points/${editingPoint.value.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(formData.value)
|
||||
})
|
||||
showEditModal.value = false
|
||||
loadPoints()
|
||||
} catch (err) {
|
||||
error.value = err.message || '更新失败'
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(point) {
|
||||
if (!confirm(`确定要删除点位「${point.name}」吗?`)) return
|
||||
try {
|
||||
await apiFetch(`/api/points/${point.id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
loadPoints()
|
||||
} catch (err) {
|
||||
error.value = err.message || '删除失败'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadPoints()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="points-page">
|
||||
<div class="page-header">
|
||||
<h1>监测点位管理</h1>
|
||||
<button class="btn2 btn2--primary" @click="openAddModal">添加点位</button>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
class="search-input"
|
||||
placeholder="搜索点位名称或编号"
|
||||
/>
|
||||
<select v-model="typeFilter" class="filter-select">
|
||||
<option value="">全部类型</option>
|
||||
<option v-for="type in pointTypes" :key="type.value" :value="type.value">
|
||||
{{ type.label }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-model="statusFilter" class="filter-select">
|
||||
<option v-for="opt in statusOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else-if="error" class="error">{{ error }}</div>
|
||||
<div v-else class="points-list">
|
||||
<div v-for="point in filteredPoints" :key="point.id" class="point-item">
|
||||
<div class="point-main">
|
||||
<div class="point-header">
|
||||
<div class="point-name">{{ point.name }}</div>
|
||||
<div :class="['status-badge', getStatusClass(point.status)]">
|
||||
{{ getStatusLabel(point.status) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="point-code">编号:{{ point.code }}</div>
|
||||
<div class="point-details">
|
||||
<span class="detail-item">类型:{{ getTypeLabel(point.type) }}</span>
|
||||
<span class="detail-item">项目:{{ point.projectName || '-' }}</span>
|
||||
<span class="detail-item">设备:{{ point.deviceName || '-' }}</span>
|
||||
</div>
|
||||
<div v-if="point.lastUpdate" class="point-update">
|
||||
最后更新:{{ point.lastUpdate }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="point-actions">
|
||||
<button class="btn2" @click="openEditModal(point)">编辑</button>
|
||||
<button class="btn2" @click="handleDelete(point)">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filteredPoints.length === 0" class="empty">暂无监测点位</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showAddModal" class="modal-overlay" @click.self="showAddModal = false">
|
||||
<div class="modal">
|
||||
<div class="modal-header">添加监测点位</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>点位名称</label>
|
||||
<input v-model="formData.name" type="text" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>点位编号</label>
|
||||
<input v-model="formData.code" type="text" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>点位类型</label>
|
||||
<select v-model="formData.type" class="form-select">
|
||||
<option value="">请选择</option>
|
||||
<option v-for="type in pointTypes" :key="type.value" :value="type.value">
|
||||
{{ type.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>所属项目</label>
|
||||
<input v-model="formData.projectId" type="text" class="form-input" placeholder="项目ID" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>关联设备</label>
|
||||
<input v-model="formData.deviceId" type="text" class="form-input" placeholder="设备ID" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>状态</label>
|
||||
<select v-model="formData.status" class="form-select">
|
||||
<option value="active">在线</option>
|
||||
<option value="inactive">离线</option>
|
||||
<option value="maintenance">维护中</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn2" @click="showAddModal = false">取消</button>
|
||||
<button class="btn2 btn2--primary" @click="handleAdd">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showEditModal" class="modal-overlay" @click.self="showEditModal = false">
|
||||
<div class="modal">
|
||||
<div class="modal-header">编辑监测点位</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>点位名称</label>
|
||||
<input v-model="formData.name" type="text" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>点位编号</label>
|
||||
<input v-model="formData.code" type="text" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>点位类型</label>
|
||||
<select v-model="formData.type" class="form-select">
|
||||
<option value="">请选择</option>
|
||||
<option v-for="type in pointTypes" :key="type.value" :value="type.value">
|
||||
{{ type.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>所属项目</label>
|
||||
<input v-model="formData.projectId" type="text" class="form-input" placeholder="项目ID" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>关联设备</label>
|
||||
<input v-model="formData.deviceId" type="text" class="form-input" placeholder="设备ID" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>状态</label>
|
||||
<select v-model="formData.status" class="form-select">
|
||||
<option value="active">在线</option>
|
||||
<option value="inactive">离线</option>
|
||||
<option value="maintenance">维护中</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn2" @click="showEditModal = false">取消</button>
|
||||
<button class="btn2 btn2--primary" @click="handleEdit">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.points-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error,
|
||||
.empty {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: rgba(180, 44, 44, 0.95);
|
||||
}
|
||||
|
||||
.points-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.point-item {
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: var(--surface);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.point-main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.point-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.point-name {
|
||||
font-weight: 800;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: rgba(34, 197, 94, 0.95);
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: rgba(239, 68, 68, 0.95);
|
||||
}
|
||||
|
||||
.status-maintenance {
|
||||
background: rgba(234, 179, 8, 0.15);
|
||||
color: rgba(234, 179, 8, 0.95);
|
||||
}
|
||||
|
||||
.point-code {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.point-details {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.point-update {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.point-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn2 {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(31, 35, 40, 0.12);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
color: rgba(31, 35, 40, 0.85);
|
||||
}
|
||||
|
||||
.btn2:hover {
|
||||
border-color: rgba(180, 128, 64, 0.35);
|
||||
}
|
||||
|
||||
.btn2--primary {
|
||||
border-color: rgba(180, 128, 64, 0.35);
|
||||
background: rgba(180, 128, 64, 0.16);
|
||||
}
|
||||
|
||||
.btn2--primary:hover {
|
||||
background: rgba(180, 128, 64, 0.22);
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: var(--surface);
|
||||
border-radius: 14px;
|
||||
width: 90%;
|
||||
max-width: 480px;
|
||||
max-height: 90vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 16px;
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-select {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface);
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 16px;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.point-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.point-actions {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.point-details {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 模拟数据
|
||||
const records = ref([
|
||||
{
|
||||
id: '1',
|
||||
deviceId: 'DEV001',
|
||||
deviceName: '温湿度传感器',
|
||||
projectId: 'PROJ001',
|
||||
projectName: '故宫文物保护',
|
||||
pointId: 'POINT001',
|
||||
pointName: '太和殿东配殿',
|
||||
value: '25.5°C, 45%RH',
|
||||
timestamp: '2024-03-20 14:30:00',
|
||||
status: '正常'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
deviceId: 'DEV002',
|
||||
deviceName: '振动传感器',
|
||||
projectId: 'PROJ001',
|
||||
projectName: '故宫文物保护',
|
||||
pointId: 'POINT002',
|
||||
pointName: '太和殿西配殿',
|
||||
value: '0.02mm/s',
|
||||
timestamp: '2024-03-20 14:35:00',
|
||||
status: '正常'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
deviceId: 'DEV003',
|
||||
deviceName: '光照传感器',
|
||||
projectId: 'PROJ002',
|
||||
projectName: '长城保护',
|
||||
pointId: 'POINT003',
|
||||
pointName: '八达岭长城南段',
|
||||
value: '1200 lux',
|
||||
timestamp: '2024-03-20 14:40:00',
|
||||
status: '警告'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
deviceId: 'DEV004',
|
||||
deviceName: '温湿度传感器',
|
||||
projectId: 'PROJ002',
|
||||
projectName: '长城保护',
|
||||
pointId: 'POINT004',
|
||||
pointName: '八达岭长城北段',
|
||||
value: '28.0°C, 30%RH',
|
||||
timestamp: '2024-03-20 14:45:00',
|
||||
status: '警告'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
deviceId: 'DEV005',
|
||||
deviceName: '振动传感器',
|
||||
projectId: 'PROJ003',
|
||||
projectName: '秦始皇兵马俑保护',
|
||||
pointId: 'POINT005',
|
||||
pointName: '一号坑',
|
||||
value: '0.01mm/s',
|
||||
timestamp: '2024-03-20 14:50:00',
|
||||
status: '正常'
|
||||
}
|
||||
])
|
||||
|
||||
// 分页相关
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(records.value.length)
|
||||
|
||||
// 加载数据(模拟)
|
||||
async function loadRecords() {
|
||||
// 这里将来会调用API获取数据
|
||||
console.log('加载数据...')
|
||||
}
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
loadRecords()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="record-page">
|
||||
<div class="page-header">
|
||||
<h1>数据记录管理</h1>
|
||||
</div>
|
||||
|
||||
<div class="record-list">
|
||||
<table class="record-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>设备名称</th>
|
||||
<th>项目名称</th>
|
||||
<th>监测点位</th>
|
||||
<th>数据值</th>
|
||||
<th>时间戳</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="record in records" :key="record.id">
|
||||
<td>{{ record.deviceName }}</td>
|
||||
<td>{{ record.projectName }}</td>
|
||||
<td>{{ record.pointName }}</td>
|
||||
<td>{{ record.value }}</td>
|
||||
<td>{{ record.timestamp }}</td>
|
||||
<td>
|
||||
<span :class="['status-badge', record.status === '正常' ? 'status-normal' : 'status-warning']">
|
||||
{{ record.status }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="records.length === 0" class="empty">暂无数据记录</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div v-if="total > 0" class="pagination">
|
||||
<div class="pagination-info">
|
||||
共 {{ total }} 条记录,每页 {{ pageSize }} 条,第 {{ page }} / {{ Math.ceil(total / pageSize) }} 页
|
||||
</div>
|
||||
<div class="pagination-controls">
|
||||
<button
|
||||
class="pagination-btn"
|
||||
@click="page = 1; loadRecords()"
|
||||
:disabled="page === 1"
|
||||
>
|
||||
首页
|
||||
</button>
|
||||
<button
|
||||
class="pagination-btn"
|
||||
@click="page--; loadRecords()"
|
||||
:disabled="page === 1"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<button
|
||||
class="pagination-btn"
|
||||
@click="page++; loadRecords()"
|
||||
:disabled="page >= Math.ceil(total / pageSize)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
<button
|
||||
class="pagination-btn"
|
||||
@click="page = Math.ceil(total / pageSize); loadRecords()"
|
||||
:disabled="page >= Math.ceil(total / pageSize)"
|
||||
>
|
||||
末页
|
||||
</button>
|
||||
<select v-model="pageSize" @change="page = 1; loadRecords()" class="pagination-select">
|
||||
<option value="10">10条/页</option>
|
||||
<option value="20">20条/页</option>
|
||||
<option value="50">50条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.record-page {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.record-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.record-table th {
|
||||
background: #f9fafb;
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
font-weight: 800;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.record-table td {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.record-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.status-normal {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 48px;
|
||||
text-align: center;
|
||||
color: #6b7280;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: white;
|
||||
color: #374151;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.pagination-btn:hover:not(:disabled) {
|
||||
background: #f3f4f6;
|
||||
border-color: #b48040;
|
||||
}
|
||||
|
||||
.pagination-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-select {
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: white;
|
||||
color: #374151;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination-select:focus {
|
||||
outline: none;
|
||||
border-color: #b48040;
|
||||
box-shadow: 0 0 0 3px rgba(180, 128, 64, 0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.record-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.record-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.record-table th,
|
||||
.record-table td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -59,7 +59,9 @@ const router = createRouter({
|
|||
{ path: 'device/list', name: 'admin-device-list', component: () => import('../pages/device/list.vue') },
|
||||
{ path: 'project/list', name: 'admin-project-list', component: () => import('../pages/project/list.vue') },
|
||||
{ path: 'project/detail', name: 'admin-project-detail', component: () => import('../pages/project/detail.vue') },
|
||||
{path:'task/list',name:'admin-task-list',component:()=> import('../pages/task/list.vue')}
|
||||
{path:'task/list',name:'admin-task-list',component:()=> import('../pages/task/list.vue')},
|
||||
{path:'data/list',name:'admin-record-list',component:()=> import('../pages/record/list.vue')},
|
||||
{path:'point/list',name:'admin-point-list',component:()=> import('../pages/point/list.vue')}
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue