黄伟杰 2 weeks ago
commit 02a8a7225e

@ -1,34 +1,22 @@
# 开发环境本地只启动前端项目依赖开发环境后端、APP
NODE_ENV=production
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
VITE_BASE_URL=' '
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server
VITE_UPLOAD_URL='/admin-api/infra/file/upload'
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true
# 打包路径
VITE_BASE_PATH=/
# 输出路径
VITE_OUT_DIR=dist
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true
VITE_APP_CAPTCHA_ENABLE=false

@ -1,16 +1,16 @@
# 使用轻量级的 Nginx 镜像
FROM nginx:alpine
# 复制自定义 Nginx 配置(如果有)
ARG NGINX_CONF=nginx.conf
RUN rm -f /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY ${NGINX_CONF} /etc/nginx/conf.d/default.conf
# 将本地 dist 目录复制到容器中
COPY dist /usr/share/nginx/html
# 暴露 8088 端口
EXPOSE 8088
# 创建启动脚本
RUN echo -e '#!/bin/sh\n\
echo "Waiting for MySQL/Redis/MinIO to start..."\n\

@ -0,0 +1,130 @@
server {
listen 8088;
server_name localhost;
# 允许上传最大100MB
client_max_body_size 100m;
# =========================
# 后端接口代理DEV
# =========================
location /admin-api/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /api/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /jmreport/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /v3/api-docs/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /doc.html {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /swagger-ui/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /webjars/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location ~ /v3/api-docs/.*\.json$ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# =========================
# 前端静态页面
# =========================
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# =========================
# 错误页
# =========================
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
# =========================
# FRP / 转发代理
# =========================
server {
listen 39002;
server_name localhost;
location /besure/ {
proxy_pass http://ngsk.tech:39001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_buffering off;
}
}

@ -87,8 +87,8 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/page`, params })
},
// 查询物联设备
getDeviceList: async () => {
return await request.get({ url: `/iot/device/deviceList` })
getDeviceList: async (params?: any) => {
return await request.get({ url: `/iot/device/deviceList`, params })
},
// 查询物联设备详情
getDevice: async (id: number) => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -1,4 +1,17 @@
<template>
<ContentWrap>
<div class="device-statistics">
<div
v-for="item in deviceStatisticsItems"
:key="item.key"
class="device-statistics__item"
:class="`device-statistics__item--${item.key}`"
>
<span class="device-statistics__label">{{ item.label }}</span>
<span class="device-statistics__value">{{ item.value }}</span>
</div>
</div>
</ContentWrap>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px">
@ -214,9 +227,9 @@
<!-- <i>📊</i>-->
<el-image
style="width: 60px; height: 60px"
:src="item.images || 'https://p119-minio-upload.ngsk.tech:7001/besure/998a6fc5d7da4ad079cc5afaa1ca5293917c17080c9805dc791039d3534e9dba.jpeg'"
:src="item.images || getImgUrl('wutu.jpeg')"
:preview-src-list="[
item.images || 'https://p119-minio-upload.ngsk.tech:7001/besure/998a6fc5d7da4ad079cc5afaa1ca5293917c17080c9805dc791039d3534e9dba.jpeg'
item.images || getImgUrl('wutu.jpeg')
]"
fit="cover"
preview-teleported
@ -857,6 +870,9 @@ const handleView = (row) => {
query: {id: row.id}
})
}
const getImgUrl = (imgName) => {
return new URL(`/src/assets/imgs/${imgName}`, import.meta.url).href
}
/** 设备类型 列表 */
//defineOptions({name: 'DeviceLedger'})
const getEquipmentColor = (type) => {
@ -882,27 +898,6 @@ const getEquipmentIcon = (type) => {
}
//
const getStatusTag = (status) => {
const tagMap = {
'运行': 'success',//running
'待机中': 'info',//standby
'故障中': 'danger',//fault
'报警中': 'warning',//maintenance
'离线': 'info'//stopped
}
return tagMap[status] || 'info'
}
const getStatusText = (status) => {
const textMap = {
'运行': 'status-running',
'待机中': 'status-standby',
'故障中': 'status-fault',
'报警中': 'status-maintenance',
'离线': 'status-stopped'
}
return textMap[status] || '未知'
}
//
const toggleView = () => {
@ -951,20 +946,93 @@ const queryParams = reactive({
const queryFormRef = ref() //
const exportLoading = ref(false) //
type DeviceOperatingStatusKey = 'running' | 'standby' | 'fault' | 'alarm' | 'offline'
type DeviceStatisticKey = 'total' | DeviceOperatingStatusKey
const deviceStatistics = reactive<Record<DeviceStatisticKey, number>>({
total: 0,
running: 0,
standby: 0,
fault: 0,
alarm: 0,
offline: 0
})
const deviceStatisticsItems = computed(() => [
{ key: 'total', label: '总数', value: deviceStatistics.total },
{ key: 'running', label: '运行数', value: deviceStatistics.running },
{ key: 'standby', label: '待机数', value: deviceStatistics.standby },
{ key: 'fault', label: '故障数', value: deviceStatistics.fault },
{ key: 'alarm', label: '报警数', value: deviceStatistics.alarm },
{ key: 'offline', label: '离线数', value: deviceStatistics.offline }
])
const normalizeDeviceOperatingStatus = (value: string | number | undefined): DeviceOperatingStatusKey => {
const text = String(value ?? '').trim().toLowerCase()
if (['运行', '运行中', 'running', 'run', '1'].includes(text)) return 'running'
if (['待机', '待机中', 'standby', 'idle', '2'].includes(text)) return 'standby'
if (['故障', '故障中', 'fault', 'error', '3'].includes(text)) return 'fault'
if (['报警', '报警中', 'alarm', 'warning', '4', '5'].includes(text)) return 'alarm'
return 'offline'
}
const operatingStatusLabelMap: Record<DeviceOperatingStatusKey, string> = {
running: '运行',
standby: '待机中',
fault: '故障中',
alarm: '报警中',
offline: '离线'
}
const operatingStatusTypeMap: Record<DeviceOperatingStatusKey, 'success' | 'info' | 'danger' | 'warning'> = {
running: 'success',
standby: 'info',
fault: 'danger',
alarm: 'warning',
offline: 'info'
}
const setDeviceStatistics = (rows: DeviceVO[] = []) => {
const nextStats: Record<DeviceStatisticKey, number> = {
total: rows.length,
running: 0,
standby: 0,
fault: 0,
alarm: 0,
offline: 0
}
rows.forEach((row) => {
const key = normalizeDeviceOperatingStatus(row.operatingStatus)
nextStats[key] += 1
})
Object.assign(deviceStatistics, nextStats)
}
const getDeviceStatistics = async (totalCount = total.value) => {
const { pageNo, pageSize, ...params } = queryParams
const statisticsPageSize = Math.max(Number(totalCount) || 0, Number(pageSize) || 10, 1)
try {
const data = await DeviceApi.getDevicePage({
...params,
pageNo: 1,
pageSize: statisticsPageSize
})
const rows = Array.isArray(data) ? data : Array.isArray((data as any)?.list) ? (data as any).list : []
setDeviceStatistics(rows)
} catch {
setDeviceStatistics(list.value)
}
}
const getOperatingStatusLabel = (value: string | number | undefined) => {
const text = String(value ?? '').trim()
if (!text) return '离线'
return text
return text || operatingStatusLabelMap[normalizeDeviceOperatingStatus(value)]
}
const getOperatingStatusType = (value: string | number | undefined) => {
const text = String(value ?? '').trim()
if (!text) return 'info'
if (text === '运行') return 'success'
if (text === '待机中') return 'info'
if (text === '故障中') return 'danger'
if (text === '报警中') return 'warning'
return 'info'
return operatingStatusTypeMap[normalizeDeviceOperatingStatus(value)]
}
const getStatusText = (status: string | number | undefined) => {
return `status-${normalizeDeviceOperatingStatus(status)}`
}
const isDeviceEnabled = (row: DeviceVO) => {
@ -976,14 +1044,15 @@ const isDeviceEnabled = (row: DeviceVO) => {
return false
}
const handleDeviceEnableChange = async (row: DeviceVO, value: boolean) => {
const handleDeviceEnableChange = async (row: DeviceVO, value: string | number | boolean) => {
if (!row.id) return
const enabled = value === true || value === 'true' || value === '1' || value === 1
const oldValue = (row as any).isEnable
;(row as any).isEnable = value
;(row as any).isEnable = enabled
try {
await DeviceApi.updateDeviceEnabled(row.id, value ? 'true' : 'false')
await DeviceApi.updateDeviceEnabled(row.id, enabled ? 'true' : 'false')
const name = (row as any).deviceName ?? (row as any).deviceCode ?? ''
const suffix = value ? '已启用' : '已停用'
const suffix = enabled ? '已启用' : '已停用'
if (name) {
message.success(`${name}${suffix}`)
} else {
@ -1009,6 +1078,7 @@ const getList = async (showLoading = true) => {
const data = await DeviceApi.getDevicePage(queryParams)
list.value = data.list
total.value = data.total
await getDeviceStatistics(data.total)
} finally {
if (showLoading) {
loading.value = false
@ -1025,6 +1095,7 @@ const getListOne = async (showLoading = true) => {
const data = await DeviceApi.getDevicePage(queryParams)
list.value = data.list
total.value = data.total
await getDeviceStatistics(data.total)
} finally {
if (showLoading) {
loading.value = false
@ -1972,6 +2043,71 @@ const handleShowDeviceAlarmHistory = async () => {
</script>
<style lang="scss" scoped>
.device-statistics {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 10px;
margin-bottom: 16px;
}
.device-statistics__item {
min-height: 72px;
padding: 12px 16px;
border: 1px solid #e4e7ed;
border-radius: 8px;
background: linear-gradient(180deg, #ffffff 0%, #fafafa 100%);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
.device-statistics__label {
display: block;
color: #606266;
font-size: 13px;
line-height: 18px;
}
.device-statistics__value {
display: block;
margin-top: 8px;
color: #303133;
font-size: 26px;
font-weight: 600;
line-height: 30px;
}
.device-statistics__item--running .device-statistics__value {
color: #67c23a;
}
.device-statistics__item--fault .device-statistics__value {
color: #f56c6c;
}
.device-statistics__item--alarm .device-statistics__value {
color: #e6a23c;
}
.device-statistics__item--offline .device-statistics__value,
.device-statistics__item--standby .device-statistics__value {
color: #909399;
}
@media (max-width: 1200px) {
.device-statistics {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.device-statistics {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.device-statistics__item {
padding: 10px 12px;
}
}
.simple-grid-view {
background-color: #fff;
border-radius: 4px;

@ -52,7 +52,7 @@
:placeholder="t('QualityManagement.ZjItem.placeholderLowerVal')"
/>
</el-form-item>
<el-form-item :label="t('QualityManagement.ZjItem.unit')" prop="unitId">
<el-form-item :label="t('QualityManagement.ZjItem.unit')" prop="unit">
<el-select
v-model="formData.unit"
clearable
@ -63,7 +63,7 @@
v-for="unit in unitList"
:key="unit.id"
:label="unit.name"
:value="unit.id"
:value="String(unit.id)"
/>
</el-select>
</el-form-item>
@ -86,7 +86,7 @@
</template>
<script setup lang="ts">
import { ZjItemApi, ZjItemVO } from '@/api/mes/zjitem'
import {ZjTypeApi, ZjTypeVO} from "@/api/mes/zjtype";
import { ZjTypeApi, ZjTypeVO } from '@/api/mes/zjtype'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
/** 质量管理-检验项目 表单 */
@ -125,17 +125,18 @@ const open = async (type: string, id?: number) => {
formType.value = type
resetForm()
typeList.value = await ZjTypeApi.getZjTypeList()
unitList.value = await ProductUnitApi.getProductUnitSimpleList()
//
if (id) {
formLoading.value = true
try {
formData.value = await ZjItemApi.getZjItem(id)
formData.value.unit = formData.value.unit == null ? undefined : String(formData.value.unit)
} finally {
formLoading.value = false
}
}
//
unitList.value = await ProductUnitApi.getProductUnitSimpleList()
}
defineExpose({ open }) // open

Loading…
Cancel
Save