Files
bj_power/bj_power_mes/frontend/src/pages/Performance.vue
T
SunYF 0ab21b1234 feat&refactor: 完成多模块功能迭代与配置优化
本次提交覆盖多个业务模块的功能完善与体验优化:
1.  **鉴权与配置调整**:
    - 统一JWT滑动续签逻辑,简化Token存储,移除RefreshToken相关冗余代码
    - 调整多项目配置文件中JWT过期时间为3600秒,统一会话闲置窗口
    - 工位配置放开1~12限制,改为仅校验大于0
2.  **术语统一替换**:全链路将"精密件"替换为"电气件",修正物料管理描述
3.  **功能新增**:
    - 新增工位类型、工艺路线与产线点位台账模块
    - 添加工艺PDF预览面板、工位终端代理转发接口
    - 新增操作日志按操作人列表筛选、工位登出日志记录
    - 新增PLC移料指令与产线点位状态管理
4.  **业务流程优化**:
    - 调整BOM物料删除校验逻辑,优化工单备料计算
    - 补充物料图号、检测单号等追溯字段
    - 完善工艺流程图与工位绑定关系说明
    - 优化前端页面文案与交互细节
5.  **代码规范与维护**:
    - 新增通用工具函数与前端静态资源
    - 整理路由权限与中间件逻辑
    - 修复部分接口与配置的不兼容问题
2026-09-10 16:59:15 +08:00

