IIS部署准备:csproj输出到bin\ + 前端build输出到admin + 修复全部TS类型错误

- CncWebApi.csproj: OutputPath改为bin\,AppendTargetFrameworkToOutputPath=false
- .gitignore: 排除src/CncWebApi/admin/目录
- vite.config.ts: build.outDir指向../src/CncWebApi/admin
- deploy-admin.ps1: 一键编译后端+前端部署脚本
- request.ts: 类型安全封装,返回Promise<ApiResponse<T>>
- types/index.ts: 扩展Machine/Alert/OperationLog/ScreenCard/CollectAddress/MachineStatus字段
- 修复11个页面文件的TS类型错误(vue-tsc 0错误)
main
haoliang 1 week ago
parent 16016d0df7
commit 126154fc7b

3
.gitignore vendored

@ -39,3 +39,6 @@ src/**/bin/
src/**/obj/
tests/**/bin/
tests/**/obj/
# === 前端部署到CncWebApi/admin ===
src/CncWebApi/admin/

@ -0,0 +1,67 @@
# ============================================================
# deploy-admin.ps1 — 一键编译后端+前端并部署到 admin 目录
# 用法:在项目根目录执行 .\deploy-admin.ps1
# ============================================================
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
$projectRoot = $PSScriptRoot
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " CNC 系统一键部署脚本" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# --------------------------------------------------
# 第1步编译后端
# --------------------------------------------------
Write-Host "[1/2] 编译后端 API ..." -ForegroundColor Yellow
dotnet build "$projectRoot\CncDataSystem.sln"
if ($LASTEXITCODE -ne 0) {
Write-Host "后端编译失败!" -ForegroundColor Red
exit 1
}
Write-Host "后端编译完成 ✓" -ForegroundColor Green
Write-Host ""
# --------------------------------------------------
# 第2步编译前端并输出到 admin 目录
# --------------------------------------------------
Write-Host "[2/2] 编译前端(输出到 src\CncWebApi\admin\..." -ForegroundColor Yellow
$frontendDir = Join-Path $projectRoot "frontend"
# 安装依赖(如果 node_modules 不存在)
if (-not (Test-Path "$frontendDir\node_modules")) {
Write-Host " 安装前端依赖 ..." -ForegroundColor Gray
npm install --prefix $frontendDir
if ($LASTEXITCODE -ne 0) {
Write-Host "前端依赖安装失败!" -ForegroundColor Red
exit 1
}
}
# 构建前端vite.config.ts 已配置 outDir 指向 ../src/CncWebApi/admin
npm run build --prefix $frontendDir
if ($LASTEXITCODE -ne 0) {
Write-Host "前端编译失败!" -ForegroundColor Red
exit 1
}
$adminDir = Join-Path $projectRoot "src\CncWebApi\admin"
$fileCount = (Get-ChildItem $adminDir -Recurse -File).Count
Write-Host "前端编译完成 ✓($fileCount 个文件)" -ForegroundColor Green
Write-Host ""
# --------------------------------------------------
# 完成
# --------------------------------------------------
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " 部署完成!" -ForegroundColor Cyan
Write-Host " 后端 APIhttp://192.168.1.202/api/health" -ForegroundColor White
Write-Host " 前端页面http://192.168.1.202/admin/" -ForegroundColor White
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""

