refactor(developer-config): 移除开发者配置页面相关代码和文档

- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue
- 移除开发者文档页面及其导航与样式实现
- 清理开发者侧功能完善工作日志文件
- 删除全局.gitignore配置文件,清理无用忽略规则
- 优化应用配置页面的参数读取和路由结构,解决刷新404问题
- 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入
- 移除对后端配置加密字段的 secret 标记,修正加密异常问题
This commit is contained in:
2026-04-09 07:35:34 +08:00
parent 3209d92cc5
commit f9e1286ab1
130 changed files with 18656 additions and 22143 deletions

View File

@@ -0,0 +1,113 @@
<script setup lang="ts">
definePageMeta({ layout: 'admin' })
const { activeTab } = useNav()
activeTab.value = 'production-control'
const realTimeData = ref([
{ id: 'WO2026040901', product: '精密轴承组件 A型', line: '产线1', planQty: 500, doneQty: 325, passQty: 318, passRate: 97.8, status: '生产中' },
{ id: 'WO2026040703', product: '密封圈组件 D型', line: '产线3', planQty: 3000, doneQty: 2100, passQty: 2058, passRate: 98.0, status: '生产中' },
])
const alerts = ref([
{ id: 1, type: 'warning', msg: '产线1主轴温度偏高当前78°C', time: '10:30' },
{ id: 2, type: 'info', msg: 'WO2026040901 完成500件当前进度65%', time: '10:15' },
{ id: 3, type: 'error', msg: '产线2刀具寿命预警请及时更换', time: '09:45' },
])
const activeTab2 = ref('realtime')
</script>
<template>
<div class="page-container">
<div class="page-header">
<h2 class="page-title">生产管控</h2>
<a-space>
<a-button @click="() => {}">导出报表</a-button>
<a-button type="primary">
<template #icon><PlusOutlined /></template>
新建工单
</a-button>
</a-space>
</div>
<a-tabs v-model:activeKey="activeTab2">
<a-tab-pane key="realtime" tab="实时监控">
<a-row :gutter="[20, 20]">
<a-col :xs="24" :xl="16">
<div class="card">
<div class="card-title">实时生产进度</div>
<a-table :dataSource="realTimeData" :pagination="false" size="small" rowKey="id">
<a-table-column title="工单编号" dataIndex="id" width="150" />
<a-table-column title="产品" dataIndex="product" />
<a-table-column title="产线" dataIndex="line" width="80" align="center" />
<a-table-column title="计划数量" dataIndex="planQty" width="100" align="center" />
<a-table-column title="完成数量" dataIndex="doneQty" width="100" align="center">
<template #default="{ record }">
<span class="num-highlight">{{ record.doneQty }}</span>
</template>
</a-table-column>
<a-table-column title="良品数量" dataIndex="passQty" width="100" align="center" />
<a-table-column title="良品率" dataIndex="passRate" width="90" align="center">
<template #default="{ text }">
<span :class="text >= 97 ? 'rate-good' : 'rate-warn'">{{ text }}%</span>
</template>
</a-table-column>
<a-table-column title="进度" dataIndex="doneQty" width="150" align="center">
<template #default="{ record }">
<a-progress :percent="Math.round(record.doneQty / record.planQty * 100)" size="small" />
</template>
</a-table-column>
<a-table-column title="状态" dataIndex="status" width="100" align="center">
<template #default="{ text }">
<a-badge status="processing" :text="text" />
</template>
</a-table-column>
</a-table>
</div>
</a-col>
<a-col :xs="24" :xl="8">
<div class="card">
<div class="card-title">实时告警</div>
<div class="alert-list">
<div v-for="alert in alerts" :key="alert.id" class="alert-item" :class="alert.type">
<span class="alert-icon">{{ alert.type === 'error' ? '🔴' : alert.type === 'warning' ? '🟡' : '🔵' }}</span>
<div class="alert-body">
<div class="alert-msg">{{ alert.msg }}</div>
<div class="alert-time">{{ alert.time }}</div>
</div>
</div>
</div>
</div>
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane key="history" tab="历史记录">
<div class="card">
<a-empty description="历史记录列表" />
</div>
</a-tab-pane>
</a-tabs>
</div>
</template>
<style scoped>
.page-container { padding: 24px; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.page-title { font-size: 20px; font-weight: 600; color: #1f2937; margin: 0; }
.card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
.card-title { font-size: 16px; font-weight: 600; color: #1f2937; margin-bottom: 16px; }
.num-highlight { font-weight: 700; color: #4f46e5; }
.rate-good { color: #10b981; font-weight: 600; }
.rate-warn { color: #f59e0b; font-weight: 600; }
.alert-list { display: flex; flex-direction: column; gap: 10px; }
.alert-item { display: flex; align-items: flex-start; gap: 10px; padding: 12px; border-radius: 8px; }
.alert-item.error { background: #fef2f2; }
.alert-item.warning { background: #fffbeb; }
.alert-item.info { background: #eff6ff; }
.alert-icon { font-size: 14px; }
.alert-msg { font-size: 13px; color: #374151; }
.alert-time { font-size: 11px; color: #9ca3af; margin-top: 4px; }
</style>

View File

@@ -0,0 +1,111 @@
<script setup lang="ts">
definePageMeta({ layout: 'admin' })
const { activeTab } = useNav()
activeTab.value = 'production-energy'
const stats = ref([
{ label: '今日用电', value: 2856, unit: 'kWh', cost: '¥2,142', icon: '⚡', color: '#f59e0b' },
{ label: '今日用水', value: 128, unit: 'm³', cost: '¥384', icon: '💧', color: '#3b82f6' },
{ label: '今日用气', value: 560, unit: 'm³', cost: '¥840', icon: '🌬️', color: '#6366f1' },
{ label: '碳排放', value: 1.8, unit: '吨', icon: '🌱', color: '#10b981' },
])
const energyUsage = ref([
{ name: 'CNC-01', electric: 450, water: 8, gas: 120, cost: 337.5 },
{ name: 'CNC-02', electric: 420, water: 7, gas: 115, cost: 315.0 },
{ name: 'CNC-03', electric: 0, water: 5, gas: 0, cost: 0 },
{ name: 'MILL-01', electric: 0, water: 3, gas: 0, cost: 0 },
{ name: 'MILL-02', electric: 380, water: 6, gas: 100, cost: 285.0 },
])
</script>
<template>
<div class="page-container">
<div class="page-header">
<h2 class="page-title">能耗管理</h2>
<a-space>
<a-select value="2026-04" style="width: 120px">
<a-select-option value="2026-04">2026年4月</a-select-option>
<a-select-option value="2026-03">2026年3月</a-select-option>
</a-select>
<a-button @click="() => {}">导出报表</a-button>
</a-space>
</div>
<a-row :gutter="[16, 16]" class="mb-6">
<a-col :xs="12" :sm="6" v-for="stat in stats" :key="stat.label">
<div class="stat-card" :style="{ '--c': stat.color }">
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-body">
<div class="stat-value">{{ stat.value }}<span class="stat-unit">{{ stat.unit }}</span></div>
<div class="stat-sub">{{ stat.cost }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</a-col>
</a-row>
<div class="card">
<div class="card-title">设备能耗明细</div>
<a-table :dataSource="energyUsage" :pagination="false" size="small" rowKey="name">
<a-table-column title="设备" dataIndex="name" width="120" />
<a-table-column title="用电(kWh)" dataIndex="electric" width="120" align="center">
<template #default="{ text }">
<span :class="text > 0 ? 'val' : 'dim'">{{ text }}</span>
</template>
</a-table-column>
<a-table-column title="用水(m³)" dataIndex="water" width="120" align="center">
<template #default="{ text }">
<span :class="text > 0 ? 'val' : 'dim'">{{ text }}</span>
</template>
</a-table-column>
<a-table-column title="用气(m³)" dataIndex="gas" width="120" align="center">
<template #default="{ text }">
<span :class="text > 0 ? 'val' : 'dim'">{{ text }}</span>
</template>
</a-table-column>
<a-table-column title="能耗成本(元)" dataIndex="cost" width="130" align="right">
<template #default="{ text }">
<span :class="text > 0 ? 'val' : 'dim'">¥{{ text.toFixed(1) }}</span>
</template>
</a-table-column>
<a-table-column title="占比" dataIndex="cost" align="center">
<template #default="{ record, text }">
<a-progress
:percent="Math.round(text / energyUsage.reduce((a, b) => a + b.cost, 0) * 100)"
size="small"
:showInfo="false"
/>
</template>
</a-table-column>
</a-table>
<div class="total-row">
<span>合计</span>
<span>2856 kWh</span>
<span>128 </span>
<span>560 </span>
<span>¥3,366.00</span>
</div>
</div>
</div>
</template>
<style scoped>
.page-container { padding: 24px; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.page-title { font-size: 20px; font-weight: 600; color: #1f2937; margin: 0; }
.stat-card { background: white; border-radius: 12px; padding: 16px; display: flex; align-items: center; gap: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
.stat-icon { font-size: 28px; }
.stat-value { font-size: 22px; font-weight: 700; color: #1f2937; }
.stat-unit { font-size: 12px; color: #9ca3af; margin-left: 2px; }
.stat-sub { font-size: 13px; color: #6b7280; }
.stat-label { font-size: 12px; color: #9ca3af; margin-top: 2px; }
.card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
.card-title { font-size: 16px; font-weight: 600; color: #1f2937; margin-bottom: 16px; }
.val { font-weight: 600; color: #374151; }
.dim { color: #d1d5db; }
.total-row { display: flex; justify-content: space-between; padding: 12px 8px; border-top: 1px solid #f0f0f0; font-weight: 600; font-size: 13px; color: #374151; }
.total-row span:nth-child(2), .total-row span:nth-child(3), .total-row span:nth-child(4) { width: 120px; text-align: center; }
.total-row span:nth-child(5) { width: 130px; text-align: right; }
.mb-6 { margin-bottom: 20px; }
</style>

View File

@@ -0,0 +1,617 @@
<script setup lang="ts">
definePageMeta({ layout: 'admin' })
// 设备统计数据
const equipmentStats = ref([
{ label: '设备总数', value: 128, icon: 'fa-cogs', color: 'blue', gradient: 'from-blue-500 to-purple-500', change: '+12%', up: true },
{ label: '运行中', value: 96, icon: 'fa-play-circle', color: 'green', gradient: 'from-green-500 to-teal-500', change: '+8%', up: true },
{ label: '待机中', value: 24, icon: 'fa-pause-circle', color: 'orange', gradient: 'from-orange-500 to-yellow-500', change: '-5%', up: false },
{ label: '故障告警', value: 8, icon: 'fa-exclamation-triangle', color: 'red', gradient: 'from-red-500 to-pink-500', change: '+2', up: false },
])
// 设备列表
const equipmentList = ref([
{ id: 'EQ-001', name: 'CNC加工中心 01', model: 'CNC-850', location: '1号车间 A区', status: 'running', utilization: 92, temp: 45, vibration: 0.8, lastMaintenance: '2026-04-01', nextMaintenance: '2026-05-01' },
{ id: 'EQ-002', name: 'CNC加工中心 02', model: 'CNC-850', location: '1号车间 A区', status: 'running', utilization: 88, temp: 43, vibration: 0.7, lastMaintenance: '2026-04-02', nextMaintenance: '2026-05-02' },
{ id: 'EQ-003', name: 'CNC加工中心 03', model: 'CNC-650', location: '1号车间 B区', status: 'warning', utilization: 0, temp: 78, vibration: 2.5, lastMaintenance: '2026-03-15', nextMaintenance: '2026-04-15' },
{ id: 'EQ-004', name: '数控铣床 01', model: 'XK-500', location: '2号车间', status: 'idle', utilization: 0, temp: 28, vibration: 0.3, lastMaintenance: '2026-03-28', nextMaintenance: '2026-04-28' },
{ id: 'EQ-005', name: '数控铣床 02', model: 'XK-500', location: '2号车间', status: 'running', utilization: 75, temp: 38, vibration: 0.6, lastMaintenance: '2026-04-03', nextMaintenance: '2026-05-03' },
{ id: 'EQ-006', name: '冲压机 01', model: 'CP-200T', location: '3号车间', status: 'running', utilization: 85, temp: 42, vibration: 1.2, lastMaintenance: '2026-03-20', nextMaintenance: '2026-04-20' },
{ id: 'EQ-007', name: '激光切割机 01', model: 'JC-3000', location: '3号车间', status: 'idle', utilization: 0, temp: 25, vibration: 0.2, lastMaintenance: '2026-04-05', nextMaintenance: '2026-05-05' },
{ id: 'EQ-008', name: '空压机 01', model: 'KY-50', location: '动力站房', status: 'running', utilization: 68, temp: 55, vibration: 1.5, lastMaintenance: '2026-03-10', nextMaintenance: '2026-05-10' },
])
// 筛选状态
const statusFilter = ref('all')
const searchKeyword = ref('')
// 筛选后的列表
const filteredList = computed(() => {
return equipmentList.value.filter((item) => {
const matchStatus = statusFilter.value === 'all' || item.status === statusFilter.value
const matchSearch = !searchKeyword.value ||
item.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
item.id.toLowerCase().includes(searchKeyword.value.toLowerCase())
return matchStatus && matchSearch
})
})
const statusMap: Record<string, { label: string; color: string; bg: string }> = {
running: { label: '运行中', color: 'text-green-600', bg: 'bg-green-100' },
idle: { label: '待机', color: 'text-orange-600', bg: 'bg-orange-100' },
warning: { label: '告警', color: 'text-red-600', bg: 'bg-red-100' },
offline: { label: '离线', color: 'text-gray-600', bg: 'bg-gray-100' },
}
// 设备详情
const selectedEquipment = ref<typeof equipmentList.value[0] | null>(null)
const detailVisible = ref(false)
function showDetail(item: typeof equipmentList.value[0]) {
selectedEquipment.value = item
detailVisible.value = true
}
// 维保记录
const maintenanceRecords = ref([
{ date: '2026-04-01', type: '例行保养', technician: '李师傅', cost: 500, status: '完成' },
{ date: '2026-03-01', type: '例行保养', technician: '李师傅', cost: 480, status: '完成' },
{ date: '2026-02-01', type: '更换配件', technician: '王师傅', cost: 1200, status: '完成' },
])
// 设备效率趋势(模拟数据)
const efficiencyTrend = ref([85, 88, 82, 90, 92, 88, 91, 89, 93, 92])
const days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日', '周一', '周二', '今天']
// 添加设备表单
const addFormVisible = ref(false)
const addForm = reactive({
name: '',
model: '',
location: '',
supplier: '',
purchaseDate: '',
warrantyEnd: '',
})
function handleAdd() {
// 模拟添加
addFormVisible.value = false
message.success('设备添加成功')
}
</script>
<template>
<div class="equipment-page">
<!-- 页面标题 -->
<div class="page-header mb-6">
<div>
<h2 class="text-2xl font-bold text-gray-800">设备管理</h2>
<p class="text-gray-500 mt-1">实时监控设备状态优化生产效率</p>
</div>
<a-button type="primary" @click="addFormVisible = true">
<template #icon><i class="fas fa-plus mr-1"></i></template>
添加设备
</a-button>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div
v-for="stat in equipmentStats"
:key="stat.label"
class="glass rounded-2xl p-6 card-hover cursor-pointer"
>
<div class="flex items-center justify-between mb-4">
<div
class="w-12 h-12 rounded-xl flex items-center justify-center text-white"
:class="`bg-gradient-to-br ${stat.gradient}`"
>
<i :class="`fas ${stat.icon}`"></i>
</div>
<span
class="text-sm font-medium"
:class="stat.up ? 'text-green-500' : 'text-red-500'"
>
<i :class="stat.up ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"></i>
{{ stat.change }}
</span>
</div>
<h3 class="text-3xl font-bold text-gray-800 mb-1">{{ stat.value }}</h3>
<p class="text-gray-500 text-sm">{{ stat.label }}</p>
</div>
</div>
<!-- 筛选栏 -->
<div class="glass rounded-2xl p-4 mb-6">
<div class="flex items-center justify-between gap-4">
<div class="flex items-center gap-3">
<span class="text-gray-600 font-medium">状态筛选</span>
<a-radio-group v-model:value="statusFilter" button-style="solid">
<a-radio-button value="all">全部</a-radio-button>
<a-radio-button value="running">运行中</a-radio-button>
<a-radio-button value="idle">待机</a-radio-button>
<a-radio-button value="warning">告警</a-radio-button>
</a-radio-group>
</div>
<a-input-search
v-model:value="searchKeyword"
placeholder="搜索设备名称或编号..."
style="width: 280px"
allow-clear
/>
</div>
</div>
<!-- 设备列表 -->
<div class="glass rounded-2xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-bold text-lg text-gray-800">
<i class="fas fa-list text-purple-500 mr-2"></i>
设备列表
</h3>
<span class="text-sm text-gray-500"> {{ filteredList.length }} 台设备</span>
</div>
<a-table
:dataSource="filteredList"
:pagination="{ pageSize: 10 }"
rowKey="id"
:scroll="{ x: 1200 }"
>
<a-table-column title="设备编号" dataIndex="id" width="100" />
<a-table-column title="设备名称" dataIndex="name" width="180">
<template #default="{ record }">
<div class="flex items-center gap-2">
<div
class="w-8 h-8 rounded-lg flex items-center justify-center text-white text-xs"
:class="statusMap[record.status]?.bg.replace('100', '500') || 'bg-gray-500'"
>
<i class="fas fa-cog"></i>
</div>
<span class="font-medium">{{ record.name }}</span>
</div>
</template>
</a-table-column>
<a-table-column title="型号" dataIndex="model" width="100" />
<a-table-column title="位置" dataIndex="location" width="120" />
<a-table-column title="状态" dataIndex="status" width="100" align="center">
<template #default="{ text }">
<span
:class="statusMap[text]?.color"
class="px-2 py-1 rounded-lg text-sm font-medium"
:class="statusMap[text]?.bg"
>
{{ statusMap[text]?.label }}
</span>
</template>
</a-table-column>
<a-table-column title="利用率" dataIndex="utilization" width="140">
<template #default="{ record }">
<div class="flex items-center gap-2">
<a-progress
:percent="record.utilization"
:status="record.utilization > 80 ? 'success' : 'active'"
:showInfo="false"
size="small"
:stroke-color="record.utilization > 80 ? '#10b981' : '#6366f1'"
style="width: 80px"
/>
<span class="text-sm text-gray-600">{{ record.utilization }}%</span>
</div>
</template>
</a-table-column>
<a-table-column title="温度(°C)" dataIndex="temp" width="100" align="center">
<template #default="{ text, record }">
<span :class="text > 60 ? 'text-red-500 font-medium' : 'text-gray-600'">
{{ text }}
</span>
</template>
</a-table-column>
<a-table-column title="振动(mm/s)" dataIndex="vibration" width="110" align="center">
<template #default="{ text }">
<span :class="text > 2 ? 'text-red-500 font-medium' : 'text-gray-600'">
{{ text }}
</span>
</template>
</a-table-column>
<a-table-column title="下次保养" dataIndex="nextMaintenance" width="120" />
<a-table-column title="操作" width="120" align="center" fixed="right">
<template #default="{ record }">
<div class="flex items-center gap-2 justify-center">
<a-button type="link" size="small" @click="showDetail(record)">
<i class="fas fa-eye"></i>
</a-button>
<a-button type="link" size="small">
<i class="fas fa-edit"></i>
</a-button>
</div>
</template>
</a-table-column>
</a-table>
</div>
<!-- 设备详情抽屉 -->
<a-drawer
v-model:open="detailVisible"
:title="selectedEquipment?.name"
width="500"
placement="right"
>
<template v-if="selectedEquipment">
<!-- 基本信息 -->
<div class="mb-6">
<h4 class="font-bold text-gray-800 mb-3 flex items-center gap-2">
<i class="fas fa-info-circle text-blue-500"></i>
基本信息
</h4>
<a-descriptions :column="2" bordered size="small">
<a-descriptions-item label="设备编号">{{ selectedEquipment.id }}</a-descriptions-item>
<a-descriptions-item label="设备型号">{{ selectedEquipment.model }}</a-descriptions-item>
<a-descriptions-item label="安装位置">{{ selectedEquipment.location }}</a-descriptions-item>
<a-descriptions-item label="当前状态">
<span
:class="statusMap[selectedEquipment.status]?.color"
class="px-2 py-0.5 rounded text-sm font-medium"
:class="statusMap[selectedEquipment.status]?.bg"
>
{{ statusMap[selectedEquipment.status]?.label }}
</span>
</a-descriptions-item>
</a-descriptions>
</div>
<!-- 实时监控 -->
<div class="mb-6">
<h4 class="font-bold text-gray-800 mb-3 flex items-center gap-2">
<i class="fas fa-chart-line text-green-500"></i>
实时监控
</h4>
<div class="grid grid-cols-3 gap-3">
<div class="bg-gray-50 rounded-xl p-4 text-center">
<div class="text-2xl font-bold text-blue-600">{{ selectedEquipment.utilization }}%</div>
<div class="text-xs text-gray-500 mt-1">设备利用率</div>
</div>
<div class="bg-gray-50 rounded-xl p-4 text-center">
<div
class="text-2xl font-bold"
:class="selectedEquipment.temp > 60 ? 'text-red-600' : 'text-orange-600'"
>
{{ selectedEquipment.temp }}°C
</div>
<div class="text-xs text-gray-500 mt-1">当前温度</div>
</div>
<div class="bg-gray-50 rounded-xl p-4 text-center">
<div
class="text-2xl font-bold"
:class="selectedEquipment.vibration > 2 ? 'text-red-600' : 'text-purple-600'"
>
{{ selectedEquipment.vibration }}
</div>
<div class="text-xs text-gray-500 mt-1">振动值</div>
</div>
</div>
</div>
<!-- 效率趋势图 -->
<div class="mb-6">
<h4 class="font-bold text-gray-800 mb-3 flex items-center gap-2">
<i class="fas fa-chart-area text-purple-500"></i>
效率趋势
</h4>
<div class="h-32 flex items-end gap-2">
<div
v-for="(value, idx) in efficiencyTrend"
:key="idx"
class="flex-1 flex flex-col items-center"
>
<div
class="w-full bg-gradient-to-t from-purple-500 to-purple-300 rounded-t-lg transition-all hover:opacity-80"
:style="{ height: value + '%' }"
></div>
<span class="text-xs text-gray-400 mt-1">{{ days[idx] }}</span>
</div>
</div>
</div>
<!-- 维保记录 -->
<div>
<h4 class="font-bold text-gray-800 mb-3 flex items-center gap-2">
<i class="fas fa-wrench text-orange-500"></i>
维保记录
</h4>
<a-timeline>
<a-timeline-item
v-for="record in maintenanceRecords"
:key="record.date"
:color="record.status === '完成' ? 'green' : 'gray'"
>
<div class="flex justify-between">
<span class="font-medium">{{ record.type }}</span>
<span class="text-gray-500 text-sm">{{ record.date }}</span>
</div>
<div class="text-sm text-gray-500">
技师:{{ record.technician }} | 费用:¥{{ record.cost }}
</div>
</a-timeline-item>
</a-timeline>
</div>
<!-- 操作按钮 -->
<div class="mt-6 flex gap-3">
<a-button type="primary" block>发起保养</a-button>
<a-button block>导出报表</a-button>
</div>
</template>
</a-drawer>
<!-- 添加设备弹窗 -->
<a-modal
v-model:open="addFormVisible"
title="添加新设备"
@ok="handleAdd"
ok-text="确认添加"
cancel-text="取消"
>
<a-form :model="addForm" layout="vertical">
<a-form-item label="设备名称" required>
<a-input v-model:value="addForm.name" placeholder="请输入设备名称" />
</a-form-item>
<a-form-item label="设备型号" required>
<a-input v-model:value="addForm.model" placeholder="请输入设备型号" />
</a-form-item>
<a-form-item label="安装位置" required>
<a-input v-model:value="addForm.location" placeholder="请输入安装位置" />
</a-form-item>
<a-form-item label="供应商">
<a-input v-model:value="addForm.supplier" placeholder="请输入供应商" />
</a-form-item>
<a-form-item label="采购日期">
<a-date-picker v-model:value="addForm.purchaseDate" style="width: 100%" />
</a-form-item>
<a-form-item label="保修截止日期">
<a-date-picker v-model:value="addForm.warrantyEnd" style="width: 100%" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<style scoped>
.equipment-page {
padding: 0;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.glass {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.grid {
display: grid;
}
.grid-cols-4 {
grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 1200px) {
.grid-cols-4 {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.grid-cols-4 {
grid-template-columns: 1fr;
}
}
.mb-6 {
margin-bottom: 24px;
}
.mb-4 {
margin-bottom: 16px;
}
.mb-3 {
margin-bottom: 12px;
}
.mt-1 {
margin-top: 4px;
}
.mr-2 {
margin-right: 8px;
}
.mr-1 {
margin-right: 4px;
}
.mt-6 {
margin-top: 24px;
}
.text-2xl {
font-size: 24px;
}
.text-3xl {
font-size: 30px;
}
.text-lg {
font-size: 18px;
}
.text-sm {
font-size: 14px;
}
.text-xs {
font-size: 12px;
}
.rounded-2xl {
border-radius: 16px;
}
.p-6 {
padding: 24px;
}
.p-4 {
padding: 16px;
}
.gap-6 {
gap: 24px;
}
.gap-4 {
gap: 16px;
}
.gap-3 {
gap: 12px;
}
.gap-2 {
gap: 8px;
}
.flex {
display: flex;
}
.grid {
display: grid;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.text-gray-800 {
color: #1f2937;
}
.text-gray-600 {
color: #4b5563;
}
.text-gray-500 {
color: #6b7280;
}
.text-gray-400 {
color: #9ca3af;
}
.text-blue-600 {
color: #2563eb;
}
.text-green-600 {
color: #16a34a;
}
.text-orange-600 {
color: #ea580c;
}
.text-red-600 {
color: #dc2626;
}
.text-purple-600 {
color: #9333ea;
}
.bg-gray-50 {
background-color: #f9fafb;
}
.bg-green-100 {
background-color: #dcfce7;
}
.bg-orange-100 {
background-color: #ffedd5;
}
.bg-red-100 {
background-color: #fee2e2;
}
.font-bold {
font-weight: 700;
}
.font-medium {
font-weight: 500;
}
.from-blue-500 {
--tw-gradient-from: #3b82f6;
}
.to-purple-500 {
--tw-gradient-to: #a855f7;
}
.from-green-500 {
--tw-gradient-from: #22c55e;
}
.to-teal-500 {
--tw-gradient-to: #14b8a6;
}
.from-orange-500 {
--tw-gradient-from: #f97316;
}
.to-yellow-500 {
--tw-gradient-to: #eab308;
}
.from-red-500 {
--tw-gradient-from: #ef4444;
}
.to-pink-500 {
--tw-gradient-to: #ec4899;
}
.rounded-xl {
border-radius: 10px;
}
.rounded-lg {
border-radius: 8px;
}
</style>

View File

@@ -0,0 +1,98 @@
<script setup lang="ts">
definePageMeta({ layout: 'admin' })
const { activeTab } = useNav()
activeTab.value = 'production-quality'
const qualityRecords = ref([
{ id: 'QC-2026040901', wo: 'WO2026040901', product: '精密轴承组件 A型', batch: 'B-20260409-01', inspector: '质检员A', checkTime: '2026-04-09 10:30', sampleSize: 50, passSize: 49, passRate: 98.0, result: '合格', issues: '1件尺寸超差' },
{ id: 'QC-2026040801', wo: 'WO2026040703', product: '密封圈组件 D型', batch: 'B-20260408-03', inspector: '质检员B', checkTime: '2026-04-08 14:20', sampleSize: 100, passSize: 100, passRate: 100, result: '合格', issues: '-' },
{ id: 'QC-2026040702', wo: 'WO2026040702', product: '弹簧组件 E型', batch: 'B-20260407-02', inspector: '质检员A', checkTime: '2026-04-07 09:00', sampleSize: 80, passSize: 72, passRate: 90.0, result: '不合格', issues: '8件弹性不足' },
])
const resultColor: Record<string, string> = { '合格': 'success', '不合格': 'error' }
const activeTab3 = ref('records')
</script>
<template>
<div class="page-container">
<div class="page-header">
<h2 class="page-title">质量管理</h2>
<a-button type="primary">
<template #icon><PlusOutlined /></template>
新建质检
</a-button>
</div>
<a-row :gutter="[16, 16]" class="mb-6">
<a-col :xs="12" :sm="6">
<div class="stat-card" style="--c:#10b981">
<div class="stat-num">98.5%</div>
<div class="stat-lbl">今日良品率</div>
</div>
</a-col>
<a-col :xs="12" :sm="6">
<div class="stat-card" style="--c:#3b82f6">
<div class="stat-num">156</div>
<div class="stat-lbl">今日检验数</div>
</div>
</a-col>
<a-col :xs="12" :sm="6">
<div class="stat-card" style="--c:#f59e0b">
<div class="stat-num">3</div>
<div class="stat-lbl">待处理异常</div>
</div>
</a-col>
<a-col :xs="12" :sm="6">
<div class="stat-card" style="--c:#6366f1">
<div class="stat-num">12</div>
<div class="stat-lbl">质检标准数</div>
</div>
</a-col>
</a-row>
<a-tabs v-model:activeKey="activeTab3">
<a-tab-pane key="records" tab="检验记录">
<div class="card">
<a-table :dataSource="qualityRecords" :pagination="{ pageSize: 10 }" size="small" rowKey="id">
<a-table-column title="质检编号" dataIndex="id" width="150" />
<a-table-column title="工单号" dataIndex="wo" width="150" />
<a-table-column title="产品" dataIndex="product" />
<a-table-column title="批次" dataIndex="batch" width="160" />
<a-table-column title="检验员" dataIndex="inspector" width="90" align="center" />
<a-table-column title="检验时间" dataIndex="checkTime" width="160" />
<a-table-column title="抽样数" dataIndex="sampleSize" width="80" align="center" />
<a-table-column title="良品数" dataIndex="passSize" width="80" align="center" />
<a-table-column title="良品率" dataIndex="passRate" width="90" align="center">
<template #default="{ text, record }">
<span :class="record.result === '合格' ? 'ok' : 'warn'">{{ text }}%</span>
</template>
</a-table-column>
<a-table-column title="结果" dataIndex="result" width="90" align="center">
<template #default="{ text }">
<a-tag :color="resultColor[text]">{{ text }}</a-tag>
</template>
</a-table-column>
<a-table-column title="问题描述" dataIndex="issues" />
</a-table>
</div>
</a-tab-pane>
<a-tab-pane key="standards" tab="质检标准">
<div class="card"><a-empty description="质检标准管理" /></div>
</a-tab-pane>
</a-tabs>
</div>
</template>
<style scoped>
.page-container { padding: 24px; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.page-title { font-size: 20px; font-weight: 600; color: #1f2937; margin: 0; }
.stat-card { background: white; border-radius: 12px; padding: 20px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; position: relative; overflow: hidden; }
.stat-card::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 3px; background: var(--c); }
.stat-num { font-size: 28px; font-weight: 700; color: #1f2937; }
.stat-lbl { font-size: 13px; color: #9ca3af; margin-top: 4px; }
.card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
.ok { color: #10b981; font-weight: 600; }
.warn { color: #ef4444; font-weight: 600; }
.mb-6 { margin-bottom: 20px; }
</style>

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
definePageMeta({ layout: 'admin' })
const { activeTab } = useNav()
activeTab.value = 'production-safety'
const stats = ref([
{ label: '安全生产天数', value: 156, unit: '天', icon: '🛡️', color: '#10b981' },
{ label: '本月巡检次数', value: 28, unit: '次', icon: '🔍', color: '#3b82f6' },
{ label: '安全隐患', value: 3, unit: '条', icon: '⚠️', color: '#f59e0b' },
{ label: '应急演练', value: 2, unit: '次', icon: '🚨', color: '#6366f1' },
])
const safetyLogs = ref([
{ id: 'SL-001', type: '巡检', area: '生产车间A区', inspector: '安全员A', result: '正常', time: '2026-04-09 08:30', remark: '-' },
{ id: 'SL-002', type: '巡检', area: '电工房', inspector: '安全员B', result: '隐患', time: '2026-04-09 09:15', remark: '发现1处接线松动' },
{ id: 'SL-003', type: '设备检查', area: 'CNC-03区域', inspector: '安全员A', result: '告警', time: '2026-04-09 10:00', remark: '设备温度异常,已停机' },
{ id: 'SL-004', type: '巡检', area: '仓储区', inspector: '安全员C', result: '正常', time: '2026-04-08 14:00', remark: '-' },
])
const resultColor: Record<string, string> = { '正常': 'success', '隐患': 'warning', '告警': 'error' }
</script>
<template>
<div class="page-container">
<div class="page-header">
<h2 class="page-title">安全生产</h2>
<a-button type="primary">
<template #icon><PlusOutlined /></template>
新建巡检
</a-button>
</div>
<a-row :gutter="[16, 16]" class="mb-6">
<a-col :xs="12" :sm="6" v-for="stat in stats" :key="stat.label">
<div class="stat-card" :style="{ '--c': stat.color }">
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-body">
<div class="stat-value">{{ stat.value }}<span class="stat-unit">{{ stat.unit }}</span></div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</a-col>
</a-row>
<a-row :gutter="[20, 20]">
<a-col :xs="24" :xl="16">
<div class="card">
<div class="card-title">安全巡检记录</div>
<a-table :dataSource="safetyLogs" :pagination="{ pageSize: 10 }" size="small" rowKey="id">
<a-table-column title="记录编号" dataIndex="id" width="100" />
<a-table-column title="类型" dataIndex="type" width="100" align="center" />
<a-table-column title="区域" dataIndex="area" />
<a-table-column title="检查人" dataIndex="inspector" width="100" align="center" />
<a-table-column title="结果" dataIndex="result" width="90" align="center">
<template #default="{ text }">
<a-tag :color="resultColor[text]">{{ text }}</a-tag>
</template>
</a-table-column>
<a-table-column title="备注" dataIndex="remark" />
<a-table-column title="时间" dataIndex="time" width="160" />
</a-table>
</div>
</a-col>
<a-col :xs="24" :xl="8">
<div class="card">
<div class="card-title">安全知识库</div>
<div class="doc-list">
<div class="doc-item">📄 安全生产管理制度 v3.2</div>
<div class="doc-item">📄 应急救援预案</div>
<div class="doc-item">📄 特种设备操作规程</div>
<div class="doc-item">📄 危险源辨识清单</div>
</div>
</div>
</a-col>
</a-row>
</div>
</template>
<style scoped>
.page-container { padding: 24px; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.page-title { font-size: 20px; font-weight: 600; color: #1f2937; margin: 0; }
.stat-card { background: white; border-radius: 12px; padding: 16px; display: flex; align-items: center; gap: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
.stat-card::before { content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background: var(--c); }
.stat-icon { font-size: 28px; }
.stat-value { font-size: 22px; font-weight: 700; color: #1f2937; }
.stat-unit { font-size: 12px; color: #9ca3af; margin-left: 2px; }
.stat-label { font-size: 12px; color: #9ca3af; }
.card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
.card-title { font-size: 16px; font-weight: 600; color: #1f2937; margin-bottom: 16px; }
.doc-list { display: flex; flex-direction: column; gap: 10px; }
.doc-item { padding: 10px; background: #fafafa; border-radius: 6px; font-size: 13px; color: #374151; cursor: pointer; }
.doc-item:hover { background: #f0f0f0; }
.mb-6 { margin-bottom: 20px; }
</style>

View File

@@ -0,0 +1,155 @@
<script setup lang="ts">
definePageMeta({ layout: 'admin' })
const { activeTab } = useNav()
activeTab.value = 'production-schedule'
const selectedDate = ref('2026-04-09')
const scheduleData = ref([
{ time: '08:00', tasks: [
{ wo: 'WO2026040901', product: '精密轴承组件 A型', qty: 500, line: '产线1', status: 'running' },
]},
{ time: '10:00', tasks: [
{ wo: 'WO2026040802', product: '液压缸体 B型', qty: 200, line: '产线2', status: 'pending' },
]},
{ time: '14:00', tasks: [
{ wo: 'WO2026040703', product: '密封圈组件 D型', qty: 3000, line: '产线3', status: 'pending' },
]},
])
const productionLines = ref([
{ id: 'L1', name: '产线1', status: 'running', utilization: 92, output: 245, target: 260 },
{ id: 'L2', name: '产线2', status: 'idle', utilization: 0, output: 0, target: 200 },
{ id: 'L3', name: '产线3', status: 'running', utilization: 78, output: 180, target: 220 },
{ id: 'L4', name: '产线4', status: 'maintenance', utilization: 0, output: 0, target: 180 },
])
const statusColor: Record<string, string> = {
running: '#10b981',
pending: '#f59e0b',
idle: '#9ca3af',
maintenance: '#ef4444',
}
const lineStatusMap: Record<string, string> = {
running: { status: 'running', text: '运行中' },
idle: { status: 'default', text: '空闲' },
maintenance: { status: 'exception', text: '维护中' },
}
</script>
<template>
<div class="page-container">
<div class="page-header">
<h2 class="page-title">计划排程</h2>
<a-space>
<a-date-picker v-model:value="selectedDate" />
<a-button type="primary">
<template #icon><PlusOutlined /></template>
新建排程
</a-button>
</a-space>
</div>
<a-row :gutter="[20, 20]">
<!-- 排程甘特图 -->
<a-col :xs="24" :xl="16">
<div class="card">
<div class="card-title">生产排程</div>
<div class="gantt">
<div class="gantt-header">
<div class="gantt-time">时间段</div>
<div class="gantt-tasks">排程任务</div>
</div>
<div v-for="slot in scheduleData" :key="slot.time" class="gantt-row">
<div class="gantt-time">{{ slot.time }}</div>
<div class="gantt-tasks">
<div v-for="task in slot.tasks" :key="task.wo" class="task-card" :class="task.status">
<div class="task-wo">{{ task.wo }}</div>
<div class="task-info">{{ task.product }} · {{ task.qty }}</div>
<div class="task-line">{{ task.line }}</div>
</div>
<div v-if="slot.tasks.length === 0" class="task-empty">暂无排程</div>
</div>
</div>
</div>
</div>
</a-col>
<!-- 产线状态 -->
<a-col :xs="24" :xl="8">
<div class="card">
<div class="card-title">产线状态</div>
<div class="line-list">
<div v-for="line in productionLines" :key="line.id" class="line-item">
<div class="line-header">
<span class="line-name">{{ line.name }}</span>
<a-badge :status="lineStatusMap[line.status].status as any" :text="lineStatusMap[line.status].text" />
</div>
<div class="line-stats">
<div class="line-stat">
<span class="line-num">{{ line.utilization }}%</span>
<span class="line-lbl">利用率</span>
</div>
<div class="line-stat">
<span class="line-num">{{ line.output }}</span>
<span class="line-lbl">实际产量</span>
</div>
<div class="line-stat">
<span class="line-num">{{ line.target }}</span>
<span class="line-lbl">目标产量</span>
</div>
</div>
<a-progress
:percent="line.utilization"
:showInfo="false"
size="small"
:status="line.status === 'running' ? 'active' : 'exception'"
:strokeColor="statusColor[line.status]"
/>
</div>
</div>
</div>
</a-col>
</a-row>
</div>
</template>
<style scoped>
.page-container { padding: 24px; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.page-title { font-size: 20px; font-weight: 600; color: #1f2937; margin: 0; }
.card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; margin-bottom: 20px; }
.card-title { font-size: 16px; font-weight: 600; color: #1f2937; margin-bottom: 16px; }
.gantt { border: 1px solid #f0f0f0; border-radius: 8px; overflow: hidden; }
.gantt-header { display: flex; background: #fafafa; border-bottom: 1px solid #f0f0f0; font-weight: 600; font-size: 13px; }
.gantt-time { width: 80px; padding: 10px 12px; border-right: 1px solid #f0f0f0; }
.gantt-tasks { flex: 1; padding: 10px 12px; }
.gantt-row { display: flex; border-bottom: 1px solid #f0f0f0; }
.gantt-row:last-child { border-bottom: none; }
.gantt-row .gantt-time { padding: 16px 12px; font-size: 13px; color: #6b7280; }
.gantt-row .gantt-tasks { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px; min-height: 60px; align-items: center; }
.task-card {
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
}
.task-card.running { background: #ecfdf5; border-left: 3px solid #10b981; }
.task-card.pending { background: #fffbeb; border-left: 3px solid #f59e0b; }
.task-wo { font-weight: 600; color: #374151; }
.task-info { color: #6b7280; margin: 2px 0; }
.task-line { color: #9ca3af; font-size: 11px; }
.task-empty { color: #d1d5db; font-size: 13px; }
.line-list { display: flex; flex-direction: column; gap: 16px; }
.line-item { padding: 14px; background: #fafafa; border-radius: 8px; }
.line-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.line-name { font-weight: 600; color: #374151; font-size: 14px; }
.line-stats { display: flex; justify-content: space-between; margin-bottom: 10px; }
.line-stat { text-align: center; }
.line-num { display: block; font-size: 16px; font-weight: 700; color: #1f2937; }
.line-lbl { font-size: 11px; color: #9ca3af; }
</style>