221 lines
9.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-card>
<el-form inline>
<el-form-item label="操作人">
<el-input v-model="query.operator" placeholder="输入操作人" clearable style="width:180px" @keyup.enter="load" />
</el-form-item>
<el-form-item label="工位">
<el-select v-model="query.stationNo" clearable placeholder="全部工位" style="width:130px">
<el-option v-for="i in stationNos" :key="i" :label="`工位${i}`" :value="String(i)" />
</el-select>
</el-form-item>
<el-form-item label="日期范围">
<el-date-picker
v-model="query.range"
type="daterange"
value-format="YYYY-MM-DD"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width:260px"
@change="load" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="load">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="success" @click="exportXlsx">导出 xlsx(三 sheet)</el-button>
</el-form-item>
</el-form>
<!-- 汇总卡(按人聚合是全量推算的分页首页,统计卡随查询刷新) -->
<el-row :gutter="16" style="margin-bottom:14px">
<el-col :span="6">
<el-card shadow="never" class="stat-card">
<div class="stat-label">总完成数</div>
<div class="stat-value">{{ summary.doneCount }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="never" class="stat-card">
<div class="stat-label">总合格数</div>
<div class="stat-value ok">{{ summary.okCount }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="never" class="stat-card">
<div class="stat-label">总不合格数</div>
<div class="stat-value ng">{{ summary.ngCount }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="never" class="stat-card">
<div class="stat-label">总合格率</div>
<div class="stat-value">{{ summary.rate }}</div>
</el-card>
</el-col>
</el-row>
<el-tabs v-model="activeTab">
<!-- 按人 -->
<el-tab-pane label="按人" name="byOperator">
<el-table :data="views.byOperator.list" border size="small">
<el-table-column prop="operator" label="操作人" min-width="130" />
<el-table-column prop="doneCount" label="完成数" width="110" align="right" />
<el-table-column prop="okCount" label="合格数" width="110" align="right" />
<el-table-column prop="ngCount" label="不合格数" width="110" align="right" />
<el-table-column label="合格率" width="110" align="center">
<template #default="{ row }">
<el-tag :type="rateOf(row) >= 100 ? 'success' : (rateOf(row) >= 0 ? 'warning' : 'info')" effect="plain">
{{ rateText(row) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" background layout="total, prev, pager, next"
:total="views.byOperator.total" :page-size="pageSize" v-model:current-page="views.byOperator.page"
@current-change="load" />
</el-tab-pane>
<!-- 按工位 -->
<el-tab-pane label="按工位" name="byStation">
<el-table :data="views.byStation.list" border size="small">
<el-table-column prop="stationNo" label="工位" width="90" align="center" />
<el-table-column prop="doneCount" label="完成数" width="110" align="right" />
<el-table-column prop="okCount" label="合格数" width="110" align="right" />
<el-table-column prop="ngCount" label="不合格数" width="110" align="right" />
<el-table-column label="合格率" width="110" align="center">
<template #default="{ row }">
<el-tag :type="rateOf(row) >= 100 ? 'success' : (rateOf(row) >= 0 ? 'warning' : 'info')" effect="plain">
{{ rateText(row) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" background layout="total, prev, pager, next"
:total="views.byStation.total" :page-size="pageSize" v-model:current-page="views.byStation.page"
@current-change="load" />
</el-tab-pane>
<!-- 明细 -->
<el-tab-pane label="明细" name="detail">
<el-table :data="views.detail.list" border size="small">
<el-table-column prop="operator" label="操作人" min-width="110" />
<el-table-column prop="date" label="日期" width="110" />
<el-table-column prop="stationNo" label="工位" width="80" align="center" />
<el-table-column prop="processName" label="工序名" min-width="130" show-overflow-tooltip />
<el-table-column prop="doneCount" label="完成数" width="90" align="right" />
<el-table-column prop="okCount" label="合格数" width="90" align="right" />
<el-table-column prop="ngCount" label="不合格数" width="90" align="right" />
<el-table-column label="合格率" width="100" align="center">
<template #default="{ row }">
<el-tag :type="rateOf(row) >= 100 ? 'success' : (rateOf(row) >= 0 ? 'warning' : 'info')" effect="plain">
{{ rateText(row) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" background layout="total, prev, pager, next"
:total="views.detail.total" :page-size="pageSize" v-model:current-page="views.detail.page"
@current-change="load" />
</el-tab-pane>
</el-tabs>
</el-card>
<PageHelp :data="helpPerformance" page="Performance" />
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import PageHelp from '../components/PageHelp.vue'
import { helpPerformance } from '../help'
import { loadStationNos } from '../utils/stations'
import request from '../utils/request'
// 后端三视图各自真分页(默认20):byOperator/byStation/detail 各带 {total, list}
const activeTab = ref('byOperator')
const pageSize = ref(20)
const stationNos = ref([])
const query = reactive({ operator: '', stationNo: '', range: null })
const views = reactive({
byOperator: { total: 0, page: 1, list: [] },
byStation: { total: 0, page: 1, list: [] },
detail: { total: 0, page: 1, list: [] }
})
const summary = computed(() => {
let doneCount = 0, okCount = 0, ngCount = 0
views.byOperator.list.forEach((r) => {
doneCount += Number(r.doneCount) || 0
okCount += Number(r.okCount) || 0
ngCount += Number(r.ngCount) || 0
})
return {
doneCount, okCount, ngCount,
rate: doneCount ? ((okCount / doneCount) * 100).toFixed(1) + '%' : '-'
}
})
function rateOf(row) {
const done = Number(row.doneCount) || 0
const ok = Number(row.okCount) || 0
return done ? (ok / done) * 100 : -1
}
function rateText(row) {
const r = rateOf(row)
return r < 0 ? '-' : r.toFixed(1) + '%'
}
function defaultRange() {
const d = new Date()
const p = (n) => String(n).padStart(2, '0')
const end = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
const s = new Date(d.getTime() - 90 * 24 * 3600 * 1000)
return [`${s.getFullYear()}-${p(s.getMonth() + 1)}-${p(s.getDate())}`, end]
}
if (!query.range) query.range = defaultRange()
async function load() {
const [from, to] = query.range || []
const params = {
operator: query.operator || '',
stationNo: query.stationNo || '',
from: from || '',
to: to || '',
opPage: views.byOperator.page, opPageSize: pageSize.value,
stPage: views.byStation.page, stPageSize: pageSize.value,
detPage: views.detail.page, detPageSize: pageSize.value
}
const data = (await request.get('/performance', { params })) || {}
for (const k of ['byOperator', 'byStation', 'detail']) {
const v = data[k] || {}
views[k].total = v.total || 0
views[k].list = v.list || []
}
}
// 导出:后端一个 xlsx 三个 sheet(按人/按工位/明细),执行统一导出时间硬规则
async function exportXlsx() {
const [from, to] = query.range || []
const params = {
operator: query.operator || '', stationNo: query.stationNo || '',
startDate: from || '', endDate: to || ''
}
const blob = await request.get('/performance/export', { params, responseType: 'blob' })
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = '绩效报表.xlsx'
a.click()
URL.revokeObjectURL(a.href)
}
onMounted(async () => { await load(); stationNos.value = await loadStationNos() })
</script>
<style scoped>
.stat-card { text-align: center; }
.stat-label { color: #909399; font-size: 13px; margin-bottom: 6px; }
.stat-value { font-size: 26px; font-weight: 700; color: #303133; }
.stat-value.ok { color: #67c23a; }
.stat-value.ng { color: #f56c6c; }
</style>