@ -39,6 +39,12 @@ export interface Machine {
isEnabled: boolean
workerName?: string
collectAddressName?: string
/** 编辑表单用所属车间ID */
workshopId?: number
/** 编辑表单用采集地址ID */
collectAddressId?: number
/** 编辑表单用绑定工人ID */
workerId?: number
}
/** 机床状态 */
@ -47,6 +53,12 @@ export interface MachineStatus {
partCount: number
runStatus: string
operationMode: string
/** 主轴设定转速 */
spindleSpeedSet?: number
/** 进给设定速度 */
feedSpeedSet?: number
/** 主轴实际转速 */
spindleSpeedActual?: number
spindleSpeed: number
feedRate: number
spindleLoad: number
@ -81,10 +93,14 @@ export interface CollectAddress {
url: string
brandName: string
interval: number
/** 详情页显示用:采集间隔(秒) */
collectInterval?: number
isEnabled: boolean
lastCollectTime: string
machineCount: number
failCount: number
/** 详情页原始JSON */
rawJson?: string
}
/** 工人 */
@ -103,6 +119,8 @@ export interface Alert {
alertType: string
machineName: string
message: string
title?: string
detail?: string
isResolved: boolean
createdAt: string
resolvedAt?: string
@ -151,12 +169,21 @@ export interface Workshop {
/** 操作日志 */
export interface OperationLog {
id: number
timestamp: string
level: string
/** 日志时间 */
createdAt: string
/** 日志级别ERROR/WARN/INFO/DEBUG */
logLevel: string
source: string
message: string
stackTrace?: string
extraData?: string
/** 产量修正日志扩展字段 */
targetTable?: string
targetId?: number
oldValue?: number
newValue?: number
reason?: string
operatorIp?: string
}
/** 大屏卡片配置 */
@ -170,6 +197,13 @@ export interface ScreenCard {
sortOrder: number
isEnabled: boolean
chartConfig?: string
/** 前端展示用标题(同 cardName */
title?: string
/** 前端展示用(同 cardKey */
screenKey?: string
filterType?: string
filterValue?: string
isDefault?: boolean | number
}
/** 大屏筛选配置 */

@ -1,6 +1,7 @@
import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios'
import axios, { type AxiosResponse, type InternalAxiosRequestConfig, type Method } from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router'
import type { ApiResponse } from '@/types'
// 创建axios实例
const service = axios.create({
@ -24,10 +25,10 @@ service.interceptors.request.use(
(error) => Promise.reject(error)
)
// 响应拦截器
// 响应拦截器:解包 AxiosResponse → ApiResponse
service.interceptors.response.use(
(response: AxiosResponse) => {
const res = response.data
(response: AxiosResponse): any => {
const res = response.data as ApiResponse
if (res.code === 0) {
return res
}
@ -63,4 +64,24 @@ service.interceptors.response.use(
}
)
export default service
/**
*
* AxiosResponse ApiResponse<T>
* ApiResponse<T>访 .data / .code / .message
*/
const request = {
get<T = unknown>(url: string, config?: object): Promise<ApiResponse<T>> {
return service.get(url, config) as unknown as Promise<ApiResponse<T>>
},
post<T = unknown>(url: string, data?: unknown, config?: object): Promise<ApiResponse<T>> {
return service.post(url, data, config) as unknown as Promise<ApiResponse<T>>
},
put<T = unknown>(url: string, data?: unknown, config?: object): Promise<ApiResponse<T>> {
return service.put(url, data, config) as unknown as Promise<ApiResponse<T>>
},
delete<T = unknown>(url: string, config?: object): Promise<ApiResponse<T>> {
return service.delete(url, config) as unknown as Promise<ApiResponse<T>>
},
}
export default request

@ -45,11 +45,11 @@
<!-- 操作栏 -->
<div style="margin-bottom:12px">
<el-button type="primary" @click="batchResolve" :disabled="!selectedRows.length || !selectedRows.some(r => !r.isResolved)">批量标记已处理</el-button>
<el-button type="primary" @click="batchResolve" :disabled="!selectedRows.length || !selectedRows.some((r: Alert) => !r.isResolved)">批量标记已处理</el-button>
</div>
<!-- 列表 -->
<el-table :data="tableData" border stripe v-loading="loading" @selection-change="(rows: Record<string,unknown>[]) => selectedRows = rows">
<el-table :data="tableData" border stripe v-loading="loading" @selection-change="(rows: Alert[]) => selectedRows = rows">
<el-table-column type="selection" width="40" fixed="left" align="center" />
<el-table-column prop="createdAt" label="告警时间" width="170" sortable />
<el-table-column label="告警类型" width="100" align="center">
@ -107,12 +107,22 @@
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
import type { ApiResponse, Alert, Machine } from '@/types'
/** 告警统计信息 */
interface AlertStats {
unresolved: number
collectFail: number
deviceOffline: number
productionAnomaly: number
unknownDevice: number
}
const loading = ref(false)
const tableData = ref<Alert[]>([])
const selectedRows = ref<Alert[]>([])
const stats = ref<Alert>({} as Alert)
const machineList = ref<Alert[]>([])
const stats = ref<AlertStats>({} as AlertStats)
const machineList = ref<Machine[]>([])
const detailVisible = ref(false)
const detailRow = ref<Alert>({} as Alert)
@ -152,11 +162,11 @@ function alertTypeLabel(type: string): string {
async function loadData() {
loading.value = true
try {
const [s, d]: Record<string,unknown>[] = await Promise.all([
request.get('/admin/alert/statistics'),
request.get('/admin/alert', { params: { ...query, ...page } }),
const [s, d] = await Promise.all([
request.get<AlertStats>('/admin/alert/statistics'),
request.get<{ items: Alert[]; total: number }>('/admin/alert', { params: { ...query, ...page } }),
])
stats.value = s.data || {}
stats.value = s.data || {} as AlertStats
tableData.value = d.data?.items || []
page.total = d.data?.total || 0
} finally {
@ -177,9 +187,9 @@ async function handleResolve(row: Alert) {
}
async function batchResolve() {
const unresolved = selectedRows.value.filter(r => !r.isResolved)
const unresolved = selectedRows.value.filter((r: Alert) => !r.isResolved)
await ElMessageBox.confirm(`确定对选中的${unresolved.length}项标记为已处理?`, '提示', { type: 'warning' })
await request.post('/admin/alert/batch-resolve', { ids: unresolved.map(r => r.id) })
await request.post('/admin/alert/batch-resolve', { ids: unresolved.map((r: Alert) => r.id) })
ElMessage.success('批量标记成功')
loadData()
}
@ -190,7 +200,7 @@ function viewDetail(row: Alert) {
}
async function loadDrops() {
const r: Record<string,unknown> = await request.get('/admin/machine/list')
const r = await request.get<{ items: Machine[] }>('/admin/machine/list')
machineList.value = r.data?.items || []
}

@ -44,8 +44,8 @@ const form = reactive({ brandName: '', deviceField: 'device', tagsPath: 'tags',
function addMapping() { form.mappings.push({ standardField: '', fieldName: '', matchBy: 'id', dataType: 'string', isRequired: 0 }) }
async function loadData() {
if (!isEdit) return
const r: Record<string,unknown> = await request.get('/admin/brand/detail', { params: { id: route.params.id } })
if (r.data) { form.brandName = r.data.brandName; form.deviceField = r.data.deviceField; form.tagsPath = r.data.tagsPath; form.mappings = r.data.mappings || [] }
const r = await request.get<Brand>('/admin/brand/detail', { params: { id: route.params.id } })
if (r.data) { form.brandName = r.data.brandName; form.deviceField = r.data.deviceField; form.tagsPath = r.data.tagsPath; form.mappings = (r.data as any).mappings || [] }
}
async function handleSave() {
await ElMessageBox.confirm('品牌模板修改不影响历史数据,确定保存?', '提示', { type: 'warning' })

@ -15,7 +15,7 @@
<el-descriptions-item label="名称">{{detail.name}}</el-descriptions-item>
<el-descriptions-item label="URL">{{detail.url}}</el-descriptions-item>
<el-descriptions-item label="品牌">{{detail.brandName}}</el-descriptions-item>
<el-descriptions-item label="采集间隔">{{detail.collectInterval}}</el-descriptions-item>
<el-descriptions-item label="采集间隔">{{detail.collectInterval ?? detail.interval}}</el-descriptions-item>
<el-descriptions-item label="状态"><el-tag :type="detail.isEnabled?'success':'danger'" size="small">{{detail.isEnabled?'启用':'停用'}}</el-tag></el-descriptions-item>
<el-descriptions-item label="最后采集">{{detail.lastCollectTime||'-'}}</el-descriptions-item>
</el-descriptions>
@ -46,35 +46,43 @@
import {ref,onMounted} from 'vue'
import {useRoute} from 'vue-router'
import request from '@/utils/request'
import type { ApiResponse, CollectAddress, Machine } from '@/types'
import type { ApiResponse, CollectAddress } from '@/types'
import PageHeader from '@/components/PageHeader.vue'
import { useMockPath } from '@/composables/useMockPath'
type CollectMachineRow = { machineName: string; deviceCode?: string; workshopName?: string; isOnline?: boolean; programName?: string }
type CollectRecordRow = { requestTime: string; duration: number; isSuccess: boolean; machineCount: number; machineName?: string }
const { isMock } = useMockPath()
const homePath = isMock ? '/mock/dashboard' : '/dashboard'
const collectAddressPath = isMock ? '/mock/collect-address' : '/collect-address'
const route=useRoute()
const detail=ref<CollectAddress>({} as CollectAddress); type CollectMachineRow = { machineName: string; deviceCode?: string; workshopName?: string; isOnline?: boolean; programName?: string }; const machines=ref<CollectMachineRow[]>([]); const records=ref<CollectAddress[]>([])
const detail=ref<CollectAddress>({} as CollectAddress)
const machines=ref<CollectMachineRow[]>([])
const records=ref<CollectRecordRow[]>([])
// JSON
const rawJsonDialogVisible = ref(false)
const rawJsonContent = ref('')
const rawJsonTitle = ref('原始采集数据')
async function viewRawJson(record: Record<string,unknown>){
// JSONmock
async function viewRawJson(record: CollectRecordRow){
// JSON
const id = route.params.id
const resp: Record<string,unknown> = await request.get('/admin/collect-address/raw-json', { params: { id, recordId: record.requestTime } })
const raw = resp?.data?.rawJson ?? '[]'
const resp = await request.get<{ rawJson: string }>('/admin/collect-address/raw-json', { params: { id, recordId: record.requestTime } })
const raw = resp.data?.rawJson ?? '[]'
let parsed: unknown
try { parsed = JSON.parse(raw) } catch { parsed = raw }
rawJsonContent.value = JSON.stringify(parsed, null, 2)
rawJsonTitle.value = `原始采集数据 - ${record.machineName ?? ''}`
rawJsonDialogVisible.value = true
}
async function loadData(){
const id=route.params.id
const [d,m,r]: Record<string,unknown>[] = await Promise.all([
request.get('/admin/collect-address/detail', { params: { id } }),
request.get('/admin/collect-address/machines', { params: { id } }),
request.get('/admin/collect-address/collect-records', { params: { id } })
const [d,m,r] = await Promise.all([
request.get<CollectAddress>('/admin/collect-address/detail', { params: { id } }),
request.get<{ items: CollectMachineRow[] }>('/admin/collect-address/machines', { params: { id } }),
request.get<{ items: CollectRecordRow[] }>('/admin/collect-address/collect-records', { params: { id } }),
])
detail.value = d.data ?? ({} as CollectAddress)
machines.value = m.data?.items ?? []

@ -446,7 +446,7 @@ function disposeCharts() {
async function loadWorkshopData() {
try {
const { startDate, endDate } = getDateRange(workshopDateType.value, workshopDateRange.value)
const res: ApiResponse<{ items: WorkshopProduction }> = await request.get('/admin/dashboard/workshop-production', { params: { startDate, endDate } })
const res = await request.get<{ items: WorkshopProduction[] }>('/admin/dashboard/workshop-production', { params: { startDate, endDate } })
workshopData.value = res.data?.items || []
workshopChart?.dispose(); workshopChart = null
await nextTick(); initWorkshopChart()
@ -471,17 +471,12 @@ async function loadWorkerRankData() {
async function loadData() {
try {
const [summaryRes, collectorRes, trendRes, statusRes, alertsRes]: [
ApiResponse<DashboardSummary>, ApiResponse<CollectorStatus>,
ApiResponse<{ items: DashboardTrendItem[] }>,
ApiResponse<MachineStatusDistribution>,
ApiResponse<{ items: RecentAlert[] }>
] = await Promise.all([
request.get('/admin/dashboard/summary'),
request.get('/admin/collector/status'),
request.get('/admin/dashboard/trend'),
request.get('/admin/dashboard/machine-status-distribution'),
request.get('/admin/dashboard/recent-alerts'),
const [summaryRes, collectorRes, trendRes, statusRes, alertsRes] = await Promise.all([
request.get<DashboardSummary>('/admin/dashboard/summary'),
request.get<CollectorStatus>('/admin/collector/status'),
request.get<{ items: DashboardTrendItem[] }>('/admin/dashboard/trend'),
request.get<MachineStatusDistribution>('/admin/dashboard/machine-status-distribution'),
request.get<{ items: RecentAlert[] }>('/admin/dashboard/recent-alerts'),
])
summary.value = summaryRes.data || summary.value
collectorStatus.value = collectorRes.data || collectorStatus.value

@ -145,6 +145,7 @@
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
import type { ApiResponse, OperationLog } from '@/types'
const activeTab = ref('adjustment')
@ -169,11 +170,11 @@ function targetTableLabel(table: string): string {
async function loadAdjustment() {
adjLoading.value = true
try {
const params: Record<string,unknown> = { page: adjPage.page, pageSize: adjPage.pageSize }
const params: Record<string, string | number> = { page: adjPage.page, pageSize: adjPage.pageSize }
if (adjQuery.dateRange?.length === 2) { params.startDate = adjQuery.dateRange[0]; params.endDate = adjQuery.dateRange[1] }
if (adjQuery.targetTable) params.targetTable = adjQuery.targetTable
if (adjQuery.keyword) params.keyword = adjQuery.keyword
const r: Record<string,unknown> = await request.get('/admin/log/adjustment', { params })
const r = await request.get<{ items: OperationLog[]; total: number }>('/admin/log/adjustment', { params })
adjList.value = r.data?.items || []
adjPage.total = r.data?.total || 0
} finally { adjLoading.value = false }
@ -187,14 +188,15 @@ function resetAdjQuery() {
async function exportAdjustment() {
try {
const params: Record<string,unknown> = {}
const params: Record<string, string> = {}
if (adjQuery.dateRange?.length === 2) { params.startDate = adjQuery.dateRange[0]; params.endDate = adjQuery.dateRange[1] }
if (adjQuery.targetTable) params.targetTable = adjQuery.targetTable
if (adjQuery.keyword) params.keyword = adjQuery.keyword
// Excel
const baseURL = (request as any).defaults?.baseURL || ''
const qs = new URLSearchParams(params).toString()
window.open(`${baseURL}/admin/log/adjustment/export?${qs}`, '_blank')
const isMock = window.location.pathname.startsWith('/mock')
const base = isMock ? '/mock-api' : '/api'
window.open(`${base}/admin/log/adjustment/export?${qs}`, '_blank')
ElMessage.success('正在导出...')
} catch { ElMessage.error('导出失败') }
}
@ -220,12 +222,12 @@ function logLevelTag(level: string): string {
async function loadSystem() {
sysLoading.value = true
try {
const params: Record<string,unknown> = { page: sysPage.page, pageSize: sysPage.pageSize }
const params: Record<string, string | number> = { page: sysPage.page, pageSize: sysPage.pageSize }
if (sysQuery.logLevel) params.logLevel = sysQuery.logLevel
if (sysQuery.source) params.source = sysQuery.source
if (sysQuery.dateRange?.length === 2) { params.startDate = sysQuery.dateRange[0]; params.endDate = sysQuery.dateRange[1] }
if (sysQuery.keyword) params.keyword = sysQuery.keyword
const r: Record<string,unknown> = await request.get('/admin/log/system', { params })
const r = await request.get<{ items: OperationLog[]; total: number }>('/admin/log/system', { params })
sysList.value = r.data?.items || []
sysPage.total = r.data?.total || 0
} finally { sysLoading.value = false }

@ -28,7 +28,7 @@
<el-descriptions-item label="NC程序名">{{status.programName||'-'}}</el-descriptions-item>
<el-descriptions-item label="零件数">{{status.partCount??'-'}}</el-descriptions-item>
<el-descriptions-item label="运行状态">{{status.runStatus||'-'}}</el-descriptions-item>
<el-descriptions-item label="操作模式">{{status.operateMode||'-'}}</el-descriptions-item>
<el-descriptions-item label="操作模式">{{status.operationMode||'-'}}</el-descriptions-item>
<el-descriptions-item label="主轴设定">{{status.spindleSpeedSet??'-'}}</el-descriptions-item>
<el-descriptions-item label="进给设定">{{status.feedSpeedSet??'-'}}</el-descriptions-item>
<el-descriptions-item label="主轴实际">{{status.spindleSpeedActual??'-'}}</el-descriptions-item>
@ -59,41 +59,54 @@ import { ref, onMounted, nextTick, onBeforeUnmount } from 'vue'
import { useRoute } from 'vue-router'
import request from '@/utils/request'
import echarts from '@/utils/echarts'
// ECharts 便 TS
import type { ECharts } from 'echarts/core'
import type { ApiResponse, Machine, MachineStatus } from '@/types'
/** 今日产量行 */
interface TodayProdRow { programName: string; quantity: number; runTime: number; cuttingTime: number }
/** 采集记录行 */
interface CollectRecordRow { collectTime: string; programName: string; partCount: number; runStatus: string }
/** 趋势数据项 */
interface TrendItem { date: string; quantity: number }
const route = useRoute()
// Mock
const isMockPath = typeof window !== 'undefined' && window.location.pathname.startsWith('/mock')
const homePath = isMockPath ? '/mock/dashboard' : '/dashboard'
const machinePath = isMockPath ? '/mock/machine' : '/machine'
const detail = ref<Machine>({} as Machine)
const status = ref<Machine>({} as Machine)
const status = ref<MachineStatus>({} as MachineStatus)
let statusInterval: number | undefined
const todayProd = ref<Machine[]>([]); const records = ref<Machine[]>([])
const todayProd = ref<TodayProdRow[]>([])
const records = ref<CollectRecordRow[]>([])
const chartRef = ref<HTMLElement>()
let chart: ECharts | null = null
async function loadData() {
const id = route.params.id
const [d, s, t, r]: Record<string,unknown>[] = await Promise.all([
request.get('/admin/machine/detail', { params: { id } }),
request.get('/admin/machine/status', { params: { id } }),
request.get('/admin/machine/production/today', { params: { id } }),
request.get('/admin/machine/collect-records', { params: { id } }),
const [d, s, t, r] = await Promise.all([
request.get<Machine>('/admin/machine/detail', { params: { id } }),
request.get<MachineStatus>('/admin/machine/status', { params: { id } }),
request.get<{ items: TodayProdRow[] }>('/admin/machine/production/today', { params: { id } }),
request.get<{ items: CollectRecordRow[] }>('/admin/machine/collect-records', { params: { id } }),
])
detail.value = d.data || {}; status.value = s.data || {}
todayProd.value = t.data?.items || []; records.value = r.data?.items || []
const trend: Record<string,unknown> = await request.get('/admin/machine/production/trend', { params: { id } })
detail.value = d.data || {} as Machine
status.value = s.data || {} as MachineStatus
todayProd.value = t.data?.items || []
records.value = r.data?.items || []
const trend = await request.get<{ items: TrendItem[] }>('/admin/machine/production/trend', { params: { id } })
await nextTick()
if (chartRef.value) {
chart = echarts.init(chartRef.value)
const items = trend.data?.items || []
chart.setOption({ xAxis: { type: 'category', data: items.map((i: Record<string,unknown>) => i.date.slice(5)) }, yAxis: { type: 'value' }, series: [{ type: 'line', data: items.map((i: Record<string,unknown>) => i.quantity), smooth: true, areaStyle: { opacity: 0.1 } }], tooltip: { trigger: 'axis' }, grid: { left: 40, right: 20, top: 10, bottom: 30 } })
chart.setOption({ xAxis: { type: 'category', data: items.map((i: TrendItem) => i.date.slice(5)) }, yAxis: { type: 'value' }, series: [{ type: 'line', data: items.map((i: TrendItem) => i.quantity), smooth: true, areaStyle: { opacity: 0.1 } }], tooltip: { trigger: 'axis' }, grid: { left: 40, right: 20, top: 10, bottom: 30 } })
}
}
async function fetchStatus() {
const id = route.params.id
const r: Record<string,unknown> = await request.get('/admin/machine/status', { params: { id } })
status.value = r.data || {}
const r = await request.get<MachineStatus>('/admin/machine/status', { params: { id } })
status.value = r.data || {} as MachineStatus
}
onMounted(() => {

@ -32,7 +32,7 @@
</el-form-item>
</el-form>
<el-table :data="tableData" border stripe v-loading="loading" @selection-change="(rows: Record<string,unknown>[]) => selectedRows = rows">
<el-table :data="tableData" border stripe v-loading="loading" @selection-change="(rows: Machine[]) => selectedRows = rows">
<el-table-column type="selection" width="40" fixed="left" align="center" />
<el-table-column label="机床名称" min-width="120" fixed="left" show-overflow-tooltip>
<template #default="{ row }">
@ -267,7 +267,7 @@ async function handleExport() {
ElMessage.success('正在导出...')
}
function onFileChange(file: Record<string,unknown>) { importFile.value = file.raw }
function onFileChange(file: any) { importFile.value = file.raw }
function downloadTemplate() { window.open('/api/admin/machine/import-template', '_blank') }
async function handleImport() {
@ -276,7 +276,7 @@ async function handleImport() {
try {
const formData = new FormData()
formData.append('file', importFile.value)
const r: Record<string,unknown> = await request.post('/admin/machine/import', formData)
const r = await request.post('/admin/machine/import', formData) as any
importResult.value = r.data
if (r.data?.successCount) ElMessage.success(`成功导入${r.data.successCount}`)
loadData()

@ -79,11 +79,12 @@
import { ref, reactive, onMounted, toRefs } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
import type { ProductionRecord, ProductionDashboardSummary } from '@/types'
//
const loading = ref(false)
const tableData = ref<Array<any>>([])
const summary = ref<ProductionRecord>({} as ProductionRecord)
const summary = ref<ProductionDashboardSummary>({} as ProductionDashboardSummary)
//
const page = reactive({ page: 1, pageSize: 20, total: 0 })
//
@ -144,8 +145,8 @@ async function loadData() {
programName: filters.programName ?? ''
}
const [s, d] = await Promise.all([
request.get('/admin/production/daily-summary', { params }),
request.get('/admin/production/daily', { params })
request.get<ProductionDashboardSummary>('/admin/production/daily-summary', { params }),
request.get<{ items: ProductionRecord[]; total: number }>('/admin/production/daily', { params })
])
summary.value = s.data || {}
tableData.value = d.data?.items ?? []
@ -178,9 +179,9 @@ async function init() {
//
try {
const [ws, mc, wr] = await Promise.all([
request.get('/admin/workshop/list'),
request.get('/admin/machine/list'),
request.get('/admin/worker/list')
request.get<{ items: any[] }>('/admin/workshop/list'),
request.get<{ items: any[] }>('/admin/machine/list'),
request.get<{ items: any[] }>('/admin/worker/list')
])
options.workshops = ws.data?.items ?? []
options.machines = mc.data?.items ?? []
@ -199,7 +200,7 @@ async function showHistory(row: ProductionRecord) {
historyTitle.value = `修正历史 - ${row.machineName} - ${row.date}`
historyLoading.value = true
try {
const res = await request.get('/admin/production/adjustment-history', { params: { recordId: row.id } })
const res = await request.get<{ items: any[] }>('/admin/production/adjustment-history', { params: { recordId: row.id } })
historyRows.value = res.data?.items ?? []
} finally {
historyLoading.value = false

@ -130,6 +130,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import request from '@/utils/request'
import { onBeforeUnmount } from 'vue'
import type { ScreenCard } from '@/types'
// ===== =====
const cardLoading = ref(false)
@ -176,7 +177,7 @@ function onCardTypeChange() {
async function loadCards() {
cardLoading.value = true
try { const r: Record<string,unknown> = await request.get('/admin/screen-config'); cardList.value = r.data?.items || [] } finally { cardLoading.value = false }
try { const r = await request.get('/admin/screen-config') as any; cardList.value = r.data?.items || [] } finally { cardLoading.value = false }
}
// ===== =====
@ -284,7 +285,7 @@ const filterRules: FormRules = {
async function loadFilters() {
filterLoading.value = true
try { const r: Record<string,unknown> = await request.get('/admin/screen-filter'); filterList.value = r.data?.items || [] } finally { filterLoading.value = false }
try { const r = await request.get('/admin/screen-filter') as any; filterList.value = r.data?.items || [] } finally { filterLoading.value = false }
}
function addFilter() {

@ -45,8 +45,8 @@
<div class="chart-title">机床产量排行</div>
<div class="rank-list">
<div v-if="machineRank.length === 0" class="no-data"></div>
<div v-for="item in machineRank" :key="item.rank" class="rank-item">
<span class="rank-num" :class="{ 'rank-top': item.rank <= 3 }">{{ item.rank }}</span>
<div v-for="item in machineRank" :key="item.rank ?? item.id" class="rank-item">
<span class="rank-num" :class="{ 'rank-top': (item.rank ?? 99) <= 3 }">{{ item.rank ?? '-' }}</span>
<span class="rank-name">{{ item.machineName }}</span>
<span class="rank-value">{{ item.quantity }}</span>
</div>
@ -56,10 +56,10 @@
<div class="chart-title">工人产量排行</div>
<div class="rank-list">
<div v-if="workerRank.length === 0" class="no-data"></div>
<div v-for="item in workerRank" :key="item.rank" class="rank-item">
<span class="rank-num" :class="{ 'rank-top': item.rank <= 3 }">{{ item.rank }}</span>
<div v-for="item in workerRank" :key="item.rank ?? item.id" class="rank-item">
<span class="rank-num" :class="{ 'rank-top': (item.rank ?? 99) <= 3 }">{{ item.rank ?? '-' }}</span>
<span class="rank-name">{{ item.workerName }}</span>
<span class="rank-value">{{ item.quantity }}</span>
<span class="rank-value">{{ item.totalQuantity ?? item.machineCount ?? '-' }}</span>
</div>
</div>
</div>
@ -99,6 +99,7 @@ import type { ECharts } from 'echarts/core'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
import { watch } from 'vue'
import type { MachineRankRow, WorkerRankRow } from '@/types'
const filterWorkshop = ref('')
const filterBrand = ref('')
@ -120,9 +121,9 @@ const lineChartRef = ref<HTMLElement>()
let barChart: ECharts | null = null
let lineChart: ECharts | null = null
const machineRank = ref<DashboardSummary[]>([])
const workerRank = ref<DashboardSummary[]>([])
const machineStatus = ref<DashboardSummary[]>([])
const machineRank = ref<MachineRankRow[]>([])
const workerRank = ref<WorkerRankRow[]>([])
const machineStatus = ref<any[]>([])
let refreshTimer: ReturnType<typeof setInterval> | null = null
const refreshIntervalMs = ref<number>(10000)
@ -147,7 +148,7 @@ function filterParams(): Record<string, string> {
async function loadFilters() {
try {
const r: Record<string,unknown> = await request.get('/screen/filters')
const r = await request.get('/screen/filters') as any
const items = r.data?.items || []
workshopOptions.value = items.filter((i: Record<string,unknown>) => i.filterType === 'workshop').map((i: Record<string,unknown>) => i.filterValue)
brandOptions.value = items.filter((i: Record<string,unknown>) => i.filterType === 'brand').map((i: Record<string,unknown>) => i.filterValue)
@ -160,7 +161,7 @@ async function loadFilters() {
async function loadSummary() {
try {
const r: Record<string,unknown> = await request.get('/screen/summary', { params: filterParams() })
const r = await request.get('/screen/summary', { params: filterParams() }) as any
const d = r.data || {}
statCards[0].value = d.onlineCount != null ? String(d.onlineCount) : '--'
statCards[0].sub = d.totalMachines ? '/ ' + d.totalMachines : ''
@ -172,7 +173,7 @@ async function loadSummary() {
async function loadCollectorStatus() {
try {
const r: Record<string,unknown> = await request.get('/screen/collector-status')
const r = await request.get('/screen/collector-status') as any
const d = r.data || {}
if (d.status === 'running') {
statCards[2].value = '运行中'
@ -188,7 +189,7 @@ async function loadCollectorStatus() {
async function loadWorkshopProduction() {
try {
const r: Record<string,unknown> = await request.get('/screen/workshop-production', { params: filterParams() })
const r = await request.get('/screen/workshop-production', { params: filterParams() }) as any
const items = r.data?.items || []
if (!barChart) return
barChart.setOption({
@ -203,7 +204,7 @@ async function loadWorkshopProduction() {
async function loadProductionTrend() {
try {
const r: Record<string,unknown> = await request.get('/screen/production-trend', { params: filterParams() })
const r = await request.get('/screen/production-trend', { params: filterParams() }) as any
const items = r.data?.items || []
if (!lineChart) return
lineChart.setOption({
@ -222,21 +223,21 @@ async function loadProductionTrend() {
async function loadMachineRank() {
try {
const r: Record<string,unknown> = await request.get('/screen/machine-rank', { params: filterParams() })
const r = await request.get('/screen/machine-rank', { params: filterParams() }) as any
machineRank.value = r.data?.items || []
} catch { /* 静默 */ }
}
async function loadWorkerRank() {
try {
const r: Record<string,unknown> = await request.get('/screen/worker-rank', { params: filterParams() })
const r = await request.get('/screen/worker-rank', { params: filterParams() }) as any
workerRank.value = r.data?.items || []
} catch { /* 静默 */ }
}
async function loadMachineStatus() {
try {
const r: Record<string,unknown> = await request.get('/screen/machine-status', { params: filterParams() })
const r = await request.get('/screen/machine-status', { params: filterParams() }) as any
machineStatus.value = r.data?.items || []
} catch { /* 静默 */ }
}
@ -292,7 +293,7 @@ onUnmounted(() => {
//
async function fetchRefreshInterval() {
try {
const r: Record<string,unknown> = await request.get('/screen/refresh-interval')
const r = await request.get('/screen/refresh-interval') as any
const v = r?.data?.interval ?? 10000
refreshIntervalMs.value = v
} catch {

@ -116,6 +116,16 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import request from '@/utils/request'
import { useMockMode } from '@/composables/useMockMode'
import type { SysConfig } from '@/types'
/** 车间管理表格行类型(包含管理页面扩展字段) */
interface WorkshopRow {
id: number
name: string
sortOrder: number
isEnabled: boolean
machineCount: number
}
const router = useRouter()
const { isMock } = useMockMode()
@ -166,7 +176,7 @@ function filterConfig() { /* computed auto-filters */ }
async function loadConfigs() {
configLoading.value = true
try { const r: Record<string,unknown> = await request.get('/admin/sys-config'); configList.value = r.data?.items || [] } finally { configLoading.value = false }
try { const r = await request.get('/admin/sys-config') as any; configList.value = r.data?.items || [] } finally { configLoading.value = false }
}
function editConfig(row: SysConfig) {
@ -209,14 +219,14 @@ async function saveConfig() {
async function resetToken(row: SysConfig) {
await ElMessageBox.confirm(`确定重置${row.description}?重置后需同步更新相关服务配置。`, '提示', { type: 'warning' })
const r: Record<string,unknown> = await request.post('/admin/sys-config/reset-token', { configKey: row.configKey })
const r = await request.post('/admin/sys-config/reset-token', { configKey: row.configKey }) as any
ElMessage.success('重置成功,新值:' + (r.data?.newValue || '已生成'))
loadConfigs()
}
// ===== Tab2: =====
const workshopLoading = ref(false)
const workshopList = ref<SysConfig[]>([])
const workshopList = ref<WorkshopRow[]>([])
const workshopDialogVisible = ref(false)
const workshopSubmitting = ref(false)
const editingWorkshopId = ref<number | null>(null)
@ -229,7 +239,7 @@ const workshopRules: FormRules = {
async function loadWorkshops() {
workshopLoading.value = true
try { const r: Record<string,unknown> = await request.get('/admin/workshop'); workshopList.value = r.data?.items || [] } finally { workshopLoading.value = false }
try { const r = await request.get('/admin/workshop') as any; workshopList.value = r.data?.items || [] } finally { workshopLoading.value = false }
}
function addWorkshop() {
@ -238,7 +248,7 @@ function addWorkshop() {
workshopDialogVisible.value = true
}
function editWorkshop(row: SysConfig) {
function editWorkshop(row: WorkshopRow) {
editingWorkshopId.value = row.id
Object.assign(workshopForm, { name: row.name, sortOrder: row.sortOrder })
workshopDialogVisible.value = true
@ -256,14 +266,14 @@ async function saveWorkshop() {
} finally { workshopSubmitting.value = false }
}
async function toggleWorkshop(row: SysConfig) {
async function toggleWorkshop(row: WorkshopRow) {
await ElMessageBox.confirm(`确定${row.isEnabled ? '停用' : '启用'}车间【${row.name}】?`, '提示', { type: 'warning' })
await request.post('/admin/workshop/toggle', { id: row.id })
ElMessage.success('操作成功')
loadWorkshops()
}
async function deleteWorkshop(row: SysConfig) {
async function deleteWorkshop(row: WorkshopRow) {
await ElMessageBox.confirm(`确定删除车间【${row.name}】?此操作不可恢复。`, '提示', { type: 'warning' })
await request.post('/admin/workshop/delete', { id: row.id })
ElMessage.success('已删除')

@ -55,9 +55,9 @@ const chartRef=ref<HTMLElement>();let chart: ECharts | null = null
async function loadData(){
const id=route.params.id
const [d,m,t] = await Promise.all([
request.get('/admin/worker/detail', { params: { id } }),
request.get('/admin/worker/machines', { params: { id } }),
request.get('/admin/worker/production/today', { params: { id } }),
request.get<Worker>('/admin/worker/detail', { params: { id } }),
request.get<{ items: any[] }>('/admin/worker/machines', { params: { id } }),
request.get<{ items: any[] }>('/admin/worker/production/today', { params: { id } }),
])
detail.value = d.data || ({} as Worker)
machines.value = m.data?.items || []

@ -36,7 +36,7 @@
<el-dialog v-model="dialogVisible" :title="editingId?'编辑工人':'新增工人'" width="500px" destroy-on-close>
<el-form :model="form" :rules="rules" ref="workerForm" label-width="100px">
<el-form-item label="工号" prop="code">
<el-input v-model="form.code" maxlength="50" @blur="()=>workerForm.value?.validateField('code')"/>
<el-input v-model="form.code" maxlength="50" @blur="()=>workerForm?.validateField('code')"/>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" maxlength="50"/>
@ -88,6 +88,7 @@ function resetQuery(){query.isEnabled=undefined;query.keyword='';loadData()}
function goDetail(id:number){router.push((isMock.value?'/mock/worker/':'/worker/')+id)}
async function loadData(){loading.value=true;try{const ps=pagination.value.pageSize;const cp=pagination.value.currentPage;const r: ApiResponse<{ items: Worker[]; total?: number }> = await request.get('/admin/worker',{params:{...query,page:cp,pageSize:ps}});tableData.value=r.data?.items||[];pagination.value.total= r.data?.total ?? (r.data?.items?.length ?? 0)}finally{loading.value=false}}
function handleAdd(){editingId.value=null;Object.assign(form,{code:'',name:'',machineIds:[]});dialogVisible.value=true}
function handleEdit(row: Worker){editingId.value=row.id;Object.assign(form,{code:row.code,name:row.name,machineIds:row.machines?.map(m=>m.id)||[]});dialogVisible.value=true}
async function handleSubmit(){submitting.value=true;try{const ok = await (workerForm.value?.validate ? new Promise<boolean>((resolve)=>workerForm.value!.validate((valid:boolean)=>resolve(valid))) : Promise.resolve(true)); if(!ok){return} await request.post(editingId.value?'/admin/worker/update':'/admin/worker',{...form,id:editingId.value});ElMessage.success('保存成功');dialogVisible.value=false;loadData()}finally{submitting.value=false}}
async function handleDelete(row:any){await ElMessageBox.confirm('确定删除【'+row.name+'】?此操作不可恢复。','提示',{type:'warning'});await request.post('/admin/worker/delete',{id:row.id});ElMessage.success('已删除');loadData()}
async function batchStatus(isEnabled:number){await ElMessageBox.confirm('确定对选中的'+selectedRows.value.length+'项操作?','提示',{type:'warning'});await request.post('/admin/worker/batch-status',{ids:selectedRows.value.map((r:any)=>r.id),isEnabled});ElMessage.success('操作成功');loadData()}

@ -22,6 +22,10 @@ export default defineConfig({
},
// 构建优化配置
build: {
// 输出到 CncWebApi/admin 目录IIS 直接服务
outDir: path.resolve(__dirname, '../src/CncWebApi/admin'),
// 不清空目标目录(避免删除其他文件)
emptyOutDir: true,
rollupOptions: {
output: {
// Vite 8 (Rolldown) 使用 codeSplitting 替代 manualChunks

@ -9,6 +9,10 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<!-- Web API 项目输出到 bin 目录 -->
<OutputType>Library</OutputType>
<!-- 直接输出到 bin\IIS 从 bin\ 加载 DLL去掉默认的 Debug\net472 嵌套) -->
<OutputPath>bin\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>

Loading…
Cancel
Save